@simplysm/sd-cli 14.0.88 → 14.0.90

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.
Files changed (82) hide show
  1. package/dist/commands/init/generators/client-common.d.ts.map +1 -1
  2. package/dist/commands/init/generators/client-common.js +6 -2
  3. package/dist/commands/init/generators/client-common.js.map +1 -1
  4. package/dist/commands/init/generators/client.d.ts.map +1 -1
  5. package/dist/commands/init/generators/client.js +13 -2
  6. package/dist/commands/init/generators/client.js.map +1 -1
  7. package/dist/commands/init/generators/common.d.ts.map +1 -1
  8. package/dist/commands/init/generators/common.js +16 -1
  9. package/dist/commands/init/generators/common.js.map +1 -1
  10. package/dist/commands/init/generators/server.d.ts.map +1 -1
  11. package/dist/commands/init/generators/server.js +9 -0
  12. package/dist/commands/init/generators/server.js.map +1 -1
  13. package/dist/commands/init/init.d.ts.map +1 -1
  14. package/dist/commands/init/init.js +11 -2
  15. package/dist/commands/init/init.js.map +1 -1
  16. package/dist/commands/init/normalize.d.ts.map +1 -1
  17. package/dist/commands/init/normalize.js +40 -4
  18. package/dist/commands/init/normalize.js.map +1 -1
  19. package/dist/commands/init/prompts.d.ts +1 -1
  20. package/dist/commands/init/prompts.d.ts.map +1 -1
  21. package/dist/commands/init/prompts.js +34 -3
  22. package/dist/commands/init/prompts.js.map +1 -1
  23. package/dist/commands/init/types.d.ts +16 -1
  24. package/dist/commands/init/types.d.ts.map +1 -1
  25. package/dist/commands/init/validate.d.ts.map +1 -1
  26. package/dist/commands/init/validate.js +3 -0
  27. package/dist/commands/init/validate.js.map +1 -1
  28. package/package.json +4 -4
  29. package/src/commands/init/generators/client-common.ts +18 -5
  30. package/src/commands/init/generators/client.ts +41 -2
  31. package/src/commands/init/generators/common.ts +56 -2
  32. package/src/commands/init/generators/server.ts +30 -0
  33. package/src/commands/init/init.ts +12 -2
  34. package/src/commands/init/normalize.ts +49 -4
  35. package/src/commands/init/prompts.ts +34 -3
  36. package/src/commands/init/templates/client/login-public/assets/logo-landscape.png +0 -0
  37. package/src/commands/init/templates/client/login-public/assets/logo.png +0 -0
  38. package/src/commands/init/templates/client/package.json.hbs +3 -2
  39. package/src/commands/init/templates/client/src/app/home/home.view.ts.hbs +137 -0
  40. package/src/commands/init/templates/client/src/app/home/main/main.view.ts.hbs +16 -0
  41. package/src/commands/init/templates/client/src/app/home/my-info/my-info.detail.ts.hbs +265 -0
  42. package/src/commands/init/templates/client/src/app/login/login.view.ts.hbs +144 -0
  43. package/src/commands/init/templates/client/src/app.root.ts.hbs +64 -0
  44. package/src/commands/init/templates/client/src/index.html.hbs +75 -1
  45. package/src/commands/init/templates/client/src/main.ts.hbs +147 -7
  46. package/src/commands/init/templates/client/src/modals/dev.modal.ts.hbs +63 -0
  47. package/src/commands/init/templates/client/src/routes.ts.hbs +29 -0
  48. package/src/commands/init/templates/client-common/package.json.hbs +1 -0
  49. package/src/commands/init/templates/client-common/src/index.ts.hbs +6 -2
  50. package/src/commands/init/templates/client-common/src/providers/app-auth.provider.ts.hbs +90 -0
  51. package/src/commands/init/templates/client-common/src/providers/{AppOrmProvider.ts.hbs → app-orm.provider.ts.hbs} +5 -11
  52. package/src/commands/init/templates/client-common/src/providers/app-service.provider.ts.hbs +68 -0
  53. package/src/commands/init/templates/client-common/src/providers/app-shared-data.provider.ts.hbs +100 -0
  54. package/src/commands/init/templates/common/package.json.hbs +2 -1
  55. package/src/commands/init/templates/common/src/app-structure.ts.hbs +26 -0
  56. package/src/commands/init/templates/common/src/auth-info-changed.event.ts.hbs +3 -0
  57. package/src/commands/init/templates/common/src/db/db-context.ts.hbs +20 -0
  58. package/src/commands/init/templates/common/src/db/system-data-log.ext.ts.hbs +138 -0
  59. package/src/commands/init/templates/common/src/db/tables/master/user-config.ts.hbs +15 -0
  60. package/src/commands/init/templates/common/src/db/tables/master/user.ts.hbs +20 -0
  61. package/src/commands/init/templates/common/src/db/tables/system/role-permission.ts.hbs +16 -0
  62. package/src/commands/init/templates/common/src/db/tables/system/role.ts.hbs +16 -0
  63. package/src/commands/init/templates/common/src/db/tables/system/system-data-log.ts.hbs +23 -0
  64. package/src/commands/init/templates/common/src/db/tables/system/system-log.ts.hbs +21 -0
  65. package/src/commands/init/templates/common/src/index.ts.hbs +14 -1
  66. package/src/commands/init/templates/server/package.json.hbs +7 -3
  67. package/src/commands/init/templates/server/public-dev//354/264/210/352/270/260/355/231/224.xlsx +0 -0
  68. package/src/commands/init/templates/server/src/index.ts.hbs +4 -0
  69. package/src/commands/init/templates/server/src/main.ts.hbs +11 -1
  70. package/src/commands/init/templates/server/src/services/auth.service.ts.hbs +284 -0
  71. package/src/commands/init/templates/server/src/services/dev.service.ts.hbs +112 -0
  72. package/src/commands/init/templates/server/src/utils/orm.utils.ts.hbs +8 -0
  73. package/src/commands/init/templates/workspace-root/sd.config.ts.hbs +2 -2
  74. package/src/commands/init/types.ts +16 -1
  75. package/src/commands/init/validate.ts +6 -0
  76. package/tests/init/__snapshots__/render.spec.ts.snap +36 -9
  77. package/tests/init/normalize.spec.ts +95 -1
  78. package/tests/init/render.spec.ts +951 -10
  79. package/src/commands/init/templates/client/src/AppPage.ts.hbs +0 -18
  80. package/src/commands/init/templates/client/src/routes.ts +0 -3
  81. package/src/commands/init/templates/client-common/src/providers/AppServiceProvider.ts +0 -27
  82. package/src/commands/init/templates/common/src/DbContext.ts.hbs +0 -4
@@ -0,0 +1,265 @@
1
+ import {
2
+ ChangeDetectionStrategy,
3
+ Component,
4
+ computed,
5
+ effect,
6
+ inject,
7
+ signal,
8
+ untracked,
9
+ ViewEncapsulation,
10
+ } from "@angular/core";
11
+ import { type DateTime, json, obj } from "@simplysm/core-common";
12
+ import {
13
+ FormatPipe,
14
+ injectViewTypeSignal,
15
+ mark,
16
+ SdAppStructureProvider,
17
+ SdCrudDetail,
18
+ SdSelect,
19
+ SdSelectItem,
20
+ SdTextfield,
21
+ SdToastProvider,
22
+ setupCanDeactivate,
23
+ } from "@simplysm/angular";
24
+ import { expr } from "@simplysm/orm-common";
25
+ import { AppAuthProvider, AppOrmProvider, AppServiceProvider } from "@{{workspaceName}}/client-common";
26
+ import { {{userEntityPascal}}Config } from "@{{workspaceName}}/common";
27
+ import type { I{{userEntityPascal}}ConfigMap } from "@{{workspaceName}}/server";
28
+
29
+ @Component({
30
+ selector: "app-my-info-detail",
31
+ changeDetection: ChangeDetectionStrategy.OnPush,
32
+ encapsulation: ViewEncapsulation.None,
33
+ standalone: true,
34
+ imports: [SdCrudDetail, SdTextfield, SdSelect, SdSelectItem, FormatPipe],
35
+ template: `
36
+ <sd-crud-detail
37
+ [initialized]="initialized()"
38
+ [(busyCount)]="busyCount"
39
+ [viewType]="viewType()"
40
+ (submit)="onSubmit()"
41
+ >
42
+ <ng-template #contentTpl>
43
+ <div class="p-default">
44
+ <table class="form-table">
45
+ <tbody>
46
+ <tr><th colspan="2" class="form-table-header">기본정보</th></tr>
47
+ <tr>
48
+ <th>이름</th>
49
+ <td>
50
+ <sd-textfield [type]="'text'" [disabled]="true" [value]="data().name" />
51
+ </td>
52
+ </tr>
53
+ <tr>
54
+ <th>아이디</th>
55
+ <td>
56
+ <sd-textfield [type]="'text'" [disabled]="true" [value]="data().loginId" />
57
+ </td>
58
+ </tr>
59
+ </tbody>
60
+
61
+ <tbody>
62
+ <tr><th colspan="2" class="form-table-header">비밀번호 변경</th></tr>
63
+ <tr>
64
+ <th>기존 비밀번호</th>
65
+ <td>
66
+ <sd-textfield
67
+ [type]="'password'"
68
+ [placeholder]="'기존 비밀번호를 입력하세요.'"
69
+ [required]="!!data().newPassword"
70
+ [(value)]="data().currentPassword"
71
+ (valueChange)="mark(data)"
72
+ />
73
+ </td>
74
+ </tr>
75
+ <tr>
76
+ <th>신규 비밀번호</th>
77
+ <td>
78
+ <sd-textfield
79
+ [type]="'password'"
80
+ [placeholder]="'신규 비밀번호를 입력하세요.'"
81
+ [(value)]="data().newPassword"
82
+ (valueChange)="mark(data)"
83
+ />
84
+ </td>
85
+ </tr>
86
+ <tr>
87
+ <th>비밀번호 확인</th>
88
+ <td>
89
+ <sd-textfield
90
+ [type]="'password'"
91
+ [placeholder]="'확인을 위해 한번 더 입력하세요.'"
92
+ [required]="!!data().newPassword"
93
+ [(value)]="data().newPasswordConfirm"
94
+ (valueChange)="mark(data)"
95
+ />
96
+ </td>
97
+ </tr>
98
+ </tbody>
99
+
100
+ <tbody>
101
+ <tr><th colspan="2" class="form-table-header">시스템설정</th></tr>
102
+ <tr>
103
+ <th>로그인시 표시할 화면</th>
104
+ <td>
105
+ <sd-select
106
+ [(value)]="data().configRecord['first-router-link']"
107
+ (valueChange)="mark(data)"
108
+ >
109
+ <sd-select-item [value]="undefined">
110
+ <span class="tx-theme-gray-default">미지정</span>
111
+ </sd-select-item>
112
+ @for (flatMenu of flatMenus(); track flatMenu.codeChain.join(".")) {
113
+ <sd-select-item [value]="'/home/' + flatMenu.codeChain.join('/')">
114
+ \{{ flatMenu.titleChain.join("» ") }}
115
+ </sd-select-item>
116
+ }
117
+ </sd-select>
118
+ </td>
119
+ </tr>
120
+ </tbody>
121
+ </table>
122
+
123
+ @if (data().lastModifiedAt != null || data().lastModifiedBy != null) {
124
+ <div class="p-sm-default tx-theme-gray-default">
125
+ 최종수정:
126
+ @if (data().lastModifiedAt != null) {
127
+ \{{ data().lastModifiedAt | format: "yyyy-MM-dd HH:mm" }}
128
+ }
129
+ @if (data().lastModifiedBy != null) {
130
+ (\{{ data().lastModifiedBy }})
131
+ }
132
+ </div>
133
+ }
134
+ </div>
135
+ </ng-template>
136
+ </sd-crud-detail>
137
+ `,
138
+ })
139
+ export class MyInfoDetail {
140
+ private readonly _sdToast = inject(SdToastProvider);
141
+ private readonly _appAuth = inject(AppAuthProvider);
142
+ private readonly _appOrm = inject(AppOrmProvider);
143
+ private readonly _appService = inject(AppServiceProvider);
144
+ private readonly _sdAppStructure = inject(SdAppStructureProvider);
145
+
146
+ viewType = injectViewTypeSignal();
147
+
148
+ initialized = signal(false);
149
+ busyCount = signal(0);
150
+
151
+ flatMenus = computed(() => this._sdAppStructure.usableFlatMenus());
152
+
153
+ data = signal<IData>({ name: "", loginId: "", configRecord: {} });
154
+ private _orgData?: IData;
155
+
156
+ constructor() {
157
+ effect(() => {
158
+ this._appAuth.authInfo();
159
+
160
+ void untracked(async () => {
161
+ this.busyCount.update((v) => v + 1);
162
+ await this._sdToast.try(async () => {
163
+ await this._refresh();
164
+ });
165
+ this.busyCount.update((v) => v - 1);
166
+ this.initialized.set(true);
167
+ });
168
+ });
169
+
170
+ setupCanDeactivate(() => this._checkIgnoreChanges());
171
+ }
172
+
173
+ async onSubmit(): Promise<void> {
174
+ if (this.busyCount() > 0) return;
175
+
176
+ const data = this.data();
177
+
178
+ if (this._orgData != null && obj.equal(data, this._orgData)) {
179
+ this._sdToast.info("변경사항이 없습니다.");
180
+ return;
181
+ }
182
+
183
+ this.busyCount.update((v) => v + 1);
184
+ await this._sdToast.try(async () => {
185
+ if (Boolean(data.newPassword) && data.newPassword !== data.newPasswordConfirm) {
186
+ throw new Error("신규 비밀번호의 확인값이 서로 다릅니다.");
187
+ }
188
+
189
+ await this._appService.auth.update({
190
+ configs: data.configRecord,
191
+ currentPassword: data.currentPassword,
192
+ newPassword: data.newPassword,
193
+ });
194
+
195
+ this._sdToast.success("저장되었습니다.");
196
+ await this._refresh();
197
+ });
198
+ this.busyCount.update((v) => v - 1);
199
+ }
200
+
201
+ private async _refresh(): Promise<void> {
202
+ const {{userEntityCamel}}Id = this._appAuth.authInfo()?.{{userEntityCamel}}Id;
203
+ if ({{userEntityCamel}}Id == null) return;
204
+
205
+ const loaded = await this._appOrm.connectAsync(async (db) => {
206
+ return db
207
+ .{{userEntityCamel}}()
208
+ .where((c) => [expr.eq(c.id, {{userEntityCamel}}Id)])
209
+ .joinLastDataLog()
210
+ .join("configs", (qr, e) =>
211
+ qr
212
+ .from({{userEntityPascal}}Config)
213
+ .where((ec) => [expr.eq(ec.{{userEntityCamel}}Id, e.id)])
214
+ .select((ec) => ({ code: ec.code, valueJson: ec.valueJson })),
215
+ )
216
+ .select((item) => ({
217
+ name: item.name,
218
+ loginId: item.loginId,
219
+ lastModifiedAt: item.lastDataLog?.dateTime,
220
+ lastModifiedBy: item.lastDataLog?.{{userEntityCamel}}Name,
221
+ configs: item.configs?.map((it) => ({ code: it.code, valueJson: it.valueJson })),
222
+ }))
223
+ .single();
224
+ });
225
+
226
+ if (loaded == null) {
227
+ throw new Error("{{userEntityLabel}} 정보를 찾을 수 없습니다.");
228
+ }
229
+
230
+ const configRecord = (loaded.configs ?? []).toObject(
231
+ (c) => c.code,
232
+ (c) => json.parse(c.valueJson),
233
+ ) as I{{userEntityPascal}}ConfigMap;
234
+
235
+ this.data.set({
236
+ name: loaded.name,
237
+ loginId: loaded.loginId,
238
+ configRecord,
239
+ lastModifiedAt: loaded.lastModifiedAt,
240
+ lastModifiedBy: loaded.lastModifiedBy,
241
+ });
242
+ this._orgData = obj.clone(this.data());
243
+ }
244
+
245
+ private _checkIgnoreChanges(): boolean {
246
+ return (
247
+ this._orgData == null ||
248
+ obj.equal(this.data(), this._orgData) ||
249
+ confirm("변경사항이 있습니다. 무시하고 진행하시겠습니까?")
250
+ );
251
+ }
252
+
253
+ protected readonly mark = mark;
254
+ }
255
+
256
+ interface IData {
257
+ name: string;
258
+ loginId: string;
259
+ configRecord: I{{userEntityPascal}}ConfigMap;
260
+ currentPassword?: string;
261
+ newPassword?: string;
262
+ newPasswordConfirm?: string;
263
+ lastModifiedAt?: DateTime;
264
+ lastModifiedBy?: string;
265
+ }
@@ -0,0 +1,144 @@
1
+ import {
2
+ ChangeDetectionStrategy,
3
+ Component,
4
+ effect,
5
+ inject,
6
+ signal,
7
+ untracked,
8
+ ViewEncapsulation,
9
+ } from "@angular/core";
10
+ import { Router } from "@angular/router";
11
+ import {
12
+ mark,
13
+ SdBusyContainer,
14
+ SdButton,
15
+ SdForm,
16
+ SdTextfield,
17
+ SdToastProvider,
18
+ setupBgTheme,
19
+ } from "@simplysm/angular";
20
+ import { NgIcon } from "@ng-icons/core";
21
+ import { tablerLockOpen } from "@ng-icons/tabler-icons";
22
+ import { env, parseBoolEnv } from "@simplysm/core-common";
23
+ import { AppAuthProvider } from "@{{workspaceName}}/client-common";
24
+
25
+ interface ILoginInput {
26
+ loginId?: string;
27
+ password?: string;
28
+ }
29
+
30
+ @Component({
31
+ selector: "app-login-view",
32
+ changeDetection: ChangeDetectionStrategy.OnPush,
33
+ encapsulation: ViewEncapsulation.None,
34
+ standalone: true,
35
+ imports: [SdBusyContainer, SdForm, SdTextfield, SdButton, NgIcon],
36
+ template: `
37
+ <sd-busy-container [busy]="initBusyCount() > 0">
38
+ @if (initBusyCount() === 0) {
39
+ <div style="width: 15em; margin: 0 auto; padding-top: 80px; padding-bottom: 30px;">
40
+ <img src="assets/logo.png" alt="{{workspaceName}}" style="width: 100%; height: auto;" />
41
+ </div>
42
+
43
+ <div class="card m-0-auto bd-radius-lg" style="width: 22em;">
44
+ <div class="flex-row cross-align-center p-lg-xl bdb bdb-color-light">
45
+ <h3 class="flex-fill">로그인</h3>
46
+ <div>v\{{ VER }}.\{{ DEV ? "d" : "p" }}</div>
47
+ </div>
48
+
49
+ <sd-busy-container [busy]="busyCount() > 0" class="p-xl" [type]="'bar'">
50
+ <sd-form (formSubmit)="onSubmit()">
51
+ <table class="form-table" style="width: 100%;">
52
+ <tr>
53
+ <th>아이디</th>
54
+ <td>
55
+ <sd-textfield
56
+ [type]="'text'"
57
+ [autocomplete]="'username'"
58
+ [placeholder]="'아이디를 입력하세요.'"
59
+ [required]="true"
60
+ [(value)]="data().loginId"
61
+ (valueChange)="mark(data)"
62
+ />
63
+ </td>
64
+ </tr>
65
+ <tr>
66
+ <th>비밀번호</th>
67
+ <td>
68
+ <sd-textfield
69
+ [type]="'password'"
70
+ [autocomplete]="'current-password'"
71
+ [placeholder]="'비밀번호를 입력하세요.'"
72
+ [required]="true"
73
+ [(value)]="data().password"
74
+ (valueChange)="mark(data)"
75
+ />
76
+ </td>
77
+ </tr>
78
+ <tr>
79
+ <th></th>
80
+ <td>
81
+ <sd-button [type]="'submit'" [theme]="'primary'" [size]="'lg'">
82
+ <ng-icon [svg]="tablerLockOpen" />
83
+ 로그인
84
+ </sd-button>
85
+ </td>
86
+ </tr>
87
+ </table>
88
+ </sd-form>
89
+ </sd-busy-container>
90
+ </div>
91
+ }
92
+ </sd-busy-container>
93
+ `,
94
+ })
95
+ export class LoginView {
96
+ private readonly _sdToast = inject(SdToastProvider);
97
+ private readonly _appAuth = inject(AppAuthProvider);
98
+ private readonly _router = inject(Router);
99
+
100
+ initBusyCount = signal(0);
101
+ busyCount = signal(0);
102
+
103
+ data = signal<ILoginInput>({});
104
+
105
+ constructor() {
106
+ setupBgTheme({ theme: "gray" });
107
+
108
+ effect(() => {
109
+ void untracked(async () => {
110
+ this.initBusyCount.update((v) => v + 1);
111
+ try {
112
+ const ok = await this._appAuth.tryReloadAuth();
113
+ if (ok) {
114
+ const firstRouterLink = this._appAuth.authInfo()?.configs["first-router-link"] ?? "/home/main";
115
+ await this._router.navigateByUrl(firstRouterLink, { replaceUrl: true });
116
+ return;
117
+ }
118
+ const lastLoginId = this._appAuth.getLastLoginId();
119
+ if (lastLoginId != null && lastLoginId !== "") {
120
+ this.data.update((d) => ({ ...d, loginId: lastLoginId }));
121
+ }
122
+ } finally {
123
+ this.initBusyCount.update((v) => v - 1);
124
+ }
125
+ });
126
+ });
127
+ }
128
+
129
+ async onSubmit() {
130
+ if (this.busyCount() > 0) return;
131
+ this.busyCount.update((v) => v + 1);
132
+ await this._sdToast.try(async () => {
133
+ await this._appAuth.login(this.data().loginId!, this.data().password!);
134
+ const firstRouterLink = this._appAuth.authInfo()?.configs["first-router-link"] ?? "/home/main";
135
+ await this._router.navigateByUrl(firstRouterLink, { replaceUrl: true });
136
+ });
137
+ this.busyCount.update((v) => v - 1);
138
+ }
139
+
140
+ protected readonly VER = env("VER");
141
+ protected readonly DEV = parseBoolEnv(env("DEV"));
142
+ protected readonly mark = mark;
143
+ protected readonly tablerLockOpen = tablerLockOpen;
144
+ }
@@ -0,0 +1,64 @@
1
+ {{#if hasDb}}
2
+ import { ChangeDetectionStrategy, Component, inject, ViewEncapsulation } from "@angular/core";
3
+ {{else}}
4
+ import { ChangeDetectionStrategy, Component, ViewEncapsulation } from "@angular/core";
5
+ {{/if}}
6
+ {{#if client.hasRouter}}
7
+ import { RouterOutlet } from "@angular/router";
8
+ {{/if}}
9
+ {{#if hasDb}}
10
+ import { env, parseBoolEnv } from "@simplysm/core-common";
11
+ import { SdModalProvider } from "@simplysm/angular";
12
+ import { DevModal } from "./modals/dev.modal";
13
+ {{/if}}
14
+
15
+ @Component({
16
+ selector: "app-root",
17
+ changeDetection: ChangeDetectionStrategy.OnPush,
18
+ encapsulation: ViewEncapsulation.None,
19
+ standalone: true,
20
+ {{#if hasDb}}
21
+ host: { "(document:keydown)": "onKeydown($event)" },
22
+ {{/if}}
23
+ {{#if client.hasRouter}}
24
+ imports: [RouterOutlet],
25
+ template: `<router-outlet />`,
26
+ {{else}}
27
+ template: `<div></div>`,
28
+ {{/if}}
29
+ })
30
+ {{#if hasDb}}
31
+ export class AppRoot {
32
+ private readonly _sdModal = inject(SdModalProvider);
33
+
34
+ private _preservedKeys: string[] = [];
35
+
36
+ // 글로벌 키 시퀀스(Ctrl+Alt+Shift 누른 채 F12 → F11 → F12 → F12)로 개발자 도구 모달 호출
37
+ async onKeydown(event: KeyboardEvent) {
38
+ if (
39
+ parseBoolEnv(env("DEV")) &&
40
+ event.ctrlKey &&
41
+ event.altKey &&
42
+ event.shiftKey &&
43
+ (event.key === "F11" || event.key === "F12")
44
+ ) {
45
+ event.preventDefault();
46
+ event.stopPropagation();
47
+
48
+ this._preservedKeys.push(event.key);
49
+ this._preservedKeys = this._preservedKeys.slice(-4);
50
+
51
+ if (
52
+ this._preservedKeys[0] === "F12" &&
53
+ this._preservedKeys[1] === "F11" &&
54
+ this._preservedKeys[2] === "F12" &&
55
+ this._preservedKeys[3] === "F12"
56
+ ) {
57
+ await this._sdModal.showAsync({ type: DevModal, title: "개발자 도구", inputs: {} });
58
+ }
59
+ }
60
+ }
61
+ }
62
+ {{else}}
63
+ export class AppRoot {}
64
+ {{/if}}
@@ -3,10 +3,84 @@
3
3
  <head>
4
4
  <meta charset="utf-8" />
5
5
  <meta name="viewport" content="initial-scale=1, width=device-width, viewport-fit=cover" />
6
+ <meta name="format-detection" content="telephone=no" />
7
+ <meta name="msapplication-tap-highlight" content="no" />
6
8
  <link rel="shortcut icon" type="image/x-icon" href="favicon.ico" />
7
9
  <title>{{description}}</title>
8
10
  </head>
9
11
  <body>
10
- <app-root></app-root>
12
+ <app-root>
13
+ <style>
14
+ *,
15
+ *:after,
16
+ *:before {
17
+ box-sizing: border-box;
18
+ }
19
+
20
+ html,
21
+ body {
22
+ padding: 0;
23
+ margin: 0;
24
+ background: #f5f5f5;
25
+ }
26
+
27
+ .app-loader-bar {
28
+ position: fixed;
29
+ top: 0;
30
+ left: 0;
31
+ height: 2px;
32
+ width: 100%;
33
+ background-color: white;
34
+ }
35
+
36
+ .app-loader-bar > div {
37
+ position: fixed;
38
+ top: 0;
39
+ left: 0;
40
+ display: inline-block;
41
+ height: 4px;
42
+ width: 100%;
43
+
44
+ transform-origin: left;
45
+ }
46
+
47
+ .app-loader-bar > div:first-child {
48
+ background-color: hsl(212.7907636992, 106.3271487689%, 66.8390389975%);
49
+ animation: app-loader-bar-1 2s infinite ease-in;
50
+ }
51
+
52
+ .app-loader-bar > div:last-child {
53
+ background-color: white;
54
+ animation: app-loader-bar-2 2s infinite ease-out;
55
+ }
56
+
57
+ @keyframes app-loader-bar-1 {
58
+ 0% {
59
+ transform: scaleX(0);
60
+ }
61
+ 60%,
62
+ 100% {
63
+ transform: scaleX(1);
64
+ }
65
+ }
66
+
67
+ @keyframes app-loader-bar-2 {
68
+ 0%,
69
+ 50% {
70
+ transform: scaleX(0);
71
+ }
72
+ 100% {
73
+ transform: scaleX(1);
74
+ }
75
+ }
76
+ </style>
77
+
78
+ <div class="app-loader-bar">
79
+ <div></div>
80
+ <div></div>
81
+ </div>
82
+ </app-root>
83
+
84
+ <noscript>Please enable JavaScript to continue using this application.</noscript>
11
85
  </body>
12
86
  </html>