UNPKG

4.84 kBPlain TextView Raw
1import * as JSS from 'jss';
2import jssPresetDefault from 'jss-preset-default';
3import { Signal } from '@lumino/signaling';
4
5import { NotebookPanel } from '@jupyterlab/notebook';
6
7import { ROOT, IFontFaceOptions } from '.';
8
9import * as SCHEMA from './schema';
10
11export class Stylist {
12 fonts = new Map<string, IFontFaceOptions>();
13
14 private _globalStyles: HTMLStyleElement;
15 private _notebookStyles = new Map<NotebookPanel, HTMLStyleElement>();
16 private _jss = JSS.create(jssPresetDefault());
17 private _fontCache = new Map<string, SCHEMA.IFontFacePrimitive[]>();
18 private _cacheUpdated = new Signal<this, void>(this);
19
20 constructor() {
21 this._globalStyles = document.createElement('style');
22 }
23 get cacheUpdated() {
24 return this._cacheUpdated;
25 }
26
27 registerNotebook(notebook: NotebookPanel, register: boolean) {
28 if (register) {
29 this._notebookStyles.set(notebook, document.createElement('style'));
30 notebook.disposed.connect(this._onDisposed, this);
31 this.hack();
32 } else {
33 this._onDisposed(notebook);
34 }
35 }
36
37 private _onDisposed(notebook: NotebookPanel) {
38 if (this._notebookStyles.has(notebook)) {
39 this._notebookStyles.get(notebook)?.remove();
40 this._notebookStyles.delete(notebook);
41 notebook.disposed.disconnect(this._onDisposed, this);
42 }
43 }
44
45 get stylesheets() {
46 return [this._globalStyles, ...Array.from(this._notebookStyles.values())];
47 }
48
49 notebooks() {
50 return Array.from(this._notebookStyles.keys());
51 }
52
53 stylesheet(meta: SCHEMA.ISettings, notebook?: NotebookPanel, clear = false) {
54 let sheet = notebook
55 ? this._notebookStyles.get(notebook)
56 : this._globalStyles;
57
58 let style = notebook
59 ? this._nbMetaToStyle(meta, notebook)
60 : this._settingsToStyle(meta);
61
62 let jss = this._jss.createStyleSheet(style as any);
63 let css = jss.toString();
64
65 if (sheet && sheet.textContent !== css) {
66 sheet.textContent = css;
67 }
68 this.hack();
69 }
70
71 private _nbMetaToStyle(
72 meta: SCHEMA.ISettings,
73 notebook: NotebookPanel
74 ): SCHEMA.IStyles {
75 const id = notebook.id;
76 let jss: any = { '@font-face': [], '@global': {} };
77 let idStyles: any = (jss['@global'][`.jp-NotebookPanel[id='${id}']`] = {});
78
79 if (meta.fonts) {
80 for (let fontFamily in meta.fonts) {
81 jss['@font-face'] = jss['@font-face'].concat(meta.fonts[fontFamily]);
82 }
83 }
84
85 let styles = meta.styles || {};
86 for (let k in styles) {
87 if (k === ROOT) {
88 for (let rootK in styles[k]) {
89 if (styles == null || styles[k] == null) {
90 continue;
91 }
92 idStyles[rootK] = (styles as any)[k][rootK];
93 }
94 } else if (typeof styles[k] === 'object') {
95 idStyles[`& ${k}`] = styles[k];
96 } else {
97 idStyles[k] = styles[k];
98 }
99 }
100 return jss as SCHEMA.IStyles;
101 }
102
103 private _settingsToStyle(meta: SCHEMA.ISettings): SCHEMA.IStyles {
104 let raw = JSON.stringify(meta.styles);
105 let styles = JSON.parse(raw) as SCHEMA.ISettings;
106 let faces = {} as SCHEMA.IFontFaceObject;
107 for (let font of Array.from(this.fonts.keys())) {
108 if (raw.indexOf(`'${font}'`) > -1 && !faces[font]) {
109 const cachedFont = this._fontCache.get(font);
110 if (cachedFont != null) {
111 faces[font] = cachedFont;
112 } else {
113 // tslint:disable-next-line
114 new Promise((resolve, reject) => {
115 const options = this.fonts.get(font);
116 if (options == null) {
117 reject();
118 return;
119 } else {
120 options.faces().then(
121 faces => {
122 if (this._fontCache.has(font)) {
123 return;
124 }
125 this._fontCache.set(font, faces);
126 this._cacheUpdated.emit(void 0);
127 resolve();
128 },
129 function(err) {
130 console.error('rejected!', err);
131 reject();
132 }
133 );
134 }
135 });
136 }
137 }
138 }
139
140 let flatFaces = Object.keys(faces).reduce((m, face) => {
141 const foundFaces = faces[face];
142 if (faces && foundFaces != null) {
143 return m.concat(foundFaces);
144 }
145 }, [] as SCHEMA.IFontFacePrimitive[]);
146
147 return {
148 '@global': styles as any,
149 '@font-face': flatFaces as any
150 } as SCHEMA.IStyles;
151 }
152
153 dispose() {
154 this._globalStyles.remove();
155 for (let notebook of Array.from(this._notebookStyles.keys())) {
156 this._onDisposed(notebook);
157 }
158 }
159
160 hack(show = true) {
161 if (show) {
162 setTimeout(
163 () =>
164 this.stylesheets.map(s => {
165 document.body.appendChild(s);
166 }),
167 0
168 );
169 } else {
170 this.stylesheets.map(el => el.remove());
171 }
172 }
173}