fontastic 0.2.0 → 1.0.1
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/macos.yml +0 -7
- package/.github/workflows/ubuntu.yml +0 -7
- package/.github/workflows/windows.yml +0 -7
- package/README.md +1 -0
- package/app/core/ConnectionManager.js +7 -1
- package/app/core/ConnectionManager.ts +11 -3
- package/app/core/MessageHandler.js +24 -0
- package/app/core/MessageHandler.ts +29 -0
- package/app/core/menu/templates/DarwinTemplate.js +15 -0
- package/app/core/menu/templates/DarwinTemplate.ts +15 -0
- package/app/core/menu/templates/SystemTemplate.js +15 -0
- package/app/core/menu/templates/SystemTemplate.ts +15 -0
- package/app/database/entity/SmartCollection.schema.js +66 -0
- package/app/database/entity/SmartCollection.schema.ts +39 -0
- package/app/database/entity/index.js +1 -0
- package/app/database/entity/index.ts +2 -1
- package/app/database/repository/SmartCollection.repository.js +47 -0
- package/app/database/repository/SmartCollection.repository.ts +30 -0
- package/app/database/repository/Store.repository.js +107 -0
- package/app/database/repository/Store.repository.ts +106 -0
- package/app/database/repository/index.js +1 -0
- package/app/database/repository/index.ts +2 -1
- package/app/enums/ChannelType.js +5 -0
- package/app/enums/ChannelType.ts +6 -0
- package/app/enums/StorageType.js +1 -0
- package/app/enums/StorageType.ts +1 -0
- package/app/package.json +1 -1
- package/app/types/FontMetrics.js +3 -0
- package/app/types/SmartCollection.js +3 -0
- package/app/types/SmartCollection.ts +5 -0
- package/app/types/index.js +2 -0
- package/app/types/index.ts +1 -0
- package/package.json +1 -1
- package/src/app/core/services/database/database.service.ts +70 -1
- package/src/app/core/services/message/message.service.ts +23 -0
- package/src/app/core/services/presentation/presentation.service.ts +47 -0
- package/src/app/layout/header/header.component.html +1 -1
- package/src/app/layout/layout.component.html +1 -1
- package/src/app/layout/navigation/navigation.component.html +70 -4
- package/src/app/layout/navigation/navigation.component.ts +129 -25
- package/src/app/shared/components/datagrid/datagrid.component.html +82 -2
- package/src/app/shared/components/index.ts +1 -0
- package/src/app/shared/components/rule-builder/rule-builder.component.html +94 -0
- package/src/app/shared/components/rule-builder/rule-builder.component.ts +136 -0
- package/src/app/shared/components/toolbar/toolbar.component.html +0 -81
- package/src/app/shared/components/toolbar/toolbar.component.ts +1 -2
- package/src/styles/base/variables.css +16 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Brackets, Equal } from 'typeorm';
|
|
2
2
|
import { Store } from '../entity';
|
|
3
3
|
import { searchDbColumns } from '../../config';
|
|
4
|
+
import type { SmartCollectionRule } from '../../types';
|
|
4
5
|
|
|
5
6
|
export const StoreRepository = {
|
|
6
7
|
async search(options: any) {
|
|
@@ -330,6 +331,111 @@ export const StoreRepository = {
|
|
|
330
331
|
.catch((err: any) => console.log('insert-error', err));
|
|
331
332
|
},
|
|
332
333
|
|
|
334
|
+
async evaluateSmartRules(rules: SmartCollectionRule[], matchType: string, options: any = {}) {
|
|
335
|
+
const db = this.createQueryBuilder();
|
|
336
|
+
|
|
337
|
+
const textFields = [
|
|
338
|
+
'file_name',
|
|
339
|
+
'file_path',
|
|
340
|
+
'compatible_full_name',
|
|
341
|
+
'copyright',
|
|
342
|
+
'description',
|
|
343
|
+
'designer',
|
|
344
|
+
'designer_url',
|
|
345
|
+
'font_family',
|
|
346
|
+
'font_subfamily',
|
|
347
|
+
'full_name',
|
|
348
|
+
'license',
|
|
349
|
+
'license_url',
|
|
350
|
+
'manufacturer',
|
|
351
|
+
'manufacturer_url',
|
|
352
|
+
'post_script_name',
|
|
353
|
+
'preferred_family',
|
|
354
|
+
'preferred_sub_family',
|
|
355
|
+
'sample_text',
|
|
356
|
+
'trademark',
|
|
357
|
+
'unique_id',
|
|
358
|
+
'version',
|
|
359
|
+
];
|
|
360
|
+
const booleanFields = ['favorite', 'system', 'installable', 'temporary'];
|
|
361
|
+
const numericFields = ['file_size'];
|
|
362
|
+
const dateFields = ['created'];
|
|
363
|
+
|
|
364
|
+
if (rules.length > 0) {
|
|
365
|
+
const conditions: ((qb: any) => void)[] = rules.map((rule, idx) => {
|
|
366
|
+
const field = `store.${rule.field}`;
|
|
367
|
+
const paramName = `p${idx}`;
|
|
368
|
+
|
|
369
|
+
return (qb: any) => {
|
|
370
|
+
if (textFields.includes(rule.field)) {
|
|
371
|
+
const val = String(rule.value).toLowerCase();
|
|
372
|
+
switch (rule.operator) {
|
|
373
|
+
case 'contains':
|
|
374
|
+
qb.where(`LOWER(${field}) LIKE :${paramName}`, { [paramName]: `%${val}%` });
|
|
375
|
+
break;
|
|
376
|
+
case 'equals':
|
|
377
|
+
qb.where(`LOWER(${field}) = :${paramName}`, { [paramName]: val });
|
|
378
|
+
break;
|
|
379
|
+
case 'starts_with':
|
|
380
|
+
qb.where(`LOWER(${field}) LIKE :${paramName}`, { [paramName]: `${val}%` });
|
|
381
|
+
break;
|
|
382
|
+
case 'ends_with':
|
|
383
|
+
qb.where(`LOWER(${field}) LIKE :${paramName}`, { [paramName]: `%${val}` });
|
|
384
|
+
break;
|
|
385
|
+
default:
|
|
386
|
+
qb.where(`LOWER(${field}) LIKE :${paramName}`, { [paramName]: `%${val}%` });
|
|
387
|
+
}
|
|
388
|
+
} else if (rule.field === 'file_type') {
|
|
389
|
+
qb.where(`${field} = :${paramName}`, { [paramName]: rule.value });
|
|
390
|
+
} else if (booleanFields.includes(rule.field)) {
|
|
391
|
+
const boolVal = rule.operator === 'is_not' ? 0 : 1;
|
|
392
|
+
qb.where(`${field} = :${paramName}`, { [paramName]: boolVal });
|
|
393
|
+
} else if (numericFields.includes(rule.field)) {
|
|
394
|
+
if (rule.operator === 'greater_than') {
|
|
395
|
+
qb.where(`${field} > :${paramName}`, { [paramName]: rule.value });
|
|
396
|
+
} else if (rule.operator === 'less_than') {
|
|
397
|
+
qb.where(`${field} < :${paramName}`, { [paramName]: rule.value });
|
|
398
|
+
} else {
|
|
399
|
+
qb.where(`${field} = :${paramName}`, { [paramName]: rule.value });
|
|
400
|
+
}
|
|
401
|
+
} else if (dateFields.includes(rule.field)) {
|
|
402
|
+
if (rule.operator === 'greater_than') {
|
|
403
|
+
qb.where(`${field} >= :${paramName}`, { [paramName]: rule.value });
|
|
404
|
+
} else if (rule.operator === 'less_than') {
|
|
405
|
+
qb.where(`${field} <= :${paramName}`, { [paramName]: rule.value });
|
|
406
|
+
} else {
|
|
407
|
+
qb.where(`${field} = :${paramName}`, { [paramName]: rule.value });
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
const joiner = matchType === 'OR' ? 'orWhere' : 'andWhere';
|
|
414
|
+
conditions.forEach((condition, i) => {
|
|
415
|
+
if (i === 0) {
|
|
416
|
+
db.where(new Brackets(condition));
|
|
417
|
+
} else {
|
|
418
|
+
db[joiner](new Brackets(condition));
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (options.take) {
|
|
424
|
+
db.limit(options.take);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (options.skip) {
|
|
428
|
+
db.offset(options.skip);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
if (options.order && options.order.column) {
|
|
432
|
+
const direction = options.order.direction === 'DESC' ? 'DESC' : 'ASC';
|
|
433
|
+
db.orderBy(`store.${options.order.column}`, direction);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
return await db.getManyAndCount();
|
|
437
|
+
},
|
|
438
|
+
|
|
333
439
|
async fetchSystemStats() {
|
|
334
440
|
const rowCount = await this.createQueryBuilder().select('COUNT(*)', 'total').getRawOne();
|
|
335
441
|
|
|
@@ -15,6 +15,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./Collection.repository"), exports);
|
|
18
|
+
__exportStar(require("./SmartCollection.repository"), exports);
|
|
18
19
|
__exportStar(require("./Store.repository"), exports);
|
|
19
20
|
__exportStar(require("./Logger.repository"), exports);
|
|
20
21
|
//# sourceMappingURL=index.js.map
|
package/app/enums/ChannelType.js
CHANGED
|
@@ -58,6 +58,11 @@ var ChannelType;
|
|
|
58
58
|
ChannelType["IPC_LOGGER_DELETE"] = "IPC_LOGGER_DELETE";
|
|
59
59
|
ChannelType["IPC_LOGGER_QUERY"] = "IPC_LOGGER_QUERY";
|
|
60
60
|
ChannelType["IPC_LOGGER_TRUNCATE"] = "IPC_LOGGER_TRUNCATE";
|
|
61
|
+
ChannelType["IPC_SMART_COLLECTION_FIND"] = "IPC_SMART_COLLECTION_FIND";
|
|
62
|
+
ChannelType["IPC_SMART_COLLECTION_CREATE"] = "IPC_SMART_COLLECTION_CREATE";
|
|
63
|
+
ChannelType["IPC_SMART_COLLECTION_UPDATE"] = "IPC_SMART_COLLECTION_UPDATE";
|
|
64
|
+
ChannelType["IPC_SMART_COLLECTION_DELETE"] = "IPC_SMART_COLLECTION_DELETE";
|
|
65
|
+
ChannelType["IPC_SMART_COLLECTION_EVALUATE"] = "IPC_SMART_COLLECTION_EVALUATE";
|
|
61
66
|
ChannelType["IPC_TOGGLE_PANEL"] = "IPC_TOGGLE_PANEL";
|
|
62
67
|
})(ChannelType || (exports.ChannelType = ChannelType = {}));
|
|
63
68
|
//# sourceMappingURL=ChannelType.js.map
|
package/app/enums/ChannelType.ts
CHANGED
|
@@ -63,5 +63,11 @@ export enum ChannelType {
|
|
|
63
63
|
IPC_LOGGER_QUERY = 'IPC_LOGGER_QUERY',
|
|
64
64
|
IPC_LOGGER_TRUNCATE = 'IPC_LOGGER_TRUNCATE',
|
|
65
65
|
|
|
66
|
+
IPC_SMART_COLLECTION_FIND = 'IPC_SMART_COLLECTION_FIND',
|
|
67
|
+
IPC_SMART_COLLECTION_CREATE = 'IPC_SMART_COLLECTION_CREATE',
|
|
68
|
+
IPC_SMART_COLLECTION_UPDATE = 'IPC_SMART_COLLECTION_UPDATE',
|
|
69
|
+
IPC_SMART_COLLECTION_DELETE = 'IPC_SMART_COLLECTION_DELETE',
|
|
70
|
+
IPC_SMART_COLLECTION_EVALUATE = 'IPC_SMART_COLLECTION_EVALUATE',
|
|
71
|
+
|
|
66
72
|
IPC_TOGGLE_PANEL = 'IPC_TOGGLE_PANEL',
|
|
67
73
|
}
|
package/app/enums/StorageType.js
CHANGED
|
@@ -16,5 +16,6 @@ var StorageType;
|
|
|
16
16
|
StorageType["LayoutPreview"] = "layout.preview";
|
|
17
17
|
StorageType["LayoutTheme"] = "layout.theme";
|
|
18
18
|
StorageType["AiKeys"] = "ai.keys";
|
|
19
|
+
StorageType["NavigationExpanded"] = "navigation.expanded";
|
|
19
20
|
})(StorageType || (exports.StorageType = StorageType = {}));
|
|
20
21
|
//# sourceMappingURL=StorageType.js.map
|
package/app/enums/StorageType.ts
CHANGED
package/app/package.json
CHANGED
package/app/types/index.js
CHANGED
|
@@ -15,6 +15,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./AppAlert"), exports);
|
|
18
|
+
__exportStar(require("./FontMetrics"), exports);
|
|
18
19
|
__exportStar(require("./AuthUser"), exports);
|
|
19
20
|
__exportStar(require("./Breadcrumb"), exports);
|
|
20
21
|
__exportStar(require("./QueryOptions"), exports);
|
|
@@ -22,4 +23,5 @@ __exportStar(require("./SystemTheme"), exports);
|
|
|
22
23
|
__exportStar(require("./SystemStats"), exports);
|
|
23
24
|
__exportStar(require("./SystemConfig"), exports);
|
|
24
25
|
__exportStar(require("./ImportOptions"), exports);
|
|
26
|
+
__exportStar(require("./SmartCollection"), exports);
|
|
25
27
|
//# sourceMappingURL=index.js.map
|
package/app/types/index.ts
CHANGED
package/package.json
CHANGED
|
@@ -7,6 +7,7 @@ import type { Collection } from '@main/database/entity/Collection.schema';
|
|
|
7
7
|
import type { Logger } from '@main/database/entity/Logger.schema';
|
|
8
8
|
import type { Store, StoreManyAndCountType } from '@main/database/entity/Store.schema';
|
|
9
9
|
import type { FontMetrics, SystemStats } from '@main/types';
|
|
10
|
+
import type { SmartCollection } from '@main/database/entity/SmartCollection.schema';
|
|
10
11
|
|
|
11
12
|
@Injectable({
|
|
12
13
|
providedIn: 'root',
|
|
@@ -18,6 +19,8 @@ export class DatabaseService {
|
|
|
18
19
|
|
|
19
20
|
// Reactive state
|
|
20
21
|
readonly collections = signal<Collection[]>([]);
|
|
22
|
+
readonly smartCollections = signal<SmartCollection[]>([]);
|
|
23
|
+
readonly activeSmartCollectionId = signal<number | null>(null);
|
|
21
24
|
readonly parentId = signal<number | null>(null);
|
|
22
25
|
readonly collectionId = signal<number | null>(null);
|
|
23
26
|
readonly stores = signal<Store[]>([]);
|
|
@@ -56,6 +59,7 @@ export class DatabaseService {
|
|
|
56
59
|
this.collectionId.set(parentId);
|
|
57
60
|
this.activeFilter.set(null);
|
|
58
61
|
this.activeSearchWhere.set(null);
|
|
62
|
+
this.activeSmartCollectionId.set(null);
|
|
59
63
|
this.currentPage.set(1);
|
|
60
64
|
}
|
|
61
65
|
|
|
@@ -64,6 +68,7 @@ export class DatabaseService {
|
|
|
64
68
|
this.collectionId.set(child.id);
|
|
65
69
|
this.activeFilter.set(null);
|
|
66
70
|
this.activeSearchWhere.set(null);
|
|
71
|
+
this.activeSmartCollectionId.set(null);
|
|
67
72
|
this.currentPage.set(1);
|
|
68
73
|
}
|
|
69
74
|
|
|
@@ -78,11 +83,22 @@ export class DatabaseService {
|
|
|
78
83
|
return current.id;
|
|
79
84
|
}
|
|
80
85
|
|
|
86
|
+
selectSmartCollection(id: number) {
|
|
87
|
+
this.parentId.set(null);
|
|
88
|
+
this.collectionId.set(null);
|
|
89
|
+
this.activeFilter.set(null);
|
|
90
|
+
this.activeSearchWhere.set(null);
|
|
91
|
+
this.activeSmartCollectionId.set(id);
|
|
92
|
+
this.currentPage.set(1);
|
|
93
|
+
this.fetchCurrentPage();
|
|
94
|
+
}
|
|
95
|
+
|
|
81
96
|
selectFilter(filter: string) {
|
|
82
97
|
this.parentId.set(null);
|
|
83
98
|
this.collectionId.set(null);
|
|
84
99
|
this.activeFilter.set(filter);
|
|
85
100
|
this.activeSearchWhere.set(null);
|
|
101
|
+
this.activeSmartCollectionId.set(null);
|
|
86
102
|
this.currentPage.set(1);
|
|
87
103
|
|
|
88
104
|
const whereMap: Record<string, { key: string; value: number }[]> = {
|
|
@@ -122,6 +138,12 @@ export class DatabaseService {
|
|
|
122
138
|
const skip = (this.currentPage() - 1) * this.pageSize();
|
|
123
139
|
const take = this.pageSize();
|
|
124
140
|
|
|
141
|
+
const smartCollectionId = this.activeSmartCollectionId();
|
|
142
|
+
if (smartCollectionId) {
|
|
143
|
+
this.smartCollectionEvaluate(smartCollectionId, { skip, take });
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
125
147
|
const searchWhere = this.activeSearchWhere();
|
|
126
148
|
if (searchWhere) {
|
|
127
149
|
const searchOrder = this.activeSearchOrder();
|
|
@@ -150,13 +172,15 @@ export class DatabaseService {
|
|
|
150
172
|
|
|
151
173
|
constructor() {
|
|
152
174
|
this.electron.ready.then(async () => {
|
|
153
|
-
const [collections, savedCollectionId, savedStoreId] = await Promise.all([
|
|
175
|
+
const [collections, smartCollections, savedCollectionId, savedStoreId] = await Promise.all([
|
|
154
176
|
this.message.collectionFetch({}),
|
|
177
|
+
this.message.smartCollectionFind(),
|
|
155
178
|
this.message.get(StorageType.CollectionId, null),
|
|
156
179
|
this.message.get(StorageType.StoreId, null),
|
|
157
180
|
]);
|
|
158
181
|
|
|
159
182
|
this.collections.set(collections);
|
|
183
|
+
this.smartCollections.set(smartCollections);
|
|
160
184
|
console.log('System Boot:', collections);
|
|
161
185
|
|
|
162
186
|
if (savedCollectionId) {
|
|
@@ -294,6 +318,50 @@ export class DatabaseService {
|
|
|
294
318
|
);
|
|
295
319
|
}
|
|
296
320
|
|
|
321
|
+
// Smart Collection
|
|
322
|
+
|
|
323
|
+
smartCollectionCreate(args: any): Promise<SmartCollection[]> {
|
|
324
|
+
return this.track(
|
|
325
|
+
this.message.smartCollectionCreate(args).then((result) => {
|
|
326
|
+
this.smartCollections.set(result);
|
|
327
|
+
return result;
|
|
328
|
+
}),
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
smartCollectionUpdate(id: number, data: any): Promise<SmartCollection[]> {
|
|
333
|
+
return this.track(
|
|
334
|
+
this.message.smartCollectionUpdate(id, data).then((result) => {
|
|
335
|
+
this.smartCollections.set(result);
|
|
336
|
+
return result;
|
|
337
|
+
}),
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
smartCollectionDelete(id: number): Promise<SmartCollection[]> {
|
|
342
|
+
return this.track(
|
|
343
|
+
this.message.smartCollectionDelete(id).then((result) => {
|
|
344
|
+
this.smartCollections.set(result);
|
|
345
|
+
if (this.activeSmartCollectionId() === id) {
|
|
346
|
+
this.activeSmartCollectionId.set(null);
|
|
347
|
+
this.stores.set([]);
|
|
348
|
+
this.storeCount.set(0);
|
|
349
|
+
}
|
|
350
|
+
return result;
|
|
351
|
+
}),
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
smartCollectionEvaluate(id: number, options: any): Promise<StoreManyAndCountType> {
|
|
356
|
+
return this.track(
|
|
357
|
+
this.message.smartCollectionEvaluate(id, options).then((result) => {
|
|
358
|
+
this.stores.set(result[0] as Store[]);
|
|
359
|
+
this.storeCount.set(result[1] as number);
|
|
360
|
+
return result;
|
|
361
|
+
}),
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
|
|
297
365
|
// Store
|
|
298
366
|
|
|
299
367
|
storeFind(args: any): Promise<Store[]> {
|
|
@@ -336,6 +404,7 @@ export class DatabaseService {
|
|
|
336
404
|
this.activeFilter.set(null);
|
|
337
405
|
this.activeSearchWhere.set(where);
|
|
338
406
|
this.activeSearchOrder.set(order ?? null);
|
|
407
|
+
this.activeSmartCollectionId.set(null);
|
|
339
408
|
this.currentPage.set(1);
|
|
340
409
|
this.fetchCurrentPage();
|
|
341
410
|
}
|
|
@@ -5,6 +5,7 @@ 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
7
|
import type { FontMetrics, SystemConfig, SystemStats } from '@main/types';
|
|
8
|
+
import type { SmartCollection } from '@main/database/entity/SmartCollection.schema';
|
|
8
9
|
|
|
9
10
|
@Injectable({
|
|
10
11
|
providedIn: 'root',
|
|
@@ -213,6 +214,28 @@ export class MessageService {
|
|
|
213
214
|
return this.invoke<Collection[]>(ChannelType.IPC_COLLECTION_MOVE, { collectionId, newParentId, newIndex });
|
|
214
215
|
}
|
|
215
216
|
|
|
217
|
+
// Smart Collection
|
|
218
|
+
|
|
219
|
+
smartCollectionFind(): Promise<SmartCollection[]> {
|
|
220
|
+
return this.invoke<SmartCollection[]>(ChannelType.IPC_SMART_COLLECTION_FIND);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
smartCollectionCreate(args: any): Promise<SmartCollection[]> {
|
|
224
|
+
return this.invoke<SmartCollection[]>(ChannelType.IPC_SMART_COLLECTION_CREATE, args);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
smartCollectionUpdate(id: number, data: any): Promise<SmartCollection[]> {
|
|
228
|
+
return this.invoke<SmartCollection[]>(ChannelType.IPC_SMART_COLLECTION_UPDATE, { id, data });
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
smartCollectionDelete(id: number): Promise<SmartCollection[]> {
|
|
232
|
+
return this.invoke<SmartCollection[]>(ChannelType.IPC_SMART_COLLECTION_DELETE, { id });
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
smartCollectionEvaluate(id: number, options: any): Promise<StoreManyAndCountType> {
|
|
236
|
+
return this.invoke<StoreManyAndCountType>(ChannelType.IPC_SMART_COLLECTION_EVALUATE, { id, ...options });
|
|
237
|
+
}
|
|
238
|
+
|
|
216
239
|
// Store
|
|
217
240
|
|
|
218
241
|
storeFind(args: any): Promise<Store[]> {
|
|
@@ -41,6 +41,7 @@ export class PresentationService {
|
|
|
41
41
|
this.loadThemeSettings();
|
|
42
42
|
this.loadLayoutSettings();
|
|
43
43
|
this.loadPreviewSettings();
|
|
44
|
+
this.loadNavigationExpandedSettings();
|
|
44
45
|
this.listenForMenuToggle();
|
|
45
46
|
|
|
46
47
|
let themeInitialized = false;
|
|
@@ -70,6 +71,15 @@ export class PresentationService {
|
|
|
70
71
|
panelInitialized = true;
|
|
71
72
|
});
|
|
72
73
|
|
|
74
|
+
let navExpandedInitialized = false;
|
|
75
|
+
effect(() => {
|
|
76
|
+
const ids = this.navigationExpandedIds();
|
|
77
|
+
if (navExpandedInitialized) {
|
|
78
|
+
untracked(() => this.messageService.set(StorageType.NavigationExpanded, ids));
|
|
79
|
+
}
|
|
80
|
+
navExpandedInitialized = true;
|
|
81
|
+
});
|
|
82
|
+
|
|
73
83
|
let previewInitialized = false;
|
|
74
84
|
effect(() => {
|
|
75
85
|
const settings: LayoutPreviewType = {
|
|
@@ -120,6 +130,8 @@ export class PresentationService {
|
|
|
120
130
|
readonly letterSpacing = signal(0);
|
|
121
131
|
readonly wordSpacing = signal(0);
|
|
122
132
|
|
|
133
|
+
readonly navigationExpandedIds = signal<number[]>([]);
|
|
134
|
+
|
|
123
135
|
readonly gridEnabled = signal(true);
|
|
124
136
|
readonly toolbarEnabled = signal(true);
|
|
125
137
|
readonly previewEnabled = signal(true);
|
|
@@ -191,6 +203,41 @@ export class PresentationService {
|
|
|
191
203
|
});
|
|
192
204
|
}
|
|
193
205
|
|
|
206
|
+
isNavigationExpanded(id: number): boolean {
|
|
207
|
+
return this.navigationExpandedIds().includes(id);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
toggleNavigationExpanded(id: number) {
|
|
211
|
+
const ids = this.navigationExpandedIds();
|
|
212
|
+
if (ids.includes(id)) {
|
|
213
|
+
this.navigationExpandedIds.set(ids.filter((i) => i !== id));
|
|
214
|
+
} else {
|
|
215
|
+
this.navigationExpandedIds.set([...ids, id]);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
expandNavigationId(id: number) {
|
|
220
|
+
const ids = this.navigationExpandedIds();
|
|
221
|
+
if (!ids.includes(id)) {
|
|
222
|
+
this.navigationExpandedIds.set([...ids, id]);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
setAllNavigationExpanded(allIds: number[]) {
|
|
227
|
+
this.navigationExpandedIds.set(allIds);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
clearAllNavigationExpanded() {
|
|
231
|
+
this.navigationExpandedIds.set([]);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
private async loadNavigationExpandedSettings() {
|
|
235
|
+
const ids = (await this.messageService.get(StorageType.NavigationExpanded, null)) as number[] | null;
|
|
236
|
+
if (Array.isArray(ids)) {
|
|
237
|
+
this.navigationExpandedIds.set(ids);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
194
241
|
private async loadThemeSettings() {
|
|
195
242
|
const settings = (await this.messageService.get(StorageType.LayoutTheme, null)) as { theme: string } | null;
|
|
196
243
|
if (settings?.theme && PresentationService.themes.includes(settings.theme as (typeof PresentationService.themes)[number])) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<header class="grid
|
|
1
|
+
<header class="grid w-full h-full" [style.grid-template-columns]="'var(--panel-width) 1fr var(--panel-width)'">
|
|
2
2
|
<div class="relative">
|
|
3
3
|
<div class="p-0 h-full flex justify-items-start items-center">
|
|
4
4
|
<a
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<div
|
|
8
8
|
class="grid overflow-hidden"
|
|
9
9
|
[style.grid-template-columns]="
|
|
10
|
-
(presentation.navigationEnabled() ? '
|
|
10
|
+
(presentation.navigationEnabled() ? 'var(--panel-width) ' : '') + '1fr' + (presentation.asideEnabled() ? ' var(--panel-width)' : '')
|
|
11
11
|
"
|
|
12
12
|
>
|
|
13
13
|
@if (presentation.navigationEnabled()) {
|
|
@@ -4,10 +4,58 @@
|
|
|
4
4
|
<app-library />
|
|
5
5
|
</app-collapsible-panel>
|
|
6
6
|
|
|
7
|
+
<app-collapsible-panel title="Smart Collections">
|
|
8
|
+
<span
|
|
9
|
+
panelActions
|
|
10
|
+
class="material-symbols-outlined cursor-pointer"
|
|
11
|
+
[style.color]="'var(--text-muted)'"
|
|
12
|
+
style="
|
|
13
|
+
font-size: 18px;
|
|
14
|
+
font-variation-settings:
|
|
15
|
+
'opsz' 20,
|
|
16
|
+
'wght' 300;
|
|
17
|
+
"
|
|
18
|
+
title="New Smart Collection"
|
|
19
|
+
(click)="openCreateSmartCollection()"
|
|
20
|
+
>add</span
|
|
21
|
+
>
|
|
22
|
+
<ul class="flex flex-col py-0.5">
|
|
23
|
+
@for (sc of db.smartCollections(); track sc.id) {
|
|
24
|
+
<li>
|
|
25
|
+
<a
|
|
26
|
+
class="flex items-center px-3 py-1 text-xs font-normal cursor-pointer transition-colors"
|
|
27
|
+
[style.background-color]="isSmartSelected(sc) ? 'var(--selected-bg)' : 'transparent'"
|
|
28
|
+
[style.color]="isSmartSelected(sc) ? 'var(--text-primary)' : 'inherit'"
|
|
29
|
+
(click)="selectSmartCollection(sc)"
|
|
30
|
+
(dblclick)="editSmartCollection(sc)"
|
|
31
|
+
(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
|
+
>
|
|
35
|
+
<span
|
|
36
|
+
class="material-symbols-outlined icon-sm mr-1"
|
|
37
|
+
[style.color]="'var(--text-muted)'"
|
|
38
|
+
style="
|
|
39
|
+
font-size: 16px;
|
|
40
|
+
font-variation-settings:
|
|
41
|
+
'opsz' 20,
|
|
42
|
+
'wght' 300;
|
|
43
|
+
"
|
|
44
|
+
>auto_awesome</span
|
|
45
|
+
>
|
|
46
|
+
{{ sc.title }}
|
|
47
|
+
</a>
|
|
48
|
+
</li>
|
|
49
|
+
} @empty {
|
|
50
|
+
<li class="px-3 py-1 text-xs" [style.color]="'var(--text-muted)'">No smart collections</li>
|
|
51
|
+
}
|
|
52
|
+
</ul>
|
|
53
|
+
</app-collapsible-panel>
|
|
54
|
+
|
|
7
55
|
<app-collapsible-panel title="Explorer" class="flex-1 min-h-0">
|
|
8
56
|
<span
|
|
9
57
|
panelActions
|
|
10
|
-
class="material-symbols-outlined cursor-pointer
|
|
58
|
+
class="material-symbols-outlined cursor-pointer"
|
|
11
59
|
[style.color]="'var(--text-muted)'"
|
|
12
60
|
style="
|
|
13
61
|
font-size: 18px;
|
|
@@ -15,9 +63,9 @@
|
|
|
15
63
|
'opsz' 20,
|
|
16
64
|
'wght' 300;
|
|
17
65
|
"
|
|
18
|
-
|
|
19
|
-
(click)="
|
|
20
|
-
>
|
|
66
|
+
title="New Collection"
|
|
67
|
+
(click)="openCreateRootCollection()"
|
|
68
|
+
>add</span
|
|
21
69
|
>
|
|
22
70
|
<ul class="flex-1 overflow-auto flex flex-col py-0.5">
|
|
23
71
|
<ng-container *ngTemplateOutlet="subtree; context: { $implicit: tree(), level: 0 }" />
|
|
@@ -122,5 +170,23 @@
|
|
|
122
170
|
(cancelled)="onCollectionCancelled()"
|
|
123
171
|
/>
|
|
124
172
|
}
|
|
173
|
+
|
|
174
|
+
@if (smartContextMenu) {
|
|
175
|
+
<app-context-menu
|
|
176
|
+
[x]="smartContextMenu.x"
|
|
177
|
+
[y]="smartContextMenu.y"
|
|
178
|
+
[items]="smartContextMenuItems"
|
|
179
|
+
(menuSelect)="onSmartContextMenuSelect($event)"
|
|
180
|
+
(menuClose)="closeSmartContextMenu()"
|
|
181
|
+
/>
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
@if (showRuleBuilder) {
|
|
185
|
+
<app-rule-builder
|
|
186
|
+
[smartCollection]="editingSmartCollection"
|
|
187
|
+
(saved)="onRuleBuilderSaved($event)"
|
|
188
|
+
(cancelled)="onRuleBuilderCancelled()"
|
|
189
|
+
/>
|
|
190
|
+
}
|
|
125
191
|
</div>
|
|
126
192
|
</nav>
|