@simplysm/angular 14.0.85 → 14.0.87
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/dist/controls/pagination/sd-pagination.d.ts.map +1 -1
- package/dist/controls/pagination/sd-pagination.js +2 -0
- package/dist/controls/select/sd-select-item.js +1 -1
- package/dist/controls/select/sd-select.js +1 -1
- package/dist/core/app-structure/sd-app-structure.provider.d.ts +2 -3
- package/dist/core/app-structure/sd-app-structure.provider.d.ts.map +1 -1
- package/dist/core/app-structure/sd-app-structure.provider.js +5 -9
- package/dist/core/app-structure/sd-app-structure.utils.d.ts +2 -0
- package/dist/core/app-structure/sd-app-structure.utils.d.ts.map +1 -1
- package/dist/core/app-structure/sd-app-structure.utils.js +9 -1
- package/dist/core/busy/sd-busy-container.d.ts +1 -1
- package/dist/core/busy/sd-busy-container.d.ts.map +1 -1
- package/dist/core/busy/sd-busy-container.js +11 -11
- package/dist/core/busy/sd-busy.provider.d.ts.map +1 -1
- package/dist/core/busy/sd-busy.provider.js +3 -0
- package/dist/core/config/sd-system-config.provider.d.ts +4 -4
- package/dist/core/config/sd-system-config.provider.d.ts.map +1 -1
- package/dist/core/config/sd-system-config.provider.js +3 -0
- package/dist/core/error-handler/sd-global-error-handler.plugin.d.ts +1 -1
- package/dist/core/error-handler/sd-global-error-handler.plugin.d.ts.map +1 -1
- package/dist/core/file-dialog/sd-file-dialog.provider.d.ts.map +1 -1
- package/dist/core/file-dialog/sd-file-dialog.provider.js +4 -0
- package/dist/core/modal/sd-activated-modal.provider.d.ts +2 -1
- package/dist/core/modal/sd-activated-modal.provider.d.ts.map +1 -1
- package/dist/core/modal/sd-modal.d.ts.map +1 -1
- package/dist/core/modal/sd-modal.js +3 -3
- package/dist/core/modal/sd-modal.provider.d.ts.map +1 -1
- package/dist/core/modal/sd-modal.provider.js +8 -0
- package/dist/core/modal/sd-prompt-modal.d.ts +2 -2
- package/dist/core/modal/sd-prompt-modal.d.ts.map +1 -1
- package/dist/core/modal/sd-prompt-modal.js +32 -37
- package/dist/core/print/sd-print.provider.d.ts.map +1 -1
- package/dist/core/print/sd-print.provider.js +19 -6
- package/dist/core/provideSdAngular.d.ts.map +1 -1
- package/dist/core/provideSdAngular.js +4 -1
- package/dist/core/routing/injectViewTitleSignal.d.ts.map +1 -1
- package/dist/core/routing/injectViewTitleSignal.js +1 -6
- package/dist/core/shared-data/sd-shared-data.provider.d.ts +2 -1
- package/dist/core/shared-data/sd-shared-data.provider.d.ts.map +1 -1
- package/dist/core/shared-data/sd-shared-data.provider.js +3 -3
- package/dist/core/toast/sd-toast.provider.d.ts +1 -0
- package/dist/core/toast/sd-toast.provider.d.ts.map +1 -1
- package/dist/core/toast/sd-toast.provider.js +18 -4
- package/dist/data/crud/sd-base-container.d.ts.map +1 -1
- package/dist/data/crud/sd-base-container.js +56 -54
- package/dist/data/kanban/sd-kanban-lane.d.ts.map +1 -1
- package/dist/data/shared-data/sd-shared-data-select-button.d.ts +1 -2
- package/dist/data/shared-data/sd-shared-data-select-button.d.ts.map +1 -1
- package/dist/data/shared-data/sd-shared-data-select-button.js +18 -21
- package/dist/data/shared-data/sd-shared-data-select.d.ts +3 -2
- package/dist/data/shared-data/sd-shared-data-select.d.ts.map +1 -1
- package/dist/data/shared-data/sd-shared-data-select.js +10 -10
- package/dist/data/sheet/sd-sheet-config.modal.d.ts +3 -3
- package/dist/data/sheet/sd-sheet-config.modal.d.ts.map +1 -1
- package/dist/data/sheet/sd-sheet-config.modal.js +37 -34
- package/dist/data/sheet/sd-sheet.d.ts.map +1 -1
- package/dist/data/sheet/sd-sheet.js +5 -1
- package/dist/data/sheet/useSheetCellStyling.d.ts.map +1 -1
- package/dist/data/sheet/useSheetCellStyling.js +4 -22
- package/dist/data/state-preset/sd-state-preset.d.ts +10 -10
- package/dist/data/state-preset/sd-state-preset.d.ts.map +1 -1
- package/dist/data/state-preset/sd-state-preset.js +4 -4
- package/dist/features/address/sd-address-search.modal.js +1 -1
- package/dist/features/editor/sd-tiptap-editor.d.ts.map +1 -1
- package/dist/features/editor/sd-tiptap-editor.js +3 -2
- package/dist/features/theme/sd-theme-selector.d.ts +6 -8
- package/dist/features/theme/sd-theme-selector.d.ts.map +1 -1
- package/dist/features/theme/sd-theme-selector.js +54 -51
- package/dist/features/visual/sd-barcode.d.ts.map +1 -1
- package/dist/features/visual/sd-barcode.js +5 -10
- package/dist/layout/sidebar/sd-sidebar-user.d.ts.map +1 -1
- package/dist/layout/sidebar/sd-sidebar-user.js +26 -36
- package/package.json +28 -28
- package/src/controls/button/sd-modal-select-button.ts +1 -1
- package/src/controls/form/sd-form.ts +1 -1
- package/src/controls/input/sd-textfield-type-handlers.ts +2 -2
- package/src/controls/pagination/sd-pagination.ts +1 -0
- package/src/controls/select/sd-select-item.ts +1 -1
- package/src/controls/select/sd-select.ts +1 -1
- package/src/core/app-structure/sd-app-structure.provider.ts +7 -13
- package/src/core/app-structure/sd-app-structure.utils.ts +14 -2
- package/src/core/busy/sd-busy-container.ts +6 -13
- package/src/core/busy/sd-busy.provider.ts +4 -0
- package/src/core/config/injectSdSystemConfigResource.ts +1 -1
- package/src/core/config/sd-system-config.provider.ts +5 -3
- package/src/core/error-handler/sd-global-error-handler.plugin.ts +1 -1
- package/src/core/file-dialog/sd-file-dialog.provider.ts +5 -1
- package/src/core/modal/sd-activated-modal.provider.ts +2 -1
- package/src/core/modal/sd-modal.provider.ts +8 -0
- package/src/core/modal/sd-modal.ts +6 -3
- package/src/core/modal/sd-prompt-modal.ts +13 -23
- package/src/core/print/sd-print.provider.ts +21 -6
- package/src/core/provideSdAngular.ts +5 -1
- package/src/core/routing/injectViewTitleSignal.ts +1 -5
- package/src/core/shared-data/sd-shared-data.provider.ts +4 -4
- package/src/core/toast/sd-toast.provider.ts +35 -12
- package/src/data/crud/sd-base-container.ts +120 -118
- package/src/data/kanban/sd-kanban-lane.ts +2 -2
- package/src/data/shared-data/sd-shared-data-select-button.ts +15 -20
- package/src/data/shared-data/sd-shared-data-select-list.ts +4 -4
- package/src/data/shared-data/sd-shared-data-select.ts +10 -12
- package/src/data/sheet/sd-sheet-column.ts +1 -1
- package/src/data/sheet/sd-sheet-config.modal.ts +39 -35
- package/src/data/sheet/sd-sheet.ts +6 -1
- package/src/data/sheet/useSheetCellStyling.ts +3 -20
- package/src/data/state-preset/sd-state-preset.ts +11 -11
- package/src/features/address/sd-address-search.modal.ts +1 -1
- package/src/features/editor/sd-tiptap-editor.ts +3 -2
- package/src/features/theme/sd-theme-selector.ts +77 -75
- package/src/features/visual/sd-barcode.ts +5 -9
- package/src/features/visual/sd-calendar.ts +1 -1
- package/src/layout/sidebar/sd-sidebar-user.ts +3 -5
|
@@ -28,6 +28,9 @@ import { SdThemeProvider } from "../features/theme/sd-theme-provider";
|
|
|
28
28
|
import { SdLocalStorageProvider } from "./config/sd-local-storage.provider";
|
|
29
29
|
import { SdGlobalErrorHandlerPlugin } from "./error-handler/sd-global-error-handler.plugin";
|
|
30
30
|
import { SdOptionEventPlugin } from "./events/sd-option-event.plugin";
|
|
31
|
+
import { createLogger } from "@simplysm/core-common";
|
|
32
|
+
|
|
33
|
+
const logger = createLogger("angular:sw-update");
|
|
31
34
|
|
|
32
35
|
export function provideSdAngular(opt: { clientName: string }): EnvironmentProviders {
|
|
33
36
|
return makeEnvironmentProviders([
|
|
@@ -131,8 +134,9 @@ export function provideSdAngular(opt: { clientName: string }): EnvironmentProvid
|
|
|
131
134
|
}
|
|
132
135
|
}
|
|
133
136
|
failCount = 0;
|
|
134
|
-
} catch {
|
|
137
|
+
} catch (err) {
|
|
135
138
|
failCount++;
|
|
139
|
+
logger.error(err);
|
|
136
140
|
} finally {
|
|
137
141
|
const interval = Math.min(
|
|
138
142
|
BASE_INTERVAL * Math.pow(2, Math.max(0, failCount - 1)),
|
|
@@ -16,10 +16,6 @@ export function injectViewTitleSignal(): Signal<string> {
|
|
|
16
16
|
return sdActivatedModal.modalComponent()?.title() ?? "";
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
return sdAppStructure.getTitleByFullCode(currPageCode?.() ?? fullPageCode());
|
|
21
|
-
} catch {
|
|
22
|
-
return "";
|
|
23
|
-
}
|
|
19
|
+
return sdAppStructure.findTitleByFullCode(currPageCode?.() ?? fullPageCode()) ?? "";
|
|
24
20
|
});
|
|
25
21
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { computed, ErrorHandler, inject, Injectable, signal, type Signal, type WritableSignal } from "@angular/core";
|
|
2
2
|
import { defineEvent } from "@simplysm/service-common";
|
|
3
|
-
import { obj, wait as waitUtil } from "@simplysm/core-common";
|
|
3
|
+
import { DateOnly, DateTime, obj, Time, wait as waitUtil } from "@simplysm/core-common";
|
|
4
4
|
import { SdServiceClientFactoryProvider } from "../service-client/sd-service-client-factory.provider";
|
|
5
5
|
import "@simplysm/core-common";
|
|
6
6
|
|
|
@@ -15,7 +15,7 @@ export interface SharedDataInfo<T extends SharedDataBase<string | number>> {
|
|
|
15
15
|
serviceKey: string;
|
|
16
16
|
getter: (changeKeys?: (string | number)[]) => Promise<T[]>;
|
|
17
17
|
filter?: unknown;
|
|
18
|
-
orderBy?: (item: T) =>
|
|
18
|
+
orderBy?: (item: T) => string | number | DateOnly | DateTime | Time | undefined;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
export interface SharedDataHandle<T extends SharedDataBase<string | number>> {
|
|
@@ -110,7 +110,7 @@ export abstract class SdSharedDataProvider<T extends Record<string, SharedDataBa
|
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
const client = this._sdServiceClientFactory.get(entry.info.serviceKey);
|
|
113
|
-
const event = client.getEvent
|
|
113
|
+
const event = client.getEvent(SdSharedDataChangeEvent);
|
|
114
114
|
await event.emit(
|
|
115
115
|
(item) => item.name === (name as string) && obj.equal(item.filter, entry.info.filter),
|
|
116
116
|
changeKeys,
|
|
@@ -138,7 +138,7 @@ export abstract class SdSharedDataProvider<T extends Record<string, SharedDataBa
|
|
|
138
138
|
// 이벤트 리스너 등록
|
|
139
139
|
if (entry.listenerKey == null) {
|
|
140
140
|
const client = this._sdServiceClientFactory.get(entry.info.serviceKey);
|
|
141
|
-
const event = client.getEvent
|
|
141
|
+
const event = client.getEvent(SdSharedDataChangeEvent);
|
|
142
142
|
entry.listenerKey = await event.addListener(
|
|
143
143
|
{ name, filter: entry.info.filter },
|
|
144
144
|
async (changeKeys) => {
|
|
@@ -45,6 +45,7 @@ export class SdToastProvider {
|
|
|
45
45
|
private _containerRef: ComponentRef<SdToastContainer> | undefined;
|
|
46
46
|
private readonly _toastRefs: ComponentRef<SdToast>[] = [];
|
|
47
47
|
private readonly _contentRefs = new Map<ComponentRef<SdToast>, ComponentRef<any>>();
|
|
48
|
+
private readonly _autoDismissCleanups = new Map<ComponentRef<SdToast>, () => void>();
|
|
48
49
|
|
|
49
50
|
constructor() {
|
|
50
51
|
effect(() => {
|
|
@@ -234,19 +235,30 @@ export class SdToastProvider {
|
|
|
234
235
|
onDismiss?: () => void,
|
|
235
236
|
): void {
|
|
236
237
|
const el = toastRef.location.nativeElement as HTMLElement;
|
|
238
|
+
const ac = new AbortController();
|
|
239
|
+
let timerId: ReturnType<typeof setTimeout> | undefined;
|
|
237
240
|
let isHovering = false;
|
|
238
241
|
let dismissPending = false;
|
|
239
242
|
let dismissed = false;
|
|
240
243
|
|
|
244
|
+
// 타이머·리스너 정리. 외부 파괴 경로(_dismissToast/_destroyToast)에서도 호출되어
|
|
245
|
+
// 죽은 toastRef 에 대해 타이머가 깨어나는 race 와 리스너 누수를 막는다.
|
|
246
|
+
const cleanup = () => {
|
|
247
|
+
dismissed = true;
|
|
248
|
+
if (timerId != null) clearTimeout(timerId);
|
|
249
|
+
ac.abort();
|
|
250
|
+
this._autoDismissCleanups.delete(toastRef);
|
|
251
|
+
};
|
|
252
|
+
this._autoDismissCleanups.set(toastRef, cleanup);
|
|
253
|
+
|
|
241
254
|
const dismiss = () => {
|
|
242
255
|
if (dismissed) return;
|
|
243
|
-
dismissed = true;
|
|
244
256
|
onDismiss?.();
|
|
245
257
|
this._dismissToast(toastRef);
|
|
246
258
|
};
|
|
247
259
|
|
|
248
260
|
const dismissAfterDelay = (ms: number) => {
|
|
249
|
-
setTimeout(() => {
|
|
261
|
+
timerId = setTimeout(() => {
|
|
250
262
|
if (dismissed) return;
|
|
251
263
|
if (isHovering) {
|
|
252
264
|
dismissPending = true;
|
|
@@ -256,21 +268,30 @@ export class SdToastProvider {
|
|
|
256
268
|
}, ms);
|
|
257
269
|
};
|
|
258
270
|
|
|
259
|
-
el.addEventListener(
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
271
|
+
el.addEventListener(
|
|
272
|
+
"mouseenter",
|
|
273
|
+
() => {
|
|
274
|
+
isHovering = true;
|
|
275
|
+
},
|
|
276
|
+
{ signal: ac.signal },
|
|
277
|
+
);
|
|
278
|
+
el.addEventListener(
|
|
279
|
+
"mouseleave",
|
|
280
|
+
() => {
|
|
281
|
+
isHovering = false;
|
|
282
|
+
if (dismissPending) {
|
|
283
|
+
dismissPending = false;
|
|
284
|
+
dismissAfterDelay(1000);
|
|
285
|
+
}
|
|
286
|
+
},
|
|
287
|
+
{ signal: ac.signal },
|
|
288
|
+
);
|
|
269
289
|
|
|
270
290
|
dismissAfterDelay(delayMs);
|
|
271
291
|
}
|
|
272
292
|
|
|
273
293
|
private _dismissToast(toastRef: ComponentRef<SdToast>): void {
|
|
294
|
+
this._autoDismissCleanups.get(toastRef)?.();
|
|
274
295
|
toastRef.instance.open.set(false);
|
|
275
296
|
|
|
276
297
|
const el = toastRef.location.nativeElement as HTMLElement;
|
|
@@ -292,6 +313,8 @@ export class SdToastProvider {
|
|
|
292
313
|
}
|
|
293
314
|
|
|
294
315
|
private _destroyToast(toastRef: ComponentRef<SdToast>): void {
|
|
316
|
+
this._autoDismissCleanups.get(toastRef)?.();
|
|
317
|
+
|
|
295
318
|
const idx = this._toastRefs.indexOf(toastRef);
|
|
296
319
|
if (idx === -1) return; // 이미 파괴됨
|
|
297
320
|
|
|
@@ -1,118 +1,120 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ChangeDetectionStrategy,
|
|
3
|
-
Component,
|
|
4
|
-
contentChild,
|
|
5
|
-
effect,
|
|
6
|
-
inject,
|
|
7
|
-
input,
|
|
8
|
-
model,
|
|
9
|
-
type TemplateRef,
|
|
10
|
-
untracked,
|
|
11
|
-
ViewEncapsulation,
|
|
12
|
-
} from "@angular/core";
|
|
13
|
-
import { injectViewTitleSignal } from "../../core/routing/injectViewTitleSignal";
|
|
14
|
-
import { SdBusyContainer } from "../../core/busy/sd-busy-container";
|
|
15
|
-
import { SdSharedDataProvider } from "../../core/shared-data/sd-shared-data.provider";
|
|
16
|
-
import { SdToastProvider } from "../../core/toast/sd-toast.provider";
|
|
17
|
-
import { SdTopbar } from "../../layout/topbar/sd-topbar";
|
|
18
|
-
import { SdTopbarContainer } from "../../layout/topbar/sd-topbar-container";
|
|
19
|
-
import type { SdViewType } from "../../core/routing/injectViewTypeSignal";
|
|
20
|
-
import { NgIcon } from "@ng-icons/core";
|
|
21
|
-
import { tablerAlertTriangle } from "@ng-icons/tabler-icons";
|
|
22
|
-
import { NgTemplateOutlet } from "@angular/common";
|
|
23
|
-
|
|
24
|
-
@Component({
|
|
25
|
-
selector: "sd-base-container",
|
|
26
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
27
|
-
encapsulation: ViewEncapsulation.None,
|
|
28
|
-
standalone: true,
|
|
29
|
-
imports: [SdBusyContainer, NgIcon, SdTopbarContainer, NgTemplateOutlet, SdTopbar],
|
|
30
|
-
template: `
|
|
31
|
-
<sd-busy-container [busy]="initialized() && busyCount() > 0">
|
|
32
|
-
@if (initialized()) {
|
|
33
|
-
@if (restricted()) {
|
|
34
|
-
<div class="fill tx-theme-gray-light p-xxl tx-center">
|
|
35
|
-
<br />
|
|
36
|
-
<ng-icon [svg]="tablerAlertTriangle" [size]="'5rem'" />
|
|
37
|
-
<br />
|
|
38
|
-
<br />
|
|
39
|
-
'{{ viewTitle() }}'에 대한 사용권한이 없습니다. 시스템 관리자에게 문의하세요.
|
|
40
|
-
</div>
|
|
41
|
-
} @else if (viewType() === "page") {
|
|
42
|
-
<sd-topbar-container>
|
|
43
|
-
<sd-topbar>
|
|
44
|
-
<h4>{{ viewTitle() }}</h4>
|
|
45
|
-
|
|
46
|
-
<ng-template [ngTemplateOutlet]="topbarTplRef()" />
|
|
47
|
-
</sd-topbar>
|
|
48
|
-
|
|
49
|
-
<ng-template [ngTemplateOutlet]="content" />
|
|
50
|
-
</sd-topbar-container>
|
|
51
|
-
} @else {
|
|
52
|
-
<ng-template [ngTemplateOutlet]="content" />
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
</sd-busy-container>
|
|
56
|
-
|
|
57
|
-
<ng-template #content>
|
|
58
|
-
<div class="flex-column fill">
|
|
59
|
-
@if (commandTplRef()) {
|
|
60
|
-
<div class="p-default flex-row gap-default bdb bdb-theme-gray-lightest">
|
|
61
|
-
<ng-template [ngTemplateOutlet]="commandTplRef()" />
|
|
62
|
-
</div>
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
<div class="flex-fill">
|
|
66
|
-
@if (contentTplRef()) {
|
|
67
|
-
<ng-template [ngTemplateOutlet]="contentTplRef()" />
|
|
68
|
-
}
|
|
69
|
-
</div>
|
|
70
|
-
|
|
71
|
-
@if (bottomCommandTplRef()) {
|
|
72
|
-
<div class="p-sm-default flex-row main-align-end gap-sm bdt bdt-theme-gray-lightest">
|
|
73
|
-
<ng-template [ngTemplateOutlet]="bottomCommandTplRef()" />
|
|
74
|
-
</div>
|
|
75
|
-
}
|
|
76
|
-
</div>
|
|
77
|
-
</ng-template>
|
|
78
|
-
`,
|
|
79
|
-
})
|
|
80
|
-
export class SdBaseContainer {
|
|
81
|
-
private readonly _sdSharedData = inject(SdSharedDataProvider);
|
|
82
|
-
private readonly _sdToast = inject(SdToastProvider);
|
|
83
|
-
|
|
84
|
-
viewTitle = injectViewTitleSignal();
|
|
85
|
-
|
|
86
|
-
ready = model(false);
|
|
87
|
-
initialized = input(false);
|
|
88
|
-
busyCount = model(0);
|
|
89
|
-
|
|
90
|
-
restricted = input(false);
|
|
91
|
-
|
|
92
|
-
viewType = input.required<SdViewType>();
|
|
93
|
-
|
|
94
|
-
topbarTplRef = contentChild<TemplateRef<void>>("topbarTpl");
|
|
95
|
-
commandTplRef = contentChild<TemplateRef<void>>("commandTpl");
|
|
96
|
-
contentTplRef = contentChild<TemplateRef<void>>("contentTpl");
|
|
97
|
-
bottomCommandTplRef = contentChild<TemplateRef<void>>("bottomCommandTpl");
|
|
98
|
-
|
|
99
|
-
constructor() {
|
|
100
|
-
effect(() => {
|
|
101
|
-
if (this.restricted()) {
|
|
102
|
-
this.ready.set(true);
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
void untracked(async () => {
|
|
107
|
-
this.
|
|
108
|
-
|
|
109
|
-
await this.
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
1
|
+
import {
|
|
2
|
+
ChangeDetectionStrategy,
|
|
3
|
+
Component,
|
|
4
|
+
contentChild,
|
|
5
|
+
effect,
|
|
6
|
+
inject,
|
|
7
|
+
input,
|
|
8
|
+
model,
|
|
9
|
+
type TemplateRef,
|
|
10
|
+
untracked,
|
|
11
|
+
ViewEncapsulation,
|
|
12
|
+
} from "@angular/core";
|
|
13
|
+
import { injectViewTitleSignal } from "../../core/routing/injectViewTitleSignal";
|
|
14
|
+
import { SdBusyContainer } from "../../core/busy/sd-busy-container";
|
|
15
|
+
import { SdSharedDataProvider } from "../../core/shared-data/sd-shared-data.provider";
|
|
16
|
+
import { SdToastProvider } from "../../core/toast/sd-toast.provider";
|
|
17
|
+
import { SdTopbar } from "../../layout/topbar/sd-topbar";
|
|
18
|
+
import { SdTopbarContainer } from "../../layout/topbar/sd-topbar-container";
|
|
19
|
+
import type { SdViewType } from "../../core/routing/injectViewTypeSignal";
|
|
20
|
+
import { NgIcon } from "@ng-icons/core";
|
|
21
|
+
import { tablerAlertTriangle } from "@ng-icons/tabler-icons";
|
|
22
|
+
import { NgTemplateOutlet } from "@angular/common";
|
|
23
|
+
|
|
24
|
+
@Component({
|
|
25
|
+
selector: "sd-base-container",
|
|
26
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
27
|
+
encapsulation: ViewEncapsulation.None,
|
|
28
|
+
standalone: true,
|
|
29
|
+
imports: [SdBusyContainer, NgIcon, SdTopbarContainer, NgTemplateOutlet, SdTopbar],
|
|
30
|
+
template: `
|
|
31
|
+
<sd-busy-container [busy]="initialized() && busyCount() > 0">
|
|
32
|
+
@if (initialized()) {
|
|
33
|
+
@if (restricted()) {
|
|
34
|
+
<div class="fill tx-theme-gray-light p-xxl tx-center">
|
|
35
|
+
<br />
|
|
36
|
+
<ng-icon [svg]="tablerAlertTriangle" [size]="'5rem'" />
|
|
37
|
+
<br />
|
|
38
|
+
<br />
|
|
39
|
+
'{{ viewTitle() }}'에 대한 사용권한이 없습니다. 시스템 관리자에게 문의하세요.
|
|
40
|
+
</div>
|
|
41
|
+
} @else if (viewType() === "page") {
|
|
42
|
+
<sd-topbar-container>
|
|
43
|
+
<sd-topbar>
|
|
44
|
+
<h4>{{ viewTitle() }}</h4>
|
|
45
|
+
|
|
46
|
+
<ng-template [ngTemplateOutlet]="topbarTplRef()" />
|
|
47
|
+
</sd-topbar>
|
|
48
|
+
|
|
49
|
+
<ng-template [ngTemplateOutlet]="content" />
|
|
50
|
+
</sd-topbar-container>
|
|
51
|
+
} @else {
|
|
52
|
+
<ng-template [ngTemplateOutlet]="content" />
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
</sd-busy-container>
|
|
56
|
+
|
|
57
|
+
<ng-template #content>
|
|
58
|
+
<div class="flex-column fill">
|
|
59
|
+
@if (commandTplRef()) {
|
|
60
|
+
<div class="p-default flex-row gap-default bdb bdb-theme-gray-lightest">
|
|
61
|
+
<ng-template [ngTemplateOutlet]="commandTplRef()" />
|
|
62
|
+
</div>
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
<div class="flex-fill">
|
|
66
|
+
@if (contentTplRef()) {
|
|
67
|
+
<ng-template [ngTemplateOutlet]="contentTplRef()" />
|
|
68
|
+
}
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
@if (bottomCommandTplRef()) {
|
|
72
|
+
<div class="p-sm-default flex-row main-align-end gap-sm bdt bdt-theme-gray-lightest">
|
|
73
|
+
<ng-template [ngTemplateOutlet]="bottomCommandTplRef()" />
|
|
74
|
+
</div>
|
|
75
|
+
}
|
|
76
|
+
</div>
|
|
77
|
+
</ng-template>
|
|
78
|
+
`,
|
|
79
|
+
})
|
|
80
|
+
export class SdBaseContainer {
|
|
81
|
+
private readonly _sdSharedData = inject(SdSharedDataProvider, { optional: true });
|
|
82
|
+
private readonly _sdToast = inject(SdToastProvider);
|
|
83
|
+
|
|
84
|
+
viewTitle = injectViewTitleSignal();
|
|
85
|
+
|
|
86
|
+
ready = model(false);
|
|
87
|
+
initialized = input(false);
|
|
88
|
+
busyCount = model(0);
|
|
89
|
+
|
|
90
|
+
restricted = input(false);
|
|
91
|
+
|
|
92
|
+
viewType = input.required<SdViewType>();
|
|
93
|
+
|
|
94
|
+
topbarTplRef = contentChild<TemplateRef<void>>("topbarTpl");
|
|
95
|
+
commandTplRef = contentChild<TemplateRef<void>>("commandTpl");
|
|
96
|
+
contentTplRef = contentChild<TemplateRef<void>>("contentTpl");
|
|
97
|
+
bottomCommandTplRef = contentChild<TemplateRef<void>>("bottomCommandTpl");
|
|
98
|
+
|
|
99
|
+
constructor() {
|
|
100
|
+
effect(() => {
|
|
101
|
+
if (this.restricted()) {
|
|
102
|
+
this.ready.set(true);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
void untracked(async () => {
|
|
107
|
+
if (this._sdSharedData) {
|
|
108
|
+
this.busyCount.update((v) => v + 1);
|
|
109
|
+
await this._sdToast.try(async () => {
|
|
110
|
+
await this._sdSharedData!.wait();
|
|
111
|
+
});
|
|
112
|
+
this.busyCount.update((v) => v - 1);
|
|
113
|
+
}
|
|
114
|
+
this.ready.set(true);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
protected readonly tablerAlertTriangle = tablerAlertTriangle;
|
|
120
|
+
}
|
|
@@ -133,8 +133,8 @@ export class SdKanbanLane<L, T> implements SdKanbanDropTarget<L, T> {
|
|
|
133
133
|
|
|
134
134
|
kanbanControls = contentChildren<SdKanban<L, T>>(SdKanban, { descendants: true });
|
|
135
135
|
|
|
136
|
-
toolTplRef = contentChild<
|
|
137
|
-
titleTplRef = contentChild<
|
|
136
|
+
toolTplRef = contentChild<unknown, TemplateRef<void>>("toolTpl", { read: TemplateRef });
|
|
137
|
+
titleTplRef = contentChild<unknown, TemplateRef<void>>("titleTpl", { read: TemplateRef });
|
|
138
138
|
|
|
139
139
|
isAllSelected = computed(() => {
|
|
140
140
|
const selectableControls = this.kanbanControls().filter((ctrl) => ctrl.selectable());
|
|
@@ -3,11 +3,10 @@ import {
|
|
|
3
3
|
booleanAttribute,
|
|
4
4
|
ChangeDetectionStrategy,
|
|
5
5
|
Component,
|
|
6
|
+
computed,
|
|
6
7
|
contentChild,
|
|
7
|
-
effect,
|
|
8
8
|
input,
|
|
9
9
|
model,
|
|
10
|
-
signal,
|
|
11
10
|
TemplateRef,
|
|
12
11
|
ViewEncapsulation,
|
|
13
12
|
} from "@angular/core";
|
|
@@ -70,27 +69,23 @@ export class SdSharedDataSelectButton<
|
|
|
70
69
|
inset = input(false, { transform: booleanAttribute });
|
|
71
70
|
size = input<"sm" | "lg">();
|
|
72
71
|
|
|
73
|
-
itemTplRef = contentChild.required<
|
|
72
|
+
itemTplRef = contentChild.required<SdItemOfTemplate<TItem>, TemplateRef<SdItemOfTemplateContext<TItem>>>(
|
|
74
73
|
SdItemOfTemplate,
|
|
75
74
|
{ read: TemplateRef },
|
|
76
75
|
);
|
|
77
76
|
|
|
78
|
-
|
|
77
|
+
_selectedItems = computed<TItem[]>(() => {
|
|
78
|
+
const v = this.value();
|
|
79
|
+
const items = this.items();
|
|
80
|
+
const mode = this.selectMode();
|
|
79
81
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
} else if (mode === "single" && !Array.isArray(v) && v != null) {
|
|
90
|
-
this._selectedItems.set(items.filter((it) => it.__valueKey === v));
|
|
91
|
-
} else {
|
|
92
|
-
this._selectedItems.set([]);
|
|
93
|
-
}
|
|
94
|
-
});
|
|
95
|
-
}
|
|
82
|
+
if (mode === "multi" && Array.isArray(v) && v.filterExists().length > 0) {
|
|
83
|
+
const keys = v.filterExists() as (string | number)[];
|
|
84
|
+
return items.filter((it) => keys.includes(it.__valueKey));
|
|
85
|
+
} else if (mode === "single" && !Array.isArray(v) && v != null) {
|
|
86
|
+
return items.filter((it) => it.__valueKey === v);
|
|
87
|
+
} else {
|
|
88
|
+
return [];
|
|
89
|
+
}
|
|
90
|
+
});
|
|
96
91
|
}
|
|
@@ -146,17 +146,17 @@ export class SdSharedDataSelectList<
|
|
|
146
146
|
|
|
147
147
|
header = input<string>();
|
|
148
148
|
|
|
149
|
-
headerTplRef = contentChild<
|
|
149
|
+
headerTplRef = contentChild<unknown, TemplateRef<void>>("headerTpl", {
|
|
150
150
|
read: TemplateRef,
|
|
151
151
|
});
|
|
152
|
-
filterTplRef = contentChild<
|
|
152
|
+
filterTplRef = contentChild<unknown, TemplateRef<void>>("filterTpl", {
|
|
153
153
|
read: TemplateRef,
|
|
154
154
|
});
|
|
155
|
-
itemTplRef = contentChild<
|
|
155
|
+
itemTplRef = contentChild<SdItemOfTemplate<TItem>, TemplateRef<SdItemOfTemplateContext<TItem>>>(
|
|
156
156
|
SdItemOfTemplate,
|
|
157
157
|
{ read: TemplateRef },
|
|
158
158
|
);
|
|
159
|
-
undefinedTplRef = contentChild<
|
|
159
|
+
undefinedTplRef = contentChild<unknown, TemplateRef<void>>("undefinedTpl", {
|
|
160
160
|
read: TemplateRef,
|
|
161
161
|
});
|
|
162
162
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { NgTemplateOutlet } from "@angular/common";
|
|
2
|
+
import type { DateOnly, DateTime, Time } from "@simplysm/core-common";
|
|
2
3
|
import {
|
|
3
4
|
booleanAttribute,
|
|
4
5
|
ChangeDetectionStrategy,
|
|
@@ -170,13 +171,13 @@ export class SdSharedDataSelect<
|
|
|
170
171
|
getSearchTextFn = input<(item: TItem, index: number) => string>(
|
|
171
172
|
(item) => item.__searchText,
|
|
172
173
|
);
|
|
173
|
-
|
|
174
|
+
displayOrderByFn = input<(item: TItem) => string | number | DateOnly | DateTime | Time | undefined>();
|
|
174
175
|
|
|
175
|
-
itemTplRef = contentChild<
|
|
176
|
+
itemTplRef = contentChild<SdItemOfTemplate<TItem>, TemplateRef<SdItemOfTemplateContext<TItem>>>(
|
|
176
177
|
SdItemOfTemplate,
|
|
177
178
|
{ read: TemplateRef },
|
|
178
179
|
);
|
|
179
|
-
undefinedTplRef = contentChild<
|
|
180
|
+
undefinedTplRef = contentChild<unknown, TemplateRef<void>>("undefinedTpl", {
|
|
180
181
|
read: TemplateRef,
|
|
181
182
|
});
|
|
182
183
|
|
|
@@ -219,9 +220,9 @@ export class SdSharedDataSelect<
|
|
|
219
220
|
return true;
|
|
220
221
|
});
|
|
221
222
|
|
|
222
|
-
const
|
|
223
|
-
if (
|
|
224
|
-
result = result.orderBy(
|
|
223
|
+
const orderBy = this.displayOrderByFn();
|
|
224
|
+
if (orderBy != null) {
|
|
225
|
+
result = result.orderBy(orderBy);
|
|
225
226
|
}
|
|
226
227
|
|
|
227
228
|
return result;
|
|
@@ -316,15 +317,12 @@ export class SdSharedDataSelect<
|
|
|
316
317
|
const parentMap = this.itemByParentKeyMap();
|
|
317
318
|
if (parentMap == null) return undefined;
|
|
318
319
|
|
|
319
|
-
const
|
|
320
|
-
if (
|
|
320
|
+
const orderBy = this.displayOrderByFn();
|
|
321
|
+
if (orderBy == null) return parentMap;
|
|
321
322
|
|
|
322
323
|
const sorted = new Map<TItem["__valueKey"] | undefined, TItem[]>();
|
|
323
324
|
for (const [key, children] of parentMap) {
|
|
324
|
-
sorted.set(
|
|
325
|
-
key,
|
|
326
|
-
children.orderBy((item) => (item as any)[orderProp]),
|
|
327
|
-
);
|
|
325
|
+
sorted.set(key, children.orderBy(orderBy));
|
|
328
326
|
}
|
|
329
327
|
return sorted;
|
|
330
328
|
});
|
|
@@ -32,7 +32,7 @@ export class SdSheetColumn<T = unknown> {
|
|
|
32
32
|
disableResizing = input(false, { transform: booleanAttribute });
|
|
33
33
|
ordering = input(0);
|
|
34
34
|
|
|
35
|
-
cellTplRef = contentChild.required<
|
|
35
|
+
cellTplRef = contentChild.required<SdSheetColumnCellTemplate<T>, TemplateRef<SdSheetCellContext<T>>>(
|
|
36
36
|
SdSheetColumnCellTemplate,
|
|
37
37
|
{ read: TemplateRef },
|
|
38
38
|
);
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ChangeDetectionStrategy,
|
|
3
3
|
Component,
|
|
4
|
-
effect,
|
|
5
4
|
forwardRef,
|
|
5
|
+
inject,
|
|
6
6
|
input,
|
|
7
|
+
linkedSignal,
|
|
7
8
|
output,
|
|
8
9
|
signal,
|
|
9
10
|
ViewEncapsulation,
|
|
10
11
|
} from "@angular/core";
|
|
11
|
-
import type
|
|
12
|
+
import { SdModalProvider, type SdModalContentDef } from "../../core/modal/sd-modal.provider";
|
|
13
|
+
import { SdConfirmModal } from "../../core/modal/sd-confirm-modal";
|
|
12
14
|
import type { SdSheetConfig } from "./types";
|
|
13
15
|
import type { SdSheetColumn } from "./sd-sheet-column";
|
|
14
16
|
import { SdSheet } from "./sd-sheet";
|
|
@@ -178,14 +180,36 @@ interface ConfigItem {
|
|
|
178
180
|
`,
|
|
179
181
|
})
|
|
180
182
|
export class SdSheetConfigModal implements SdModalContentDef<SdSheetConfig | undefined> {
|
|
181
|
-
|
|
183
|
+
private readonly _sdModal = inject(SdModalProvider);
|
|
184
|
+
|
|
185
|
+
initialized = signal(true);
|
|
182
186
|
close = output<SdSheetConfig | undefined>();
|
|
183
187
|
|
|
184
188
|
sheetKey = input.required<string>();
|
|
185
189
|
controls = input.required<readonly SdSheetColumn[]>();
|
|
186
190
|
config = input.required<SdSheetConfig | undefined>();
|
|
187
191
|
|
|
188
|
-
items =
|
|
192
|
+
items = linkedSignal<ConfigItem[]>(() => {
|
|
193
|
+
const cfg = this.config();
|
|
194
|
+
return this.controls()
|
|
195
|
+
.map((ctrl): ConfigItem => {
|
|
196
|
+
const key = ctrl.key();
|
|
197
|
+
const cfgCol = cfg?.columnRecord[key];
|
|
198
|
+
return {
|
|
199
|
+
key,
|
|
200
|
+
header: Array.isArray(ctrl.header())
|
|
201
|
+
? (ctrl.header() as string[]).join(" > ")
|
|
202
|
+
: (ctrl.header() as string),
|
|
203
|
+
disableResizing: ctrl.disableResizing(),
|
|
204
|
+
fixed: cfgCol?.fixed ?? ctrl.fixed(),
|
|
205
|
+
hidden: cfgCol?.hidden ?? ctrl.hidden(),
|
|
206
|
+
width: cfgCol?.width ?? ctrl.width(),
|
|
207
|
+
ordering: cfgCol?.ordering ?? ctrl.ordering(),
|
|
208
|
+
};
|
|
209
|
+
})
|
|
210
|
+
.orderBy((i) => i.ordering ?? 0)
|
|
211
|
+
.orderBy((i) => (i.fixed ? 0 : 1));
|
|
212
|
+
});
|
|
189
213
|
|
|
190
214
|
trackByFn = (item: ConfigItem): string => item.key;
|
|
191
215
|
|
|
@@ -194,33 +218,6 @@ export class SdSheetConfigModal implements SdModalContentDef<SdSheetConfig | und
|
|
|
194
218
|
protected readonly tablerX = tablerX;
|
|
195
219
|
protected readonly mark = mark;
|
|
196
220
|
|
|
197
|
-
constructor() {
|
|
198
|
-
effect(() => {
|
|
199
|
-
const cfg = this.config();
|
|
200
|
-
const items = this.controls()
|
|
201
|
-
.map((ctrl): ConfigItem => {
|
|
202
|
-
const key = ctrl.key();
|
|
203
|
-
const cfgCol = cfg?.columnRecord[key];
|
|
204
|
-
return {
|
|
205
|
-
key,
|
|
206
|
-
header: Array.isArray(ctrl.header())
|
|
207
|
-
? (ctrl.header() as string[]).join(" > ")
|
|
208
|
-
: (ctrl.header() as string),
|
|
209
|
-
disableResizing: ctrl.disableResizing(),
|
|
210
|
-
fixed: cfgCol?.fixed ?? ctrl.fixed(),
|
|
211
|
-
hidden: cfgCol?.hidden ?? ctrl.hidden(),
|
|
212
|
-
width: cfgCol?.width ?? ctrl.width(),
|
|
213
|
-
ordering: cfgCol?.ordering ?? ctrl.ordering(),
|
|
214
|
-
};
|
|
215
|
-
})
|
|
216
|
-
.orderBy((i) => i.ordering ?? 0)
|
|
217
|
-
.orderBy((i) => (i.fixed ? 0 : 1));
|
|
218
|
-
|
|
219
|
-
this.items.set(items);
|
|
220
|
-
this.initialized.set(true);
|
|
221
|
-
});
|
|
222
|
-
}
|
|
223
|
-
|
|
224
221
|
onMoveUp(item: ConfigItem): void {
|
|
225
222
|
this.items.update((v) => {
|
|
226
223
|
const r = [...v];
|
|
@@ -264,9 +261,16 @@ export class SdSheetConfigModal implements SdModalContentDef<SdSheetConfig | und
|
|
|
264
261
|
this.close.emit(undefined);
|
|
265
262
|
}
|
|
266
263
|
|
|
267
|
-
onResetClick(): void {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
264
|
+
async onResetClick(): Promise<void> {
|
|
265
|
+
const confirmed = await this._sdModal.showAsync(
|
|
266
|
+
{
|
|
267
|
+
title: "설정 초기화",
|
|
268
|
+
type: SdConfirmModal,
|
|
269
|
+
inputs: { message: "설정값이 모두 초기화 됩니다." },
|
|
270
|
+
},
|
|
271
|
+
{ useCloseByBackdrop: false },
|
|
272
|
+
);
|
|
273
|
+
if (confirmed !== true) return;
|
|
274
|
+
this.close.emit({ columnRecord: {} });
|
|
271
275
|
}
|
|
272
276
|
}
|