@simplysm/angular 14.0.52 → 14.0.54
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/button/sd-modal-select-button.d.ts +9 -10
- package/dist/controls/button/sd-modal-select-button.d.ts.map +1 -1
- package/dist/controls/button/sd-modal-select-button.js +4 -7
- package/dist/core/select-modal-output-result.d.ts +2 -3
- package/dist/core/select-modal-output-result.d.ts.map +1 -1
- package/dist/core/selection/useSelectionManager.d.ts +11 -11
- package/dist/core/selection/useSelectionManager.d.ts.map +1 -1
- package/dist/core/selection/useSelectionManager.js +29 -28
- package/dist/data/crud/sd-base-container.d.ts +21 -0
- package/dist/data/crud/sd-base-container.d.ts.map +1 -0
- package/dist/data/crud/sd-base-container.js +200 -0
- package/dist/data/crud/sd-crud-detail.d.ts +22 -0
- package/dist/data/crud/sd-crud-detail.d.ts.map +1 -0
- package/dist/data/crud/sd-crud-detail.js +267 -0
- package/dist/data/crud/sd-crud-list.d.ts +54 -0
- package/dist/data/crud/sd-crud-list.d.ts.map +1 -0
- package/dist/data/crud/sd-crud-list.js +592 -0
- 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 +4 -5
- package/dist/data/shared-data/sd-shared-data-select-list.js +2 -2
- package/dist/data/shared-data/sd-shared-data-select.js +3 -3
- package/dist/data/sheet/injectSheetSelectRowIndicator.d.ts +4 -3
- package/dist/data/sheet/injectSheetSelectRowIndicator.d.ts.map +1 -1
- package/dist/data/sheet/injectSheetSelectRowIndicator.js +12 -4
- package/dist/data/sheet/sd-sheet.d.ts +39 -39
- package/dist/data/sheet/sd-sheet.d.ts.map +1 -1
- package/dist/data/sheet/sd-sheet.js +96 -74
- package/dist/data/sheet/types.d.ts +1 -0
- package/dist/data/sheet/types.d.ts.map +1 -1
- package/dist/data/sheet/useSheetLayoutEngine.d.ts.map +1 -1
- package/dist/data/sheet/useSheetLayoutEngine.js +3 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/styles.css +2 -2
- package/package.json +13 -13
- package/scss/commons/_variables.scss +2 -2
- package/src/controls/button/sd-modal-select-button.ts +9 -13
- package/src/core/select-modal-output-result.ts +2 -3
- package/src/core/selection/useSelectionManager.ts +47 -49
- package/src/data/crud/sd-base-container.ts +115 -0
- package/src/data/crud/sd-crud-detail.ts +122 -0
- package/src/data/crud/sd-crud-list.ts +316 -0
- package/src/data/shared-data/sd-shared-data-select-button.ts +0 -1
- package/src/data/shared-data/sd-shared-data-select-list.ts +2 -2
- package/src/data/shared-data/sd-shared-data-select.ts +3 -3
- package/src/data/sheet/injectSheetSelectRowIndicator.ts +17 -7
- package/src/data/sheet/sd-sheet.ts +59 -42
- package/src/data/sheet/types.ts +1 -0
- package/src/data/sheet/useSheetLayoutEngine.ts +3 -1
- package/src/index.ts +5 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@simplysm/angular",
|
|
3
|
-
"version": "14.0.
|
|
3
|
+
"version": "14.0.54",
|
|
4
4
|
"description": "심플리즘 패키지 - Angular",
|
|
5
5
|
"author": "심플리즘",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -24,12 +24,12 @@
|
|
|
24
24
|
"./dist/**/*.css"
|
|
25
25
|
],
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@angular/animations": "^21.2.
|
|
28
|
-
"@angular/common": "^21.2.
|
|
29
|
-
"@angular/core": "^21.2.
|
|
30
|
-
"@angular/platform-browser": "^21.2.
|
|
31
|
-
"@angular/router": "^21.2.
|
|
32
|
-
"@angular/service-worker": "^21.2.
|
|
27
|
+
"@angular/animations": "^21.2.10",
|
|
28
|
+
"@angular/common": "^21.2.10",
|
|
29
|
+
"@angular/core": "^21.2.10",
|
|
30
|
+
"@angular/platform-browser": "^21.2.10",
|
|
31
|
+
"@angular/router": "^21.2.10",
|
|
32
|
+
"@angular/service-worker": "^21.2.10",
|
|
33
33
|
"@ng-icons/core": "^33.2.2",
|
|
34
34
|
"@ng-icons/tabler-icons": "^33.2.2",
|
|
35
35
|
"@tiptap/core": "^3.22.4",
|
|
@@ -50,14 +50,14 @@
|
|
|
50
50
|
"jspdf": "^4.2.1",
|
|
51
51
|
"rxjs": "^7.8.2",
|
|
52
52
|
"tabbable": "^6.4.0",
|
|
53
|
-
"@simplysm/core-browser": "14.0.
|
|
54
|
-
"@simplysm/
|
|
55
|
-
"@simplysm/
|
|
56
|
-
"@simplysm/service-common": "14.0.
|
|
53
|
+
"@simplysm/core-browser": "14.0.54",
|
|
54
|
+
"@simplysm/service-client": "14.0.54",
|
|
55
|
+
"@simplysm/core-common": "14.0.54",
|
|
56
|
+
"@simplysm/service-common": "14.0.54"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
|
-
"@angular/compiler": "^21.2.
|
|
60
|
-
"@angular/platform-browser-dynamic": "^21.2.
|
|
59
|
+
"@angular/compiler": "^21.2.10",
|
|
60
|
+
"@angular/platform-browser-dynamic": "^21.2.10"
|
|
61
61
|
},
|
|
62
62
|
"scripts": {}
|
|
63
63
|
}
|
|
@@ -25,19 +25,19 @@ import { tablerSearch, tablerEraser } from "@ng-icons/tabler-icons";
|
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
27
|
* 모달 선택 컴포넌트가 구현해야 하는 인터페이스
|
|
28
|
-
* SdModalContentDef을 확장하여 selectMode와
|
|
28
|
+
* SdModalContentDef을 확장하여 selectMode와 selectedKeys를 추가한다.
|
|
29
29
|
*/
|
|
30
|
-
export interface SdSelectModal<
|
|
30
|
+
export interface SdSelectModal<TKey> extends SdModalContentDef<SelectModalOutputResult<TKey>> {
|
|
31
31
|
selectMode: InputSignal<"single" | "multi" | undefined>;
|
|
32
|
-
|
|
32
|
+
selectedKeys: InputSignal<TKey[]>;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
/**
|
|
36
|
-
* 모달 선택 정보 (selectMode/
|
|
36
|
+
* 모달 선택 정보 (selectMode/selectedKeys를 제외한 inputs)
|
|
37
37
|
*/
|
|
38
38
|
export type SdSelectModalInfo<T extends SdSelectModal<any>> = SdModalInfo<
|
|
39
39
|
T,
|
|
40
|
-
"selectMode" | "
|
|
40
|
+
"selectMode" | "selectedKeys"
|
|
41
41
|
>;
|
|
42
42
|
|
|
43
43
|
@Component({
|
|
@@ -146,16 +146,14 @@ export type SdSelectModalInfo<T extends SdSelectModal<any>> = SdModalInfo<
|
|
|
146
146
|
},
|
|
147
147
|
})
|
|
148
148
|
export class SdModalSelectButton<
|
|
149
|
-
T extends object,
|
|
150
149
|
K,
|
|
151
150
|
M extends keyof SelectModeValue<K> = keyof SelectModeValue<K>,
|
|
152
151
|
> {
|
|
153
152
|
private readonly _sdModal = inject(SdModalProvider);
|
|
154
153
|
|
|
155
|
-
modal = input.required<SdSelectModalInfo<SdSelectModal<
|
|
154
|
+
modal = input.required<SdSelectModalInfo<SdSelectModal<K>>>();
|
|
156
155
|
|
|
157
156
|
value = model<SelectModeValue<K>[M]>();
|
|
158
|
-
selectedItems = model<T[]>([]);
|
|
159
157
|
|
|
160
158
|
disabled = input(false, { transform: booleanAttribute });
|
|
161
159
|
required = input(false, { transform: booleanAttribute });
|
|
@@ -187,7 +185,6 @@ export class SdModalSelectButton<
|
|
|
187
185
|
|
|
188
186
|
onEraseClick(): void {
|
|
189
187
|
this.value.set((this.selectMode() === "multi" ? [] : undefined) as any);
|
|
190
|
-
this.selectedItems.set([]);
|
|
191
188
|
}
|
|
192
189
|
|
|
193
190
|
async onSearchClick(event: MouseEvent): Promise<void> {
|
|
@@ -199,7 +196,7 @@ export class SdModalSelectButton<
|
|
|
199
196
|
...modal,
|
|
200
197
|
inputs: {
|
|
201
198
|
selectMode: this.selectMode(),
|
|
202
|
-
|
|
199
|
+
selectedKeys: (this.selectMode() === "multi"
|
|
203
200
|
? ((this.value() as any[] | undefined) ?? [])
|
|
204
201
|
: [this.value()]
|
|
205
202
|
).filterExists(),
|
|
@@ -209,9 +206,8 @@ export class SdModalSelectButton<
|
|
|
209
206
|
|
|
210
207
|
if (result) {
|
|
211
208
|
const newValue =
|
|
212
|
-
this.selectMode() === "multi" ? result.
|
|
213
|
-
this.value.set(newValue);
|
|
214
|
-
this.selectedItems.set(result.selectedItems);
|
|
209
|
+
this.selectMode() === "multi" ? result.selectedKeys : result.selectedKeys[0];
|
|
210
|
+
this.value.set(newValue as SelectModeValue<K>[M]);
|
|
215
211
|
}
|
|
216
212
|
}
|
|
217
213
|
}
|
|
@@ -1,27 +1,33 @@
|
|
|
1
1
|
import { computed, type Signal, type WritableSignal } from "@angular/core";
|
|
2
2
|
import { obj } from "@simplysm/core-common";
|
|
3
3
|
|
|
4
|
-
export function useSelectionManager<
|
|
5
|
-
displayItems: Signal<
|
|
6
|
-
|
|
4
|
+
export function useSelectionManager<TItem, TKey>(options: {
|
|
5
|
+
displayItems: Signal<TItem[]>;
|
|
6
|
+
selectedKeys: WritableSignal<NonNullable<TKey>[]>;
|
|
7
7
|
selectMode: Signal<"single" | "multi" | undefined>;
|
|
8
|
-
getItemSelectableFn: Signal<((item:
|
|
9
|
-
trackByFn: Signal<(item:
|
|
8
|
+
getItemSelectableFn: Signal<((item: TItem) => boolean | string) | undefined>;
|
|
9
|
+
trackByFn: Signal<(item: TItem, index: number) => TKey>;
|
|
10
10
|
}): {
|
|
11
11
|
hasSelectable: Signal<boolean>;
|
|
12
12
|
isAllSelected: Signal<boolean>;
|
|
13
|
-
getSelectable(item:
|
|
14
|
-
getCanChangeFn(item:
|
|
15
|
-
select(item:
|
|
16
|
-
deselect(item:
|
|
17
|
-
toggle(item:
|
|
13
|
+
getSelectable(item: TItem): true | string | undefined;
|
|
14
|
+
getCanChangeFn(item: TItem): () => boolean;
|
|
15
|
+
select(item: TItem): void;
|
|
16
|
+
deselect(item: TItem): void;
|
|
17
|
+
toggle(item: TItem): void;
|
|
18
18
|
toggleAll(): void;
|
|
19
|
-
isSelected(item:
|
|
19
|
+
isSelected(item: TItem): boolean;
|
|
20
20
|
} {
|
|
21
21
|
const selectableItems = computed(() => {
|
|
22
|
+
const trackFn = options.trackByFn();
|
|
23
|
+
const idxMap = new Map<TItem, number>();
|
|
24
|
+
options.displayItems().forEach((it, i) => idxMap.set(it, i));
|
|
22
25
|
const fn = options.getItemSelectableFn();
|
|
23
|
-
|
|
24
|
-
|
|
26
|
+
return options.displayItems().filter((item) => {
|
|
27
|
+
if (trackFn(item, idxMap.get(item) ?? 0) == null) return false;
|
|
28
|
+
if (fn == null) return true;
|
|
29
|
+
return fn(item) === true;
|
|
30
|
+
});
|
|
25
31
|
});
|
|
26
32
|
|
|
27
33
|
const hasSelectable = computed(() => {
|
|
@@ -29,22 +35,17 @@ export function useSelectionManager<T>(options: {
|
|
|
29
35
|
});
|
|
30
36
|
|
|
31
37
|
const displayItemIndexMap = computed(() => {
|
|
32
|
-
const map = new Map<
|
|
38
|
+
const map = new Map<TItem, number>();
|
|
33
39
|
options.displayItems().forEach((it, i) => map.set(it, i));
|
|
34
40
|
return map;
|
|
35
41
|
});
|
|
36
42
|
|
|
37
|
-
const selectedKeys = computed(() => {
|
|
38
|
-
const fn = options.trackByFn();
|
|
39
|
-
return options.selectedItems().map((it, i) => fn(it, i));
|
|
40
|
-
});
|
|
41
|
-
|
|
42
43
|
function isKeyEqual(a: unknown, b: unknown): boolean {
|
|
43
44
|
if (a === b) return true;
|
|
44
45
|
return obj.equal(a, b);
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
function keyOf(item:
|
|
48
|
+
function keyOf(item: TItem): TKey {
|
|
48
49
|
const idx = displayItemIndexMap().get(item);
|
|
49
50
|
return options.trackByFn()(item, idx ?? 0);
|
|
50
51
|
}
|
|
@@ -52,7 +53,7 @@ export function useSelectionManager<T>(options: {
|
|
|
52
53
|
const isAllSelected = computed(() => {
|
|
53
54
|
const items = selectableItems();
|
|
54
55
|
if (items.length === 0) return false;
|
|
55
|
-
const keys = selectedKeys();
|
|
56
|
+
const keys = options.selectedKeys();
|
|
56
57
|
const fn = options.trackByFn();
|
|
57
58
|
return items.every((it, i) => {
|
|
58
59
|
const key = fn(it, i);
|
|
@@ -60,9 +61,10 @@ export function useSelectionManager<T>(options: {
|
|
|
60
61
|
});
|
|
61
62
|
});
|
|
62
63
|
|
|
63
|
-
function getSelectable(item:
|
|
64
|
+
function getSelectable(item: TItem): true | string | undefined {
|
|
64
65
|
const mode = options.selectMode();
|
|
65
66
|
if (mode == null) return undefined;
|
|
67
|
+
if (keyOf(item) == null) return undefined;
|
|
66
68
|
const fn = options.getItemSelectableFn();
|
|
67
69
|
if (fn == null) return true;
|
|
68
70
|
const result = fn(item);
|
|
@@ -71,40 +73,40 @@ export function useSelectionManager<T>(options: {
|
|
|
71
73
|
return result; // string reason
|
|
72
74
|
}
|
|
73
75
|
|
|
74
|
-
function getCanChangeFn(item:
|
|
76
|
+
function getCanChangeFn(item: TItem): () => boolean {
|
|
75
77
|
return () => {
|
|
76
78
|
const selectable = getSelectable(item);
|
|
77
79
|
return selectable === true;
|
|
78
80
|
};
|
|
79
81
|
}
|
|
80
82
|
|
|
81
|
-
function select(item:
|
|
83
|
+
function select(item: TItem): void {
|
|
82
84
|
const mode = options.selectMode();
|
|
83
85
|
if (mode == null) return;
|
|
84
86
|
if (getSelectable(item) !== true) return;
|
|
85
87
|
|
|
88
|
+
const key = keyOf(item);
|
|
89
|
+
if (key == null) return;
|
|
90
|
+
|
|
86
91
|
if (mode === "single") {
|
|
87
|
-
options.
|
|
92
|
+
options.selectedKeys.set([key]);
|
|
88
93
|
return;
|
|
89
94
|
}
|
|
90
95
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
if (arr.some((it, i) => isKeyEqual(fn(it, i), key))) return arr;
|
|
95
|
-
return [...arr, item];
|
|
96
|
+
options.selectedKeys.update((arr) => {
|
|
97
|
+
if (arr.some((k) => isKeyEqual(k, key))) return arr;
|
|
98
|
+
return [...arr, key];
|
|
96
99
|
});
|
|
97
100
|
}
|
|
98
101
|
|
|
99
|
-
function deselect(item:
|
|
102
|
+
function deselect(item: TItem): void {
|
|
100
103
|
const key = keyOf(item);
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
arr.filter((it, i) => !isKeyEqual(fn(it, i), key)),
|
|
104
|
+
options.selectedKeys.update((arr) =>
|
|
105
|
+
arr.filter((k) => !isKeyEqual(k, key)),
|
|
104
106
|
);
|
|
105
107
|
}
|
|
106
108
|
|
|
107
|
-
function toggle(item:
|
|
109
|
+
function toggle(item: TItem): void {
|
|
108
110
|
if (isSelected(item)) {
|
|
109
111
|
deselect(item);
|
|
110
112
|
} else {
|
|
@@ -119,27 +121,23 @@ export function useSelectionManager<T>(options: {
|
|
|
119
121
|
|
|
120
122
|
if (isAllSelected()) {
|
|
121
123
|
const keysToRemove = selectable.map((it) => fn(it, idxMap.get(it) ?? 0));
|
|
122
|
-
options.
|
|
123
|
-
arr.filter((
|
|
124
|
-
const k = fn(it, i);
|
|
125
|
-
return !keysToRemove.some((rk) => isKeyEqual(rk, k));
|
|
126
|
-
}),
|
|
124
|
+
options.selectedKeys.update((arr) =>
|
|
125
|
+
arr.filter((k) => !keysToRemove.some((rk) => isKeyEqual(rk, k))),
|
|
127
126
|
);
|
|
128
127
|
} else {
|
|
129
|
-
options.
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
return [...arr, ...toAdd];
|
|
128
|
+
options.selectedKeys.update((arr) => {
|
|
129
|
+
const newKeys = selectable
|
|
130
|
+
.map((it) => fn(it, idxMap.get(it) ?? 0))
|
|
131
|
+
.filter((k): k is NonNullable<TKey> => k != null)
|
|
132
|
+
.filter((k) => !arr.some((ek) => isKeyEqual(ek, k)));
|
|
133
|
+
return [...arr, ...newKeys];
|
|
136
134
|
});
|
|
137
135
|
}
|
|
138
136
|
}
|
|
139
137
|
|
|
140
|
-
function isSelected(item:
|
|
138
|
+
function isSelected(item: TItem): boolean {
|
|
141
139
|
const key = keyOf(item);
|
|
142
|
-
return selectedKeys().some((k) => isKeyEqual(k, key));
|
|
140
|
+
return options.selectedKeys().some((k) => isKeyEqual(k, key));
|
|
143
141
|
}
|
|
144
142
|
|
|
145
143
|
return {
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ChangeDetectionStrategy,
|
|
3
|
+
Component,
|
|
4
|
+
contentChild,
|
|
5
|
+
effect,
|
|
6
|
+
inject,
|
|
7
|
+
input,
|
|
8
|
+
model,
|
|
9
|
+
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
|
+
<ng-content />
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
@if (bottomCommandTplRef()) {
|
|
70
|
+
<div class="p-sm-default flex-row main-align-end gap-sm bdt bdt-theme-gray-lightest">
|
|
71
|
+
<ng-template [ngTemplateOutlet]="bottomCommandTplRef()" />
|
|
72
|
+
</div>
|
|
73
|
+
}
|
|
74
|
+
</div>
|
|
75
|
+
</ng-template>
|
|
76
|
+
`,
|
|
77
|
+
})
|
|
78
|
+
export class SdBaseContainer {
|
|
79
|
+
private readonly _sdSharedData = inject(SdSharedDataProvider);
|
|
80
|
+
private readonly _sdToast = inject(SdToastProvider);
|
|
81
|
+
|
|
82
|
+
viewTitle = injectViewTitleSignal();
|
|
83
|
+
|
|
84
|
+
ready = model(false);
|
|
85
|
+
initialized = input(false);
|
|
86
|
+
busyCount = model(0);
|
|
87
|
+
|
|
88
|
+
restricted = input(false);
|
|
89
|
+
|
|
90
|
+
viewType = input.required<SdViewType>();
|
|
91
|
+
|
|
92
|
+
topbarTplRef = contentChild<TemplateRef<void>>("topbarTpl");
|
|
93
|
+
commandTplRef = contentChild<TemplateRef<void>>("commandTpl");
|
|
94
|
+
bottomCommandTplRef = contentChild<TemplateRef<void>>("bottomCommandTpl");
|
|
95
|
+
|
|
96
|
+
constructor() {
|
|
97
|
+
effect(() => {
|
|
98
|
+
if (this.restricted()) {
|
|
99
|
+
this.ready.set(true);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
void untracked(async () => {
|
|
104
|
+
this.busyCount.update((v) => v + 1);
|
|
105
|
+
await this._sdToast.try(async () => {
|
|
106
|
+
await this._sdSharedData.wait();
|
|
107
|
+
});
|
|
108
|
+
this.busyCount.update((v) => v - 1);
|
|
109
|
+
this.ready.set(true);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
protected readonly tablerAlertTriangle = tablerAlertTriangle;
|
|
115
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ChangeDetectionStrategy,
|
|
3
|
+
Component,
|
|
4
|
+
contentChild,
|
|
5
|
+
input,
|
|
6
|
+
model,
|
|
7
|
+
output,
|
|
8
|
+
type TemplateRef,
|
|
9
|
+
viewChild,
|
|
10
|
+
ViewEncapsulation,
|
|
11
|
+
} from "@angular/core";
|
|
12
|
+
import { SdButton } from "../../controls/button/sd-button";
|
|
13
|
+
import { SdCommandDirective } from "../../core/commands/sd-command";
|
|
14
|
+
import { SdForm } from "../../controls/form/sd-form";
|
|
15
|
+
import type { SdViewType } from "../../core/routing/injectViewTypeSignal";
|
|
16
|
+
import { SdBaseContainer } from "./sd-base-container";
|
|
17
|
+
import { NgIcon } from "@ng-icons/core";
|
|
18
|
+
import { NgTemplateOutlet } from "@angular/common";
|
|
19
|
+
import { tablerDeviceFloppy } from "@ng-icons/tabler-icons";
|
|
20
|
+
|
|
21
|
+
@Component({
|
|
22
|
+
selector: "sd-crud-detail",
|
|
23
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
24
|
+
encapsulation: ViewEncapsulation.None,
|
|
25
|
+
standalone: true,
|
|
26
|
+
imports: [SdBaseContainer, SdButton, NgIcon, NgTemplateOutlet, SdForm],
|
|
27
|
+
hostDirectives: [{ directive: SdCommandDirective, outputs: ["sdSaveCommand"] }],
|
|
28
|
+
host: {
|
|
29
|
+
"(sdSaveCommand)": "onSaveButtonClick()",
|
|
30
|
+
},
|
|
31
|
+
template: `
|
|
32
|
+
<sd-base-container
|
|
33
|
+
[(ready)]="ready"
|
|
34
|
+
[initialized]="initialized()"
|
|
35
|
+
[(busyCount)]="busyCount"
|
|
36
|
+
[restricted]="restricted()"
|
|
37
|
+
[viewType]="viewType()"
|
|
38
|
+
>
|
|
39
|
+
<!-- TOP -->
|
|
40
|
+
@if (viewType() === "page" && (!readonly() || commandTplRef())) {
|
|
41
|
+
<ng-template #topbarTpl>
|
|
42
|
+
@if (!readonly()) {
|
|
43
|
+
<sd-button [theme]="'link-primary'" (click)="formCtrl()?.requestSubmit()">
|
|
44
|
+
<ng-icon [svg]="tablerDeviceFloppy" />
|
|
45
|
+
저장
|
|
46
|
+
<small>(CTRL+S)</small>
|
|
47
|
+
</sd-button>
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
<ng-template [ngTemplateOutlet]="commandTplRef()" />
|
|
51
|
+
</ng-template>
|
|
52
|
+
} @else if (viewType() === "control" && (!readonly() || commandTplRef())) {
|
|
53
|
+
<ng-template #commandTpl>
|
|
54
|
+
@if (!readonly()) {
|
|
55
|
+
<sd-button [theme]="'primary'" (click)="onSaveButtonClick()">
|
|
56
|
+
<ng-icon [svg]="tablerDeviceFloppy" />
|
|
57
|
+
저장
|
|
58
|
+
<small>(CTRL+S)</small>
|
|
59
|
+
</sd-button>
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
<ng-template [ngTemplateOutlet]="commandTplRef()" />
|
|
63
|
+
</ng-template>
|
|
64
|
+
} @else if (commandTplRef()) {
|
|
65
|
+
<ng-template #commandTpl>
|
|
66
|
+
<ng-template [ngTemplateOutlet]="commandTplRef()" />
|
|
67
|
+
</ng-template>
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
<!-- BOTTOM -->
|
|
71
|
+
@if (viewType() === "modal" || bottomCommandTplRef()) {
|
|
72
|
+
<ng-template #bottomCommandTpl>
|
|
73
|
+
@if (bottomCommandTplRef()) {
|
|
74
|
+
<div class="flex-fill flex-row main-align-start gap-sm">
|
|
75
|
+
<ng-template [ngTemplateOutlet]="bottomCommandTplRef()" />
|
|
76
|
+
</div>
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
<sd-button [size]="'sm'" [theme]="'primary'" (click)="onSaveButtonClick()">
|
|
80
|
+
확인
|
|
81
|
+
</sd-button>
|
|
82
|
+
</ng-template>
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
<!-- CONTENT -->
|
|
86
|
+
<ng-template #contentTpl>
|
|
87
|
+
<ng-content />
|
|
88
|
+
</ng-template>
|
|
89
|
+
|
|
90
|
+
@if (readonly()) {
|
|
91
|
+
<div class="fill">
|
|
92
|
+
<ng-template [ngTemplateOutlet]="contentTpl" />
|
|
93
|
+
</div>
|
|
94
|
+
} @else {
|
|
95
|
+
<sd-form #formCtrl (formSubmit)="submit.emit()" class="block fill">
|
|
96
|
+
<ng-template [ngTemplateOutlet]="contentTpl" />
|
|
97
|
+
</sd-form>
|
|
98
|
+
}
|
|
99
|
+
</sd-base-container>
|
|
100
|
+
`,
|
|
101
|
+
})
|
|
102
|
+
export class SdCrudDetail {
|
|
103
|
+
ready = model(false);
|
|
104
|
+
initialized = input(false);
|
|
105
|
+
busyCount = model(0);
|
|
106
|
+
restricted = input(false);
|
|
107
|
+
readonly = input(false);
|
|
108
|
+
viewType = input.required<SdViewType>();
|
|
109
|
+
|
|
110
|
+
formCtrl = viewChild<SdForm>("formCtrl");
|
|
111
|
+
|
|
112
|
+
submit = output();
|
|
113
|
+
|
|
114
|
+
commandTplRef = contentChild<TemplateRef<void>>("commandTpl");
|
|
115
|
+
bottomCommandTplRef = contentChild<TemplateRef<void>>("bottomCommandTpl");
|
|
116
|
+
|
|
117
|
+
onSaveButtonClick() {
|
|
118
|
+
this.formCtrl()?.requestSubmit();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
protected readonly tablerDeviceFloppy = tablerDeviceFloppy;
|
|
122
|
+
}
|