feat: manually mirror opencoze's code from bytedance

Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
fanlv
2025-07-20 17:36:12 +08:00
commit 890153324f
14811 changed files with 1923430 additions and 0 deletions

View File

@@ -0,0 +1,31 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { type SchemaDecoration } from '@flowgram-adapter/common';
import { type ColorService } from './color-service';
interface PreferenceSchema {
properties: Record<string, SchemaDecoration>;
}
interface ColorContribution {
registerColors: (colors: ColorService) => void;
}
const ColorContribution = Symbol('ColorContribution');
export { ColorContribution, type PreferenceSchema };

View File

@@ -0,0 +1,124 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { inject, injectable, named } from 'inversify';
import {
Disposable,
type SchemaDecoration,
ContributionProvider,
} from '@flowgram-adapter/common';
import { type ColorDefinition, type ThemeType } from '../types';
import { ColorContribution } from './color-contribution';
type ColorSchemaType = SchemaDecoration & {
properties: {
[key: string]: SchemaDecoration;
};
};
@injectable()
class ColorService {
@inject(ContributionProvider)
@named(ColorContribution)
colorContributions: ContributionProvider<ColorContribution>;
/** 颜色表 */
private colors: Record<string, ColorDefinition> = {};
/** 颜色的 schema */
private schema: ColorSchemaType = { type: 'object', properties: {} };
/** 颜色偏好设置的 schema */
// private referenceSchema = { type: 'string', enum: [], enumDescriptions: [] };
init() {
this.colorContributions.getContributions().forEach(contribution => {
contribution.registerColors(this);
});
}
/**
* 批量注册 color
*/
register(...colors: ColorDefinition[]): Disposable[] {
return colors.map(definition => {
const { id } = definition;
this.colors[id] = definition;
this.schema.properties[id] = {
type: 'string',
description: definition.description,
};
return Disposable.create(() => this.deregisterColor(id));
});
}
/**
* 注销 color
*/
deregisterColor(id: string): void {
delete this.colors[id];
delete this.schema.properties[id];
}
/**
* 获取所有的 color definition
*/
getColors(): ColorDefinition[] {
return Object.keys(this.colors).map(id => this.colors[id]);
}
getColor(id: string) {
return this.colors[id];
}
/**
* 获取指定 theme 的 color value
*/
getThemeColor(id: string, themeType: ThemeType) {
const color = this.getColor(id);
let value = color.defaults?.[themeType];
// 色值可以从其他色值继承
if (
value &&
typeof value === 'string' &&
!value.startsWith('#') &&
!value?.startsWith('rgb')
) {
const parentColor = this.colors[value];
value = parentColor?.defaults?.[themeType];
}
return value;
}
toCssColor(id: string, themeType: ThemeType) {
// 转换为变量名
const variableName = `--${id.replace(/\./g, '-')}`;
const value = this.getThemeColor(id, themeType);
return `${variableName}: ${value};`;
}
/**
* 所有色值转化为 css 变量
*/
toCss(themeType: ThemeType) {
return `body {\n${this.getColors()
.map(color => this.toCssColor(color.id, themeType))
.join('\n')}\n}`;
}
}
export { ColorService };

View File

@@ -0,0 +1,156 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { type ColorDefinition } from '../../types';
// white
const baseWhite: ColorDefinition = {
id: 'flowide.color.base.white',
defaults: {
dark: 'rgba(255, 255, 255)',
light: 'rgba(255, 255, 255)',
},
};
// background
const baseBg0: ColorDefinition = {
id: 'flowide.color.base.bg.0',
defaults: {
dark: 'rgba(22, 22, 26, 1)',
light: 'rgba(255, 255, 255)',
},
};
const baseBg1: ColorDefinition = {
id: 'flowide.color.base.bg.1',
defaults: {
dark: 'rgba(35, 36, 41, 1)',
light: 'rgba(255, 255, 255)',
},
};
const baseBg2: ColorDefinition = {
id: 'flowide.color.base.bg.2',
defaults: {
dark: 'rgba(53, 54, 60, 1)',
light: 'rgba(255, 255, 255)',
},
};
// text
const baseText0: ColorDefinition = {
id: 'flowide.color.base.text.0',
defaults: {
dark: 'rgba(249,249,249)',
light: 'rgba(28,31,35)',
},
};
const baseText1: ColorDefinition = {
id: 'flowide.color.base.text.1',
defaults: {
dark: 'rgba(249,249,249, 0.8)',
light: 'rgba(28,31,35, 0.8)',
},
};
const baseText2: ColorDefinition = {
id: 'flowide.color.base.text.2',
defaults: {
dark: 'rgba(249,249,249, 0.6)',
light: 'rgba(28,31,35, 0.62)',
},
};
// border color
const baseBorder: ColorDefinition = {
id: 'flowide.color.base.border',
defaults: {
dark: 'rgba(255, 255, 255, 0.08)',
light: 'rgba(28,31,35, 0.08)',
},
};
// menu color
const menuBorder: ColorDefinition = {
id: 'flowide.color.menu.border',
defaults: {
dark: '#454545',
light: 'unset',
},
};
// menu box shadow color
const menuBoxShadow: ColorDefinition = {
id: 'flowide.color.menu.box.shadow',
defaults: {
dark: 'rgba(0, 0, 0, 0.36)',
light: 'rgba(0, 0, 0, 0.16)',
},
};
// fill color
const baseFill0: ColorDefinition = {
id: 'flowide.color.base.fill.0',
defaults: {
dark: 'rgba(255,255,255, 0.12)',
light: 'rgba(46,50,56, 0.05)',
},
};
const baseFill1: ColorDefinition = {
id: 'flowide.color.base.fill.1',
defaults: {
dark: 'rgba(255,255,255, 0.16)',
light: 'rgba(46,50,56, 0.09)',
},
};
const baseFill2: ColorDefinition = {
id: 'flowide.color.base.fill.2',
defaults: {
dark: 'rgba(255,255,255, 0.2)',
light: 'rgba(46,50,56, 0.05)',
},
};
// primary
const basePrimary: ColorDefinition = {
id: 'flowide.color.base.primary',
defaults: {
dark: 'rgb(84,169,255)',
light: 'rgb(0,100,250)',
},
};
const basePrimaryHover: ColorDefinition = {
id: 'flowide.color.base.primary.hover',
defaults: {
dark: 'rgb(127,193,255)',
light: 'rgb(0,98,214)',
},
};
export {
baseWhite,
baseBg0,
baseBg1,
baseBg2,
baseText0,
baseText1,
baseText2,
baseBorder,
menuBorder,
menuBoxShadow,
baseFill0,
baseFill1,
baseFill2,
basePrimary,
basePrimaryHover,
};

View File

@@ -0,0 +1,22 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { viewColors } from './view-colors';
import * as baseColors from './base-colors';
const colors = [...Object.values(baseColors), ...viewColors];
export { colors };

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { baseBg0 } from './base-colors';
const viewColors = [
{
id: 'flowide.color.view.topBar.bg',
defaults: {
light: baseBg0.id,
dark: baseBg0.id,
},
},
];
export { viewColors };

View File

@@ -0,0 +1,19 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { ColorService } from './color-service';
export { ColorContribution } from './color-contribution';
export { colors } from './colors';

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { bindContributions, bindContributionProvider } from '@flowgram-adapter/common';
import { definePluginCreator, LifecycleContribution } from '../common';
import { ThemeService } from './theme';
import { StylingService, StylingContribution } from './styling';
import { StylesContribution } from './styles-contribution';
import { ColorService, ColorContribution } from './color';
const createStylesPlugin = definePluginCreator({
onBind({ bind }) {
// service
bind(ThemeService).toSelf().inSingletonScope();
bind(StylingService).toSelf().inSingletonScope();
bind(ColorService).toSelf().inSingletonScope();
// provider
bindContributionProvider(bind, StylingContribution);
bindContributionProvider(bind, ColorContribution);
// contribution
bindContributions(bind, StylesContribution, [
LifecycleContribution,
StylingContribution,
ColorContribution,
]);
},
});
export { createStylesPlugin };

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { createStylesPlugin } from './create-styles-plugin';
export {
StylingContribution,
type Collector,
type ColorTheme,
StylingService,
} from './styling';
export { ThemeService } from './theme';
export { ColorService } from './color';

View File

@@ -0,0 +1,70 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { inject, injectable } from 'inversify';
import { Deferred, logger } from '@flowgram-adapter/common';
import { type LifecycleContribution } from '../common';
import { ThemeService } from './theme';
import {
type StylingContribution,
StylingService,
type Collector,
type ColorTheme,
} from './styling';
import { type ColorContribution, ColorService, colors } from './color';
@injectable()
class StylesContribution
implements LifecycleContribution, ColorContribution, StylingContribution
{
private ready = new Deferred<void>();
registerColors(colorService: ColorService) {
colorService.register(...colors);
}
registerStyle({ add }: Collector, { type }: ColorTheme) {
add(this.colorService.toCss(type));
}
@inject(ColorService)
protected readonly colorService: ColorService;
@inject(ThemeService)
protected readonly themeService: ThemeService;
@inject(StylingService)
protected readonly stylingService: StylingService;
async onLoading() {
this.colorService.init();
this.themeService.onDidThemeChange(e => {
this.stylingService.apply(e.next);
this.ready.resolve();
});
this.themeService.init();
await this.ready.promise;
logger.log('theme loaded');
}
onDispose(): void {
this.themeService.dispose();
this.stylingService.dispose();
}
}
export { StylesContribution };

View File

@@ -0,0 +1,22 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { StylingService } from './styling-service';
export {
StylingContribution,
type Collector,
type ColorTheme,
} from './styling-contribution';

View File

@@ -0,0 +1,32 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { type Theme } from '../types';
interface Collector {
prefix: string;
add: (rule: string) => void;
}
type ColorTheme = Pick<Theme, 'type' | 'label'> & {
getColor: (id: string) => string;
};
interface StylingContribution {
registerStyle: (collector: Collector, theme: ColorTheme) => void;
}
const StylingContribution = Symbol('StylingContribution');
export { type Collector, type ColorTheme, StylingContribution };

View File

@@ -0,0 +1,108 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { injectable, inject, named } from 'inversify';
import {
ContributionProvider,
DecorationStyle,
Disposable,
} from '@flowgram-adapter/common';
import { type Theme } from '../types';
import { ThemeService } from '../theme/theme-service';
import { ColorService } from '../color';
import {
StylingContribution,
type ColorTheme,
type Collector,
} from './styling-contribution';
@injectable()
class StylingService {
@inject(ThemeService)
protected readonly themeService: ThemeService;
@inject(ColorService)
protected readonly colorService: ColorService;
@inject(ContributionProvider)
@named(StylingContribution)
protected readonly stylingContributions: ContributionProvider<StylingContribution>;
static readonly PREFIX = 'flowide';
/**
* 后面移到 map 里面,现在暂时没想好
*/
private cssElement: HTMLStyleElement | undefined;
private css = new Map<string, HTMLStyleElement>();
/**
* 收集所有 css 挂载到 <head> 上
*/
apply(theme: Theme) {
const rules: string[] = [];
const colorTheme: ColorTheme = {
type: theme.type,
label: theme.label,
getColor: id => this.colorService.getThemeColor(id, theme.type),
};
const collector: Collector = {
prefix: StylingService.PREFIX,
add: rule => rules.push(rule),
};
const cssElement = DecorationStyle.createStyleElement('flowide-styles');
this.stylingContributions
.getContributions()
.forEach(stylingContribution => {
stylingContribution.registerStyle(collector, colorTheme);
});
const css = rules.join('\n');
cssElement.innerHTML = css;
this.clear();
this.cssElement = cssElement;
return Disposable.create(() => {
this.clear();
});
}
register(id: string, css: string) {
const el = this.css.get(id) || DecorationStyle.createStyleElement(id);
el.innerHTML = css;
this.css.set(id, el);
return Disposable.create(() => {
document.head.removeChild(el);
this.css.delete(id);
});
}
clear() {
if (this.cssElement) {
document.head.removeChild(this.cssElement);
this.cssElement = undefined;
}
}
dispose() {
this.clear();
}
}
export { StylingService };

View File

@@ -0,0 +1,17 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { ThemeService } from './theme-service';

View File

@@ -0,0 +1,111 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { inject, injectable } from 'inversify';
import { Emitter, type Event, Disposable } from '@flowgram-adapter/common';
import { type Theme } from '../types';
import { PreferencesManager } from '../../preference';
interface ThemeDidChangeEvent {
readonly next: Theme;
readonly prev: Theme;
}
const DEFAULT_THEME: Theme = {
id: 'flowide',
type: 'dark',
label: 'flow ide default theme',
};
const DEFAULT_LIGHT_THEME: Theme = {
id: 'flowide',
type: 'light',
label: 'flow ide default theme',
};
@injectable()
class ThemeService {
@inject(PreferencesManager)
protected readonly preferencesManager: PreferencesManager;
/** 所有注册的 theme */
private themes: Map<string, Theme> = new Map([
[DEFAULT_THEME.type, DEFAULT_THEME],
[DEFAULT_LIGHT_THEME.type, DEFAULT_LIGHT_THEME],
]);
/** 当前 theme */
private current: Theme = DEFAULT_THEME;
private readonly themeChange = new Emitter<ThemeDidChangeEvent>();
readonly onDidThemeChange: Event<ThemeDidChangeEvent> =
this.themeChange.event;
init() {
this.changeWithPreferences();
// 先手动触发一次 change 模拟从 preference 读取配置
this.preferencesManager.onDidPreferencesChange(() => {
this.changeWithPreferences();
});
}
changeWithPreferences() {
const type =
this.preferencesManager.getPreferenceData('theme') || DEFAULT_THEME.type;
this.themeChange.fire({
next: {
...DEFAULT_THEME,
type,
},
prev: this.current,
});
}
/**
* 注册 theme
*/
register(...themes: Theme[]) {
themes.forEach(theme => this.themes.set(theme.id, theme));
return Disposable.create(() => {
themes.forEach(theme => {
if (theme && theme === this.current) {
this.setCurrent(DEFAULT_THEME.id);
}
this.themes.delete(theme.id);
});
});
}
setCurrent(themeId: string): void {
const next = this.themes.get(themeId);
const prev = this.current;
if (next && next !== prev) {
this.current = next;
this.themeChange.fire({ next, prev });
}
}
getCurrent() {
return this.current;
}
dispose() {
this.themeChange.dispose();
}
}
export { ThemeService };

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* theme
*/
type ThemeType = 'light' | 'dark';
interface Theme {
readonly id: string;
readonly type: ThemeType;
readonly label: string;
readonly description?: string;
}
/**
* color
*/
type Color = string;
type ColorDefaults = Required<Record<ThemeType, Color>>;
interface ColorDefinition {
id: string;
defaults: ColorDefaults;
description?: string;
}
export type { ThemeType, Theme, Color, ColorDefaults, ColorDefinition };