@opensumi/ide-theme 2.21.13 → 2.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/browser/icon-theme-data.js.map +1 -1
- package/lib/browser/icon-theme-store.js.map +1 -1
- package/lib/browser/icon.service.d.ts +3 -1
- package/lib/browser/icon.service.d.ts.map +1 -1
- package/lib/browser/icon.service.js +56 -24
- package/lib/browser/icon.service.js.map +1 -1
- package/lib/browser/index.js.map +1 -1
- package/lib/browser/semantic-tokens-registry.js.map +1 -1
- package/lib/browser/style.service.js.map +1 -1
- package/lib/browser/theme-data.js +7 -7
- package/lib/browser/theme-data.js.map +1 -1
- package/lib/browser/theme-store.js.map +1 -1
- package/lib/browser/theme.contribution.js +3 -3
- package/lib/browser/theme.contribution.js.map +1 -1
- package/lib/browser/workbench.theme.service.js +2 -2
- package/lib/browser/workbench.theme.service.js.map +1 -1
- package/lib/common/color-tokens/basic-color.d.ts +1 -1
- package/lib/common/color-tokens/basic-color.d.ts.map +1 -1
- package/lib/common/color-tokens/editor.d.ts +13 -0
- package/lib/common/color-tokens/editor.d.ts.map +1 -1
- package/lib/common/color-tokens/editor.js +35 -1
- package/lib/common/color-tokens/editor.js.map +1 -1
- package/lib/common/color.js +40 -40
- package/lib/common/color.js.map +1 -1
- package/lib/common/mocks/theme.service.js.map +1 -1
- package/lib/common/plistParser.js +40 -40
- package/lib/common/plistParser.js.map +1 -1
- package/lib/common/semantic-tokens-registry.d.ts +7 -7
- package/lib/common/semantic-tokens-registry.d.ts.map +1 -1
- package/lib/common/theme.service.d.ts +26 -6
- package/lib/common/theme.service.d.ts.map +1 -1
- package/lib/common/theme.service.js.map +1 -1
- package/package.json +11 -10
- package/src/browser/default-theme.ts +547 -0
- package/src/browser/icon-theme-data.ts +294 -0
- package/src/browser/icon-theme-store.ts +38 -0
- package/src/browser/icon.less +15 -0
- package/src/browser/icon.service.ts +457 -0
- package/src/browser/index.ts +45 -0
- package/src/browser/semantic-tokens-registry.ts +217 -0
- package/src/browser/style.service.ts +51 -0
- package/src/browser/theme-data.ts +719 -0
- package/src/browser/theme-store.ts +95 -0
- package/src/browser/theme.contribution.ts +343 -0
- package/src/browser/workbench.theme.service.ts +703 -0
- package/src/common/color-registry.ts +52 -0
- package/src/common/color-tokens/activity-bar.ts +122 -0
- package/src/common/color-tokens/badge.ts +31 -0
- package/src/common/color-tokens/base.ts +90 -0
- package/src/common/color-tokens/basic-color.ts +9 -0
- package/src/common/color-tokens/breadcrumb.ts +60 -0
- package/src/common/color-tokens/button.ts +69 -0
- package/src/common/color-tokens/charts.ts +68 -0
- package/src/common/color-tokens/checkbox.ts +23 -0
- package/src/common/color-tokens/custom/actionbar.ts +51 -0
- package/src/common/color-tokens/custom/activity-bar.ts +16 -0
- package/src/common/color-tokens/custom/badge.ts +30 -0
- package/src/common/color-tokens/custom/base.ts +111 -0
- package/src/common/color-tokens/custom/button.ts +359 -0
- package/src/common/color-tokens/custom/checkbox.ts +36 -0
- package/src/common/color-tokens/custom/decoration.ts +71 -0
- package/src/common/color-tokens/custom/editor.ts +27 -0
- package/src/common/color-tokens/custom/extension.ts +9 -0
- package/src/common/color-tokens/custom/icon.ts +30 -0
- package/src/common/color-tokens/custom/index.ts +26 -0
- package/src/common/color-tokens/custom/input.ts +48 -0
- package/src/common/color-tokens/custom/menu.ts +61 -0
- package/src/common/color-tokens/custom/modal.ts +57 -0
- package/src/common/color-tokens/custom/notification.ts +16 -0
- package/src/common/color-tokens/custom/panel.ts +112 -0
- package/src/common/color-tokens/custom/popover.ts +28 -0
- package/src/common/color-tokens/custom/select.ts +155 -0
- package/src/common/color-tokens/custom/settings.ts +32 -0
- package/src/common/color-tokens/custom/statusbar.ts +16 -0
- package/src/common/color-tokens/custom/tab.ts +31 -0
- package/src/common/color-tokens/custom/tooltip.ts +55 -0
- package/src/common/color-tokens/custom/tree.ts +106 -0
- package/src/common/color-tokens/debug.ts +103 -0
- package/src/common/color-tokens/debugToolbar.ts +134 -0
- package/src/common/color-tokens/dropdown.ts +27 -0
- package/src/common/color-tokens/editor.ts +945 -0
- package/src/common/color-tokens/index.ts +35 -0
- package/src/common/color-tokens/input.ts +105 -0
- package/src/common/color-tokens/list-tree.ts +205 -0
- package/src/common/color-tokens/menu-bar.ts +43 -0
- package/src/common/color-tokens/menu.ts +53 -0
- package/src/common/color-tokens/merge-conflict.ts +145 -0
- package/src/common/color-tokens/minimap.ts +99 -0
- package/src/common/color-tokens/notification.ts +169 -0
- package/src/common/color-tokens/panel.ts +177 -0
- package/src/common/color-tokens/pick-view.ts +96 -0
- package/src/common/color-tokens/picker.ts +15 -0
- package/src/common/color-tokens/progress-bar.ts +12 -0
- package/src/common/color-tokens/quick-input.ts +57 -0
- package/src/common/color-tokens/scrollbar.ts +42 -0
- package/src/common/color-tokens/settings.ts +126 -0
- package/src/common/color-tokens/sidebar.ts +121 -0
- package/src/common/color-tokens/snippet.ts +33 -0
- package/src/common/color-tokens/status-bar.ts +350 -0
- package/src/common/color-tokens/tab.ts +346 -0
- package/src/common/color-tokens/testing.ts +105 -0
- package/src/common/color-tokens/text.ts +41 -0
- package/src/common/color-tokens/title-bar.ts +62 -0
- package/src/common/color-tokens/toolbar.ts +28 -0
- package/src/common/color-tokens/welcome-page.ts +27 -0
- package/src/common/color.ts +647 -0
- package/src/common/default-themes.ts +273 -0
- package/src/common/event.ts +9 -0
- package/src/common/index.ts +8 -0
- package/src/common/mocks/theme.service.ts +55 -0
- package/src/common/plistParser.ts +525 -0
- package/src/common/semantic-tokens-registry.ts +439 -0
- package/src/common/style.ts +9 -0
- package/src/common/theme.service.ts +363 -0
- package/src/common/themeCompatibility.ts +95 -0
- package/src/common/utils.ts +195 -0
- package/src/index.ts +1 -0
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
import { Injectable, Autowired } from '@opensumi/di';
|
|
2
|
+
import {
|
|
3
|
+
URI,
|
|
4
|
+
PreferenceService,
|
|
5
|
+
PreferenceSchemaProvider,
|
|
6
|
+
IPreferenceSettingsService,
|
|
7
|
+
Emitter,
|
|
8
|
+
Event,
|
|
9
|
+
ILogger,
|
|
10
|
+
CODICON_OWNER,
|
|
11
|
+
Deferred,
|
|
12
|
+
OnEvent,
|
|
13
|
+
WithEventBus,
|
|
14
|
+
ExtensionDidContributes,
|
|
15
|
+
GeneralSettingsId,
|
|
16
|
+
} from '@opensumi/ide-core-browser';
|
|
17
|
+
import { StaticResourceService } from '@opensumi/ide-static-resource/lib/browser';
|
|
18
|
+
|
|
19
|
+
import {
|
|
20
|
+
ThemeType,
|
|
21
|
+
IconThemeType,
|
|
22
|
+
IIconService,
|
|
23
|
+
ThemeContribution,
|
|
24
|
+
getThemeId,
|
|
25
|
+
IIconTheme,
|
|
26
|
+
getThemeTypeSelector,
|
|
27
|
+
IconType,
|
|
28
|
+
IconShape,
|
|
29
|
+
IconThemeInfo,
|
|
30
|
+
FontIconDefinition,
|
|
31
|
+
IconFontFamily,
|
|
32
|
+
} from '../common';
|
|
33
|
+
|
|
34
|
+
import { IconThemeStore } from './icon-theme-store';
|
|
35
|
+
|
|
36
|
+
import './icon.less';
|
|
37
|
+
|
|
38
|
+
@Injectable()
|
|
39
|
+
export class IconService extends WithEventBus implements IIconService {
|
|
40
|
+
@Autowired()
|
|
41
|
+
staticResourceService: StaticResourceService;
|
|
42
|
+
|
|
43
|
+
@Autowired()
|
|
44
|
+
iconThemeStore: IconThemeStore;
|
|
45
|
+
|
|
46
|
+
@Autowired(PreferenceService)
|
|
47
|
+
private preferenceService: PreferenceService;
|
|
48
|
+
|
|
49
|
+
@Autowired(PreferenceSchemaProvider)
|
|
50
|
+
private preferenceSchemaProvider: PreferenceSchemaProvider;
|
|
51
|
+
|
|
52
|
+
@Autowired(IPreferenceSettingsService)
|
|
53
|
+
private preferenceSettings: IPreferenceSettingsService;
|
|
54
|
+
|
|
55
|
+
@Autowired(ILogger)
|
|
56
|
+
private readonly logger: ILogger;
|
|
57
|
+
|
|
58
|
+
iconThemeLoaded: Deferred<void> = new Deferred<void>();
|
|
59
|
+
|
|
60
|
+
private themeChangeEmitter: Emitter<IIconTheme> = new Emitter();
|
|
61
|
+
|
|
62
|
+
public onThemeChange: Event<IIconTheme> = this.themeChangeEmitter.event;
|
|
63
|
+
|
|
64
|
+
private iconThemes: Map<string, IIconTheme> = new Map();
|
|
65
|
+
|
|
66
|
+
private iconContributionRegistry: Map<string, { contribution: ThemeContribution; basePath: URI }> = new Map();
|
|
67
|
+
|
|
68
|
+
public currentThemeId: string;
|
|
69
|
+
public currentTheme: IIconTheme;
|
|
70
|
+
private latestApplyTheme: string;
|
|
71
|
+
|
|
72
|
+
private iconMap: Map<string, string> = new Map();
|
|
73
|
+
|
|
74
|
+
// eg. $(codicon/sync~spin)
|
|
75
|
+
private _regexFromString = /^\$\(([a-z.]+\/)?([a-z-]+)(~[a-z]+)?\)$/i;
|
|
76
|
+
|
|
77
|
+
private getPath(basePath: string, relativePath: string): URI {
|
|
78
|
+
if (relativePath.startsWith('./')) {
|
|
79
|
+
const uri = new URI(basePath).resolve(relativePath.replace(/^\.\//, ''));
|
|
80
|
+
return uri.scheme ? uri : URI.file(uri.toString());
|
|
81
|
+
} else if (/^http(s)?/.test(relativePath)) {
|
|
82
|
+
return new URI(relativePath);
|
|
83
|
+
} else if (basePath) {
|
|
84
|
+
const uri = new URI(basePath).resolve(relativePath);
|
|
85
|
+
return uri.scheme ? uri : URI.file(uri.toString());
|
|
86
|
+
} else if (/^file:\/\//.test(relativePath)) {
|
|
87
|
+
return new URI(relativePath);
|
|
88
|
+
} else {
|
|
89
|
+
return URI.file(relativePath);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
constructor() {
|
|
94
|
+
super();
|
|
95
|
+
this.listen();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
private listen() {
|
|
99
|
+
this.preferenceService.onPreferenceChanged(async (e) => {
|
|
100
|
+
if (e.preferenceName === GeneralSettingsId.Icon && this.iconContributionRegistry.has(e.newValue)) {
|
|
101
|
+
await this.applyTheme(this.preferenceService.get<string>(GeneralSettingsId.Icon)!);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private styleSheetCollection = '';
|
|
107
|
+
|
|
108
|
+
private appendStylesTimer: number | undefined;
|
|
109
|
+
private appendStyleCounter = 0;
|
|
110
|
+
|
|
111
|
+
private doAppend(targetElement: HTMLElement | null) {
|
|
112
|
+
if (targetElement) {
|
|
113
|
+
const textContent = targetElement?.textContent + this.styleSheetCollection;
|
|
114
|
+
targetElement.textContent = textContent;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
this.styleSheetCollection = '';
|
|
118
|
+
this.appendStylesTimer = undefined;
|
|
119
|
+
this.appendStyleCounter = 0;
|
|
120
|
+
clearTimeout(this.appendStylesTimer);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
protected appendStyleSheet(styleSheet: string, fromExtension = false) {
|
|
124
|
+
let iconStyleNode = document.getElementById('plugin-icons');
|
|
125
|
+
if (!iconStyleNode) {
|
|
126
|
+
iconStyleNode = document.createElement('style');
|
|
127
|
+
iconStyleNode.id = 'plugin-icons';
|
|
128
|
+
document.getElementsByTagName('head')[0].appendChild(iconStyleNode);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// 非插件进程注册的 icon 正常 append
|
|
132
|
+
if (!fromExtension) {
|
|
133
|
+
iconStyleNode.append(styleSheet);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// 针对插件进程注册的 icon 进行 append 分段处理
|
|
138
|
+
// 避免因为过多 icon 导致页面卡顿
|
|
139
|
+
// 例如 GitLens 插件会注册超过 800 个 icon
|
|
140
|
+
this.styleSheetCollection += '\r\n' + styleSheet;
|
|
141
|
+
this.appendStyleCounter += 1;
|
|
142
|
+
|
|
143
|
+
// 超过 100 个样式
|
|
144
|
+
if (this.appendStyleCounter >= 150 && this.appendStylesTimer) {
|
|
145
|
+
clearTimeout(this.appendStylesTimer);
|
|
146
|
+
this.appendExtensionIconStyle();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (!this.appendStylesTimer) {
|
|
150
|
+
// 超过 100 毫秒
|
|
151
|
+
this.appendStylesTimer = window.setTimeout(() => {
|
|
152
|
+
this.appendExtensionIconStyle();
|
|
153
|
+
}, 100);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private appendExtensionIconStyle(styleNode?: HTMLElement | null) {
|
|
158
|
+
if (styleNode) {
|
|
159
|
+
this.doAppend(styleNode);
|
|
160
|
+
} else {
|
|
161
|
+
const randomClass = this.getRandomIconClass('extension-');
|
|
162
|
+
const styleNode = document.createElement('style');
|
|
163
|
+
styleNode!.className = randomClass;
|
|
164
|
+
this.doAppend(styleNode);
|
|
165
|
+
document.getElementsByTagName('head')[0].appendChild(styleNode);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
protected getRandomIconClass(prefix = '') {
|
|
170
|
+
return `${prefix}icon-${Math.random().toString(36).slice(-8)}`;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
protected getMaskStyleSheet(iconUrl: string, className: string, baseTheme?: string): string {
|
|
174
|
+
const cssRule = `${baseTheme || ''} .${className} {-webkit-mask: url("${iconUrl}") no-repeat 50% 50%;}`;
|
|
175
|
+
return cssRule;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
protected getMaskStyleSheetWithStaticService(path: URI, className: string, baseTheme?: string): string {
|
|
179
|
+
const iconUrl = this.staticResourceService.resolveStaticResource(path).toString();
|
|
180
|
+
return this.getMaskStyleSheet(iconUrl, className, baseTheme);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
protected getBackgroundStyleSheet(iconUrl: string, className: string, baseTheme?: string): string {
|
|
184
|
+
const cssRule = `${
|
|
185
|
+
baseTheme || ''
|
|
186
|
+
} .${className} {background: url("${iconUrl}") no-repeat 0 0;background-size: contain;}`;
|
|
187
|
+
return cssRule;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
protected getBackgroundStyleSheetWithStaticService(path: URI, className: string, baseTheme?: string): string {
|
|
191
|
+
const iconUrl = this.staticResourceService.resolveStaticResource(path).toString();
|
|
192
|
+
return this.getBackgroundStyleSheet(iconUrl, className, baseTheme);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
fromString(str: string): string | undefined {
|
|
196
|
+
if (typeof str !== 'string') {
|
|
197
|
+
return undefined;
|
|
198
|
+
}
|
|
199
|
+
const matched = str.match(this._regexFromString);
|
|
200
|
+
if (!matched) {
|
|
201
|
+
return undefined;
|
|
202
|
+
}
|
|
203
|
+
const [, owner, name, modifier] = matched;
|
|
204
|
+
const iconOwner = owner ? owner.slice(0, -1) : CODICON_OWNER;
|
|
205
|
+
let className = `${iconOwner} ${iconOwner}-${name}`;
|
|
206
|
+
if (modifier) {
|
|
207
|
+
className += ` ${modifier.slice(1)}`;
|
|
208
|
+
}
|
|
209
|
+
return className;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
encodeBase64Path(iconPath: string) {
|
|
213
|
+
// 由于进行 Background 样式拼接时采用的 `url("${iconPath}")` 结构
|
|
214
|
+
// 故这里需要对 iconPath 中的 `"` 及常见字符进行转义处理
|
|
215
|
+
return iconPath.replace(/"/g, '\\"').replace(/\?|#/g, (m) => encodeURIComponent(m));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
fromIcon(
|
|
219
|
+
basePath = '',
|
|
220
|
+
icon?: { [index in IconThemeType]: string } | string,
|
|
221
|
+
type: IconType = IconType.Mask,
|
|
222
|
+
shape: IconShape = IconShape.Square,
|
|
223
|
+
fromExtension = false,
|
|
224
|
+
): string | undefined {
|
|
225
|
+
if (!icon) {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
const iconPath = typeof icon === 'string' ? icon : icon.dark;
|
|
229
|
+
const iconId = `${basePath}-${iconPath}-${type}-${shape}`;
|
|
230
|
+
if (this.iconMap.get(iconId)) {
|
|
231
|
+
return this.iconMap.get(iconId);
|
|
232
|
+
}
|
|
233
|
+
const randomClass = this.getRandomIconClass();
|
|
234
|
+
if (typeof icon === 'string') {
|
|
235
|
+
/**
|
|
236
|
+
* 处理 data:image 格式,/^data:image\//
|
|
237
|
+
* 如 data:image/svg+xml or data:image/gif;base64,
|
|
238
|
+
* 此时无需 static service
|
|
239
|
+
*/
|
|
240
|
+
if (type === IconType.Base64) {
|
|
241
|
+
this.appendStyleSheet(this.getBackgroundStyleSheet(this.encodeBase64Path(icon), randomClass), fromExtension);
|
|
242
|
+
} else {
|
|
243
|
+
const targetPath = this.getPath(basePath, icon);
|
|
244
|
+
if (type === IconType.Mask) {
|
|
245
|
+
this.appendStyleSheet(this.getMaskStyleSheetWithStaticService(targetPath, randomClass), fromExtension);
|
|
246
|
+
} else {
|
|
247
|
+
this.appendStyleSheet(this.getBackgroundStyleSheetWithStaticService(targetPath, randomClass), fromExtension);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
} else {
|
|
251
|
+
// eslint-disable-next-line guard-for-in
|
|
252
|
+
for (const themeType in icon) {
|
|
253
|
+
const themeSelector = getThemeTypeSelector(themeType as ThemeType);
|
|
254
|
+
const iconPath = icon[themeType];
|
|
255
|
+
const targetPath = this.getPath(basePath, iconPath);
|
|
256
|
+
if (type === IconType.Base64) {
|
|
257
|
+
/**
|
|
258
|
+
* 处理 data:image 格式,/^data:image\//
|
|
259
|
+
* 如 data:image/svg+xml or data:image/gif;base64,
|
|
260
|
+
* 此时无需 static service
|
|
261
|
+
*/
|
|
262
|
+
this.appendStyleSheet(
|
|
263
|
+
this.getBackgroundStyleSheet(this.encodeBase64Path(iconPath), randomClass, `.${themeSelector}`),
|
|
264
|
+
fromExtension,
|
|
265
|
+
);
|
|
266
|
+
} else if (type === IconType.Mask) {
|
|
267
|
+
this.appendStyleSheet(
|
|
268
|
+
this.getMaskStyleSheetWithStaticService(targetPath, randomClass, `.${themeSelector}`),
|
|
269
|
+
fromExtension,
|
|
270
|
+
);
|
|
271
|
+
} else {
|
|
272
|
+
this.appendStyleSheet(
|
|
273
|
+
this.getBackgroundStyleSheetWithStaticService(targetPath, randomClass, `.${themeSelector}`),
|
|
274
|
+
fromExtension,
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
const targetIconClass = [
|
|
280
|
+
'kaitian-icon',
|
|
281
|
+
randomClass,
|
|
282
|
+
{
|
|
283
|
+
[IconType.Background]: 'background-mode',
|
|
284
|
+
[IconType.Base64]: 'background-mode',
|
|
285
|
+
[IconType.Mask]: 'mask-mode',
|
|
286
|
+
}[type],
|
|
287
|
+
shape === IconShape.Circle ? 'circle' : '',
|
|
288
|
+
].join(' ');
|
|
289
|
+
this.iconMap.set(iconId, targetIconClass);
|
|
290
|
+
return targetIconClass;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
@OnEvent(ExtensionDidContributes)
|
|
294
|
+
async onDidExtensionContributes() {
|
|
295
|
+
await this.updateIconThemes();
|
|
296
|
+
this.iconThemeLoaded.resolve();
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
get preferenceThemeId(): string | undefined {
|
|
300
|
+
return this.preferenceService.get<string>(GeneralSettingsId.Icon);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
private async updateIconThemes() {
|
|
304
|
+
const themeMap = this.getAvailableThemeInfos().reduce((pre: Map<string, string>, cur: IconThemeInfo) => {
|
|
305
|
+
if (!pre.has(cur.themeId)) {
|
|
306
|
+
pre.set(cur.themeId, cur.name);
|
|
307
|
+
}
|
|
308
|
+
return pre;
|
|
309
|
+
}, new Map());
|
|
310
|
+
|
|
311
|
+
this.preferenceSettings.setEnumLabels(GeneralSettingsId.Icon, Object.fromEntries(themeMap.entries()));
|
|
312
|
+
// 当前没有主题,或没有缓存的主题时,将第一个注册主题设置为当前主题
|
|
313
|
+
if (!this.currentTheme) {
|
|
314
|
+
if (!this.preferenceThemeId || !themeMap.has(this.preferenceThemeId)) {
|
|
315
|
+
const themeId = Array.from(themeMap.keys())[0];
|
|
316
|
+
if (themeId) {
|
|
317
|
+
await this.applyTheme(themeId);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
registerIconThemes(iconContributions: ThemeContribution[], basePath: URI) {
|
|
324
|
+
for (const contribution of iconContributions) {
|
|
325
|
+
const themeId = getThemeId(contribution);
|
|
326
|
+
this.iconContributionRegistry.set(themeId, { contribution, basePath });
|
|
327
|
+
if (this.preferenceThemeId && this.preferenceThemeId === themeId) {
|
|
328
|
+
this.applyTheme(this.preferenceThemeId);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const currentSchemas = this.preferenceSchemaProvider.getPreferenceProperty(GeneralSettingsId.Icon);
|
|
333
|
+
if (currentSchemas) {
|
|
334
|
+
delete currentSchemas.scope;
|
|
335
|
+
}
|
|
336
|
+
this.preferenceSchemaProvider.setSchema(
|
|
337
|
+
{
|
|
338
|
+
properties: {
|
|
339
|
+
[GeneralSettingsId.Icon]: {
|
|
340
|
+
...currentSchemas,
|
|
341
|
+
enum: this.getAvailableThemeInfos().map((info) => info.themeId),
|
|
342
|
+
},
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
true,
|
|
346
|
+
);
|
|
347
|
+
|
|
348
|
+
this.updateIconThemes();
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
registerFontIcons(definitions: FontIconDefinition[], iconFontFamilies: IconFontFamily[]) {
|
|
352
|
+
const styleSheetContnt: string[] = [];
|
|
353
|
+
|
|
354
|
+
for (const def of definitions) {
|
|
355
|
+
styleSheetContnt.push(
|
|
356
|
+
`.codicon-${def.id}::before { content: '${def.content}'; font-family: '${def.fontFamily}' }`,
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
for (const font of iconFontFamilies) {
|
|
361
|
+
styleSheetContnt.push(
|
|
362
|
+
`@font-face {src: url('${font.source}') format('${font.format}'); font-family: '${font.fontFamily}'; font-display: ${font.display}; }`,
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
let styleNode = document.getElementById('codiconStyles');
|
|
367
|
+
if (styleNode) {
|
|
368
|
+
styleNode.innerHTML = styleSheetContnt.join('\r');
|
|
369
|
+
} else {
|
|
370
|
+
styleNode = document.createElement('style');
|
|
371
|
+
styleNode.id = 'codiconStyles';
|
|
372
|
+
styleNode.innerHTML = styleSheetContnt.join('\r');
|
|
373
|
+
document.getElementsByTagName('head')[0].appendChild(styleNode);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
getAvailableThemeInfos(): IconThemeInfo[] {
|
|
378
|
+
const themeInfos: IconThemeInfo[] = [];
|
|
379
|
+
for (const { contribution } of this.iconContributionRegistry.values()) {
|
|
380
|
+
const { label, id } = contribution;
|
|
381
|
+
themeInfos.push({
|
|
382
|
+
themeId: id || getThemeId(contribution),
|
|
383
|
+
name: label,
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
return themeInfos;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
async getIconTheme(themeId: string): Promise<IIconTheme | undefined> {
|
|
390
|
+
let theme = this.iconThemes.get(themeId);
|
|
391
|
+
if (theme) {
|
|
392
|
+
return theme;
|
|
393
|
+
}
|
|
394
|
+
const extContribution = this.iconContributionRegistry.get(themeId);
|
|
395
|
+
if (extContribution) {
|
|
396
|
+
theme = await this.iconThemeStore.getIconTheme(extContribution.contribution, extContribution.basePath);
|
|
397
|
+
return theme;
|
|
398
|
+
}
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
async applyTheme(themeId: string) {
|
|
403
|
+
this.toggleIconVisible(true);
|
|
404
|
+
if (this.currentTheme && this.currentThemeId === themeId) {
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* 这里 `applyTheme` 默认应该按照最后一个应用的主题进行加载
|
|
409
|
+
* 但由于 `getIconTheme` 存在时序问题,例如:
|
|
410
|
+
* 主题 A,E,分别由插件 A,E 贡献
|
|
411
|
+
* 这里先调用 applyTheme(E), 再调用 applyTheme(A)
|
|
412
|
+
* 旧的逻辑由于插件 A ... E 的加载顺序问题,会存在 A 比 E 快加载的情况导致最终应用了错误的主题
|
|
413
|
+
*
|
|
414
|
+
* 故这里增加额外判断,保障最后一个加载的主题应用
|
|
415
|
+
*/
|
|
416
|
+
this.latestApplyTheme = themeId;
|
|
417
|
+
const iconThemeData = await this.getIconTheme(themeId);
|
|
418
|
+
if (this.latestApplyTheme !== themeId) {
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
if (!iconThemeData) {
|
|
422
|
+
this.logger.warn('Target IconTheme extension not detected, use built-in icons.');
|
|
423
|
+
document.getElementsByTagName('body')[0].classList.add('default-file-icons');
|
|
424
|
+
this.iconThemeLoaded.resolve();
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
this.currentThemeId = themeId;
|
|
428
|
+
document.getElementsByTagName('body')[0].classList.remove('default-file-icons');
|
|
429
|
+
this.currentTheme = iconThemeData;
|
|
430
|
+
const { styleSheetContent } = iconThemeData;
|
|
431
|
+
let styleNode = document.getElementById('icon-style');
|
|
432
|
+
if (styleNode) {
|
|
433
|
+
styleNode.innerHTML = styleSheetContent;
|
|
434
|
+
} else {
|
|
435
|
+
styleNode = document.createElement('style');
|
|
436
|
+
styleNode.id = 'icon-style';
|
|
437
|
+
styleNode.innerHTML = styleSheetContent;
|
|
438
|
+
document.getElementsByTagName('head')[0].appendChild(styleNode);
|
|
439
|
+
}
|
|
440
|
+
this.themeChangeEmitter.fire(this.currentTheme);
|
|
441
|
+
|
|
442
|
+
if (!this.preferenceThemeId) {
|
|
443
|
+
this.iconThemeLoaded.resolve();
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
toggleIconVisible(show?: boolean) {
|
|
448
|
+
const rootNode = document.getElementsByTagName('body')[0]!;
|
|
449
|
+
if (show === undefined) {
|
|
450
|
+
rootNode.classList.toggle('show-file-icons');
|
|
451
|
+
} else if (show === true) {
|
|
452
|
+
rootNode.classList.add('show-file-icons');
|
|
453
|
+
} else {
|
|
454
|
+
rootNode.classList.remove('show-file-icons');
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Provider, Injectable } from '@opensumi/di';
|
|
2
|
+
import { BrowserModule } from '@opensumi/ide-core-browser';
|
|
3
|
+
|
|
4
|
+
import { ISemanticTokenRegistry } from '../common/semantic-tokens-registry';
|
|
5
|
+
import { ICSSStyleService } from '../common/style';
|
|
6
|
+
import { ThemeServicePath, IThemeService, IIconService } from '../common/theme.service';
|
|
7
|
+
|
|
8
|
+
import { IconService } from './icon.service';
|
|
9
|
+
import { SemanticTokenRegistryImpl } from './semantic-tokens-registry';
|
|
10
|
+
import { CSSStyleService } from './style.service';
|
|
11
|
+
import { ThemeContribution } from './theme.contribution';
|
|
12
|
+
import { WorkbenchThemeService } from './workbench.theme.service';
|
|
13
|
+
|
|
14
|
+
@Injectable()
|
|
15
|
+
export class ThemeModule extends BrowserModule {
|
|
16
|
+
providers: Provider[] = [
|
|
17
|
+
{
|
|
18
|
+
token: ICSSStyleService,
|
|
19
|
+
useClass: CSSStyleService,
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
token: IThemeService,
|
|
23
|
+
useClass: WorkbenchThemeService,
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
token: IIconService,
|
|
27
|
+
useClass: IconService,
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
token: ISemanticTokenRegistry,
|
|
31
|
+
useClass: SemanticTokenRegistryImpl,
|
|
32
|
+
},
|
|
33
|
+
ThemeContribution,
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
// 依赖 fileService 服务
|
|
37
|
+
backServices = [
|
|
38
|
+
{
|
|
39
|
+
servicePath: ThemeServicePath,
|
|
40
|
+
clientToken: IThemeService,
|
|
41
|
+
},
|
|
42
|
+
];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export * from './icon.service';
|