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.
- package/.github/workflows/claude-code-review.yml +0 -6
- package/.github/workflows/release-please.yml +25 -0
- package/.github/workflows/release.yml +5 -109
- package/.release-please-manifest.json +3 -0
- package/CHANGELOG.md +39 -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 +98 -10
- package/app/main.ts +126 -19
- 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 +2 -2
- package/release-please-config.json +20 -0
- package/scripts/patch-electron-plist.js +41 -0
- 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 +100 -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/header/header.component.html +0 -10
- package/src/app/layout/header/header.component.ts +4 -23
- package/src/app/layout/navigation/navigation.component.html +66 -16
- package/src/app/layout/navigation/navigation.component.ts +65 -12
- 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
- package/src/assets/icons/favicon.256x256.png +0 -0
- package/src/assets/icons/favicon.512x512.png +0 -0
- package/src/assets/icons/favicon.icns +0 -0
- package/src/assets/icons/favicon.ico +0 -0
- package/src/assets/icons/favicon.png +0 -0
- package/src/favicon.ico +0 -0
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,
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
],
|
|
25
25
|
"main": "app/main.js",
|
|
26
26
|
"scripts": {
|
|
27
|
-
"postinstall": "electron-builder install-app-deps",
|
|
27
|
+
"postinstall": "electron-builder install-app-deps && node scripts/patch-electron-plist.js",
|
|
28
28
|
"ng": "ng",
|
|
29
29
|
"start": "npm-run-all -p electron:serve ng:serve",
|
|
30
30
|
"ng:serve": "ng serve -c dev -o",
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"last-release-sha": "c7f3f9afba466a150f90e374f8a401daf1a56d11",
|
|
3
|
+
"packages": {
|
|
4
|
+
".": {
|
|
5
|
+
"release-type": "node",
|
|
6
|
+
"extra-files": ["app/package.json"],
|
|
7
|
+
"changelog-sections": [
|
|
8
|
+
{ "type": "feat", "section": "🎉 Features", "hidden": false },
|
|
9
|
+
{ "type": "fix", "section": "🛠️ Fixes", "hidden": false },
|
|
10
|
+
{ "type": "docs", "section": "📄 Documentation", "hidden": false },
|
|
11
|
+
{ "type": "perf", "section": "⚡ Performance", "hidden": false },
|
|
12
|
+
{ "type": "refactor", "section": "🏗️ Refactor", "hidden": false },
|
|
13
|
+
{ "type": "chore", "section": "♻️ Chores", "hidden": true },
|
|
14
|
+
{ "type": "test", "section": "♻️ Chores", "hidden": true },
|
|
15
|
+
{ "type": "build", "section": "⚙️ Automation", "hidden": true },
|
|
16
|
+
{ "type": "ci", "section": "⚙️ Automation", "hidden": true }
|
|
17
|
+
]
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const { execSync } = require('child_process');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
|
|
5
|
+
if (process.platform !== 'darwin') {
|
|
6
|
+
process.exit(0);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const electronAppDir = path.join(
|
|
10
|
+
__dirname,
|
|
11
|
+
'..',
|
|
12
|
+
'node_modules',
|
|
13
|
+
'electron',
|
|
14
|
+
'dist',
|
|
15
|
+
'Electron.app'
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
const plist = path.join(electronAppDir, 'Contents', 'Info.plist');
|
|
19
|
+
|
|
20
|
+
if (!fs.existsSync(plist)) {
|
|
21
|
+
process.exit(0);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const pkg = require(path.join(__dirname, '..', 'package.json'));
|
|
25
|
+
const appName = 'Fontastic';
|
|
26
|
+
const appVersion = pkg.version;
|
|
27
|
+
|
|
28
|
+
execSync(`/usr/libexec/PlistBuddy -c "Set :CFBundleName ${appName}" "${plist}"`);
|
|
29
|
+
execSync(`/usr/libexec/PlistBuddy -c "Set :CFBundleDisplayName ${appName}" "${plist}"`);
|
|
30
|
+
execSync(`/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString ${appVersion}" "${plist}"`);
|
|
31
|
+
|
|
32
|
+
// Copy app icon into the Electron.app bundle
|
|
33
|
+
const srcIcon = path.join(__dirname, '..', 'src', 'assets', 'icons', 'favicon.icns');
|
|
34
|
+
const destIcon = path.join(electronAppDir, 'Contents', 'Resources', 'electron.icns');
|
|
35
|
+
|
|
36
|
+
if (fs.existsSync(srcIcon)) {
|
|
37
|
+
fs.copyFileSync(srcIcon, destIcon);
|
|
38
|
+
console.log('Copied app icon into Electron.app bundle');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
console.log(`Patched Electron.app plist: name="${appName}", version="${appVersion}"`);
|
|
@@ -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(() => {
|
|
@@ -133,6 +157,13 @@ export class PresentationService {
|
|
|
133
157
|
|
|
134
158
|
readonly selectedGlyph = signal<number | null>(null);
|
|
135
159
|
|
|
160
|
+
readonly createRootCollectionRequest = signal(0);
|
|
161
|
+
|
|
162
|
+
requestCreateRootCollection() {
|
|
163
|
+
this.navigationEnabled.set(true);
|
|
164
|
+
this.createRootCollectionRequest.update((v) => v + 1);
|
|
165
|
+
}
|
|
166
|
+
|
|
136
167
|
readonly navigationExpandedIds = signal<number[]>([]);
|
|
137
168
|
|
|
138
169
|
readonly gridEnabled = signal(true);
|
|
@@ -234,6 +265,74 @@ export class PresentationService {
|
|
|
234
265
|
this.navigationExpandedIds.set([]);
|
|
235
266
|
}
|
|
236
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
|
+
|
|
237
336
|
private async loadNavigationExpandedSettings() {
|
|
238
337
|
const ids = (await this.messageService.get(StorageType.NavigationExpanded, null)) as number[] | null;
|
|
239
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
|
}
|
|
@@ -106,13 +106,3 @@
|
|
|
106
106
|
</div>
|
|
107
107
|
</div>
|
|
108
108
|
</header>
|
|
109
|
-
|
|
110
|
-
@if (showCollectionDialog) {
|
|
111
|
-
<app-prompt-dialog
|
|
112
|
-
title="New Collection"
|
|
113
|
-
placeholder="Collection name"
|
|
114
|
-
confirmText="Create"
|
|
115
|
-
(confirmed)="onCollectionConfirmed($event)"
|
|
116
|
-
(cancelled)="onCollectionCancelled()"
|
|
117
|
-
/>
|
|
118
|
-
}
|
|
@@ -1,42 +1,23 @@
|
|
|
1
|
-
import { Component, inject
|
|
1
|
+
import { Component, inject } from '@angular/core';
|
|
2
2
|
import { Router } from '@angular/router';
|
|
3
|
-
import {
|
|
4
|
-
import { DatabaseService, PresentationService } from '../../core/services';
|
|
3
|
+
import { PresentationService } from '../../core/services';
|
|
5
4
|
|
|
6
5
|
@Component({
|
|
7
6
|
selector: 'app-header',
|
|
8
7
|
standalone: true,
|
|
9
|
-
imports: [
|
|
8
|
+
imports: [],
|
|
10
9
|
templateUrl: './header.component.html',
|
|
11
10
|
})
|
|
12
11
|
export class HeaderComponent {
|
|
13
|
-
readonly db = inject(DatabaseService);
|
|
14
12
|
private router = inject(Router);
|
|
15
13
|
readonly presentation = inject(PresentationService);
|
|
16
14
|
|
|
17
|
-
constructor() {
|
|
18
|
-
effect(() => {
|
|
19
|
-
console.log('Selected collection ID:', this.db.collectionId());
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
|
|
23
15
|
currentUser: { name: string } | null = null;
|
|
24
16
|
gravatarUrl = '';
|
|
25
17
|
|
|
26
|
-
showCollectionDialog = false;
|
|
27
|
-
|
|
28
18
|
handleCreateCollection(event: Event) {
|
|
29
19
|
event.stopPropagation();
|
|
30
|
-
this.
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
onCollectionConfirmed(name: string) {
|
|
34
|
-
this.db.collectionCreate({ title: name });
|
|
35
|
-
this.showCollectionDialog = false;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
onCollectionCancelled() {
|
|
39
|
-
this.showCollectionDialog = false;
|
|
20
|
+
this.presentation.requestCreateRootCollection();
|
|
40
21
|
}
|
|
41
22
|
|
|
42
23
|
handleToggleSearch(_event: Event) {
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
'wght' 300;
|
|
17
17
|
"
|
|
18
18
|
title="New Smart Collection"
|
|
19
|
+
appStopPropagation
|
|
19
20
|
(click)="openCreateSmartCollection()"
|
|
20
21
|
>add</span
|
|
21
22
|
>
|
|
@@ -24,13 +25,12 @@
|
|
|
24
25
|
<li>
|
|
25
26
|
<a
|
|
26
27
|
class="flex items-center px-3 py-1 text-xs font-normal cursor-pointer transition-colors"
|
|
27
|
-
[
|
|
28
|
-
|
|
28
|
+
[appHoverHighlight]="isSmartSelected(sc)"
|
|
29
|
+
selectedColor="var(--text-primary)"
|
|
30
|
+
normalColor="inherit"
|
|
29
31
|
(click)="selectSmartCollection(sc)"
|
|
30
32
|
(dblclick)="editSmartCollection(sc)"
|
|
31
33
|
(contextmenu)="onSmartContextMenu($event, sc)"
|
|
32
|
-
(mouseenter)="$any($event.target).style.backgroundColor = isSmartSelected(sc) ? 'var(--selected-bg)' : 'var(--hover-bg)'"
|
|
33
|
-
(mouseleave)="$any($event.target).style.backgroundColor = isSmartSelected(sc) ? 'var(--selected-bg)' : 'transparent'"
|
|
34
34
|
>
|
|
35
35
|
<span
|
|
36
36
|
class="material-symbols-outlined icon-sm mr-1"
|
|
@@ -64,10 +64,40 @@
|
|
|
64
64
|
'wght' 300;
|
|
65
65
|
"
|
|
66
66
|
title="New Collection"
|
|
67
|
+
appStopPropagation
|
|
67
68
|
(click)="openCreateRootCollection()"
|
|
68
69
|
>add</span
|
|
69
70
|
>
|
|
70
71
|
<ul class="flex-1 overflow-auto flex flex-col py-0.5">
|
|
72
|
+
@if (isCreating && pendingParentId === 0) {
|
|
73
|
+
<li>
|
|
74
|
+
<a class="flex items-center pr-3 py-1 text-xs font-normal" style="padding-left: 14px">
|
|
75
|
+
<span
|
|
76
|
+
class="material-symbols-outlined icon-sm mr-1"
|
|
77
|
+
[style.color]="'var(--text-muted)'"
|
|
78
|
+
style="
|
|
79
|
+
font-variation-settings:
|
|
80
|
+
'opsz' 20,
|
|
81
|
+
'wght' 300;
|
|
82
|
+
"
|
|
83
|
+
>chevron_right</span
|
|
84
|
+
>
|
|
85
|
+
<input
|
|
86
|
+
class="border rounded px-1 w-full outline-none text-xs"
|
|
87
|
+
[style.background-color]="'var(--input-bg)'"
|
|
88
|
+
[style.border-color]="'var(--input-border)'"
|
|
89
|
+
[style.color]="'var(--text-primary)'"
|
|
90
|
+
[(ngModel)]="creatingTitle"
|
|
91
|
+
(blur)="saveCreating()"
|
|
92
|
+
(keydown.enter)="saveCreating()"
|
|
93
|
+
(keydown.escape)="cancelCreating()"
|
|
94
|
+
appStopPropagation
|
|
95
|
+
placeholder="Collection name"
|
|
96
|
+
appAutofocus
|
|
97
|
+
/>
|
|
98
|
+
</a>
|
|
99
|
+
</li>
|
|
100
|
+
}
|
|
71
101
|
<ng-container *ngTemplateOutlet="subtree; context: { $implicit: tree(), level: 0 }" />
|
|
72
102
|
<li
|
|
73
103
|
class="flex-1 min-h-6"
|
|
@@ -135,15 +165,44 @@
|
|
|
135
165
|
(blur)="saveEditing(node.collection.id)"
|
|
136
166
|
(keydown.enter)="saveEditing(node.collection.id)"
|
|
137
167
|
(keydown.escape)="cancelEditing()"
|
|
138
|
-
|
|
168
|
+
appStopPropagation
|
|
139
169
|
appAutofocus
|
|
140
170
|
/>
|
|
141
171
|
} @else {
|
|
142
172
|
{{ node.collection.title }}
|
|
143
173
|
}
|
|
144
174
|
</a>
|
|
145
|
-
@if (node.children.length && isExpanded(node.collection.id)) {
|
|
175
|
+
@if ((node.children.length && isExpanded(node.collection.id)) || (isCreating && pendingParentId === node.collection.id)) {
|
|
146
176
|
<ul>
|
|
177
|
+
@if (isCreating && pendingParentId === node.collection.id) {
|
|
178
|
+
<li>
|
|
179
|
+
<a class="flex items-center pr-3 py-1 text-xs font-normal" [style.padding-left.px]="14 + (level + 1) * 14">
|
|
180
|
+
<span
|
|
181
|
+
class="material-symbols-outlined icon-sm mr-1"
|
|
182
|
+
[style.color]="'var(--text-muted)'"
|
|
183
|
+
style="
|
|
184
|
+
font-variation-settings:
|
|
185
|
+
'opsz' 20,
|
|
186
|
+
'wght' 300;
|
|
187
|
+
"
|
|
188
|
+
>chevron_right</span
|
|
189
|
+
>
|
|
190
|
+
<input
|
|
191
|
+
class="border rounded px-1 w-full outline-none text-xs"
|
|
192
|
+
[style.background-color]="'var(--input-bg)'"
|
|
193
|
+
[style.border-color]="'var(--input-border)'"
|
|
194
|
+
[style.color]="'var(--text-primary)'"
|
|
195
|
+
[(ngModel)]="creatingTitle"
|
|
196
|
+
(blur)="saveCreating()"
|
|
197
|
+
(keydown.enter)="saveCreating()"
|
|
198
|
+
(keydown.escape)="cancelCreating()"
|
|
199
|
+
appStopPropagation
|
|
200
|
+
placeholder="Collection name"
|
|
201
|
+
appAutofocus
|
|
202
|
+
/>
|
|
203
|
+
</a>
|
|
204
|
+
</li>
|
|
205
|
+
}
|
|
147
206
|
<ng-container *ngTemplateOutlet="subtree; context: { $implicit: node.children, level: level + 1 }" />
|
|
148
207
|
</ul>
|
|
149
208
|
}
|
|
@@ -161,16 +220,6 @@
|
|
|
161
220
|
/>
|
|
162
221
|
}
|
|
163
222
|
|
|
164
|
-
@if (showCollectionDialog) {
|
|
165
|
-
<app-prompt-dialog
|
|
166
|
-
title="New Collection"
|
|
167
|
-
placeholder="Collection name"
|
|
168
|
-
confirmText="Create"
|
|
169
|
-
(confirmed)="onCollectionConfirmed($event)"
|
|
170
|
-
(cancelled)="onCollectionCancelled()"
|
|
171
|
-
/>
|
|
172
|
-
}
|
|
173
|
-
|
|
174
223
|
@if (smartContextMenu) {
|
|
175
224
|
<app-context-menu
|
|
176
225
|
[x]="smartContextMenu.x"
|
|
@@ -185,6 +234,7 @@
|
|
|
185
234
|
<app-rule-builder
|
|
186
235
|
[smartCollection]="editingSmartCollection"
|
|
187
236
|
(saved)="onRuleBuilderSaved($event)"
|
|
237
|
+
(savedAndSync)="onRuleBuilderSavedAndSync($event)"
|
|
188
238
|
(cancelled)="onRuleBuilderCancelled()"
|
|
189
239
|
/>
|
|
190
240
|
}
|