fontastic 1.0.2 → 1.2.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 +17 -0
- package/README.md +8 -19
- package/app/enums/StorageType.js +2 -0
- package/app/enums/StorageType.ts +2 -0
- package/app/main.js +19 -8
- package/app/main.ts +27 -17
- package/app/package.json +1 -1
- package/package.json +3 -3
- package/release-please-config.json +20 -0
- package/scripts/patch-electron-plist.js +41 -0
- package/src/app/core/services/database/database.service.ts +51 -4
- package/src/app/core/services/presentation/presentation.service.ts +7 -0
- 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 +61 -13
- package/src/app/layout/navigation/navigation.component.ts +30 -12
- package/src/app/shared/components/datagrid/datagrid.component.html +25 -7
- package/src/app/shared/components/datagrid/datagrid.component.ts +10 -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
|
@@ -12,12 +12,6 @@ on:
|
|
|
12
12
|
|
|
13
13
|
jobs:
|
|
14
14
|
claude-review:
|
|
15
|
-
# Optional: Filter by PR author
|
|
16
|
-
# if: |
|
|
17
|
-
# github.event.pull_request.user.login == 'external-contributor' ||
|
|
18
|
-
# github.event.pull_request.user.login == 'new-developer' ||
|
|
19
|
-
# github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR'
|
|
20
|
-
|
|
21
15
|
runs-on: ubuntu-latest
|
|
22
16
|
permissions:
|
|
23
17
|
contents: read
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
on:
|
|
2
|
+
push:
|
|
3
|
+
branches:
|
|
4
|
+
- master
|
|
5
|
+
workflow_dispatch:
|
|
6
|
+
|
|
7
|
+
concurrency:
|
|
8
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
9
|
+
cancel-in-progress: true
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
contents: write
|
|
13
|
+
pull-requests: write
|
|
14
|
+
|
|
15
|
+
name: release-please
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
release-please:
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
steps:
|
|
21
|
+
- uses: googleapis/release-please-action@v4.4.0
|
|
22
|
+
with:
|
|
23
|
+
token: ${{ secrets.RELEASE_PLEASE_TOKEN }}
|
|
24
|
+
config-file: release-please-config.json
|
|
25
|
+
manifest-file: .release-please-manifest.json
|
|
@@ -1,117 +1,14 @@
|
|
|
1
|
-
name: Release
|
|
1
|
+
name: Release Build
|
|
2
2
|
|
|
3
3
|
on:
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
bump:
|
|
7
|
-
description: 'Version bump type'
|
|
8
|
-
required: true
|
|
9
|
-
type: choice
|
|
10
|
-
options: [patch, minor, major]
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
11
6
|
|
|
12
7
|
permissions:
|
|
13
8
|
contents: write
|
|
14
9
|
|
|
15
10
|
jobs:
|
|
16
|
-
version:
|
|
17
|
-
runs-on: ubuntu-22.04
|
|
18
|
-
outputs:
|
|
19
|
-
version: ${{ steps.version.outputs.version }}
|
|
20
|
-
steps:
|
|
21
|
-
- name: Guard — owner only
|
|
22
|
-
if: github.actor != github.repository_owner
|
|
23
|
-
run: |
|
|
24
|
-
echo "Only the repository owner can trigger releases."
|
|
25
|
-
exit 1
|
|
26
|
-
|
|
27
|
-
- name: Checkout
|
|
28
|
-
uses: actions/checkout@v6
|
|
29
|
-
with:
|
|
30
|
-
fetch-depth: 0
|
|
31
|
-
|
|
32
|
-
- name: Setup Node 24
|
|
33
|
-
uses: actions/setup-node@v6
|
|
34
|
-
with:
|
|
35
|
-
node-version: '24'
|
|
36
|
-
|
|
37
|
-
- name: Configure git
|
|
38
|
-
run: |
|
|
39
|
-
git config user.name "github-actions[bot]"
|
|
40
|
-
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
41
|
-
|
|
42
|
-
- name: Bump version
|
|
43
|
-
env:
|
|
44
|
-
BUMP: ${{ github.event.inputs.bump }}
|
|
45
|
-
run: |
|
|
46
|
-
npm version "$BUMP" --no-git-tag-version
|
|
47
|
-
node -e "const fs=require('fs');const app=JSON.parse(fs.readFileSync('./app/package.json'));app.version=require('./package.json').version;fs.writeFileSync('./app/package.json',JSON.stringify(app,null,2)+'\n');"
|
|
48
|
-
|
|
49
|
-
- name: Capture new version
|
|
50
|
-
id: version
|
|
51
|
-
run: echo "version=$(jq -r .version package.json)" >> "$GITHUB_OUTPUT"
|
|
52
|
-
|
|
53
|
-
- name: Generate changelog
|
|
54
|
-
run: |
|
|
55
|
-
node -e "
|
|
56
|
-
const { execSync } = require('child_process');
|
|
57
|
-
const fs = require('fs');
|
|
58
|
-
|
|
59
|
-
const lastTag = execSync('git describe --tags --abbrev=0').toString().trim();
|
|
60
|
-
const log = execSync('git log ' + lastTag + '..HEAD --format=%s').toString().trim();
|
|
61
|
-
|
|
62
|
-
const types = [
|
|
63
|
-
['feat', '### Features'],
|
|
64
|
-
['fix', '### Bug Fixes'],
|
|
65
|
-
['refactor', '### Refactoring'],
|
|
66
|
-
['revert', '### Reverts'],
|
|
67
|
-
['perf', '### Performance'],
|
|
68
|
-
];
|
|
69
|
-
const typeMap = Object.fromEntries(types.map(([k, v]) => [k, v]));
|
|
70
|
-
|
|
71
|
-
const pattern = /^(\w+)(\(.+?\))?(!)?: (.+)$/;
|
|
72
|
-
const groups = {};
|
|
73
|
-
|
|
74
|
-
for (const line of log.split('\n')) {
|
|
75
|
-
const m = line.match(pattern);
|
|
76
|
-
if (!m) continue;
|
|
77
|
-
const type = m[1];
|
|
78
|
-
const scope = m[2] ? m[2].slice(1, -1) : null;
|
|
79
|
-
const msg = m[4];
|
|
80
|
-
const label = typeMap[type];
|
|
81
|
-
if (!label) continue;
|
|
82
|
-
if (!groups[type]) groups[type] = [];
|
|
83
|
-
groups[type].push(scope ? '- **' + scope + '**: ' + msg : '- ' + msg);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const sections = types
|
|
87
|
-
.filter(([k]) => groups[k])
|
|
88
|
-
.map(([k, header]) => header + '\n' + groups[k].join('\n'))
|
|
89
|
-
.join('\n\n');
|
|
90
|
-
|
|
91
|
-
const body = sections || '_No conventional commits found since last release._';
|
|
92
|
-
fs.writeFileSync('CHANGELOG_BODY.md', body);
|
|
93
|
-
console.log('Changelog written.');
|
|
94
|
-
"
|
|
95
|
-
|
|
96
|
-
- name: Commit, tag, and push
|
|
97
|
-
env:
|
|
98
|
-
VERSION: ${{ steps.version.outputs.version }}
|
|
99
|
-
run: |
|
|
100
|
-
git add package.json app/package.json
|
|
101
|
-
git commit -m "[Bumped Version] ${VERSION}"
|
|
102
|
-
git push origin master
|
|
103
|
-
git tag "${VERSION}"
|
|
104
|
-
git push origin "${VERSION}"
|
|
105
|
-
|
|
106
|
-
- name: Create GitHub Release
|
|
107
|
-
env:
|
|
108
|
-
GH_TOKEN: ${{ github.token }}
|
|
109
|
-
VERSION: ${{ steps.version.outputs.version }}
|
|
110
|
-
run: gh release create "${VERSION}" --notes-file CHANGELOG_BODY.md --title "${VERSION}"
|
|
111
|
-
|
|
112
|
-
|
|
113
11
|
build:
|
|
114
|
-
needs: version
|
|
115
12
|
strategy:
|
|
116
13
|
matrix:
|
|
117
14
|
include:
|
|
@@ -126,7 +23,7 @@ jobs:
|
|
|
126
23
|
- name: Checkout tag
|
|
127
24
|
uses: actions/checkout@v6
|
|
128
25
|
with:
|
|
129
|
-
ref: ${{
|
|
26
|
+
ref: ${{ github.event.release.tag_name }}
|
|
130
27
|
|
|
131
28
|
- name: Setup Node 24
|
|
132
29
|
uses: actions/setup-node@v6
|
|
@@ -152,13 +49,12 @@ jobs:
|
|
|
152
49
|
- name: Upload artifacts to release
|
|
153
50
|
env:
|
|
154
51
|
GH_TOKEN: ${{ github.token }}
|
|
155
|
-
VERSION: ${{ needs.version.outputs.version }}
|
|
156
52
|
shell: bash
|
|
157
53
|
run: |
|
|
158
54
|
shopt -s nullglob
|
|
159
55
|
files=(release/*.dmg release/*.AppImage release/*.exe release/*.zip release/*.deb release/*.rpm)
|
|
160
56
|
if [ ${#files[@]} -gt 0 ]; then
|
|
161
|
-
gh release upload "${
|
|
57
|
+
gh release upload "${{ github.event.release.tag_name }}" "${files[@]}" --clobber
|
|
162
58
|
else
|
|
163
59
|
echo "No release artifacts found"
|
|
164
60
|
exit 1
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [1.2.0](https://github.com/tomshaw/fontastic/compare/fontastic-v1.1.0...fontastic-v1.2.0) (2026-03-14)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### 🎉 Features
|
|
7
|
+
|
|
8
|
+
* **app:** enhance application plist patching and update icon handling ([297c052](https://github.com/tomshaw/fontastic/commit/297c052a2561962d8d6d1dd2f28f31bf5dcb66ba))
|
|
9
|
+
* **app:** set application name to 'Fontastic' and add plist patching script ([af31cf6](https://github.com/tomshaw/fontastic/commit/af31cf61153b82c8a625755f5fc460b6d8205291))
|
|
10
|
+
* **collection:** implement root collection creation request handling ([cba01d0](https://github.com/tomshaw/fontastic/commit/cba01d098998797d914c75f6cd32f814b3703fb0))
|
|
11
|
+
* **navigation:** implement smart collection creation with input handling ([5cb72ae](https://github.com/tomshaw/fontastic/commit/5cb72ae9f576e0fb8dc1372ab411ea4e2d764191))
|
|
12
|
+
* **release:** add release-please configuration and manifest files ([5cb72ae](https://github.com/tomshaw/fontastic/commit/5cb72ae9f576e0fb8dc1372ab411ea4e2d764191))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### 🛠️ Fixes
|
|
16
|
+
|
|
17
|
+
* **release:** update release configuration and manifest version ([759486b](https://github.com/tomshaw/fontastic/commit/759486b556982d90c5e3eaef38cfa0c214e7ac38))
|
package/README.md
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
# Fontastic
|
|
2
2
|
|
|
3
|
-
](https://angular.dev)
|
|
4
|
+
[](https://electronjs.org)
|
|
5
|
+
[](https://www.typescriptlang.org)
|
|
6
|
+
[](LICENSE.md)
|
|
7
|
+
[](http://makeapullrequest.com)
|
|
6
8
|
|
|
7
9
|
[![Linux Build][linux-build-badge]][linux-build]
|
|
8
10
|
[![MacOS Build][macos-build-badge]][macos-build]
|
|
9
11
|
[![Windows Build][windows-build-badge]][windows-build]
|
|
10
12
|
|
|
11
|
-
[![
|
|
12
|
-
[![
|
|
13
|
-
[![Tweet][twitter-badge]][twitter]
|
|
13
|
+
[](https://github.com/tomshaw/fontastic/stargazers)
|
|
14
|
+
[](https://github.com/tomshaw/fontastic/watchers)
|
|
14
15
|
|
|
15
|
-
Fontastic is
|
|
16
|
+
Fontastic is an Electron-based font management and cataloging application built for organizing, browsing, and inspecting font libraries.
|
|
16
17
|
|
|
17
18
|
## Features
|
|
18
19
|
|
|
@@ -83,12 +84,6 @@ Fontastic is open-sourced software licensed under the [MIT license](https://open
|
|
|
83
84
|
|
|
84
85
|
[repo]: https://github.com/tomshaw/fontastic
|
|
85
86
|
|
|
86
|
-
[maintained-badge]: https://img.shields.io/badge/maintained-yes-brightgreen
|
|
87
|
-
[license-badge]: https://img.shields.io/badge/license-MIT-blue.svg
|
|
88
|
-
[license]: https://github.com/tomshaw/fontastic/blob/main/LICENSE.md
|
|
89
|
-
[prs-badge]: https://img.shields.io/badge/PRs-welcome-red.svg
|
|
90
|
-
[prs]: http://makeapullrequest.com
|
|
91
|
-
|
|
92
87
|
[linux-build-badge]: https://github.com/tomshaw/fontastic/workflows/Linux%20Build/badge.svg
|
|
93
88
|
[linux-build]: https://github.com/tomshaw/fontastic/actions?query=workflow%3A%22Linux+Build%22
|
|
94
89
|
[macos-build-badge]: https://github.com/tomshaw/fontastic/workflows/MacOS%20Build/badge.svg
|
|
@@ -96,9 +91,3 @@ Fontastic is open-sourced software licensed under the [MIT license](https://open
|
|
|
96
91
|
[windows-build-badge]: https://github.com/tomshaw/fontastic/workflows/Windows%20Build/badge.svg
|
|
97
92
|
[windows-build]: https://github.com/tomshaw/fontastic/actions?query=workflow%3A%22Windows+Build%22
|
|
98
93
|
|
|
99
|
-
[github-watch-badge]: https://img.shields.io/github/watchers/tomshaw/fontastic.svg?style=social
|
|
100
|
-
[github-watch]: https://github.com/tomshaw/fontastic/watchers
|
|
101
|
-
[github-star-badge]: https://img.shields.io/github/stars/tomshaw/fontastic.svg?style=social
|
|
102
|
-
[github-star]: https://github.com/tomshaw/fontastic/stargazers
|
|
103
|
-
[twitter]: https://twitter.com/intent/tweet?text=Check%20out%20fontastic!%20https://github.com/tomshaw/fontastic%20%F0%9F%91%8D
|
|
104
|
-
[twitter-badge]: https://img.shields.io/twitter/url/https/github.com/tomshaw/fontastic.svg?style=social
|
package/app/enums/StorageType.js
CHANGED
|
@@ -17,5 +17,7 @@ var StorageType;
|
|
|
17
17
|
StorageType["LayoutTheme"] = "layout.theme";
|
|
18
18
|
StorageType["AiKeys"] = "ai.keys";
|
|
19
19
|
StorageType["NavigationExpanded"] = "navigation.expanded";
|
|
20
|
+
StorageType["SortColumn"] = "datagrid.sort.column";
|
|
21
|
+
StorageType["SortDirection"] = "datagrid.sort.direction";
|
|
20
22
|
})(StorageType || (exports.StorageType = StorageType = {}));
|
|
21
23
|
//# sourceMappingURL=StorageType.js.map
|
package/app/enums/StorageType.ts
CHANGED
package/app/main.js
CHANGED
|
@@ -5,10 +5,19 @@ const path = require("path");
|
|
|
5
5
|
const fs = require("fs");
|
|
6
6
|
const node_machine_id_1 = require("node-machine-id");
|
|
7
7
|
const Application_1 = require("./Application");
|
|
8
|
+
electron_1.app.name = 'Fontastic';
|
|
9
|
+
const iconPath = path.join(__dirname, '..', 'src', 'assets', 'icons', 'favicon.512x512.png');
|
|
10
|
+
electron_1.app.setAboutPanelOptions({
|
|
11
|
+
applicationName: 'Fontastic',
|
|
12
|
+
applicationVersion: electron_1.app.getVersion(),
|
|
13
|
+
iconPath,
|
|
14
|
+
});
|
|
8
15
|
let win = null;
|
|
9
16
|
let resolveAppReady;
|
|
10
|
-
const appReadyPromise = new Promise(resolve => {
|
|
11
|
-
|
|
17
|
+
const appReadyPromise = new Promise((resolve) => {
|
|
18
|
+
resolveAppReady = resolve;
|
|
19
|
+
});
|
|
20
|
+
const args = process.argv.slice(1), serve = args.some((val) => val === '--serve');
|
|
12
21
|
function createWindow() {
|
|
13
22
|
const size = electron_1.screen.getPrimaryDisplay().workAreaSize;
|
|
14
23
|
// Create the browser window.
|
|
@@ -21,15 +30,15 @@ function createWindow() {
|
|
|
21
30
|
nodeIntegration: true,
|
|
22
31
|
allowRunningInsecureContent: serve,
|
|
23
32
|
contextIsolation: false,
|
|
24
|
-
webSecurity: !serve
|
|
33
|
+
webSecurity: !serve,
|
|
25
34
|
},
|
|
26
35
|
});
|
|
27
36
|
(0, node_machine_id_1.machineId)(true).then((machineId) => new Application_1.default(machineId, !serve, win).initialize().then(() => resolveAppReady()));
|
|
28
37
|
if (serve) {
|
|
29
|
-
Promise.resolve().then(() => require('electron-debug')).then(debug => {
|
|
38
|
+
Promise.resolve().then(() => require('electron-debug')).then((debug) => {
|
|
30
39
|
debug.default({ isEnabled: true, showDevTools: true });
|
|
31
40
|
});
|
|
32
|
-
Promise.resolve().then(() => require('electron-reloader')).then(reloader => {
|
|
41
|
+
Promise.resolve().then(() => require('electron-reloader')).then((reloader) => {
|
|
33
42
|
const reloaderFn = reloader.default || reloader;
|
|
34
43
|
// watchRenderer: false — Angular dev server handles HMR for the renderer.
|
|
35
44
|
// Without this, reloader triggers spurious reloads on macOS (issue #840).
|
|
@@ -58,10 +67,12 @@ function createWindow() {
|
|
|
58
67
|
return win;
|
|
59
68
|
}
|
|
60
69
|
try {
|
|
61
|
-
electron_1.protocol.registerSchemesAsPrivileged([
|
|
70
|
+
electron_1.protocol.registerSchemesAsPrivileged([
|
|
71
|
+
{
|
|
62
72
|
scheme: 'font',
|
|
63
|
-
privileges: { bypassCSP: true, supportFetchAPI: true }
|
|
64
|
-
}
|
|
73
|
+
privileges: { bypassCSP: true, supportFetchAPI: true },
|
|
74
|
+
},
|
|
75
|
+
]);
|
|
65
76
|
electron_1.ipcMain.handle('app:get-version', () => electron_1.app.getVersion());
|
|
66
77
|
electron_1.ipcMain.handle('app:ready', () => appReadyPromise);
|
|
67
78
|
// This method will be called when Electron has finished
|
package/app/main.ts
CHANGED
|
@@ -1,18 +1,29 @@
|
|
|
1
|
-
import {app, BrowserWindow, ipcMain, net, protocol, screen} from 'electron';
|
|
1
|
+
import { app, BrowserWindow, ipcMain, nativeImage, net, protocol, screen } from 'electron';
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import * as fs from 'fs';
|
|
4
4
|
import { machineId } from 'node-machine-id';
|
|
5
5
|
import Application from './Application';
|
|
6
6
|
|
|
7
|
+
app.name = 'Fontastic';
|
|
8
|
+
|
|
9
|
+
const iconPath = path.join(__dirname, '..', 'src', 'assets', 'icons', 'favicon.512x512.png');
|
|
10
|
+
|
|
11
|
+
app.setAboutPanelOptions({
|
|
12
|
+
applicationName: 'Fontastic',
|
|
13
|
+
applicationVersion: app.getVersion(),
|
|
14
|
+
iconPath,
|
|
15
|
+
});
|
|
16
|
+
|
|
7
17
|
let win: BrowserWindow | null = null;
|
|
8
18
|
let resolveAppReady: () => void;
|
|
9
|
-
const appReadyPromise = new Promise<void>(resolve => {
|
|
19
|
+
const appReadyPromise = new Promise<void>((resolve) => {
|
|
20
|
+
resolveAppReady = resolve;
|
|
21
|
+
});
|
|
10
22
|
|
|
11
23
|
const args = process.argv.slice(1),
|
|
12
|
-
serve = args.some(val => val === '--serve');
|
|
24
|
+
serve = args.some((val) => val === '--serve');
|
|
13
25
|
|
|
14
26
|
function createWindow(): BrowserWindow {
|
|
15
|
-
|
|
16
27
|
const size = screen.getPrimaryDisplay().workAreaSize;
|
|
17
28
|
|
|
18
29
|
// Create the browser window.
|
|
@@ -25,20 +36,18 @@ function createWindow(): BrowserWindow {
|
|
|
25
36
|
nodeIntegration: true,
|
|
26
37
|
allowRunningInsecureContent: serve,
|
|
27
38
|
contextIsolation: false,
|
|
28
|
-
webSecurity: !serve
|
|
39
|
+
webSecurity: !serve,
|
|
29
40
|
},
|
|
30
41
|
});
|
|
31
42
|
|
|
32
|
-
machineId(true).then((machineId: string) =>
|
|
33
|
-
new Application(machineId, !serve, win!).initialize().then(() => resolveAppReady())
|
|
34
|
-
);
|
|
43
|
+
machineId(true).then((machineId: string) => new Application(machineId, !serve, win!).initialize().then(() => resolveAppReady()));
|
|
35
44
|
|
|
36
45
|
if (serve) {
|
|
37
|
-
import('electron-debug').then(debug => {
|
|
38
|
-
debug.default({isEnabled: true, showDevTools: true});
|
|
46
|
+
import('electron-debug').then((debug) => {
|
|
47
|
+
debug.default({ isEnabled: true, showDevTools: true });
|
|
39
48
|
});
|
|
40
49
|
|
|
41
|
-
import('electron-reloader').then(reloader => {
|
|
50
|
+
import('electron-reloader').then((reloader) => {
|
|
42
51
|
const reloaderFn = (reloader as any).default || reloader;
|
|
43
52
|
// watchRenderer: false — Angular dev server handles HMR for the renderer.
|
|
44
53
|
// Without this, reloader triggers spurious reloads on macOS (issue #840).
|
|
@@ -50,7 +59,7 @@ function createWindow(): BrowserWindow {
|
|
|
50
59
|
let pathIndex = './browser/index.html';
|
|
51
60
|
|
|
52
61
|
if (fs.existsSync(path.join(__dirname, '../dist/browser/index.html'))) {
|
|
53
|
-
|
|
62
|
+
// Path when running electron in local folder
|
|
54
63
|
pathIndex = '../dist/browser/index.html';
|
|
55
64
|
}
|
|
56
65
|
|
|
@@ -71,10 +80,12 @@ function createWindow(): BrowserWindow {
|
|
|
71
80
|
}
|
|
72
81
|
|
|
73
82
|
try {
|
|
74
|
-
protocol.registerSchemesAsPrivileged([
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
83
|
+
protocol.registerSchemesAsPrivileged([
|
|
84
|
+
{
|
|
85
|
+
scheme: 'font',
|
|
86
|
+
privileges: { bypassCSP: true, supportFetchAPI: true },
|
|
87
|
+
},
|
|
88
|
+
]);
|
|
78
89
|
|
|
79
90
|
ipcMain.handle('app:get-version', () => app.getVersion());
|
|
80
91
|
ipcMain.handle('app:ready', () => appReadyPromise);
|
|
@@ -107,7 +118,6 @@ try {
|
|
|
107
118
|
createWindow();
|
|
108
119
|
}
|
|
109
120
|
});
|
|
110
|
-
|
|
111
121
|
} catch (e) {
|
|
112
122
|
// Catch Error
|
|
113
123
|
// throw e;
|
package/app/package.json
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fontastic",
|
|
3
|
-
"version": "1.0
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.2.0",
|
|
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,
|
|
7
7
|
"author": {
|
|
@@ -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}"`);
|
|
@@ -137,24 +137,26 @@ export class DatabaseService {
|
|
|
137
137
|
private fetchCurrentPage(extraOptions: any = {}) {
|
|
138
138
|
const skip = (this.currentPage() - 1) * this.pageSize();
|
|
139
139
|
const take = this.pageSize();
|
|
140
|
+
const sortOrder = this.getSortOrder();
|
|
140
141
|
|
|
141
142
|
const smartCollectionId = this.activeSmartCollectionId();
|
|
142
143
|
if (smartCollectionId) {
|
|
143
|
-
this.smartCollectionEvaluate(smartCollectionId, { skip, take });
|
|
144
|
+
this.smartCollectionEvaluate(smartCollectionId, { skip, take, ...(sortOrder ? { order: sortOrder } : {}) });
|
|
144
145
|
return;
|
|
145
146
|
}
|
|
146
147
|
|
|
147
148
|
const searchWhere = this.activeSearchWhere();
|
|
148
149
|
if (searchWhere) {
|
|
149
150
|
const searchOrder = this.activeSearchOrder();
|
|
150
|
-
|
|
151
|
+
const order = sortOrder ?? searchOrder;
|
|
152
|
+
this.storeSearch({ where: searchWhere, skip, take, ...(order ? { order } : {}) });
|
|
151
153
|
return;
|
|
152
154
|
}
|
|
153
155
|
|
|
154
156
|
const collectionId = this.collectionId();
|
|
155
157
|
const filter = this.activeFilter();
|
|
156
158
|
|
|
157
|
-
const options: any = { skip, take, ...extraOptions };
|
|
159
|
+
const options: any = { skip, take, ...extraOptions, ...(sortOrder ? { order: sortOrder } : {}) };
|
|
158
160
|
|
|
159
161
|
if (collectionId) {
|
|
160
162
|
options.collectionId = collectionId;
|
|
@@ -172,17 +174,24 @@ export class DatabaseService {
|
|
|
172
174
|
|
|
173
175
|
constructor() {
|
|
174
176
|
this.electron.ready.then(async () => {
|
|
175
|
-
const [collections, smartCollections, savedCollectionId, savedStoreId] = await Promise.all([
|
|
177
|
+
const [collections, smartCollections, savedCollectionId, savedStoreId, savedSortColumn, savedSortDirection] = await Promise.all([
|
|
176
178
|
this.message.collectionFetch({}),
|
|
177
179
|
this.message.smartCollectionFind(),
|
|
178
180
|
this.message.get(StorageType.CollectionId, null),
|
|
179
181
|
this.message.get(StorageType.StoreId, null),
|
|
182
|
+
this.message.get(StorageType.SortColumn, null),
|
|
183
|
+
this.message.get(StorageType.SortDirection, null),
|
|
180
184
|
]);
|
|
181
185
|
|
|
182
186
|
this.collections.set(collections);
|
|
183
187
|
this.smartCollections.set(smartCollections);
|
|
184
188
|
console.log('System Boot:', collections);
|
|
185
189
|
|
|
190
|
+
if (savedSortColumn) {
|
|
191
|
+
this.sortColumn.set(savedSortColumn);
|
|
192
|
+
this.sortDirection.set(savedSortDirection === 'DESC' ? 'DESC' : 'ASC');
|
|
193
|
+
}
|
|
194
|
+
|
|
186
195
|
if (savedCollectionId) {
|
|
187
196
|
this.collectionId.set(savedCollectionId);
|
|
188
197
|
}
|
|
@@ -398,6 +407,44 @@ export class DatabaseService {
|
|
|
398
407
|
|
|
399
408
|
readonly activeSearchOrder = signal<{ column: string; direction: string } | null>(null);
|
|
400
409
|
|
|
410
|
+
// Datagrid sort (persisted)
|
|
411
|
+
readonly sortColumn = signal<string | null>(null);
|
|
412
|
+
readonly sortDirection = signal<'ASC' | 'DESC'>('ASC');
|
|
413
|
+
|
|
414
|
+
toggleSort(column: string) {
|
|
415
|
+
const current = this.sortColumn();
|
|
416
|
+
if (current === column) {
|
|
417
|
+
if (this.sortDirection() === 'ASC') {
|
|
418
|
+
this.sortDirection.set('DESC');
|
|
419
|
+
} else {
|
|
420
|
+
// Clear sort
|
|
421
|
+
this.sortColumn.set(null);
|
|
422
|
+
this.sortDirection.set('ASC');
|
|
423
|
+
}
|
|
424
|
+
} else {
|
|
425
|
+
this.sortColumn.set(column);
|
|
426
|
+
this.sortDirection.set('ASC');
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Persist
|
|
430
|
+
const col = this.sortColumn();
|
|
431
|
+
if (col) {
|
|
432
|
+
this.message.set(StorageType.SortColumn, col);
|
|
433
|
+
this.message.set(StorageType.SortDirection, this.sortDirection());
|
|
434
|
+
} else {
|
|
435
|
+
this.message.set(StorageType.SortColumn, null);
|
|
436
|
+
this.message.set(StorageType.SortDirection, null);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
this.currentPage.set(1);
|
|
440
|
+
this.fetchCurrentPage();
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
private getSortOrder(): { column: string; direction: string } | null {
|
|
444
|
+
const col = this.sortColumn();
|
|
445
|
+
return col ? { column: col, direction: this.sortDirection() } : null;
|
|
446
|
+
}
|
|
447
|
+
|
|
401
448
|
selectSearch(where: { key: string; value: any }[], order?: { column: string; direction: string }) {
|
|
402
449
|
this.parentId.set(null);
|
|
403
450
|
this.collectionId.set(null);
|
|
@@ -133,6 +133,13 @@ export class PresentationService {
|
|
|
133
133
|
|
|
134
134
|
readonly selectedGlyph = signal<number | null>(null);
|
|
135
135
|
|
|
136
|
+
readonly createRootCollectionRequest = signal(0);
|
|
137
|
+
|
|
138
|
+
requestCreateRootCollection() {
|
|
139
|
+
this.navigationEnabled.set(true);
|
|
140
|
+
this.createRootCollectionRequest.update((v) => v + 1);
|
|
141
|
+
}
|
|
142
|
+
|
|
136
143
|
readonly navigationExpandedIds = signal<number[]>([]);
|
|
137
144
|
|
|
138
145
|
readonly gridEnabled = signal(true);
|
|
@@ -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,7 +16,7 @@
|
|
|
16
16
|
'wght' 300;
|
|
17
17
|
"
|
|
18
18
|
title="New Smart Collection"
|
|
19
|
-
(click)="openCreateSmartCollection()"
|
|
19
|
+
(click)="$event.stopPropagation(); openCreateSmartCollection()"
|
|
20
20
|
>add</span
|
|
21
21
|
>
|
|
22
22
|
<ul class="flex flex-col py-0.5">
|
|
@@ -64,10 +64,39 @@
|
|
|
64
64
|
'wght' 300;
|
|
65
65
|
"
|
|
66
66
|
title="New Collection"
|
|
67
|
-
(click)="openCreateRootCollection()"
|
|
67
|
+
(click)="$event.stopPropagation(); openCreateRootCollection()"
|
|
68
68
|
>add</span
|
|
69
69
|
>
|
|
70
70
|
<ul class="flex-1 overflow-auto flex flex-col py-0.5">
|
|
71
|
+
@if (isCreating && pendingParentId === 0) {
|
|
72
|
+
<li>
|
|
73
|
+
<a class="flex items-center pr-3 py-1 text-xs font-normal" style="padding-left: 14px">
|
|
74
|
+
<span
|
|
75
|
+
class="material-symbols-outlined icon-sm mr-1"
|
|
76
|
+
[style.color]="'var(--text-muted)'"
|
|
77
|
+
style="
|
|
78
|
+
font-variation-settings:
|
|
79
|
+
'opsz' 20,
|
|
80
|
+
'wght' 300;
|
|
81
|
+
"
|
|
82
|
+
>chevron_right</span
|
|
83
|
+
>
|
|
84
|
+
<input
|
|
85
|
+
class="border rounded px-1 w-full outline-none text-xs"
|
|
86
|
+
[style.background-color]="'var(--input-bg)'"
|
|
87
|
+
[style.border-color]="'var(--input-border)'"
|
|
88
|
+
[style.color]="'var(--text-primary)'"
|
|
89
|
+
[(ngModel)]="creatingTitle"
|
|
90
|
+
(blur)="saveCreating()"
|
|
91
|
+
(keydown.enter)="saveCreating()"
|
|
92
|
+
(keydown.escape)="cancelCreating()"
|
|
93
|
+
(click)="$event.stopPropagation()"
|
|
94
|
+
placeholder="Collection name"
|
|
95
|
+
appAutofocus
|
|
96
|
+
/>
|
|
97
|
+
</a>
|
|
98
|
+
</li>
|
|
99
|
+
}
|
|
71
100
|
<ng-container *ngTemplateOutlet="subtree; context: { $implicit: tree(), level: 0 }" />
|
|
72
101
|
<li
|
|
73
102
|
class="flex-1 min-h-6"
|
|
@@ -142,8 +171,37 @@
|
|
|
142
171
|
{{ node.collection.title }}
|
|
143
172
|
}
|
|
144
173
|
</a>
|
|
145
|
-
@if (node.children.length && isExpanded(node.collection.id)) {
|
|
174
|
+
@if ((node.children.length && isExpanded(node.collection.id)) || (isCreating && pendingParentId === node.collection.id)) {
|
|
146
175
|
<ul>
|
|
176
|
+
@if (isCreating && pendingParentId === node.collection.id) {
|
|
177
|
+
<li>
|
|
178
|
+
<a class="flex items-center pr-3 py-1 text-xs font-normal" [style.padding-left.px]="14 + (level + 1) * 14">
|
|
179
|
+
<span
|
|
180
|
+
class="material-symbols-outlined icon-sm mr-1"
|
|
181
|
+
[style.color]="'var(--text-muted)'"
|
|
182
|
+
style="
|
|
183
|
+
font-variation-settings:
|
|
184
|
+
'opsz' 20,
|
|
185
|
+
'wght' 300;
|
|
186
|
+
"
|
|
187
|
+
>chevron_right</span
|
|
188
|
+
>
|
|
189
|
+
<input
|
|
190
|
+
class="border rounded px-1 w-full outline-none text-xs"
|
|
191
|
+
[style.background-color]="'var(--input-bg)'"
|
|
192
|
+
[style.border-color]="'var(--input-border)'"
|
|
193
|
+
[style.color]="'var(--text-primary)'"
|
|
194
|
+
[(ngModel)]="creatingTitle"
|
|
195
|
+
(blur)="saveCreating()"
|
|
196
|
+
(keydown.enter)="saveCreating()"
|
|
197
|
+
(keydown.escape)="cancelCreating()"
|
|
198
|
+
(click)="$event.stopPropagation()"
|
|
199
|
+
placeholder="Collection name"
|
|
200
|
+
appAutofocus
|
|
201
|
+
/>
|
|
202
|
+
</a>
|
|
203
|
+
</li>
|
|
204
|
+
}
|
|
147
205
|
<ng-container *ngTemplateOutlet="subtree; context: { $implicit: node.children, level: level + 1 }" />
|
|
148
206
|
</ul>
|
|
149
207
|
}
|
|
@@ -161,16 +219,6 @@
|
|
|
161
219
|
/>
|
|
162
220
|
}
|
|
163
221
|
|
|
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
222
|
@if (smartContextMenu) {
|
|
175
223
|
<app-context-menu
|
|
176
224
|
[x]="smartContextMenu.x"
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { Component, inject, computed, OnInit, OnDestroy } from '@angular/core';
|
|
1
|
+
import { Component, inject, computed, effect, OnInit, OnDestroy } from '@angular/core';
|
|
2
2
|
import { CommonModule } from '@angular/common';
|
|
3
3
|
import { FormsModule } from '@angular/forms';
|
|
4
4
|
import { DatabaseService, MessageService, PresentationService } from '../../core/services';
|
|
5
5
|
import { CollapsiblePanelComponent } from '../../shared/components/collapsible-panel/collapsible-panel.component';
|
|
6
6
|
import { ContextMenuComponent, ContextMenuItem } from '../../shared/components/context-menu/context-menu.component';
|
|
7
|
-
import {
|
|
7
|
+
import { RuleBuilderComponent } from '../../shared/components';
|
|
8
8
|
import { AutofocusDirective } from '../../shared/directives/autofocus/autofocus.directive';
|
|
9
9
|
import { LibraryComponent } from './library/library.component';
|
|
10
10
|
import { NewsStatsComponent } from './stats/stats.component';
|
|
@@ -26,7 +26,6 @@ export interface TreeNode {
|
|
|
26
26
|
FormsModule,
|
|
27
27
|
CollapsiblePanelComponent,
|
|
28
28
|
ContextMenuComponent,
|
|
29
|
-
PromptDialogComponent,
|
|
30
29
|
RuleBuilderComponent,
|
|
31
30
|
AutofocusDirective,
|
|
32
31
|
LibraryComponent,
|
|
@@ -39,6 +38,15 @@ export class NavigationComponent implements OnInit, OnDestroy {
|
|
|
39
38
|
private message = inject(MessageService);
|
|
40
39
|
private presentation = inject(PresentationService);
|
|
41
40
|
|
|
41
|
+
constructor() {
|
|
42
|
+
effect(() => {
|
|
43
|
+
const req = this.presentation.createRootCollectionRequest();
|
|
44
|
+
if (req > 0) {
|
|
45
|
+
this.openCreateRootCollection();
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
42
50
|
private menuToggleListener = (_event: any, panel: string) => {
|
|
43
51
|
if (panel === 'expand-collections') {
|
|
44
52
|
this.expandAll();
|
|
@@ -72,7 +80,8 @@ export class NavigationComponent implements OnInit, OnDestroy {
|
|
|
72
80
|
editingTitle = '';
|
|
73
81
|
allExpanded = false;
|
|
74
82
|
|
|
75
|
-
|
|
83
|
+
isCreating = false;
|
|
84
|
+
creatingTitle = '';
|
|
76
85
|
pendingParentId: number | null = null;
|
|
77
86
|
|
|
78
87
|
// Smart Collection state
|
|
@@ -256,7 +265,9 @@ export class NavigationComponent implements OnInit, OnDestroy {
|
|
|
256
265
|
switch (action) {
|
|
257
266
|
case 'add-collection':
|
|
258
267
|
this.pendingParentId = collection.id;
|
|
259
|
-
this.
|
|
268
|
+
this.isCreating = true;
|
|
269
|
+
this.creatingTitle = '';
|
|
270
|
+
this.presentation.expandNavigationId(collection.id);
|
|
260
271
|
break;
|
|
261
272
|
case 'add-fonts':
|
|
262
273
|
this.handleAddFonts(collection);
|
|
@@ -273,17 +284,24 @@ export class NavigationComponent implements OnInit, OnDestroy {
|
|
|
273
284
|
|
|
274
285
|
openCreateRootCollection() {
|
|
275
286
|
this.pendingParentId = 0;
|
|
276
|
-
this.
|
|
287
|
+
this.isCreating = true;
|
|
288
|
+
this.creatingTitle = '';
|
|
277
289
|
}
|
|
278
290
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
291
|
+
saveCreating() {
|
|
292
|
+
const title = this.creatingTitle.trim();
|
|
293
|
+
if (title) {
|
|
294
|
+
this.db.collectionCreate({ title, parentId: this.pendingParentId });
|
|
295
|
+
if (this.pendingParentId) {
|
|
296
|
+
this.presentation.expandNavigationId(this.pendingParentId);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
this.cancelCreating();
|
|
283
300
|
}
|
|
284
301
|
|
|
285
|
-
|
|
286
|
-
this.
|
|
302
|
+
cancelCreating() {
|
|
303
|
+
this.isCreating = false;
|
|
304
|
+
this.creatingTitle = '';
|
|
287
305
|
this.pendingParentId = null;
|
|
288
306
|
}
|
|
289
307
|
|
|
@@ -8,13 +8,31 @@
|
|
|
8
8
|
>
|
|
9
9
|
<tr class="text-left text-[11px] uppercase tracking-wide" [style.color]="'var(--text-muted)'">
|
|
10
10
|
<th class="w-10 px-2 py-2 text-center font-medium"></th>
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
11
|
+
@for (col of sortableColumns; track col.field) {
|
|
12
|
+
<th
|
|
13
|
+
class="px-4 py-2 font-medium cursor-pointer select-none transition-colors hover:brightness-125"
|
|
14
|
+
[class.max-w-48]="col.field === 'designer'"
|
|
15
|
+
(click)="db.toggleSort(col.field)"
|
|
16
|
+
>
|
|
17
|
+
<div class="flex items-center justify-between gap-1">
|
|
18
|
+
<span>{{ col.label }}</span>
|
|
19
|
+
@if (db.sortColumn() === col.field) {
|
|
20
|
+
<span
|
|
21
|
+
class="material-symbols-outlined"
|
|
22
|
+
style="
|
|
23
|
+
font-size: 14px;
|
|
24
|
+
font-variation-settings:
|
|
25
|
+
'opsz' 20,
|
|
26
|
+
'wght' 300;
|
|
27
|
+
"
|
|
28
|
+
[style.color]="'var(--accent)'"
|
|
29
|
+
>
|
|
30
|
+
{{ db.sortDirection() === 'ASC' ? 'arrow_upward' : 'arrow_downward' }}
|
|
31
|
+
</span>
|
|
32
|
+
}
|
|
33
|
+
</div>
|
|
34
|
+
</th>
|
|
35
|
+
}
|
|
18
36
|
<th class="w-10 px-2 py-2 text-center font-medium"></th>
|
|
19
37
|
<th class="w-10 px-2 py-2 text-center font-medium"></th>
|
|
20
38
|
<th class="w-10 px-2 py-2 text-center font-medium"></th>
|
|
@@ -11,6 +11,16 @@ export class DatagridComponent {
|
|
|
11
11
|
private messageService = inject(MessageService);
|
|
12
12
|
private el = inject(ElementRef);
|
|
13
13
|
|
|
14
|
+
readonly sortableColumns = [
|
|
15
|
+
{ field: 'full_name', label: 'Name' },
|
|
16
|
+
{ field: 'font_family', label: 'Family' },
|
|
17
|
+
{ field: 'font_subfamily', label: 'Style' },
|
|
18
|
+
{ field: 'file_type', label: 'Type' },
|
|
19
|
+
{ field: 'file_size', label: 'Size' },
|
|
20
|
+
{ field: 'version', label: 'Version' },
|
|
21
|
+
{ field: 'designer', label: 'Designer' },
|
|
22
|
+
];
|
|
23
|
+
|
|
14
24
|
constructor() {
|
|
15
25
|
effect(() => {
|
|
16
26
|
this.db.currentPage();
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/src/favicon.ico
CHANGED
|
Binary file
|