fontastic 1.3.1 → 1.4.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/.github/workflows/claude-code-review.yml +1 -1
- package/.github/workflows/claude.yml +1 -1
- package/.github/workflows/macos.yml +5 -5
- package/.github/workflows/release-please.yml +1 -1
- package/.github/workflows/release.yml +2 -2
- package/.github/workflows/ubuntu.yml +7 -7
- package/.github/workflows/windows.yml +5 -5
- package/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +7 -0
- package/README.md +4 -4
- package/angular.json +8 -3
- package/app/core/FontFinder.js +17 -12
- package/app/core/FontFinder.ts +18 -12
- package/app/core/FontManager.js +13 -14
- package/app/core/FontManager.ts +12 -13
- package/app/core/MessageHandler.js +0 -1
- package/app/core/MessageHandler.ts +0 -4
- package/app/database/entity/Store.schema.js +1 -0
- package/app/database/entity/Store.schema.ts +1 -0
- package/app/database/repository/Collection.repository.js +0 -11
- package/app/database/repository/Collection.repository.ts +0 -22
- package/app/database/repository/Store.repository.js +50 -69
- package/app/database/repository/Store.repository.ts +47 -86
- package/app/enums/ChannelType.js +0 -1
- package/app/enums/ChannelType.ts +0 -1
- package/app/main.js +3 -2
- package/app/main.ts +3 -2
- package/app/package-lock.json +144 -1104
- package/app/package.json +4 -6
- package/app/preload.js +51 -0
- package/app/preload.ts +59 -0
- package/app/types/Bridge.js +3 -0
- package/app/types/Bridge.ts +19 -0
- package/app/types/index.js +1 -0
- package/app/types/index.ts +1 -0
- package/knip.json +18 -0
- package/package.json +44 -53
- package/src/app/app.component.spec.ts +3 -3
- package/src/app/app.component.ts +2 -15
- package/src/app/core/services/database/database.service.ts +8 -15
- package/src/app/core/services/electron/electron.service.ts +5 -46
- package/src/app/core/services/font-loader/font-loader.service.ts +60 -0
- package/src/app/core/services/index.ts +1 -0
- package/src/app/core/services/message/message.service.ts +19 -27
- package/src/app/core/services/presentation/presentation.service.ts +9 -2
- package/src/app/home/home.component.spec.ts +3 -3
- package/src/app/home/home.component.ts +4 -8
- package/src/app/layout/footer/footer.component.ts +6 -6
- package/src/app/shared/components/index.ts +0 -1
- package/src/app/shared/components/page-not-found/page-not-found.component.ts +2 -8
- package/src/app/shared/components/preview/preview.component.html +1 -0
- package/src/app/shared/components/preview/preview.component.ts +3 -31
- package/src/app/shared/components/rule-builder/rule-builder.component.html +4 -4
- package/src/app/shared/components/rule-builder/rule-builder.component.ts +13 -13
- package/src/app/shared/components/waterfall/waterfall.component.ts +1 -1
- package/src/app/shared/directives/index.ts +1 -1
- package/src/app/shared/directives/lazy-font/lazy-font.directive.ts +23 -0
- package/src/app/shared/shared.module.ts +3 -3
- package/src/main.ts +2 -2
- package/tsconfig.serve.json +4 -16
- package/app/helpers/command.js +0 -28
- package/app/helpers/command.ts +0 -20
- package/app/helpers/random.js +0 -16
- package/app/helpers/random.ts +0 -12
- package/src/app/shared/components/prompt-dialog/prompt-dialog.component.html +0 -36
- package/src/app/shared/components/prompt-dialog/prompt-dialog.component.ts +0 -40
- package/src/app/shared/directives/webview/webview.directive.spec.ts +0 -8
- package/src/app/shared/directives/webview/webview.directive.ts +0 -9
- package/src/styles/themes/dashboard.scss +0 -293
- package/src/styles/themes/euphoria.scss +0 -284
- package/src/styles/themes/mellow.scss +0 -281
- package/src/styles/themes/midnight.scss +0 -284
- package/src/styles/themes/passion.scss +0 -281
- package/src/styles/themes/swiss.scss +0 -284
package/app/package.json
CHANGED
|
@@ -5,19 +5,17 @@
|
|
|
5
5
|
"name": "Tom Shaw",
|
|
6
6
|
"email": ""
|
|
7
7
|
},
|
|
8
|
-
"version": "1.
|
|
8
|
+
"version": "1.4.0",
|
|
9
9
|
"main": "main.js",
|
|
10
10
|
"private": true,
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"electron-log": "^4.4
|
|
12
|
+
"electron-log": "^5.4.4",
|
|
13
13
|
"electron-store": "^8.2.0",
|
|
14
14
|
"mime": "^3.0.0",
|
|
15
|
-
"node-fetch": "^2.7.0",
|
|
16
15
|
"node-machine-id": "^1.1.12",
|
|
17
16
|
"fontkit": "^2.0.4",
|
|
18
17
|
"pretty-bytes": "^5.6.0",
|
|
19
|
-
"better-sqlite3": "^12.
|
|
20
|
-
"
|
|
21
|
-
"typeorm": "^0.3.20"
|
|
18
|
+
"better-sqlite3": "^12.11.1",
|
|
19
|
+
"typeorm": "^1.0.0"
|
|
22
20
|
}
|
|
23
21
|
}
|
package/app/preload.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var _a, _b, _c;
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const electron_1 = require("electron");
|
|
5
|
+
// NOTE: This file runs in the sandboxed preload context. It must stay
|
|
6
|
+
// self-contained — sandboxed preloads cannot require local modules, so the
|
|
7
|
+
// scan-progress channel name is duplicated here rather than imported.
|
|
8
|
+
const SCAN_PROGRESS_PORT_CHANNEL = 'IPC_SCAN_PROGRESS_PORT';
|
|
9
|
+
let nextSubscriptionId = 0;
|
|
10
|
+
const subscriptions = new Map();
|
|
11
|
+
// MessagePorts cannot cross the context bridge directly; forward them into
|
|
12
|
+
// the isolated world through window.postMessage, which supports transferables.
|
|
13
|
+
electron_1.ipcRenderer.on(SCAN_PROGRESS_PORT_CHANNEL, (event) => {
|
|
14
|
+
window.postMessage({ type: SCAN_PROGRESS_PORT_CHANNEL }, '*', event.ports);
|
|
15
|
+
});
|
|
16
|
+
electron_1.contextBridge.exposeInMainWorld('fontastic', {
|
|
17
|
+
versions: {
|
|
18
|
+
app: '',
|
|
19
|
+
electron: (_a = process.versions.electron) !== null && _a !== void 0 ? _a : '',
|
|
20
|
+
chrome: (_b = process.versions.chrome) !== null && _b !== void 0 ? _b : '',
|
|
21
|
+
node: (_c = process.versions.node) !== null && _c !== void 0 ? _c : '',
|
|
22
|
+
},
|
|
23
|
+
invoke: (channel, args) => electron_1.ipcRenderer.invoke(channel, args),
|
|
24
|
+
send: (channel, args) => electron_1.ipcRenderer.send(channel, args),
|
|
25
|
+
once: (channel, listener) => {
|
|
26
|
+
electron_1.ipcRenderer.once(channel, (_event, ...args) => listener(...args));
|
|
27
|
+
},
|
|
28
|
+
on: (channel, listener) => {
|
|
29
|
+
const wrapped = (_event, ...args) => listener(...args);
|
|
30
|
+
const id = ++nextSubscriptionId;
|
|
31
|
+
subscriptions.set(id, { channel, wrapped });
|
|
32
|
+
electron_1.ipcRenderer.on(channel, wrapped);
|
|
33
|
+
return id;
|
|
34
|
+
},
|
|
35
|
+
off: (subscriptionId) => {
|
|
36
|
+
const sub = subscriptions.get(subscriptionId);
|
|
37
|
+
if (sub) {
|
|
38
|
+
electron_1.ipcRenderer.removeListener(sub.channel, sub.wrapped);
|
|
39
|
+
subscriptions.delete(subscriptionId);
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
removeAllListeners: (channel) => {
|
|
43
|
+
for (const [id, sub] of subscriptions) {
|
|
44
|
+
if (sub.channel === channel) {
|
|
45
|
+
subscriptions.delete(id);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
electron_1.ipcRenderer.removeAllListeners(channel);
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
//# sourceMappingURL=preload.js.map
|
package/app/preload.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { contextBridge, ipcRenderer, IpcRendererEvent } from 'electron';
|
|
2
|
+
|
|
3
|
+
// NOTE: This file runs in the sandboxed preload context. It must stay
|
|
4
|
+
// self-contained — sandboxed preloads cannot require local modules, so the
|
|
5
|
+
// scan-progress channel name is duplicated here rather than imported.
|
|
6
|
+
const SCAN_PROGRESS_PORT_CHANNEL = 'IPC_SCAN_PROGRESS_PORT';
|
|
7
|
+
|
|
8
|
+
type Listener = (...args: unknown[]) => void;
|
|
9
|
+
|
|
10
|
+
let nextSubscriptionId = 0;
|
|
11
|
+
const subscriptions = new Map<number, { channel: string; wrapped: (event: IpcRendererEvent, ...args: unknown[]) => void }>();
|
|
12
|
+
|
|
13
|
+
// MessagePorts cannot cross the context bridge directly; forward them into
|
|
14
|
+
// the isolated world through window.postMessage, which supports transferables.
|
|
15
|
+
ipcRenderer.on(SCAN_PROGRESS_PORT_CHANNEL, (event) => {
|
|
16
|
+
window.postMessage({ type: SCAN_PROGRESS_PORT_CHANNEL }, '*', event.ports);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
contextBridge.exposeInMainWorld('fontastic', {
|
|
20
|
+
versions: {
|
|
21
|
+
app: '',
|
|
22
|
+
electron: process.versions.electron ?? '',
|
|
23
|
+
chrome: process.versions.chrome ?? '',
|
|
24
|
+
node: process.versions.node ?? '',
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
invoke: (channel: string, args?: unknown): Promise<unknown> => ipcRenderer.invoke(channel, args),
|
|
28
|
+
|
|
29
|
+
send: (channel: string, args?: unknown): void => ipcRenderer.send(channel, args),
|
|
30
|
+
|
|
31
|
+
once: (channel: string, listener: Listener): void => {
|
|
32
|
+
ipcRenderer.once(channel, (_event, ...args) => listener(...args));
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
on: (channel: string, listener: Listener): number => {
|
|
36
|
+
const wrapped = (_event: IpcRendererEvent, ...args: unknown[]) => listener(...args);
|
|
37
|
+
const id = ++nextSubscriptionId;
|
|
38
|
+
subscriptions.set(id, { channel, wrapped });
|
|
39
|
+
ipcRenderer.on(channel, wrapped);
|
|
40
|
+
return id;
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
off: (subscriptionId: number): void => {
|
|
44
|
+
const sub = subscriptions.get(subscriptionId);
|
|
45
|
+
if (sub) {
|
|
46
|
+
ipcRenderer.removeListener(sub.channel, sub.wrapped);
|
|
47
|
+
subscriptions.delete(subscriptionId);
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
removeAllListeners: (channel: string): void => {
|
|
52
|
+
for (const [id, sub] of subscriptions) {
|
|
53
|
+
if (sub.channel === channel) {
|
|
54
|
+
subscriptions.delete(id);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
ipcRenderer.removeAllListeners(channel);
|
|
58
|
+
},
|
|
59
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The typed API exposed to the renderer by app/preload.ts via contextBridge.
|
|
3
|
+
* Available in the renderer as `window.fontastic`.
|
|
4
|
+
*/
|
|
5
|
+
export interface FontasticBridge {
|
|
6
|
+
versions: {
|
|
7
|
+
app: string;
|
|
8
|
+
electron: string;
|
|
9
|
+
chrome: string;
|
|
10
|
+
node: string;
|
|
11
|
+
};
|
|
12
|
+
invoke<T = unknown>(channel: string, args?: unknown): Promise<T>;
|
|
13
|
+
send(channel: string, args?: unknown): void;
|
|
14
|
+
once(channel: string, listener: (...args: any[]) => void): void;
|
|
15
|
+
/** Subscribes and returns a subscription id for `off()`. */
|
|
16
|
+
on(channel: string, listener: (...args: any[]) => void): number;
|
|
17
|
+
off(subscriptionId: number): void;
|
|
18
|
+
removeAllListeners(channel: string): void;
|
|
19
|
+
}
|
package/app/types/index.js
CHANGED
|
@@ -27,4 +27,5 @@ __exportStar(require("./SmartCollection"), exports);
|
|
|
27
27
|
__exportStar(require("./NativeThemeState"), exports);
|
|
28
28
|
__exportStar(require("./SystemPreferencesState"), exports);
|
|
29
29
|
__exportStar(require("./ScanProgress"), exports);
|
|
30
|
+
__exportStar(require("./Bridge"), exports);
|
|
30
31
|
//# sourceMappingURL=index.js.map
|
package/app/types/index.ts
CHANGED
package/knip.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://unpkg.com/knip@6/schema.json",
|
|
3
|
+
"entry": ["src/main.ts", "app/main.ts", "e2e/**/*.spec.ts", "scripts/*.js"],
|
|
4
|
+
"project": ["src/**/*.ts", "app/**/*.ts"],
|
|
5
|
+
"ignore": ["app/**/*.js"],
|
|
6
|
+
"ignoreDependencies": [
|
|
7
|
+
"@fontsource-variable/inter",
|
|
8
|
+
"material-icons",
|
|
9
|
+
"electron-debug",
|
|
10
|
+
"electron-reloader",
|
|
11
|
+
"@angular-eslint/schematics",
|
|
12
|
+
"@angular-eslint/builder",
|
|
13
|
+
"@angular/language-service",
|
|
14
|
+
"@vitest/browser-playwright",
|
|
15
|
+
"@vitest/coverage-v8",
|
|
16
|
+
"@vitest/ui"
|
|
17
|
+
]
|
|
18
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fontastic",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.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,
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"angular",
|
|
13
13
|
"angular 21",
|
|
14
14
|
"electron",
|
|
15
|
-
"electron
|
|
15
|
+
"electron 43",
|
|
16
16
|
"nodejs",
|
|
17
17
|
"typescript",
|
|
18
18
|
"vitest",
|
|
@@ -51,72 +51,63 @@
|
|
|
51
51
|
"prepare": "husky"
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
|
-
"@angular/
|
|
55
|
-
"@angular/
|
|
56
|
-
"@angular/
|
|
57
|
-
"@angular/
|
|
58
|
-
"@angular/
|
|
59
|
-
"@angular/
|
|
60
|
-
"@angular/platform-browser": "21.2.0",
|
|
61
|
-
"@angular/platform-browser-dynamic": "21.2.0",
|
|
62
|
-
"@angular/router": "21.2.0",
|
|
54
|
+
"@angular/common": "21.2.17",
|
|
55
|
+
"@angular/compiler": "21.2.17",
|
|
56
|
+
"@angular/core": "21.2.17",
|
|
57
|
+
"@angular/forms": "21.2.17",
|
|
58
|
+
"@angular/platform-browser": "21.2.17",
|
|
59
|
+
"@angular/router": "21.2.17",
|
|
63
60
|
"@fontsource-variable/inter": "5.2.8",
|
|
64
|
-
"electron-store": "11.0.2",
|
|
65
|
-
"fontkit": "2.0.4",
|
|
66
61
|
"material-icons": "1.13.14",
|
|
67
62
|
"rxjs": "7.8.2",
|
|
68
|
-
"tslib": "2.8.1"
|
|
69
|
-
"zone.js": "0.16.1"
|
|
63
|
+
"tslib": "2.8.1"
|
|
70
64
|
},
|
|
71
65
|
"devDependencies": {
|
|
72
|
-
"@angular-eslint/builder": "21.
|
|
73
|
-
"@angular-eslint/eslint-plugin": "21.
|
|
74
|
-
"@angular-eslint/eslint-plugin-template": "21.
|
|
75
|
-
"@angular-eslint/schematics": "21.
|
|
76
|
-
"@angular-eslint/template-parser": "21.
|
|
77
|
-
"@angular/build": "21.2.
|
|
78
|
-
"@angular/cli": "21.2.
|
|
79
|
-
"@angular/compiler-cli": "21.2.
|
|
80
|
-
"@
|
|
81
|
-
"@
|
|
82
|
-
"@
|
|
83
|
-
"@
|
|
84
|
-
"@
|
|
85
|
-
"@
|
|
86
|
-
"@
|
|
87
|
-
"@
|
|
88
|
-
"@
|
|
89
|
-
"@vitest/
|
|
90
|
-
"
|
|
91
|
-
"
|
|
66
|
+
"@angular-eslint/builder": "21.4.0",
|
|
67
|
+
"@angular-eslint/eslint-plugin": "21.4.0",
|
|
68
|
+
"@angular-eslint/eslint-plugin-template": "21.4.0",
|
|
69
|
+
"@angular-eslint/schematics": "21.4.0",
|
|
70
|
+
"@angular-eslint/template-parser": "21.4.0",
|
|
71
|
+
"@angular/build": "21.2.18",
|
|
72
|
+
"@angular/cli": "21.2.18",
|
|
73
|
+
"@angular/compiler-cli": "21.2.17",
|
|
74
|
+
"@angular/language-service": "21.2.17",
|
|
75
|
+
"@eslint/js": "10.0.1",
|
|
76
|
+
"@ngx-translate/core": "18.0.0",
|
|
77
|
+
"@ngx-translate/http-loader": "18.0.0",
|
|
78
|
+
"@playwright/test": "1.61.1",
|
|
79
|
+
"@tailwindcss/postcss": "4.3.2",
|
|
80
|
+
"@types/node": "24.13.2",
|
|
81
|
+
"@typescript-eslint/eslint-plugin": "8.62.1",
|
|
82
|
+
"@typescript-eslint/parser": "8.62.1",
|
|
83
|
+
"@vitest/browser-playwright": "4.1.9",
|
|
84
|
+
"@vitest/coverage-v8": "4.1.9",
|
|
85
|
+
"@vitest/ui": "4.1.9",
|
|
86
|
+
"electron": "43.0.0",
|
|
87
|
+
"electron-builder": "26.15.3",
|
|
92
88
|
"electron-debug": "4.1.0",
|
|
93
89
|
"electron-reloader": "1.2.3",
|
|
94
|
-
"eslint": "
|
|
90
|
+
"eslint": "10.6.0",
|
|
95
91
|
"eslint-config-prettier": "10.1.8",
|
|
96
|
-
"
|
|
97
|
-
"eslint-plugin-jsdoc": "62.7.1",
|
|
98
|
-
"eslint-plugin-prefer-arrow": "1.2.3",
|
|
99
|
-
"globals": "17.4.0",
|
|
92
|
+
"globals": "17.7.0",
|
|
100
93
|
"husky": "9.1.7",
|
|
101
|
-
"jsdom": "
|
|
102
|
-
"knip": "
|
|
103
|
-
"lint-staged": "
|
|
94
|
+
"jsdom": "29.1.1",
|
|
95
|
+
"knip": "6.24.0",
|
|
96
|
+
"lint-staged": "17.0.8",
|
|
104
97
|
"npm-run-all": "4.1.5",
|
|
105
|
-
"playwright": "1.
|
|
106
|
-
"postcss": "8.5.
|
|
107
|
-
"prettier": "3.
|
|
108
|
-
"tailwindcss": "4.2
|
|
109
|
-
"ts-node": "10.9.2",
|
|
98
|
+
"playwright": "1.61.1",
|
|
99
|
+
"postcss": "8.5.16",
|
|
100
|
+
"prettier": "3.9.4",
|
|
101
|
+
"tailwindcss": "4.3.2",
|
|
110
102
|
"typescript": "5.9.3",
|
|
111
|
-
"vitest": "4.
|
|
112
|
-
"wait-on": "9.0.
|
|
113
|
-
"webdriver-manager": "13.0.2"
|
|
103
|
+
"vitest": "4.1.9",
|
|
104
|
+
"wait-on": "9.0.10"
|
|
114
105
|
},
|
|
115
106
|
"engines": {
|
|
116
107
|
"node": ">= 22.12.0 || >= 24.0.0",
|
|
117
|
-
"typescript": ">= 5.
|
|
108
|
+
"typescript": ">= 5.9.0 < 6.0.0"
|
|
118
109
|
},
|
|
119
110
|
"browserslist": [
|
|
120
|
-
"chrome
|
|
111
|
+
"chrome 150"
|
|
121
112
|
]
|
|
122
113
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { TestBed } from '@angular/core/testing';
|
|
2
2
|
import { AppComponent } from './app.component';
|
|
3
|
-
import {
|
|
3
|
+
import { provideTranslateService } from '@ngx-translate/core';
|
|
4
4
|
import { ElectronService } from './core/services';
|
|
5
5
|
import { provideRouter } from '@angular/router';
|
|
6
6
|
|
|
@@ -8,8 +8,8 @@ describe('AppComponent', () => {
|
|
|
8
8
|
beforeEach(async () => {
|
|
9
9
|
await TestBed.configureTestingModule({
|
|
10
10
|
declarations: [],
|
|
11
|
-
imports: [AppComponent
|
|
12
|
-
providers: [provideRouter([]), ElectronService],
|
|
11
|
+
imports: [AppComponent],
|
|
12
|
+
providers: [provideRouter([]), provideTranslateService(), ElectronService],
|
|
13
13
|
}).compileComponents();
|
|
14
14
|
});
|
|
15
15
|
|
package/src/app/app.component.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Component, afterNextRender, inject } from '@angular/core';
|
|
2
2
|
import { ElectronService } from './core/services';
|
|
3
|
-
import { TranslateService } from '@ngx-translate/core';
|
|
4
3
|
import { APP_CONFIG } from '../environments/environment';
|
|
5
4
|
import { RouterOutlet } from '@angular/router';
|
|
6
5
|
|
|
@@ -12,22 +11,10 @@ import { RouterOutlet } from '@angular/router';
|
|
|
12
11
|
})
|
|
13
12
|
export class AppComponent {
|
|
14
13
|
private electronService = inject(ElectronService);
|
|
15
|
-
private translate = inject(TranslateService);
|
|
16
14
|
|
|
17
15
|
constructor() {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
this.translate.setDefaultLang('en');
|
|
21
|
-
console.log('APP_CONFIG', APP_CONFIG);
|
|
22
|
-
|
|
23
|
-
if (electronService.isElectron) {
|
|
24
|
-
console.log(process.env);
|
|
25
|
-
console.log('Run in electron');
|
|
26
|
-
console.log('Electron ipcRenderer', this.electronService.ipcRenderer);
|
|
27
|
-
console.log('NodeJS childProcess', this.electronService.childProcess);
|
|
28
|
-
void this.electronService.ipcRenderer.invoke('app:get-version').then((v) => console.log('App version:', v));
|
|
29
|
-
} else {
|
|
30
|
-
console.log('Run in browser');
|
|
16
|
+
if (!APP_CONFIG.production) {
|
|
17
|
+
console.log('APP_CONFIG', APP_CONFIG, this.electronService.isElectron ? 'Run in electron' : 'Run in browser');
|
|
31
18
|
}
|
|
32
19
|
|
|
33
20
|
afterNextRender(() => {
|
|
@@ -93,6 +93,12 @@ export class DatabaseService {
|
|
|
93
93
|
this.fetchCurrentPage();
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
+
private static readonly filterWhereMap: Record<string, { key: string; value: number }[]> = {
|
|
97
|
+
all: [],
|
|
98
|
+
favorites: [{ key: 'store.favorite', value: 1 }],
|
|
99
|
+
system: [{ key: 'store.system', value: 1 }],
|
|
100
|
+
};
|
|
101
|
+
|
|
96
102
|
selectFilter(filter: string) {
|
|
97
103
|
this.parentId.set(null);
|
|
98
104
|
this.collectionId.set(null);
|
|
@@ -101,13 +107,7 @@ export class DatabaseService {
|
|
|
101
107
|
this.activeSmartCollectionId.set(null);
|
|
102
108
|
this.currentPage.set(1);
|
|
103
109
|
|
|
104
|
-
|
|
105
|
-
all: [],
|
|
106
|
-
favorites: [{ key: 'store.favorite', value: 1 }],
|
|
107
|
-
system: [{ key: 'store.system', value: 1 }],
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
this.fetchCurrentPage({ where: whereMap[filter] ?? [] });
|
|
110
|
+
this.fetchCurrentPage({ where: DatabaseService.filterWhereMap[filter] ?? [] });
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
goToPage(page: number) {
|
|
@@ -161,12 +161,7 @@ export class DatabaseService {
|
|
|
161
161
|
if (collectionId) {
|
|
162
162
|
options.collectionId = collectionId;
|
|
163
163
|
} else if (filter) {
|
|
164
|
-
|
|
165
|
-
all: [],
|
|
166
|
-
favorites: [{ key: 'store.favorite', value: 1 }],
|
|
167
|
-
system: [{ key: 'store.system', value: 1 }],
|
|
168
|
-
};
|
|
169
|
-
options.where = whereMap[filter] ?? [];
|
|
164
|
+
options.where = DatabaseService.filterWhereMap[filter] ?? [];
|
|
170
165
|
}
|
|
171
166
|
|
|
172
167
|
this.storeFetch(options);
|
|
@@ -185,7 +180,6 @@ export class DatabaseService {
|
|
|
185
180
|
|
|
186
181
|
this.collections.set(collections);
|
|
187
182
|
this.smartCollections.set(smartCollections);
|
|
188
|
-
console.log('System Boot:', collections);
|
|
189
183
|
|
|
190
184
|
if (savedSortColumn) {
|
|
191
185
|
this.sortColumn.set(savedSortColumn);
|
|
@@ -258,7 +252,6 @@ export class DatabaseService {
|
|
|
258
252
|
return this.track(
|
|
259
253
|
this.message.collectionCreate(args).then((result) => {
|
|
260
254
|
this.collections.set(result);
|
|
261
|
-
console.log('Collection Created:', result);
|
|
262
255
|
return result;
|
|
263
256
|
}),
|
|
264
257
|
);
|
|
@@ -1,62 +1,21 @@
|
|
|
1
1
|
import { Injectable } from '@angular/core';
|
|
2
|
-
|
|
3
|
-
// If you import a module but never use any of the imported values other than as TypeScript types,
|
|
4
|
-
// the resulting javascript file will look as if you never imported the module at all.
|
|
5
|
-
import { ipcRenderer, webFrame } from 'electron';
|
|
6
|
-
import * as childProcess from 'child_process';
|
|
7
|
-
import * as fs from 'fs';
|
|
2
|
+
import type { FontasticBridge } from '@main/types';
|
|
8
3
|
|
|
9
4
|
@Injectable({
|
|
10
5
|
providedIn: 'root',
|
|
11
6
|
})
|
|
12
7
|
export class ElectronService {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
childProcess!: typeof childProcess;
|
|
16
|
-
fs!: typeof fs;
|
|
8
|
+
/** The typed IPC bridge exposed by the preload script, or undefined in a plain browser. */
|
|
9
|
+
readonly bridge?: FontasticBridge = (window as any).fontastic;
|
|
17
10
|
|
|
18
11
|
/** Resolves when the main process signals that IPC handlers and DB are ready. */
|
|
19
12
|
readonly ready: Promise<void>;
|
|
20
13
|
|
|
21
14
|
constructor() {
|
|
22
|
-
|
|
23
|
-
this.ipcRenderer = (window as any).require('electron').ipcRenderer;
|
|
24
|
-
this.webFrame = (window as any).require('electron').webFrame;
|
|
25
|
-
|
|
26
|
-
this.fs = (window as any).require('fs');
|
|
27
|
-
|
|
28
|
-
this.childProcess = (window as any).require('child_process');
|
|
29
|
-
this.childProcess.exec('node -v', (error, stdout, stderr) => {
|
|
30
|
-
if (error) {
|
|
31
|
-
console.error(`error: ${error.message}`);
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
if (stderr) {
|
|
35
|
-
console.error(`stderr: ${stderr}`);
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
console.log(`stdout:\n${stdout}`);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
// Notes :
|
|
42
|
-
// * A NodeJS's dependency imported with 'window.require' MUST BE present in `dependencies` of both `app/package.json`
|
|
43
|
-
// and `package.json (root folder)` in order to make it work here in Electron's Renderer process (src folder)
|
|
44
|
-
// because it will loaded at runtime by Electron.
|
|
45
|
-
// * A NodeJS's dependency imported with TS module import (ex: import { Dropbox } from 'dropbox') CAN only be present
|
|
46
|
-
// in `dependencies` of `package.json (root folder)` because it is loaded during build phase and does not need to be
|
|
47
|
-
// in the final bundle. Reminder : only if not used in Electron's Main process (app folder)
|
|
48
|
-
|
|
49
|
-
// If you want to use a NodeJS 3rd party deps in Renderer process,
|
|
50
|
-
// ipcRenderer.invoke can serve many common use cases.
|
|
51
|
-
// https://www.electronjs.org/docs/latest/api/ipc-renderer#ipcrendererinvokechannel-args
|
|
52
|
-
|
|
53
|
-
this.ready = this.ipcRenderer.invoke('app:ready');
|
|
54
|
-
} else {
|
|
55
|
-
this.ready = Promise.resolve();
|
|
56
|
-
}
|
|
15
|
+
this.ready = this.bridge ? this.bridge.invoke<void>('app:ready') : Promise.resolve();
|
|
57
16
|
}
|
|
58
17
|
|
|
59
18
|
get isElectron(): boolean {
|
|
60
|
-
return !!
|
|
19
|
+
return !!this.bridge;
|
|
61
20
|
}
|
|
62
21
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Injectable } from '@angular/core';
|
|
2
|
+
import type { Store } from '@main/database/entity/Store.schema';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Registers font files with the document on demand. Elements are observed
|
|
6
|
+
* with a shared IntersectionObserver so a font is only fetched once its
|
|
7
|
+
* preview row is about to scroll into view.
|
|
8
|
+
*/
|
|
9
|
+
@Injectable({ providedIn: 'root' })
|
|
10
|
+
export class FontLoaderService {
|
|
11
|
+
private registered = new Set<string>();
|
|
12
|
+
private pending = new WeakMap<Element, Store>();
|
|
13
|
+
|
|
14
|
+
private observer = new IntersectionObserver(
|
|
15
|
+
(entries) => {
|
|
16
|
+
for (const entry of entries) {
|
|
17
|
+
if (!entry.isIntersecting) continue;
|
|
18
|
+
const store = this.pending.get(entry.target);
|
|
19
|
+
this.unobserve(entry.target);
|
|
20
|
+
if (store) {
|
|
21
|
+
this.register(store);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
{ rootMargin: '200px' },
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
observe(element: Element, store: Store) {
|
|
29
|
+
if (this.registered.has(this.key(store))) return;
|
|
30
|
+
this.pending.set(element, store);
|
|
31
|
+
this.observer.observe(element);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
unobserve(element: Element) {
|
|
35
|
+
this.observer.unobserve(element);
|
|
36
|
+
this.pending.delete(element);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
register(store: Store) {
|
|
40
|
+
const key = this.key(store);
|
|
41
|
+
if (this.registered.has(key)) return;
|
|
42
|
+
|
|
43
|
+
const url = `font://${store.file_path}`;
|
|
44
|
+
const fontFace = new FontFace(store.full_name || store.font_family, `url('${url}')`);
|
|
45
|
+
|
|
46
|
+
fontFace
|
|
47
|
+
.load()
|
|
48
|
+
.then((loaded) => {
|
|
49
|
+
document.fonts.add(loaded);
|
|
50
|
+
this.registered.add(key);
|
|
51
|
+
})
|
|
52
|
+
.catch((err) => {
|
|
53
|
+
console.warn(`Failed to load font: ${store.file_name}`, err);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private key(store: Store): string {
|
|
58
|
+
return `${store.id}-${store.file_path}`;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -13,47 +13,43 @@ import type { SmartCollection } from '@main/database/entity/SmartCollection.sche
|
|
|
13
13
|
export class MessageService {
|
|
14
14
|
private electron = inject(ElectronService);
|
|
15
15
|
|
|
16
|
+
/** Maps registered listeners to their bridge subscription ids. */
|
|
17
|
+
private subscriptions = new Map<(...args: any[]) => void, number>();
|
|
18
|
+
|
|
16
19
|
// Low-level IPC
|
|
17
20
|
|
|
18
|
-
on(channel: string, listener: (...args: any[]) => void):
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
on(channel: string, listener: (...args: any[]) => void): void {
|
|
22
|
+
const id = this.electron.bridge?.on(channel, listener);
|
|
23
|
+
if (id !== undefined) {
|
|
24
|
+
this.subscriptions.set(listener, id);
|
|
21
25
|
}
|
|
22
|
-
return undefined;
|
|
23
26
|
}
|
|
24
27
|
|
|
25
|
-
once(channel: string, listener: (...args: any[]) => void):
|
|
26
|
-
|
|
27
|
-
return this.electron.ipcRenderer.once(channel, listener);
|
|
28
|
-
}
|
|
29
|
-
return undefined;
|
|
28
|
+
once(channel: string, listener: (...args: any[]) => void): void {
|
|
29
|
+
this.electron.bridge?.once(channel, listener);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
send(channel: string, args?: any): void {
|
|
33
|
-
|
|
34
|
-
this.electron.ipcRenderer.send(channel, args);
|
|
35
|
-
}
|
|
33
|
+
this.electron.bridge?.send(channel, args);
|
|
36
34
|
}
|
|
37
35
|
|
|
38
36
|
invoke<T = any>(channel: string, args?: any): Promise<T> {
|
|
39
|
-
if (this.electron.
|
|
40
|
-
return this.electron.
|
|
37
|
+
if (this.electron.bridge) {
|
|
38
|
+
return this.electron.bridge.invoke<T>(channel, args);
|
|
41
39
|
}
|
|
42
40
|
return Promise.reject('Not running in Electron');
|
|
43
41
|
}
|
|
44
42
|
|
|
45
|
-
removeListener(channel: string, listener: (...args: any[]) => void):
|
|
46
|
-
|
|
47
|
-
|
|
43
|
+
removeListener(channel: string, listener: (...args: any[]) => void): void {
|
|
44
|
+
const id = this.subscriptions.get(listener);
|
|
45
|
+
if (id !== undefined) {
|
|
46
|
+
this.electron.bridge?.off(id);
|
|
47
|
+
this.subscriptions.delete(listener);
|
|
48
48
|
}
|
|
49
|
-
return undefined;
|
|
50
49
|
}
|
|
51
50
|
|
|
52
|
-
removeAllListeners(channel: string):
|
|
53
|
-
|
|
54
|
-
return this.electron.ipcRenderer.removeAllListeners(channel);
|
|
55
|
-
}
|
|
56
|
-
return undefined;
|
|
51
|
+
removeAllListeners(channel: string): void {
|
|
52
|
+
this.electron.bridge?.removeAllListeners(channel);
|
|
57
53
|
}
|
|
58
54
|
|
|
59
55
|
// System
|
|
@@ -104,10 +100,6 @@ export class MessageService {
|
|
|
104
100
|
|
|
105
101
|
// Font Manager
|
|
106
102
|
|
|
107
|
-
exec(args: any): Promise<any> {
|
|
108
|
-
return this.invoke(ChannelType.IPC_EXEC_CMD, args);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
103
|
systemAuthenticate(args: any): Promise<any> {
|
|
112
104
|
return this.invoke(ChannelType.IPC_AUTH_USER, args);
|
|
113
105
|
}
|