fontastic 1.1.0 → 1.3.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.
Files changed (87) hide show
  1. package/.github/workflows/claude-code-review.yml +0 -6
  2. package/.github/workflows/release-please.yml +25 -0
  3. package/.github/workflows/release.yml +5 -109
  4. package/.release-please-manifest.json +3 -0
  5. package/CHANGELOG.md +39 -0
  6. package/README.md +6 -0
  7. package/app/Application.js +4 -4
  8. package/app/Application.ts +13 -13
  9. package/app/config/database.js +1 -1
  10. package/app/config/database.ts +1 -1
  11. package/app/config/mimes.js +23 -23
  12. package/app/config/mimes.ts +35 -29
  13. package/app/core/ConfigManager.js +27 -0
  14. package/app/core/ConfigManager.ts +28 -0
  15. package/app/core/FontFinder.js +66 -15
  16. package/app/core/FontFinder.ts +81 -22
  17. package/app/core/FontManager.js +20 -18
  18. package/app/core/FontManager.ts +21 -19
  19. package/app/core/FontObject.js +44 -24
  20. package/app/core/FontObject.ts +47 -27
  21. package/app/core/MessageHandler.js +70 -19
  22. package/app/core/MessageHandler.ts +82 -21
  23. package/app/core/SystemManager.js +5 -1
  24. package/app/core/SystemManager.ts +5 -1
  25. package/app/database/entity/Collection.schema.js +20 -18
  26. package/app/database/entity/Collection.schema.ts +22 -21
  27. package/app/database/repository/Collection.repository.js +17 -18
  28. package/app/database/repository/Collection.repository.ts +27 -18
  29. package/app/database/repository/Store.repository.js +13 -18
  30. package/app/database/repository/Store.repository.ts +13 -21
  31. package/app/enums/ChannelType.js +18 -0
  32. package/app/enums/ChannelType.ts +24 -0
  33. package/app/main.js +98 -10
  34. package/app/main.ts +126 -19
  35. package/app/package.json +1 -1
  36. package/app/types/NativeThemeState.js +3 -0
  37. package/app/types/NativeThemeState.ts +4 -0
  38. package/app/types/ScanProgress.js +3 -0
  39. package/app/types/ScanProgress.ts +6 -0
  40. package/app/types/SystemPreferencesState.js +3 -0
  41. package/app/types/SystemPreferencesState.ts +4 -0
  42. package/app/types/index.js +3 -0
  43. package/app/types/index.ts +3 -0
  44. package/package.json +2 -2
  45. package/release-please-config.json +20 -0
  46. package/scripts/patch-electron-plist.js +41 -0
  47. package/src/app/core/services/database/database.service.ts +6 -0
  48. package/src/app/core/services/message/message.service.ts +33 -1
  49. package/src/app/core/services/presentation/presentation.service.ts +100 -1
  50. package/src/app/layout/footer/footer.component.html +13 -2
  51. package/src/app/layout/footer/footer.component.ts +18 -2
  52. package/src/app/layout/header/header.component.html +0 -10
  53. package/src/app/layout/header/header.component.ts +4 -23
  54. package/src/app/layout/navigation/navigation.component.html +66 -16
  55. package/src/app/layout/navigation/navigation.component.ts +65 -12
  56. package/src/app/settings/ai-keys/ai-keys.component.ts +13 -18
  57. package/src/app/settings/danger-zone/danger-zone.component.html +8 -0
  58. package/src/app/settings/danger-zone/danger-zone.component.ts +12 -0
  59. package/src/app/settings/news-api/news-api.component.ts +6 -8
  60. package/src/app/settings/theme/theme.component.html +15 -2
  61. package/src/app/settings/theme/theme.component.ts +4 -0
  62. package/src/app/shared/components/datagrid/datagrid.component.html +8 -17
  63. package/src/app/shared/components/datagrid/datagrid.component.ts +6 -10
  64. package/src/app/shared/components/glyphs/glyphs.component.html +5 -15
  65. package/src/app/shared/components/glyphs/glyphs.component.ts +3 -0
  66. package/src/app/shared/components/preview/preview.component.html +1 -1
  67. package/src/app/shared/components/preview/preview.component.ts +3 -8
  68. package/src/app/shared/components/prompt-dialog/prompt-dialog.component.html +2 -2
  69. package/src/app/shared/components/prompt-dialog/prompt-dialog.component.ts +2 -1
  70. package/src/app/shared/components/rule-builder/rule-builder.component.html +18 -6
  71. package/src/app/shared/components/rule-builder/rule-builder.component.ts +34 -2
  72. package/src/app/shared/components/search/search.component.html +9 -36
  73. package/src/app/shared/components/search/search.component.ts +2 -1
  74. package/src/app/shared/components/waterfall/waterfall.component.html +1 -3
  75. package/src/app/shared/components/waterfall/waterfall.component.ts +2 -1
  76. package/src/app/shared/directives/disabled-opacity/disabled-opacity.directive.ts +18 -0
  77. package/src/app/shared/directives/hover-highlight/hover-highlight.directive.ts +38 -0
  78. package/src/app/shared/directives/index.ts +5 -0
  79. package/src/app/shared/directives/modal-backdrop/modal-backdrop.directive.ts +18 -0
  80. package/src/app/shared/directives/scroll-reset/scroll-reset.directive.ts +15 -0
  81. package/src/app/shared/directives/stop-propagation/stop-propagation.directive.ts +12 -0
  82. package/src/assets/icons/favicon.256x256.png +0 -0
  83. package/src/assets/icons/favicon.512x512.png +0 -0
  84. package/src/assets/icons/favicon.icns +0 -0
  85. package/src/assets/icons/favicon.ico +0 -0
  86. package/src/assets/icons/favicon.png +0 -0
  87. package/src/favicon.ico +0 -0
@@ -12,15 +12,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
12
12
  const electron_1 = require("electron");
13
13
  const FontObject_1 = require("./FontObject");
14
14
  const AppLogger_1 = require("./AppLogger");
15
- // import { Logger } from '../database/entity/Logger.schema';
16
- // import { Store, StoreManyAndCountType } from '../database/entity/Store.schema';
17
15
  const ChannelType_1 = require("../enums/ChannelType");
18
16
  class MessageHandler {
19
- constructor(systemManager, configManager, connectionManager, fontManager) {
17
+ constructor(systemManager, configManager, connectionManager, fontManager, mainWindow) {
20
18
  this.systemManager = systemManager;
21
19
  this.configManager = configManager;
22
20
  this.connectionManager = connectionManager;
23
21
  this.fontManager = fontManager;
22
+ this.mainWindow = mainWindow;
24
23
  }
25
24
  on(channel, done) {
26
25
  return electron_1.ipcMain.on(channel, done);
@@ -44,6 +43,17 @@ class MessageHandler {
44
43
  this.handle(ChannelType_1.ChannelType.IPC_SET_CONFIG, (_event, args) => __awaiter(this, void 0, void 0, function* () { return this.configManager.set(args.key, args.values); }));
45
44
  this.handle(ChannelType_1.ChannelType.IPC_GET_CONFIG, (_event, args) => __awaiter(this, void 0, void 0, function* () { return this.configManager.get(args.key); }));
46
45
  this.handle(ChannelType_1.ChannelType.IPC_ZAP_CONFIG, (_event) => __awaiter(this, void 0, void 0, function* () { return this.configManager.clear(); }));
46
+ // Safe Storage
47
+ this.handle(ChannelType_1.ChannelType.IPC_SAFE_STORE, (_event, args) => __awaiter(this, void 0, void 0, function* () {
48
+ this.configManager.setSecure(args.key, args.value);
49
+ }));
50
+ this.handle(ChannelType_1.ChannelType.IPC_SAFE_RETRIEVE, (_event, key) => __awaiter(this, void 0, void 0, function* () {
51
+ return this.configManager.getSecure(key);
52
+ }));
53
+ // Session: Clear Cache
54
+ this.handle(ChannelType_1.ChannelType.IPC_CLEAR_CACHE, () => __awaiter(this, void 0, void 0, function* () {
55
+ yield electron_1.session.defaultSession.clearCache();
56
+ }));
47
57
  // Connection Manager
48
58
  this.handle(ChannelType_1.ChannelType.IPC_DBCONNECTION_CREATE, (_event, args) => __awaiter(this, void 0, void 0, function* () { return this.configManager.createDbConnection(args); }));
49
59
  this.handle(ChannelType_1.ChannelType.IPC_DBCONNECTION_ENABLE, (_event, args) => this.configManager.enableDbConnection(args));
@@ -68,21 +78,45 @@ class MessageHandler {
68
78
  this.handle(ChannelType_1.ChannelType.IPC_EXEC_CMD, (_event, args) => __awaiter(this, void 0, void 0, function* () { return this.fontManager.executeCommand(args).catch((err) => this.sendMessage('error', err.message)); }));
69
79
  this.handle(ChannelType_1.ChannelType.IPC_AUTH_USER, (_event, args) => __awaiter(this, void 0, void 0, function* () { return this.fontManager.systemAuthenticate(args); }));
70
80
  this.handle(ChannelType_1.ChannelType.IPC_SCAN_FILES, (_event, args) => __awaiter(this, void 0, void 0, function* () {
71
- console.log('[IPC_SCAN_FILES] args:', JSON.stringify(args));
72
- const catalogFiles = yield this.fontManager.copyFiles(args.files, args.collectionId);
73
- console.log('[IPC_SCAN_FILES] copied to catalog:', catalogFiles);
74
- yield this.fontManager.scanFiles(catalogFiles, { collection_id: args.collectionId });
75
- console.log('[IPC_SCAN_FILES] scan complete');
81
+ const { port1, port2 } = new electron_1.MessageChannelMain();
82
+ this.mainWindow.webContents.postMessage(ChannelType_1.ChannelType.IPC_SCAN_PROGRESS_PORT, null, [port2]);
83
+ const onProgress = (progress) => {
84
+ try {
85
+ port1.postMessage(progress);
86
+ }
87
+ catch (_a) {
88
+ // Port may be closed if renderer navigated away
89
+ }
90
+ };
91
+ try {
92
+ const catalogFiles = yield this.fontManager.copyFiles(args.files, args.collectionId);
93
+ yield this.fontManager.scanFiles(catalogFiles, { collection_id: args.collectionId }, onProgress);
94
+ }
95
+ finally {
96
+ port1.close();
97
+ }
76
98
  }));
77
99
  this.handle(ChannelType_1.ChannelType.IPC_SCAN_FOLDERS, (_event, args) => __awaiter(this, void 0, void 0, function* () {
78
- console.log('[IPC_SCAN_FOLDERS] args:', JSON.stringify(args));
79
- const promises = args.folders.map((sourceFolder) => __awaiter(this, void 0, void 0, function* () {
80
- const dest = yield this.fontManager.copyFolder(sourceFolder, args.collectionId);
81
- console.log('[IPC_SCAN_FOLDERS] copied to:', dest);
82
- yield this.fontManager.scanFolder(dest, { collection_id: args.collectionId });
83
- console.log('[IPC_SCAN_FOLDERS] scan complete for:', dest);
84
- }));
85
- yield Promise.allSettled(promises);
100
+ const { port1, port2 } = new electron_1.MessageChannelMain();
101
+ this.mainWindow.webContents.postMessage(ChannelType_1.ChannelType.IPC_SCAN_PROGRESS_PORT, null, [port2]);
102
+ const onProgress = (progress) => {
103
+ try {
104
+ port1.postMessage(progress);
105
+ }
106
+ catch (_a) {
107
+ // Port may be closed if renderer navigated away
108
+ }
109
+ };
110
+ try {
111
+ const promises = args.folders.map((sourceFolder) => __awaiter(this, void 0, void 0, function* () {
112
+ const dest = yield this.fontManager.copyFolder(sourceFolder, args.collectionId);
113
+ yield this.fontManager.scanFolder(dest, { collection_id: args.collectionId }, onProgress);
114
+ }));
115
+ yield Promise.allSettled(promises);
116
+ }
117
+ finally {
118
+ port1.close();
119
+ }
86
120
  }));
87
121
  this.handle(ChannelType_1.ChannelType.IPC_FETCH_NEWS, (_event, args) => __awaiter(this, void 0, void 0, function* () { return this.fontManager.fetchLatestNews(args); }));
88
122
  this.handle(ChannelType_1.ChannelType.IPC_SHOW_MESSAGE_BOX, (_event, options) => __awaiter(this, void 0, void 0, function* () { return this.fontManager.showMessageBox(options); }));
@@ -158,13 +192,30 @@ class MessageHandler {
158
192
  const sc = yield this.connectionManager.getSmartCollectionRepository().findOneBy({ id: args.id });
159
193
  if (!sc)
160
194
  return [[], 0];
161
- const rules = JSON.parse(sc.rules);
195
+ let rules;
196
+ try {
197
+ rules = JSON.parse(sc.rules);
198
+ }
199
+ catch (_a) {
200
+ return [[], 0];
201
+ }
162
202
  return yield this.connectionManager.getStoreRepository().evaluateSmartRules(rules, sc.match_type, {
163
203
  skip: args.skip,
164
204
  take: args.take,
165
205
  order: args.order,
166
206
  });
167
207
  }));
208
+ this.handle(ChannelType_1.ChannelType.IPC_SMART_COLLECTION_PREVIEW, (_event, args) => __awaiter(this, void 0, void 0, function* () {
209
+ var _a;
210
+ let rules;
211
+ try {
212
+ rules = typeof args.rules === 'string' ? JSON.parse(args.rules) : args.rules;
213
+ }
214
+ catch (_b) {
215
+ return [[], 0];
216
+ }
217
+ return yield this.connectionManager.getStoreRepository().evaluateSmartRules(rules, (_a = args.match_type) !== null && _a !== void 0 ? _a : 'AND', {});
218
+ }));
168
219
  // Store
169
220
  this.handle(ChannelType_1.ChannelType.IPC_STORE_FIND, (_event, args) => __awaiter(this, void 0, void 0, function* () {
170
221
  return yield this.connectionManager.getStore().find(args);
@@ -219,7 +270,7 @@ class MessageHandler {
219
270
  }));
220
271
  this.handle(ChannelType_1.ChannelType.IPC_FONT_METRICS, (_event, filePath) => __awaiter(this, void 0, void 0, function* () {
221
272
  var _a, _b, _c, _d, _e;
222
- const fontObject = new FontObject_1.default(filePath);
273
+ const fontObject = FontObject_1.default.fromCache(filePath);
223
274
  if (fontObject.hasError()) {
224
275
  return null;
225
276
  }
@@ -233,7 +284,7 @@ class MessageHandler {
233
284
  };
234
285
  }));
235
286
  this.handle(ChannelType_1.ChannelType.IPC_FONT_GLYPHS, (_event, filePath) => __awaiter(this, void 0, void 0, function* () {
236
- const fontObject = new FontObject_1.default(filePath);
287
+ const fontObject = FontObject_1.default.fromCache(filePath);
237
288
  if (fontObject.hasError()) {
238
289
  return [];
239
290
  }
@@ -1,4 +1,4 @@
1
- import { ipcMain, IpcMainEvent } from 'electron';
1
+ import { ipcMain, IpcMainEvent, BrowserWindow, MessageChannelMain, session } from 'electron';
2
2
 
3
3
  import SystemManager from './SystemManager';
4
4
  import ConfigManager from './ConfigManager';
@@ -8,8 +8,6 @@ import FontObject from './FontObject';
8
8
  import AppLogger from './AppLogger';
9
9
 
10
10
  import { Collection } from '../database/entity/Collection.schema';
11
- // import { Logger } from '../database/entity/Logger.schema';
12
- // import { Store, StoreManyAndCountType } from '../database/entity/Store.schema';
13
11
 
14
12
  import { ChannelType } from '../enums/ChannelType';
15
13
  import { StorageType } from '../enums/StorageType';
@@ -19,12 +17,20 @@ export default class MessageHandler {
19
17
  configManager: ConfigManager;
20
18
  connectionManager: ConnectionManager;
21
19
  fontManager: FontManager;
22
-
23
- constructor(systemManager: SystemManager, configManager: ConfigManager, connectionManager: ConnectionManager, fontManager: FontManager) {
20
+ mainWindow: BrowserWindow;
21
+
22
+ constructor(
23
+ systemManager: SystemManager,
24
+ configManager: ConfigManager,
25
+ connectionManager: ConnectionManager,
26
+ fontManager: FontManager,
27
+ mainWindow: BrowserWindow,
28
+ ) {
24
29
  this.systemManager = systemManager;
25
30
  this.configManager = configManager;
26
31
  this.connectionManager = connectionManager;
27
32
  this.fontManager = fontManager;
33
+ this.mainWindow = mainWindow;
28
34
  }
29
35
 
30
36
  on(channel: string, done: any) {
@@ -57,6 +63,22 @@ export default class MessageHandler {
57
63
  this.handle(ChannelType.IPC_GET_CONFIG, async (_event: IpcMainEvent, args: any) => this.configManager.get(args.key));
58
64
  this.handle(ChannelType.IPC_ZAP_CONFIG, async (_event: IpcMainEvent) => this.configManager.clear());
59
65
 
66
+ // Safe Storage
67
+
68
+ this.handle(ChannelType.IPC_SAFE_STORE, async (_event: IpcMainEvent, args: { key: string; value: string }) => {
69
+ this.configManager.setSecure(args.key, args.value);
70
+ });
71
+
72
+ this.handle(ChannelType.IPC_SAFE_RETRIEVE, async (_event: IpcMainEvent, key: string) => {
73
+ return this.configManager.getSecure(key);
74
+ });
75
+
76
+ // Session: Clear Cache
77
+
78
+ this.handle(ChannelType.IPC_CLEAR_CACHE, async () => {
79
+ await session.defaultSession.clearCache();
80
+ });
81
+
60
82
  // Connection Manager
61
83
 
62
84
  this.handle(ChannelType.IPC_DBCONNECTION_CREATE, async (_event: IpcMainEvent, args: any) =>
@@ -93,22 +115,46 @@ export default class MessageHandler {
93
115
  this.handle(ChannelType.IPC_AUTH_USER, async (_event: IpcMainEvent, args: any) => this.fontManager.systemAuthenticate(args));
94
116
 
95
117
  this.handle(ChannelType.IPC_SCAN_FILES, async (_event: IpcMainEvent, args: any) => {
96
- console.log('[IPC_SCAN_FILES] args:', JSON.stringify(args));
97
- const catalogFiles = await this.fontManager.copyFiles(args.files, args.collectionId);
98
- console.log('[IPC_SCAN_FILES] copied to catalog:', catalogFiles);
99
- await this.fontManager.scanFiles(catalogFiles, { collection_id: args.collectionId });
100
- console.log('[IPC_SCAN_FILES] scan complete');
118
+ const { port1, port2 } = new MessageChannelMain();
119
+ this.mainWindow.webContents.postMessage(ChannelType.IPC_SCAN_PROGRESS_PORT, null, [port2]);
120
+
121
+ const onProgress = (progress: any) => {
122
+ try {
123
+ port1.postMessage(progress);
124
+ } catch {
125
+ // Port may be closed if renderer navigated away
126
+ }
127
+ };
128
+
129
+ try {
130
+ const catalogFiles = await this.fontManager.copyFiles(args.files, args.collectionId);
131
+ await this.fontManager.scanFiles(catalogFiles, { collection_id: args.collectionId }, onProgress);
132
+ } finally {
133
+ port1.close();
134
+ }
101
135
  });
102
136
 
103
137
  this.handle(ChannelType.IPC_SCAN_FOLDERS, async (_event: IpcMainEvent, args: any) => {
104
- console.log('[IPC_SCAN_FOLDERS] args:', JSON.stringify(args));
105
- const promises = args.folders.map(async (sourceFolder: string) => {
106
- const dest = await this.fontManager.copyFolder(sourceFolder, args.collectionId);
107
- console.log('[IPC_SCAN_FOLDERS] copied to:', dest);
108
- await this.fontManager.scanFolder(dest, { collection_id: args.collectionId });
109
- console.log('[IPC_SCAN_FOLDERS] scan complete for:', dest);
110
- });
111
- await Promise.allSettled(promises);
138
+ const { port1, port2 } = new MessageChannelMain();
139
+ this.mainWindow.webContents.postMessage(ChannelType.IPC_SCAN_PROGRESS_PORT, null, [port2]);
140
+
141
+ const onProgress = (progress: any) => {
142
+ try {
143
+ port1.postMessage(progress);
144
+ } catch {
145
+ // Port may be closed if renderer navigated away
146
+ }
147
+ };
148
+
149
+ try {
150
+ const promises = args.folders.map(async (sourceFolder: string) => {
151
+ const dest = await this.fontManager.copyFolder(sourceFolder, args.collectionId);
152
+ await this.fontManager.scanFolder(dest, { collection_id: args.collectionId }, onProgress);
153
+ });
154
+ await Promise.allSettled(promises);
155
+ } finally {
156
+ port1.close();
157
+ }
112
158
  });
113
159
 
114
160
  this.handle(ChannelType.IPC_FETCH_NEWS, async (_event: IpcMainEvent, args: any) => this.fontManager.fetchLatestNews(args));
@@ -205,7 +251,12 @@ export default class MessageHandler {
205
251
  this.handle(ChannelType.IPC_SMART_COLLECTION_EVALUATE, async (_event: IpcMainEvent, args: any) => {
206
252
  const sc = await this.connectionManager.getSmartCollectionRepository().findOneBy({ id: args.id });
207
253
  if (!sc) return [[], 0];
208
- const rules = JSON.parse(sc.rules);
254
+ let rules;
255
+ try {
256
+ rules = JSON.parse(sc.rules);
257
+ } catch {
258
+ return [[], 0];
259
+ }
209
260
  return await this.connectionManager.getStoreRepository().evaluateSmartRules(rules, sc.match_type, {
210
261
  skip: args.skip,
211
262
  take: args.take,
@@ -213,6 +264,16 @@ export default class MessageHandler {
213
264
  });
214
265
  });
215
266
 
267
+ this.handle(ChannelType.IPC_SMART_COLLECTION_PREVIEW, async (_event: IpcMainEvent, args: any) => {
268
+ let rules;
269
+ try {
270
+ rules = typeof args.rules === 'string' ? JSON.parse(args.rules) : args.rules;
271
+ } catch {
272
+ return [[], 0];
273
+ }
274
+ return await this.connectionManager.getStoreRepository().evaluateSmartRules(rules, args.match_type ?? 'AND', {});
275
+ });
276
+
216
277
  // Store
217
278
 
218
279
  this.handle(ChannelType.IPC_STORE_FIND, async (_event: IpcMainEvent, args: any) => {
@@ -278,7 +339,7 @@ export default class MessageHandler {
278
339
  });
279
340
 
280
341
  this.handle(ChannelType.IPC_FONT_METRICS, async (_event: IpcMainEvent, filePath: string) => {
281
- const fontObject = new FontObject(filePath);
342
+ const fontObject = FontObject.fromCache(filePath);
282
343
  if (fontObject.hasError()) {
283
344
  return null;
284
345
  }
@@ -293,7 +354,7 @@ export default class MessageHandler {
293
354
  });
294
355
 
295
356
  this.handle(ChannelType.IPC_FONT_GLYPHS, async (_event: IpcMainEvent, filePath: string) => {
296
- const fontObject = new FontObject(filePath);
357
+ const fontObject = FontObject.fromCache(filePath);
297
358
  if (fontObject.hasError()) {
298
359
  return [];
299
360
  }
@@ -7,6 +7,7 @@ const system_1 = require("../config/system");
7
7
  const root = process.cwd();
8
8
  class ConfigManager {
9
9
  constructor(machineId, isProduction) {
10
+ this.cachedFontPaths = null;
10
11
  this.machineId = machineId;
11
12
  this.isProduction = isProduction;
12
13
  }
@@ -68,9 +69,12 @@ class ConfigManager {
68
69
  return path.join(os.tmpdir(), file);
69
70
  }
70
71
  getPlatformFontPaths() {
72
+ if (this.cachedFontPaths)
73
+ return this.cachedFontPaths;
71
74
  const paths = system_1.systemFontPaths.get(this.getPlatform()) || [];
72
75
  const home = os.homedir();
73
- return paths.map((p) => (p.startsWith('~') ? path.join(home, p.slice(1)) : p));
76
+ this.cachedFontPaths = paths.map((p) => (p.startsWith('~') ? path.join(home, p.slice(1)) : p));
77
+ return this.cachedFontPaths;
74
78
  }
75
79
  getUpTime() {
76
80
  let sec = os.uptime();
@@ -90,10 +90,14 @@ export default class ConfigManager {
90
90
  return path.join(os.tmpdir(), file);
91
91
  }
92
92
 
93
+ private cachedFontPaths: string[] | null = null;
94
+
93
95
  getPlatformFontPaths() {
96
+ if (this.cachedFontPaths) return this.cachedFontPaths;
94
97
  const paths = systemFontPaths.get(this.getPlatform()) || [];
95
98
  const home = os.homedir();
96
- return paths.map((p: string) => (p.startsWith('~') ? path.join(home, p.slice(1)) : p));
99
+ this.cachedFontPaths = paths.map((p: string) => (p.startsWith('~') ? path.join(home, p.slice(1)) : p));
100
+ return this.cachedFontPaths;
97
101
  }
98
102
 
99
103
  getUpTime() {
@@ -21,63 +21,63 @@ __decorate([
21
21
  ], Collection.prototype, "id", void 0);
22
22
  __decorate([
23
23
  (0, typeorm_1.Column)({
24
- length: 100
24
+ length: 100,
25
25
  }),
26
26
  __metadata("design:type", String)
27
27
  ], Collection.prototype, "title", void 0);
28
28
  __decorate([
29
29
  (0, typeorm_1.Column)({
30
- type: "int",
31
- default: 0
30
+ type: 'int',
31
+ default: 0,
32
32
  }),
33
33
  __metadata("design:type", Number)
34
34
  ], Collection.prototype, "parent_id", void 0);
35
35
  __decorate([
36
36
  (0, typeorm_1.Column)({
37
- type: "smallint",
38
- default: 0
37
+ type: 'smallint',
38
+ default: 0,
39
39
  }),
40
40
  __metadata("design:type", Number)
41
41
  ], Collection.prototype, "left_id", void 0);
42
42
  __decorate([
43
43
  (0, typeorm_1.Column)({
44
- type: "smallint",
45
- default: 0
44
+ type: 'smallint',
45
+ default: 0,
46
46
  }),
47
47
  __metadata("design:type", Number)
48
48
  ], Collection.prototype, "right_id", void 0);
49
49
  __decorate([
50
50
  (0, typeorm_1.Column)({
51
- type: "smallint",
52
- default: 0
51
+ type: 'smallint',
52
+ default: 0,
53
53
  }),
54
54
  __metadata("design:type", Number)
55
55
  ], Collection.prototype, "count", void 0);
56
56
  __decorate([
57
57
  (0, typeorm_1.Column)({
58
- type: "smallint",
59
- default: 0
58
+ type: 'smallint',
59
+ default: 0,
60
60
  }),
61
61
  __metadata("design:type", Number)
62
62
  ], Collection.prototype, "is_system", void 0);
63
63
  __decorate([
64
64
  (0, typeorm_1.Column)({
65
- type: "smallint",
66
- default: 0
65
+ type: 'smallint',
66
+ default: 0,
67
67
  }),
68
68
  __metadata("design:type", Number)
69
69
  ], Collection.prototype, "orderby", void 0);
70
70
  __decorate([
71
71
  (0, typeorm_1.Column)({
72
- type: "smallint",
73
- default: 0
72
+ type: 'smallint',
73
+ default: 0,
74
74
  }),
75
75
  __metadata("design:type", Boolean)
76
76
  ], Collection.prototype, "enabled", void 0);
77
77
  __decorate([
78
78
  (0, typeorm_1.Column)({
79
- type: "smallint",
80
- default: 0
79
+ type: 'smallint',
80
+ default: 0,
81
81
  }),
82
82
  __metadata("design:type", Boolean)
83
83
  ], Collection.prototype, "collapsed", void 0);
@@ -94,6 +94,8 @@ __decorate([
94
94
  __metadata("design:type", Array)
95
95
  ], Collection.prototype, "stores", void 0);
96
96
  exports.Collection = Collection = __decorate([
97
- (0, typeorm_1.Entity)()
97
+ (0, typeorm_1.Entity)(),
98
+ (0, typeorm_1.Index)(['left_id', 'right_id']),
99
+ (0, typeorm_1.Index)(['parent_id'])
98
100
  ], Collection);
99
101
  //# sourceMappingURL=Collection.schema.js.map
@@ -1,62 +1,63 @@
1
- import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, OneToMany } from "typeorm";
2
- import { Store } from "./Store.schema"
1
+ import { Entity, Column, Index, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, OneToMany } from 'typeorm';
2
+ import { Store } from './Store.schema';
3
3
 
4
4
  @Entity()
5
+ @Index(['left_id', 'right_id'])
6
+ @Index(['parent_id'])
5
7
  export class Collection {
6
-
7
8
  @PrimaryGeneratedColumn()
8
9
  id: number;
9
10
 
10
11
  @Column({
11
- length: 100
12
+ length: 100,
12
13
  })
13
14
  title: string;
14
15
 
15
16
  @Column({
16
- type: "int",
17
- default: 0
17
+ type: 'int',
18
+ default: 0,
18
19
  })
19
20
  parent_id: number;
20
21
 
21
22
  @Column({
22
- type: "smallint",
23
- default: 0
23
+ type: 'smallint',
24
+ default: 0,
24
25
  })
25
26
  left_id: number;
26
27
 
27
28
  @Column({
28
- type: "smallint",
29
- default: 0
29
+ type: 'smallint',
30
+ default: 0,
30
31
  })
31
32
  right_id: number;
32
33
 
33
34
  @Column({
34
- type: "smallint",
35
- default: 0
35
+ type: 'smallint',
36
+ default: 0,
36
37
  })
37
38
  count: number;
38
39
 
39
40
  @Column({
40
- type: "smallint",
41
- default: 0
41
+ type: 'smallint',
42
+ default: 0,
42
43
  })
43
44
  is_system: number;
44
45
 
45
46
  @Column({
46
- type: "smallint",
47
- default: 0
47
+ type: 'smallint',
48
+ default: 0,
48
49
  })
49
50
  orderby: number;
50
51
 
51
52
  @Column({
52
- type: "smallint",
53
- default: 0
53
+ type: 'smallint',
54
+ default: 0,
54
55
  })
55
56
  enabled: boolean;
56
57
 
57
58
  @Column({
58
- type: "smallint",
59
- default: 0
59
+ type: 'smallint',
60
+ default: 0,
60
61
  })
61
62
  collapsed: boolean;
62
63
 
@@ -64,5 +65,5 @@ export class Collection {
64
65
  @UpdateDateColumn() public updated: Date;
65
66
 
66
67
  @OneToMany(() => Store, (store) => store.collection, { cascade: true })
67
- stores: Store[]
68
+ stores: Store[];
68
69
  }
@@ -68,13 +68,7 @@ exports.CollectionRepository = {
68
68
  },
69
69
  updateCollectionCounts(items) {
70
70
  return __awaiter(this, void 0, void 0, function* () {
71
- return items.forEach((item) => __awaiter(this, void 0, void 0, function* () {
72
- return yield this.createQueryBuilder()
73
- .update(entity_1.Collection)
74
- .set({ count: item.total })
75
- .where('id = :id', { id: item.collection_id })
76
- .execute();
77
- }));
71
+ yield Promise.all(items.map((item) => this.createQueryBuilder().update(entity_1.Collection).set({ count: item.total }).where('id = :id', { id: item.collection_id }).execute()));
78
72
  });
79
73
  },
80
74
  updateCollection(collectionId, data) {
@@ -109,10 +103,12 @@ exports.CollectionRepository = {
109
103
  },
110
104
  createParent(title) {
111
105
  return __awaiter(this, void 0, void 0, function* () {
112
- const data = yield this.createQueryBuilder()
113
- .select('MAX(collection.right_id)', 'right_id')
114
- .addSelect('MAX(collection.orderby)', 'orderby')
115
- .getRawOne();
106
+ const data = yield this.createQueryBuilder().select('MAX(collection.right_id)', 'right_id').getRawOne();
107
+ yield this.createQueryBuilder()
108
+ .update(entity_1.Collection)
109
+ .set({ orderby: () => 'orderby + 1' })
110
+ .where({ parent_id: 0 })
111
+ .execute();
116
112
  return yield this.createQueryBuilder()
117
113
  .insert()
118
114
  .into(entity_1.Collection)
@@ -121,7 +117,7 @@ exports.CollectionRepository = {
121
117
  parent_id: 0,
122
118
  left_id: data.right_id + 1,
123
119
  right_id: data.right_id + 2,
124
- orderby: data.orderby + 1,
120
+ orderby: 0,
125
121
  })
126
122
  .execute();
127
123
  });
@@ -129,16 +125,21 @@ exports.CollectionRepository = {
129
125
  createChild(parentId, title) {
130
126
  return __awaiter(this, void 0, void 0, function* () {
131
127
  const row = yield this.findOne({ where: { id: parentId } });
132
- this.createQueryBuilder()
128
+ yield this.createQueryBuilder()
133
129
  .update(entity_1.Collection)
134
130
  .set({ left_id: () => 'left_id + 2', right_id: () => 'right_id + 2' })
135
131
  .where({ left_id: (0, typeorm_1.MoreThan)(row.right_id) })
136
132
  .execute();
137
- this.createQueryBuilder()
133
+ yield this.createQueryBuilder()
138
134
  .update(entity_1.Collection)
139
135
  .set({ right_id: () => 'right_id + 2' })
140
136
  .where({ left_id: (0, typeorm_1.LessThanOrEqual)(row.left_id), right_id: (0, typeorm_1.MoreThanOrEqual)(row.left_id) })
141
137
  .execute();
138
+ yield this.createQueryBuilder()
139
+ .update(entity_1.Collection)
140
+ .set({ orderby: () => 'orderby + 1' })
141
+ .where({ parent_id: parentId })
142
+ .execute();
142
143
  return yield this.createQueryBuilder()
143
144
  .insert()
144
145
  .into(entity_1.Collection)
@@ -147,7 +148,7 @@ exports.CollectionRepository = {
147
148
  parent_id: parentId,
148
149
  left_id: row.right_id,
149
150
  right_id: row.right_id + 1,
150
- orderby: row.orderby + 1,
151
+ orderby: 0,
151
152
  })
152
153
  .execute();
153
154
  });
@@ -249,9 +250,7 @@ exports.CollectionRepository = {
249
250
  .where('collection.parent_id = :parentId', { parentId: newParentId })
250
251
  .orderBy('collection.left_id', 'ASC')
251
252
  .getMany();
252
- for (let i = 0; i < newSiblings.length; i++) {
253
- yield this.createQueryBuilder().update(entity_1.Collection).set({ orderby: i }).where('id = :id', { id: newSiblings[i].id }).execute();
254
- }
253
+ yield Promise.all(newSiblings.map((sibling, i) => this.createQueryBuilder().update(entity_1.Collection).set({ orderby: i }).where('id = :id', { id: sibling.id }).execute()));
255
254
  });
256
255
  },
257
256
  createSystemCollection() {