fontastic 1.2.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.
- package/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +22 -0
- package/README.md +6 -0
- package/app/Application.js +4 -4
- package/app/Application.ts +13 -13
- package/app/config/database.js +1 -1
- package/app/config/database.ts +1 -1
- package/app/config/mimes.js +23 -23
- package/app/config/mimes.ts +35 -29
- package/app/core/ConfigManager.js +27 -0
- package/app/core/ConfigManager.ts +28 -0
- package/app/core/FontFinder.js +66 -15
- package/app/core/FontFinder.ts +81 -22
- package/app/core/FontManager.js +20 -18
- package/app/core/FontManager.ts +21 -19
- package/app/core/FontObject.js +44 -24
- package/app/core/FontObject.ts +47 -27
- package/app/core/MessageHandler.js +70 -19
- package/app/core/MessageHandler.ts +82 -21
- package/app/core/SystemManager.js +5 -1
- package/app/core/SystemManager.ts +5 -1
- package/app/database/entity/Collection.schema.js +20 -18
- package/app/database/entity/Collection.schema.ts +22 -21
- package/app/database/repository/Collection.repository.js +17 -18
- package/app/database/repository/Collection.repository.ts +27 -18
- package/app/database/repository/Store.repository.js +13 -18
- package/app/database/repository/Store.repository.ts +13 -21
- package/app/enums/ChannelType.js +18 -0
- package/app/enums/ChannelType.ts +24 -0
- package/app/main.js +79 -2
- package/app/main.ts +100 -3
- package/app/package.json +1 -1
- package/app/types/NativeThemeState.js +3 -0
- package/app/types/NativeThemeState.ts +4 -0
- package/app/types/ScanProgress.js +3 -0
- package/app/types/ScanProgress.ts +6 -0
- package/app/types/SystemPreferencesState.js +3 -0
- package/app/types/SystemPreferencesState.ts +4 -0
- package/app/types/index.js +3 -0
- package/app/types/index.ts +3 -0
- package/package.json +1 -1
- package/src/app/core/services/database/database.service.ts +6 -0
- package/src/app/core/services/message/message.service.ts +33 -1
- package/src/app/core/services/presentation/presentation.service.ts +93 -1
- package/src/app/layout/footer/footer.component.html +13 -2
- package/src/app/layout/footer/footer.component.ts +18 -2
- package/src/app/layout/navigation/navigation.component.html +11 -9
- package/src/app/layout/navigation/navigation.component.ts +35 -0
- package/src/app/settings/ai-keys/ai-keys.component.ts +13 -18
- package/src/app/settings/danger-zone/danger-zone.component.html +8 -0
- package/src/app/settings/danger-zone/danger-zone.component.ts +12 -0
- package/src/app/settings/news-api/news-api.component.ts +6 -8
- package/src/app/settings/theme/theme.component.html +15 -2
- package/src/app/settings/theme/theme.component.ts +4 -0
- package/src/app/shared/components/datagrid/datagrid.component.html +8 -17
- package/src/app/shared/components/datagrid/datagrid.component.ts +6 -10
- package/src/app/shared/components/glyphs/glyphs.component.html +5 -15
- package/src/app/shared/components/glyphs/glyphs.component.ts +3 -0
- package/src/app/shared/components/preview/preview.component.html +1 -1
- package/src/app/shared/components/preview/preview.component.ts +3 -8
- package/src/app/shared/components/prompt-dialog/prompt-dialog.component.html +2 -2
- package/src/app/shared/components/prompt-dialog/prompt-dialog.component.ts +2 -1
- package/src/app/shared/components/rule-builder/rule-builder.component.html +18 -6
- package/src/app/shared/components/rule-builder/rule-builder.component.ts +34 -2
- package/src/app/shared/components/search/search.component.html +9 -36
- package/src/app/shared/components/search/search.component.ts +2 -1
- package/src/app/shared/components/waterfall/waterfall.component.html +1 -3
- package/src/app/shared/components/waterfall/waterfall.component.ts +2 -1
- package/src/app/shared/directives/disabled-opacity/disabled-opacity.directive.ts +18 -0
- package/src/app/shared/directives/hover-highlight/hover-highlight.directive.ts +38 -0
- package/src/app/shared/directives/index.ts +5 -0
- package/src/app/shared/directives/modal-backdrop/modal-backdrop.directive.ts +18 -0
- package/src/app/shared/directives/scroll-reset/scroll-reset.directive.ts +15 -0
- package/src/app/shared/directives/stop-propagation/stop-propagation.directive.ts +12 -0
|
@@ -135,9 +135,8 @@ export const StoreRepository = {
|
|
|
135
135
|
});
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
|
|
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
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
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
|
|
450
|
-
favoriteCount: favoriteCount
|
|
451
|
-
systemCount: systemCount
|
|
452
|
-
temporaryCount: temporaryCount
|
|
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
|
};
|
package/app/enums/ChannelType.js
CHANGED
|
@@ -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
|
package/app/enums/ChannelType.ts
CHANGED
|
@@ -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,10 +1,20 @@
|
|
|
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");
|
|
8
18
|
electron_1.app.name = 'Fontastic';
|
|
9
19
|
const iconPath = path.join(__dirname, '..', 'src', 'assets', 'icons', 'favicon.512x512.png');
|
|
10
20
|
electron_1.app.setAboutPanelOptions({
|
|
@@ -18,6 +28,32 @@ const appReadyPromise = new Promise((resolve) => {
|
|
|
18
28
|
resolveAppReady = resolve;
|
|
19
29
|
});
|
|
20
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());
|
|
21
57
|
function createWindow() {
|
|
22
58
|
const size = electron_1.screen.getPrimaryDisplay().workAreaSize;
|
|
23
59
|
// Create the browser window.
|
|
@@ -80,9 +116,50 @@ try {
|
|
|
80
116
|
// Some APIs can only be used after this event occurs.
|
|
81
117
|
// Added 400 ms to fix the black background issue while using transparent window. More detais at https://github.com/electron/electron/issues/15947
|
|
82
118
|
electron_1.app.on('ready', () => {
|
|
83
|
-
electron_1.protocol.handle('font', (request) => {
|
|
119
|
+
electron_1.protocol.handle('font', (request) => __awaiter(void 0, void 0, void 0, function* () {
|
|
84
120
|
const filePath = decodeURIComponent(request.url.replace('font://', ''));
|
|
85
|
-
|
|
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
|
+
}
|
|
86
163
|
});
|
|
87
164
|
setTimeout(createWindow, 400);
|
|
88
165
|
});
|
package/app/main.ts
CHANGED
|
@@ -1,8 +1,22 @@
|
|
|
1
|
-
import {
|
|
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';
|
|
6
20
|
|
|
7
21
|
app.name = 'Fontastic';
|
|
8
22
|
|
|
@@ -23,6 +37,39 @@ const appReadyPromise = new Promise<void>((resolve) => {
|
|
|
23
37
|
const args = process.argv.slice(1),
|
|
24
38
|
serve = args.some((val) => val === '--serve');
|
|
25
39
|
|
|
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
|
+
}
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
accentColor: systemPreferences.getAccentColor?.() ?? '',
|
|
67
|
+
reduceMotion,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
ipcMain.handle(ChannelType.IPC_GET_SYSTEM_PREFERENCES, () => getSystemPreferencesState());
|
|
72
|
+
|
|
26
73
|
function createWindow(): BrowserWindow {
|
|
27
74
|
const size = screen.getPrimaryDisplay().workAreaSize;
|
|
28
75
|
|
|
@@ -95,10 +142,60 @@ try {
|
|
|
95
142
|
// Some APIs can only be used after this event occurs.
|
|
96
143
|
// Added 400 ms to fix the black background issue while using transparent window. More detais at https://github.com/electron/electron/issues/15947
|
|
97
144
|
app.on('ready', () => {
|
|
98
|
-
protocol.handle('font', (request) => {
|
|
145
|
+
protocol.handle('font', async (request) => {
|
|
99
146
|
const filePath = decodeURIComponent(request.url.replace('font://', ''));
|
|
100
|
-
|
|
147
|
+
try {
|
|
148
|
+
return await net.fetch(`file://${filePath}`);
|
|
149
|
+
} catch {
|
|
150
|
+
return new Response('Not found', { status: 404 });
|
|
151
|
+
}
|
|
101
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
|
+
}
|
|
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
|
+
|
|
102
199
|
setTimeout(createWindow, 400);
|
|
103
200
|
});
|
|
104
201
|
|
package/app/package.json
CHANGED
package/app/types/index.js
CHANGED
|
@@ -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
|
package/app/types/index.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fontastic",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Fontastic is an Electron-based font management and cataloging application built for organizing, browsing, and inspecting font libraries.",
|
|
5
5
|
"homepage": "https://github.com/tomshaw/fontastic",
|
|
6
6
|
"private": false,
|
|
@@ -371,6 +371,12 @@ export class DatabaseService {
|
|
|
371
371
|
);
|
|
372
372
|
}
|
|
373
373
|
|
|
374
|
+
smartCollectionPreview(rules: any[], matchType: string): Promise<number> {
|
|
375
|
+
return this.message.smartCollectionPreview(rules, matchType).then((result) => {
|
|
376
|
+
return result[1] as number;
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
|
|
374
380
|
// Store
|
|
375
381
|
|
|
376
382
|
storeFind(args: any): Promise<Store[]> {
|
|
@@ -4,7 +4,7 @@ import type { Collection } from '@main/database/entity/Collection.schema';
|
|
|
4
4
|
import type { Logger } from '@main/database/entity/Logger.schema';
|
|
5
5
|
import type { Store, StoreManyAndCountType } from '@main/database/entity/Store.schema';
|
|
6
6
|
import { ChannelType } from '@main/enums';
|
|
7
|
-
import type { FontMetrics, SystemConfig, SystemStats } from '@main/types';
|
|
7
|
+
import type { FontMetrics, NativeThemeState, SystemConfig, SystemPreferencesState, SystemStats } from '@main/types';
|
|
8
8
|
import type { SmartCollection } from '@main/database/entity/SmartCollection.schema';
|
|
9
9
|
|
|
10
10
|
@Injectable({
|
|
@@ -236,6 +236,10 @@ export class MessageService {
|
|
|
236
236
|
return this.invoke<StoreManyAndCountType>(ChannelType.IPC_SMART_COLLECTION_EVALUATE, { id, ...options });
|
|
237
237
|
}
|
|
238
238
|
|
|
239
|
+
smartCollectionPreview(rules: any[], matchType: string): Promise<StoreManyAndCountType> {
|
|
240
|
+
return this.invoke<StoreManyAndCountType>(ChannelType.IPC_SMART_COLLECTION_PREVIEW, { rules, match_type: matchType });
|
|
241
|
+
}
|
|
242
|
+
|
|
239
243
|
// Store
|
|
240
244
|
|
|
241
245
|
storeFind(args: any): Promise<Store[]> {
|
|
@@ -311,4 +315,32 @@ export class MessageService {
|
|
|
311
315
|
loggerTruncate(): Promise<Logger[]> {
|
|
312
316
|
return this.invoke<Logger[]>(ChannelType.IPC_LOGGER_TRUNCATE);
|
|
313
317
|
}
|
|
318
|
+
|
|
319
|
+
// Native Theme
|
|
320
|
+
|
|
321
|
+
getNativeTheme(): Promise<NativeThemeState> {
|
|
322
|
+
return this.invoke<NativeThemeState>(ChannelType.IPC_GET_NATIVE_THEME);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Safe Storage
|
|
326
|
+
|
|
327
|
+
safeStore(key: string, value: string): Promise<void> {
|
|
328
|
+
return this.invoke(ChannelType.IPC_SAFE_STORE, { key, value });
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
safeRetrieve(key: string): Promise<string | null> {
|
|
332
|
+
return this.invoke<string | null>(ChannelType.IPC_SAFE_RETRIEVE, key);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Session
|
|
336
|
+
|
|
337
|
+
clearCache(): Promise<void> {
|
|
338
|
+
return this.invoke(ChannelType.IPC_CLEAR_CACHE);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// System Preferences
|
|
342
|
+
|
|
343
|
+
getSystemPreferences(): Promise<SystemPreferencesState> {
|
|
344
|
+
return this.invoke<SystemPreferencesState>(ChannelType.IPC_GET_SYSTEM_PREFERENCES);
|
|
345
|
+
}
|
|
314
346
|
}
|
|
@@ -2,7 +2,7 @@ import { Injectable, inject, signal, computed, effect, untracked } from '@angula
|
|
|
2
2
|
import { ElectronService } from '../electron/electron.service';
|
|
3
3
|
import { MessageService } from '../message/message.service';
|
|
4
4
|
import { ChannelType, StorageType } from '@main/enums';
|
|
5
|
-
import type { LayoutPanelType, LayoutPreviewType } from '@main/types';
|
|
5
|
+
import type { LayoutPanelType, LayoutPreviewType, NativeThemeState, ScanProgress } from '@main/types';
|
|
6
6
|
|
|
7
7
|
@Injectable({ providedIn: 'root' })
|
|
8
8
|
export class PresentationService {
|
|
@@ -21,6 +21,7 @@ export class PresentationService {
|
|
|
21
21
|
this.loadingCount--;
|
|
22
22
|
if (this.loadingCount === 0) {
|
|
23
23
|
this.loading.set(false);
|
|
24
|
+
this.scanProgress.set(null);
|
|
24
25
|
}
|
|
25
26
|
}
|
|
26
27
|
|
|
@@ -28,6 +29,20 @@ export class PresentationService {
|
|
|
28
29
|
|
|
29
30
|
readonly theme = signal<string>('light');
|
|
30
31
|
|
|
32
|
+
// Native Theme: auto-sync with OS dark mode
|
|
33
|
+
readonly autoTheme = signal(false);
|
|
34
|
+
readonly nativeThemeState = signal<NativeThemeState | null>(null);
|
|
35
|
+
|
|
36
|
+
// System Preferences
|
|
37
|
+
readonly systemAccentColor = signal('');
|
|
38
|
+
readonly reduceMotion = signal(false);
|
|
39
|
+
|
|
40
|
+
// Power Monitor
|
|
41
|
+
readonly suspended = signal(false);
|
|
42
|
+
|
|
43
|
+
// Scan Progress (MessageChannelMain)
|
|
44
|
+
readonly scanProgress = signal<ScanProgress | null>(null);
|
|
45
|
+
|
|
31
46
|
constructor() {
|
|
32
47
|
effect(() => {
|
|
33
48
|
const current = this.theme();
|
|
@@ -37,12 +52,21 @@ export class PresentationService {
|
|
|
37
52
|
}
|
|
38
53
|
});
|
|
39
54
|
|
|
55
|
+
// Apply reduce-motion class based on OS preference
|
|
56
|
+
effect(() => {
|
|
57
|
+
document.documentElement.classList.toggle('reduce-motion', this.reduceMotion());
|
|
58
|
+
});
|
|
59
|
+
|
|
40
60
|
if (this.electronService.isElectron) {
|
|
41
61
|
this.loadThemeSettings();
|
|
42
62
|
this.loadLayoutSettings();
|
|
43
63
|
this.loadPreviewSettings();
|
|
44
64
|
this.loadNavigationExpandedSettings();
|
|
45
65
|
this.listenForMenuToggle();
|
|
66
|
+
this.initNativeTheme();
|
|
67
|
+
this.initSystemPreferences();
|
|
68
|
+
this.initPowerMonitor();
|
|
69
|
+
this.initScanProgress();
|
|
46
70
|
|
|
47
71
|
let themeInitialized = false;
|
|
48
72
|
effect(() => {
|
|
@@ -241,6 +265,74 @@ export class PresentationService {
|
|
|
241
265
|
this.navigationExpandedIds.set([]);
|
|
242
266
|
}
|
|
243
267
|
|
|
268
|
+
// --- Native Theme ---
|
|
269
|
+
|
|
270
|
+
private async initNativeTheme() {
|
|
271
|
+
const state = await this.messageService.getNativeTheme();
|
|
272
|
+
this.nativeThemeState.set(state);
|
|
273
|
+
this.applyAutoTheme(state);
|
|
274
|
+
|
|
275
|
+
this.messageService.on(ChannelType.IPC_NATIVE_THEME_CHANGED, (_event: any, state: NativeThemeState) => {
|
|
276
|
+
this.nativeThemeState.set(state);
|
|
277
|
+
this.applyAutoTheme(state);
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
private applyAutoTheme(state: NativeThemeState) {
|
|
282
|
+
if (this.autoTheme()) {
|
|
283
|
+
this.theme.set(state.shouldUseDarkColors ? 'midnight' : 'light');
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
setAutoTheme(enabled: boolean) {
|
|
288
|
+
this.autoTheme.set(enabled);
|
|
289
|
+
if (enabled) {
|
|
290
|
+
const state = this.nativeThemeState();
|
|
291
|
+
if (state) {
|
|
292
|
+
this.theme.set(state.shouldUseDarkColors ? 'midnight' : 'light');
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// --- System Preferences ---
|
|
298
|
+
|
|
299
|
+
private async initSystemPreferences() {
|
|
300
|
+
const prefs = await this.messageService.getSystemPreferences();
|
|
301
|
+
this.systemAccentColor.set(prefs.accentColor);
|
|
302
|
+
this.reduceMotion.set(prefs.reduceMotion);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// --- Power Monitor ---
|
|
306
|
+
|
|
307
|
+
private initPowerMonitor() {
|
|
308
|
+
this.messageService.on(ChannelType.IPC_POWER_SUSPEND, () => {
|
|
309
|
+
this.suspended.set(true);
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
this.messageService.on(ChannelType.IPC_POWER_RESUME, () => {
|
|
313
|
+
this.suspended.set(false);
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// --- Scan Progress (MessageChannelMain) ---
|
|
318
|
+
|
|
319
|
+
private initScanProgress() {
|
|
320
|
+
this.electronService.ipcRenderer.on(ChannelType.IPC_SCAN_PROGRESS_PORT, (event: any) => {
|
|
321
|
+
const port = event.ports[0];
|
|
322
|
+
if (!port) return;
|
|
323
|
+
|
|
324
|
+
port.onmessage = (msgEvent: MessageEvent<ScanProgress>) => {
|
|
325
|
+
this.scanProgress.set(msgEvent.data);
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
port.onclose = () => {
|
|
329
|
+
this.scanProgress.set(null);
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
port.start();
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
|
|
244
336
|
private async loadNavigationExpandedSettings() {
|
|
245
337
|
const ids = (await this.messageService.get(StorageType.NavigationExpanded, null)) as number[] | null;
|
|
246
338
|
if (Array.isArray(ids)) {
|
|
@@ -1,5 +1,16 @@
|
|
|
1
|
-
<div class="panel">
|
|
2
|
-
<div class="flex items-center
|
|
1
|
+
<div class="panel h-full w-full flex items-center justify-between px-2">
|
|
2
|
+
<div class="flex items-center gap-2">
|
|
3
3
|
<app-spinner [animating]="presentation.loading()" />
|
|
4
|
+
@if (presentation.scanProgress(); as progress) {
|
|
5
|
+
<span class="text-[10px] truncate" [style.color]="'var(--text-muted)'">
|
|
6
|
+
{{ progress.currentFile }} ({{ progress.processed }}/{{ progress.total }})
|
|
7
|
+
</span>
|
|
8
|
+
}
|
|
9
|
+
</div>
|
|
10
|
+
<div class="flex items-center gap-3">
|
|
11
|
+
<span class="text-[10px]" [style.color]="'var(--text-muted)'">Fontastic v{{ appVersion() }}</span>
|
|
12
|
+
<span class="text-[10px]" [style.color]="'var(--text-muted)'">Electron {{ electronVersion() }}</span>
|
|
13
|
+
<span class="text-[10px]" [style.color]="'var(--text-muted)'">Chrome {{ chromeVersion() }}</span>
|
|
14
|
+
<span class="text-[10px]" [style.color]="'var(--text-muted)'">Node {{ nodeVersion() }}</span>
|
|
4
15
|
</div>
|
|
5
16
|
</div>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { Component, inject } from '@angular/core';
|
|
1
|
+
import { Component, inject, signal } from '@angular/core';
|
|
2
2
|
import { SpinnerComponent } from '../../shared/components';
|
|
3
|
-
import { PresentationService } from '../../core/services';
|
|
3
|
+
import { ElectronService, PresentationService } from '../../core/services';
|
|
4
4
|
|
|
5
5
|
@Component({
|
|
6
6
|
selector: 'app-footer',
|
|
@@ -10,4 +10,20 @@ import { PresentationService } from '../../core/services';
|
|
|
10
10
|
})
|
|
11
11
|
export class FooterComponent {
|
|
12
12
|
readonly presentation = inject(PresentationService);
|
|
13
|
+
private readonly electron = inject(ElectronService);
|
|
14
|
+
|
|
15
|
+
readonly appVersion = signal('');
|
|
16
|
+
readonly electronVersion = signal('');
|
|
17
|
+
readonly chromeVersion = signal('');
|
|
18
|
+
readonly nodeVersion = signal('');
|
|
19
|
+
|
|
20
|
+
constructor() {
|
|
21
|
+
if (this.electron.isElectron) {
|
|
22
|
+
const versions = (window as any).process.versions;
|
|
23
|
+
this.electronVersion.set(versions.electron ?? '');
|
|
24
|
+
this.chromeVersion.set(versions.chrome ?? '');
|
|
25
|
+
this.nodeVersion.set(versions.node ?? '');
|
|
26
|
+
this.electron.ipcRenderer.invoke('app:get-version').then((v: string) => this.appVersion.set(v));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
13
29
|
}
|