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
@@ -72,13 +72,11 @@ export const CollectionRepository = {
72
72
  },
73
73
 
74
74
  async updateCollectionCounts(items: any[]) {
75
- return items.forEach(async (item) => {
76
- return await this.createQueryBuilder()
77
- .update(Collection)
78
- .set({ count: item.total })
79
- .where('id = :id', { id: item.collection_id })
80
- .execute();
81
- });
75
+ await Promise.all(
76
+ items.map((item) =>
77
+ this.createQueryBuilder().update(Collection).set({ count: item.total }).where('id = :id', { id: item.collection_id }).execute(),
78
+ ),
79
+ );
82
80
  },
83
81
 
84
82
  updateCollection(collectionId: number, data: any) {
@@ -112,10 +110,13 @@ export const CollectionRepository = {
112
110
  },
113
111
 
114
112
  async createParent(title: string) {
115
- const data = await this.createQueryBuilder()
116
- .select('MAX(collection.right_id)', 'right_id')
117
- .addSelect('MAX(collection.orderby)', 'orderby')
118
- .getRawOne();
113
+ const data = await this.createQueryBuilder().select('MAX(collection.right_id)', 'right_id').getRawOne();
114
+
115
+ await this.createQueryBuilder()
116
+ .update(Collection)
117
+ .set({ orderby: () => 'orderby + 1' })
118
+ .where({ parent_id: 0 })
119
+ .execute();
119
120
 
120
121
  return await this.createQueryBuilder()
121
122
  .insert()
@@ -125,7 +126,7 @@ export const CollectionRepository = {
125
126
  parent_id: 0,
126
127
  left_id: data.right_id + 1,
127
128
  right_id: data.right_id + 2,
128
- orderby: data.orderby + 1,
129
+ orderby: 0,
129
130
  })
130
131
  .execute();
131
132
  },
@@ -133,18 +134,24 @@ export const CollectionRepository = {
133
134
  async createChild(parentId: number, title: string) {
134
135
  const row = await this.findOne({ where: { id: parentId } });
135
136
 
136
- this.createQueryBuilder()
137
+ await this.createQueryBuilder()
137
138
  .update(Collection)
138
139
  .set({ left_id: () => 'left_id + 2', right_id: () => 'right_id + 2' })
139
140
  .where({ left_id: MoreThan(row.right_id) })
140
141
  .execute();
141
142
 
142
- this.createQueryBuilder()
143
+ await this.createQueryBuilder()
143
144
  .update(Collection)
144
145
  .set({ right_id: () => 'right_id + 2' })
145
146
  .where({ left_id: LessThanOrEqual(row.left_id), right_id: MoreThanOrEqual(row.left_id) })
146
147
  .execute();
147
148
 
149
+ await this.createQueryBuilder()
150
+ .update(Collection)
151
+ .set({ orderby: () => 'orderby + 1' })
152
+ .where({ parent_id: parentId })
153
+ .execute();
154
+
148
155
  return await this.createQueryBuilder()
149
156
  .insert()
150
157
  .into(Collection)
@@ -153,7 +160,7 @@ export const CollectionRepository = {
153
160
  parent_id: parentId,
154
161
  left_id: row.right_id,
155
162
  right_id: row.right_id + 1,
156
- orderby: row.orderby + 1,
163
+ orderby: 0,
157
164
  })
158
165
  .execute();
159
166
  },
@@ -264,9 +271,11 @@ export const CollectionRepository = {
264
271
  .orderBy('collection.left_id', 'ASC')
265
272
  .getMany();
266
273
 
267
- for (let i = 0; i < newSiblings.length; i++) {
268
- await this.createQueryBuilder().update(Collection).set({ orderby: i }).where('id = :id', { id: newSiblings[i].id }).execute();
269
- }
274
+ await Promise.all(
275
+ newSiblings.map((sibling: Collection, i: number) =>
276
+ this.createQueryBuilder().update(Collection).set({ orderby: i }).where('id = :id', { id: sibling.id }).execute(),
277
+ ),
278
+ );
270
279
  },
271
280
 
272
281
  async createSystemCollection() {
@@ -131,9 +131,8 @@ exports.StoreRepository = {
131
131
  }
132
132
  });
133
133
  }
134
- if (options.take) {
135
- db.limit(options.take);
136
- }
134
+ const MAX_SEARCH_LIMIT = 5000;
135
+ db.limit(Math.min(options.take || 500, MAX_SEARCH_LIMIT));
137
136
  if (options.skip) {
138
137
  db.offset(options.skip);
139
138
  }
@@ -298,13 +297,7 @@ exports.StoreRepository = {
298
297
  if (item.version && item.version !== '') {
299
298
  data.version = item.version;
300
299
  }
301
- //@todo Log errors in log table.
302
- return yield this.createQueryBuilder()
303
- .insert()
304
- .into(entity_1.Store)
305
- .values(data)
306
- .execute()
307
- .catch((err) => console.log('insert-error', err));
300
+ return yield this.createQueryBuilder().insert().into(entity_1.Store).values(data).execute();
308
301
  });
309
302
  },
310
303
  evaluateSmartRules(rules_1, matchType_1) {
@@ -416,15 +409,17 @@ exports.StoreRepository = {
416
409
  },
417
410
  fetchSystemStats() {
418
411
  return __awaiter(this, void 0, void 0, function* () {
419
- const rowCount = yield this.createQueryBuilder().select('COUNT(*)', 'total').getRawOne();
420
- const favoriteCount = yield this.createQueryBuilder().select('COUNT(*)', 'total').where('store.favorite = 1').getRawOne();
421
- const systemCount = yield this.createQueryBuilder().select('COUNT(*)', 'total').where('store.system = 1').getRawOne();
422
- const temporaryCount = yield this.createQueryBuilder().select('COUNT(*)', 'total').where('store.temporary = 1').getRawOne();
412
+ const stats = yield this.createQueryBuilder()
413
+ .select('COUNT(*)', 'rowCount')
414
+ .addSelect('SUM(CASE WHEN store.favorite = 1 THEN 1 ELSE 0 END)', 'favoriteCount')
415
+ .addSelect('SUM(CASE WHEN store.system = 1 THEN 1 ELSE 0 END)', 'systemCount')
416
+ .addSelect('SUM(CASE WHEN store.temporary = 1 THEN 1 ELSE 0 END)', 'temporaryCount')
417
+ .getRawOne();
423
418
  return {
424
- rowCount: rowCount.total,
425
- favoriteCount: favoriteCount.total,
426
- systemCount: systemCount.total,
427
- temporaryCount: temporaryCount.total,
419
+ rowCount: Number(stats.rowCount) || 0,
420
+ favoriteCount: Number(stats.favoriteCount) || 0,
421
+ systemCount: Number(stats.systemCount) || 0,
422
+ temporaryCount: Number(stats.temporaryCount) || 0,
428
423
  };
429
424
  });
430
425
  },
@@ -135,9 +135,8 @@ export const StoreRepository = {
135
135
  });
136
136
  }
137
137
 
138
- if (options.take) {
139
- db.limit(options.take);
140
- }
138
+ const MAX_SEARCH_LIMIT = 5000;
139
+ db.limit(Math.min(options.take || 500, MAX_SEARCH_LIMIT));
141
140
 
142
141
  if (options.skip) {
143
142
  db.offset(options.skip);
@@ -322,13 +321,7 @@ export const StoreRepository = {
322
321
  data.version = item.version;
323
322
  }
324
323
 
325
- //@todo Log errors in log table.
326
- return await this.createQueryBuilder()
327
- .insert()
328
- .into(Store)
329
- .values(data)
330
- .execute()
331
- .catch((err: any) => console.log('insert-error', err));
324
+ return await this.createQueryBuilder().insert().into(Store).values(data).execute();
332
325
  },
333
326
 
334
327
  async evaluateSmartRules(rules: SmartCollectionRule[], matchType: string, options: any = {}) {
@@ -437,19 +430,18 @@ export const StoreRepository = {
437
430
  },
438
431
 
439
432
  async fetchSystemStats() {
440
- const rowCount = await this.createQueryBuilder().select('COUNT(*)', 'total').getRawOne();
441
-
442
- const favoriteCount = await this.createQueryBuilder().select('COUNT(*)', 'total').where('store.favorite = 1').getRawOne();
443
-
444
- const systemCount = await this.createQueryBuilder().select('COUNT(*)', 'total').where('store.system = 1').getRawOne();
445
-
446
- const temporaryCount = await this.createQueryBuilder().select('COUNT(*)', 'total').where('store.temporary = 1').getRawOne();
433
+ const stats = await this.createQueryBuilder()
434
+ .select('COUNT(*)', 'rowCount')
435
+ .addSelect('SUM(CASE WHEN store.favorite = 1 THEN 1 ELSE 0 END)', 'favoriteCount')
436
+ .addSelect('SUM(CASE WHEN store.system = 1 THEN 1 ELSE 0 END)', 'systemCount')
437
+ .addSelect('SUM(CASE WHEN store.temporary = 1 THEN 1 ELSE 0 END)', 'temporaryCount')
438
+ .getRawOne();
447
439
 
448
440
  return {
449
- rowCount: rowCount.total,
450
- favoriteCount: favoriteCount.total,
451
- systemCount: systemCount.total,
452
- temporaryCount: temporaryCount.total,
441
+ rowCount: Number(stats.rowCount) || 0,
442
+ favoriteCount: Number(stats.favoriteCount) || 0,
443
+ systemCount: Number(stats.systemCount) || 0,
444
+ temporaryCount: Number(stats.temporaryCount) || 0,
453
445
  };
454
446
  },
455
447
  };
@@ -63,6 +63,24 @@ var ChannelType;
63
63
  ChannelType["IPC_SMART_COLLECTION_UPDATE"] = "IPC_SMART_COLLECTION_UPDATE";
64
64
  ChannelType["IPC_SMART_COLLECTION_DELETE"] = "IPC_SMART_COLLECTION_DELETE";
65
65
  ChannelType["IPC_SMART_COLLECTION_EVALUATE"] = "IPC_SMART_COLLECTION_EVALUATE";
66
+ ChannelType["IPC_SMART_COLLECTION_PREVIEW"] = "IPC_SMART_COLLECTION_PREVIEW";
66
67
  ChannelType["IPC_TOGGLE_PANEL"] = "IPC_TOGGLE_PANEL";
68
+ // Native Theme
69
+ ChannelType["IPC_GET_NATIVE_THEME"] = "IPC_GET_NATIVE_THEME";
70
+ ChannelType["IPC_NATIVE_THEME_CHANGED"] = "IPC_NATIVE_THEME_CHANGED";
71
+ // Safe Storage
72
+ ChannelType["IPC_SAFE_STORE"] = "IPC_SAFE_STORE";
73
+ ChannelType["IPC_SAFE_RETRIEVE"] = "IPC_SAFE_RETRIEVE";
74
+ // Session
75
+ ChannelType["IPC_CLEAR_CACHE"] = "IPC_CLEAR_CACHE";
76
+ // Power Monitor
77
+ ChannelType["IPC_POWER_SUSPEND"] = "IPC_POWER_SUSPEND";
78
+ ChannelType["IPC_POWER_RESUME"] = "IPC_POWER_RESUME";
79
+ ChannelType["IPC_POWER_SHUTDOWN"] = "IPC_POWER_SHUTDOWN";
80
+ ChannelType["IPC_POWER_LOCK_SCREEN"] = "IPC_POWER_LOCK_SCREEN";
81
+ // System Preferences
82
+ ChannelType["IPC_GET_SYSTEM_PREFERENCES"] = "IPC_GET_SYSTEM_PREFERENCES";
83
+ // Scan Progress (MessageChannelMain)
84
+ ChannelType["IPC_SCAN_PROGRESS_PORT"] = "IPC_SCAN_PROGRESS_PORT";
67
85
  })(ChannelType || (exports.ChannelType = ChannelType = {}));
68
86
  //# sourceMappingURL=ChannelType.js.map
@@ -68,6 +68,30 @@ export enum ChannelType {
68
68
  IPC_SMART_COLLECTION_UPDATE = 'IPC_SMART_COLLECTION_UPDATE',
69
69
  IPC_SMART_COLLECTION_DELETE = 'IPC_SMART_COLLECTION_DELETE',
70
70
  IPC_SMART_COLLECTION_EVALUATE = 'IPC_SMART_COLLECTION_EVALUATE',
71
+ IPC_SMART_COLLECTION_PREVIEW = 'IPC_SMART_COLLECTION_PREVIEW',
71
72
 
72
73
  IPC_TOGGLE_PANEL = 'IPC_TOGGLE_PANEL',
74
+
75
+ // Native Theme
76
+ IPC_GET_NATIVE_THEME = 'IPC_GET_NATIVE_THEME',
77
+ IPC_NATIVE_THEME_CHANGED = 'IPC_NATIVE_THEME_CHANGED',
78
+
79
+ // Safe Storage
80
+ IPC_SAFE_STORE = 'IPC_SAFE_STORE',
81
+ IPC_SAFE_RETRIEVE = 'IPC_SAFE_RETRIEVE',
82
+
83
+ // Session
84
+ IPC_CLEAR_CACHE = 'IPC_CLEAR_CACHE',
85
+
86
+ // Power Monitor
87
+ IPC_POWER_SUSPEND = 'IPC_POWER_SUSPEND',
88
+ IPC_POWER_RESUME = 'IPC_POWER_RESUME',
89
+ IPC_POWER_SHUTDOWN = 'IPC_POWER_SHUTDOWN',
90
+ IPC_POWER_LOCK_SCREEN = 'IPC_POWER_LOCK_SCREEN',
91
+
92
+ // System Preferences
93
+ IPC_GET_SYSTEM_PREFERENCES = 'IPC_GET_SYSTEM_PREFERENCES',
94
+
95
+ // Scan Progress (MessageChannelMain)
96
+ IPC_SCAN_PROGRESS_PORT = 'IPC_SCAN_PROGRESS_PORT',
73
97
  }
package/app/main.js CHANGED
@@ -1,14 +1,59 @@
1
1
  "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
2
11
  Object.defineProperty(exports, "__esModule", { value: true });
3
12
  const electron_1 = require("electron");
4
13
  const path = require("path");
5
14
  const fs = require("fs");
6
15
  const node_machine_id_1 = require("node-machine-id");
7
16
  const Application_1 = require("./Application");
17
+ const ChannelType_1 = require("./enums/ChannelType");
18
+ electron_1.app.name = 'Fontastic';
19
+ const iconPath = path.join(__dirname, '..', 'src', 'assets', 'icons', 'favicon.512x512.png');
20
+ electron_1.app.setAboutPanelOptions({
21
+ applicationName: 'Fontastic',
22
+ applicationVersion: electron_1.app.getVersion(),
23
+ iconPath,
24
+ });
8
25
  let win = null;
9
26
  let resolveAppReady;
10
- const appReadyPromise = new Promise(resolve => { resolveAppReady = resolve; });
11
- const args = process.argv.slice(1), serve = args.some(val => val === '--serve');
27
+ const appReadyPromise = new Promise((resolve) => {
28
+ resolveAppReady = resolve;
29
+ });
30
+ const args = process.argv.slice(1), serve = args.some((val) => val === '--serve');
31
+ // --- Native Theme ---
32
+ function getNativeThemeState() {
33
+ return {
34
+ shouldUseDarkColors: electron_1.nativeTheme.shouldUseDarkColors,
35
+ themeSource: electron_1.nativeTheme.themeSource,
36
+ };
37
+ }
38
+ electron_1.ipcMain.handle(ChannelType_1.ChannelType.IPC_GET_NATIVE_THEME, () => getNativeThemeState());
39
+ electron_1.nativeTheme.on('updated', () => {
40
+ if (win && !win.isDestroyed()) {
41
+ win.webContents.send(ChannelType_1.ChannelType.IPC_NATIVE_THEME_CHANGED, getNativeThemeState());
42
+ }
43
+ });
44
+ // --- System Preferences ---
45
+ function getSystemPreferencesState() {
46
+ var _a, _b;
47
+ let reduceMotion = false;
48
+ if (process.platform === 'darwin') {
49
+ reduceMotion = electron_1.systemPreferences.getAnimationSettings().prefersReducedMotion;
50
+ }
51
+ return {
52
+ accentColor: (_b = (_a = electron_1.systemPreferences.getAccentColor) === null || _a === void 0 ? void 0 : _a.call(electron_1.systemPreferences)) !== null && _b !== void 0 ? _b : '',
53
+ reduceMotion,
54
+ };
55
+ }
56
+ electron_1.ipcMain.handle(ChannelType_1.ChannelType.IPC_GET_SYSTEM_PREFERENCES, () => getSystemPreferencesState());
12
57
  function createWindow() {
13
58
  const size = electron_1.screen.getPrimaryDisplay().workAreaSize;
14
59
  // Create the browser window.
@@ -21,15 +66,15 @@ function createWindow() {
21
66
  nodeIntegration: true,
22
67
  allowRunningInsecureContent: serve,
23
68
  contextIsolation: false,
24
- webSecurity: !serve
69
+ webSecurity: !serve,
25
70
  },
26
71
  });
27
72
  (0, node_machine_id_1.machineId)(true).then((machineId) => new Application_1.default(machineId, !serve, win).initialize().then(() => resolveAppReady()));
28
73
  if (serve) {
29
- Promise.resolve().then(() => require('electron-debug')).then(debug => {
74
+ Promise.resolve().then(() => require('electron-debug')).then((debug) => {
30
75
  debug.default({ isEnabled: true, showDevTools: true });
31
76
  });
32
- Promise.resolve().then(() => require('electron-reloader')).then(reloader => {
77
+ Promise.resolve().then(() => require('electron-reloader')).then((reloader) => {
33
78
  const reloaderFn = reloader.default || reloader;
34
79
  // watchRenderer: false — Angular dev server handles HMR for the renderer.
35
80
  // Without this, reloader triggers spurious reloads on macOS (issue #840).
@@ -58,10 +103,12 @@ function createWindow() {
58
103
  return win;
59
104
  }
60
105
  try {
61
- electron_1.protocol.registerSchemesAsPrivileged([{
106
+ electron_1.protocol.registerSchemesAsPrivileged([
107
+ {
62
108
  scheme: 'font',
63
- privileges: { bypassCSP: true, supportFetchAPI: true }
64
- }]);
109
+ privileges: { bypassCSP: true, supportFetchAPI: true },
110
+ },
111
+ ]);
65
112
  electron_1.ipcMain.handle('app:get-version', () => electron_1.app.getVersion());
66
113
  electron_1.ipcMain.handle('app:ready', () => appReadyPromise);
67
114
  // This method will be called when Electron has finished
@@ -69,9 +116,50 @@ try {
69
116
  // Some APIs can only be used after this event occurs.
70
117
  // Added 400 ms to fix the black background issue while using transparent window. More detais at https://github.com/electron/electron/issues/15947
71
118
  electron_1.app.on('ready', () => {
72
- electron_1.protocol.handle('font', (request) => {
119
+ electron_1.protocol.handle('font', (request) => __awaiter(void 0, void 0, void 0, function* () {
73
120
  const filePath = decodeURIComponent(request.url.replace('font://', ''));
74
- return electron_1.net.fetch(`file://${filePath}`);
121
+ try {
122
+ return yield electron_1.net.fetch(`file://${filePath}`);
123
+ }
124
+ catch (_a) {
125
+ return new Response('Not found', { status: 404 });
126
+ }
127
+ }));
128
+ // --- Session: deny unnecessary permissions ---
129
+ electron_1.session.defaultSession.setPermissionRequestHandler((_webContents, permission, callback) => {
130
+ const allowed = ['clipboard-read', 'clipboard-sanitized-write'];
131
+ callback(allowed.includes(permission));
132
+ });
133
+ // --- Session: set CSP in production ---
134
+ if (!serve) {
135
+ electron_1.session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
136
+ callback({
137
+ responseHeaders: Object.assign(Object.assign({}, details.responseHeaders), { 'Content-Security-Policy': [
138
+ "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; font-src 'self' font:; img-src 'self' data: https:;",
139
+ ] }),
140
+ });
141
+ });
142
+ }
143
+ // --- Power Monitor ---
144
+ electron_1.powerMonitor.on('suspend', () => {
145
+ if (win && !win.isDestroyed()) {
146
+ win.webContents.send(ChannelType_1.ChannelType.IPC_POWER_SUSPEND);
147
+ }
148
+ });
149
+ electron_1.powerMonitor.on('resume', () => {
150
+ if (win && !win.isDestroyed()) {
151
+ win.webContents.send(ChannelType_1.ChannelType.IPC_POWER_RESUME);
152
+ }
153
+ });
154
+ electron_1.powerMonitor.on('shutdown', () => {
155
+ if (win && !win.isDestroyed()) {
156
+ win.webContents.send(ChannelType_1.ChannelType.IPC_POWER_SHUTDOWN);
157
+ }
158
+ });
159
+ electron_1.powerMonitor.on('lock-screen', () => {
160
+ if (win && !win.isDestroyed()) {
161
+ win.webContents.send(ChannelType_1.ChannelType.IPC_POWER_LOCK_SCREEN);
162
+ }
75
163
  });
76
164
  setTimeout(createWindow, 400);
77
165
  });
package/app/main.ts CHANGED
@@ -1,18 +1,76 @@
1
- import {app, BrowserWindow, ipcMain, net, protocol, screen} from 'electron';
1
+ import {
2
+ app,
3
+ BrowserWindow,
4
+ ipcMain,
5
+ nativeImage,
6
+ nativeTheme,
7
+ net,
8
+ powerMonitor,
9
+ protocol,
10
+ screen,
11
+ session,
12
+ systemPreferences,
13
+ } from 'electron';
2
14
  import * as path from 'path';
3
15
  import * as fs from 'fs';
4
16
  import { machineId } from 'node-machine-id';
5
17
  import Application from './Application';
18
+ import { ChannelType } from './enums/ChannelType';
19
+ import type { NativeThemeState, SystemPreferencesState } from './types';
20
+
21
+ app.name = 'Fontastic';
22
+
23
+ const iconPath = path.join(__dirname, '..', 'src', 'assets', 'icons', 'favicon.512x512.png');
24
+
25
+ app.setAboutPanelOptions({
26
+ applicationName: 'Fontastic',
27
+ applicationVersion: app.getVersion(),
28
+ iconPath,
29
+ });
6
30
 
7
31
  let win: BrowserWindow | null = null;
8
32
  let resolveAppReady: () => void;
9
- const appReadyPromise = new Promise<void>(resolve => { resolveAppReady = resolve; });
33
+ const appReadyPromise = new Promise<void>((resolve) => {
34
+ resolveAppReady = resolve;
35
+ });
10
36
 
11
37
  const args = process.argv.slice(1),
12
- serve = args.some(val => val === '--serve');
38
+ serve = args.some((val) => val === '--serve');
13
39
 
14
- function createWindow(): BrowserWindow {
40
+ // --- Native Theme ---
41
+
42
+ function getNativeThemeState(): NativeThemeState {
43
+ return {
44
+ shouldUseDarkColors: nativeTheme.shouldUseDarkColors,
45
+ themeSource: nativeTheme.themeSource,
46
+ };
47
+ }
48
+
49
+ ipcMain.handle(ChannelType.IPC_GET_NATIVE_THEME, () => getNativeThemeState());
50
+
51
+ nativeTheme.on('updated', () => {
52
+ if (win && !win.isDestroyed()) {
53
+ win.webContents.send(ChannelType.IPC_NATIVE_THEME_CHANGED, getNativeThemeState());
54
+ }
55
+ });
56
+
57
+ // --- System Preferences ---
58
+
59
+ function getSystemPreferencesState(): SystemPreferencesState {
60
+ let reduceMotion = false;
61
+ if (process.platform === 'darwin') {
62
+ reduceMotion = systemPreferences.getAnimationSettings().prefersReducedMotion;
63
+ }
15
64
 
65
+ return {
66
+ accentColor: systemPreferences.getAccentColor?.() ?? '',
67
+ reduceMotion,
68
+ };
69
+ }
70
+
71
+ ipcMain.handle(ChannelType.IPC_GET_SYSTEM_PREFERENCES, () => getSystemPreferencesState());
72
+
73
+ function createWindow(): BrowserWindow {
16
74
  const size = screen.getPrimaryDisplay().workAreaSize;
17
75
 
18
76
  // Create the browser window.
@@ -25,20 +83,18 @@ function createWindow(): BrowserWindow {
25
83
  nodeIntegration: true,
26
84
  allowRunningInsecureContent: serve,
27
85
  contextIsolation: false,
28
- webSecurity: !serve
86
+ webSecurity: !serve,
29
87
  },
30
88
  });
31
89
 
32
- machineId(true).then((machineId: string) =>
33
- new Application(machineId, !serve, win!).initialize().then(() => resolveAppReady())
34
- );
90
+ machineId(true).then((machineId: string) => new Application(machineId, !serve, win!).initialize().then(() => resolveAppReady()));
35
91
 
36
92
  if (serve) {
37
- import('electron-debug').then(debug => {
38
- debug.default({isEnabled: true, showDevTools: true});
93
+ import('electron-debug').then((debug) => {
94
+ debug.default({ isEnabled: true, showDevTools: true });
39
95
  });
40
96
 
41
- import('electron-reloader').then(reloader => {
97
+ import('electron-reloader').then((reloader) => {
42
98
  const reloaderFn = (reloader as any).default || reloader;
43
99
  // watchRenderer: false — Angular dev server handles HMR for the renderer.
44
100
  // Without this, reloader triggers spurious reloads on macOS (issue #840).
@@ -50,7 +106,7 @@ function createWindow(): BrowserWindow {
50
106
  let pathIndex = './browser/index.html';
51
107
 
52
108
  if (fs.existsSync(path.join(__dirname, '../dist/browser/index.html'))) {
53
- // Path when running electron in local folder
109
+ // Path when running electron in local folder
54
110
  pathIndex = '../dist/browser/index.html';
55
111
  }
56
112
 
@@ -71,10 +127,12 @@ function createWindow(): BrowserWindow {
71
127
  }
72
128
 
73
129
  try {
74
- protocol.registerSchemesAsPrivileged([{
75
- scheme: 'font',
76
- privileges: { bypassCSP: true, supportFetchAPI: true }
77
- }]);
130
+ protocol.registerSchemesAsPrivileged([
131
+ {
132
+ scheme: 'font',
133
+ privileges: { bypassCSP: true, supportFetchAPI: true },
134
+ },
135
+ ]);
78
136
 
79
137
  ipcMain.handle('app:get-version', () => app.getVersion());
80
138
  ipcMain.handle('app:ready', () => appReadyPromise);
@@ -84,10 +142,60 @@ try {
84
142
  // Some APIs can only be used after this event occurs.
85
143
  // Added 400 ms to fix the black background issue while using transparent window. More detais at https://github.com/electron/electron/issues/15947
86
144
  app.on('ready', () => {
87
- protocol.handle('font', (request) => {
145
+ protocol.handle('font', async (request) => {
88
146
  const filePath = decodeURIComponent(request.url.replace('font://', ''));
89
- return net.fetch(`file://${filePath}`);
147
+ try {
148
+ return await net.fetch(`file://${filePath}`);
149
+ } catch {
150
+ return new Response('Not found', { status: 404 });
151
+ }
152
+ });
153
+
154
+ // --- Session: deny unnecessary permissions ---
155
+ session.defaultSession.setPermissionRequestHandler((_webContents, permission, callback) => {
156
+ const allowed = ['clipboard-read', 'clipboard-sanitized-write'];
157
+ callback(allowed.includes(permission));
158
+ });
159
+
160
+ // --- Session: set CSP in production ---
161
+ if (!serve) {
162
+ session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
163
+ callback({
164
+ responseHeaders: {
165
+ ...details.responseHeaders,
166
+ 'Content-Security-Policy': [
167
+ "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; font-src 'self' font:; img-src 'self' data: https:;",
168
+ ],
169
+ },
170
+ });
171
+ });
172
+ }
173
+
174
+ // --- Power Monitor ---
175
+ powerMonitor.on('suspend', () => {
176
+ if (win && !win.isDestroyed()) {
177
+ win.webContents.send(ChannelType.IPC_POWER_SUSPEND);
178
+ }
90
179
  });
180
+
181
+ powerMonitor.on('resume', () => {
182
+ if (win && !win.isDestroyed()) {
183
+ win.webContents.send(ChannelType.IPC_POWER_RESUME);
184
+ }
185
+ });
186
+
187
+ powerMonitor.on('shutdown', () => {
188
+ if (win && !win.isDestroyed()) {
189
+ win.webContents.send(ChannelType.IPC_POWER_SHUTDOWN);
190
+ }
191
+ });
192
+
193
+ powerMonitor.on('lock-screen', () => {
194
+ if (win && !win.isDestroyed()) {
195
+ win.webContents.send(ChannelType.IPC_POWER_LOCK_SCREEN);
196
+ }
197
+ });
198
+
91
199
  setTimeout(createWindow, 400);
92
200
  });
93
201
 
@@ -107,7 +215,6 @@ try {
107
215
  createWindow();
108
216
  }
109
217
  });
110
-
111
218
  } catch (e) {
112
219
  // Catch Error
113
220
  // throw e;
package/app/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "name": "Tom Shaw",
6
6
  "email": ""
7
7
  },
8
- "version": "1.1.0",
8
+ "version": "1.3.0",
9
9
  "main": "main.js",
10
10
  "private": true,
11
11
  "dependencies": {
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=NativeThemeState.js.map
@@ -0,0 +1,4 @@
1
+ export interface NativeThemeState {
2
+ shouldUseDarkColors: boolean;
3
+ themeSource: 'system' | 'light' | 'dark';
4
+ }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=ScanProgress.js.map
@@ -0,0 +1,6 @@
1
+ export interface ScanProgress {
2
+ processed: number;
3
+ total: number;
4
+ currentFile: string;
5
+ errors: number;
6
+ }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=SystemPreferencesState.js.map
@@ -0,0 +1,4 @@
1
+ export interface SystemPreferencesState {
2
+ accentColor: string;
3
+ reduceMotion: boolean;
4
+ }
@@ -24,4 +24,7 @@ __exportStar(require("./SystemStats"), exports);
24
24
  __exportStar(require("./SystemConfig"), exports);
25
25
  __exportStar(require("./ImportOptions"), exports);
26
26
  __exportStar(require("./SmartCollection"), exports);
27
+ __exportStar(require("./NativeThemeState"), exports);
28
+ __exportStar(require("./SystemPreferencesState"), exports);
29
+ __exportStar(require("./ScanProgress"), exports);
27
30
  //# sourceMappingURL=index.js.map