@things-factory/system 10.0.0-beta.5 → 10.0.0-beta.58
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-client/pages/system-info.d.ts +42 -2
- package/dist-client/pages/system-info.js +345 -15
- package/dist-client/pages/system-info.js.map +1 -1
- package/dist-client/route.d.ts +1 -1
- package/dist-client/route.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/viewparts/opensource-license.d.ts +1 -1
- package/dist-client/viewparts/opensource-license.js +2 -2
- package/dist-client/viewparts/opensource-license.js.map +1 -1
- package/dist-client/viewparts/release-note-notice.d.ts +1 -1
- package/dist-client/viewparts/release-note-notice.js +1 -1
- package/dist-client/viewparts/release-note-notice.js.map +1 -1
- package/dist-client/viewparts/release-note-setting-let.js +1 -1
- package/dist-client/viewparts/release-note-setting-let.js.map +1 -1
- package/dist-server/routes.js +2 -2
- package/dist-server/routes.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/translations/en.json +13 -0
- package/translations/ko.json +13 -0
|
@@ -7,7 +7,14 @@ import { PageView } from '@operato/shell';
|
|
|
7
7
|
declare const SystemInfo_base: (new (...args: any[]) => import("lit").LitElement) & typeof PageView;
|
|
8
8
|
export declare class SystemInfo extends SystemInfo_base {
|
|
9
9
|
static styles: import("lit").CSSResult;
|
|
10
|
+
private _swStatus;
|
|
11
|
+
private _swLastChecked;
|
|
12
|
+
private _swChecking;
|
|
10
13
|
private _applicationMeta?;
|
|
14
|
+
private _swRegistration;
|
|
15
|
+
private _swStateChangeListener;
|
|
16
|
+
private _swUpdateFoundListener;
|
|
17
|
+
private _observedSW;
|
|
11
18
|
get context(): {
|
|
12
19
|
title: string;
|
|
13
20
|
};
|
|
@@ -18,7 +25,40 @@ export declare class SystemInfo extends SystemInfo_base {
|
|
|
18
25
|
copyright: string;
|
|
19
26
|
};
|
|
20
27
|
render(): import("lit-html").TemplateResult<1>;
|
|
21
|
-
|
|
22
|
-
|
|
28
|
+
connectedCallback(): void;
|
|
29
|
+
disconnectedCallback(): void;
|
|
30
|
+
/**
|
|
31
|
+
* SW registration 과 installing/waiting 상태를 관찰.
|
|
32
|
+
* `updatefound` 이벤트로 새 SW 감지 → statechange 로 전이 추적.
|
|
33
|
+
*
|
|
34
|
+
* 개발 미리보기: URL 에 `?sw-demo=downloading` 또는 `?sw-demo=ready` 를
|
|
35
|
+
* 붙이면 실제 SW 와 무관하게 해당 상태 배너를 고정 표시 (UI 검증용).
|
|
36
|
+
*/
|
|
37
|
+
private _setupSwObserver;
|
|
38
|
+
/**
|
|
39
|
+
* 수동 또는 초기 update check. 네트워크로 SW 파일 재확인 후
|
|
40
|
+
* 새 버전이 있으면 `updatefound` 이벤트가 발생해 기존 리스너가 상태 전이.
|
|
41
|
+
*/
|
|
42
|
+
private _checkForUpdates;
|
|
43
|
+
private _bindWorker;
|
|
44
|
+
private _updateStatusFromWorker;
|
|
45
|
+
private _teardownSwObserver;
|
|
46
|
+
/**
|
|
47
|
+
* 상태에 따라 적절한 액션:
|
|
48
|
+
* - ready: waiting SW 에 SKIP_WAITING 메시지 → activate 유도 → reload
|
|
49
|
+
* - 기타: 단순 location.reload()
|
|
50
|
+
*/
|
|
51
|
+
private _applyUpdateOrReload;
|
|
52
|
+
private _renderSwStatus;
|
|
53
|
+
/**
|
|
54
|
+
* "5초 전" / "2분 전" 같은 상대 시간 문자열.
|
|
55
|
+
*/
|
|
56
|
+
private _formatAgo;
|
|
57
|
+
/**
|
|
58
|
+
* 개발 정보 (NODE_ENV, SHELL-VERSION 등) — 개발 모드에서만 노출.
|
|
59
|
+
*/
|
|
60
|
+
private _renderDevInfo;
|
|
61
|
+
onClickOpenSourceLicense(e: Event): void;
|
|
62
|
+
onClickLicenseInfo(e: Event): void;
|
|
23
63
|
}
|
|
24
64
|
export {};
|
|
@@ -1,17 +1,27 @@
|
|
|
1
|
-
import { __decorate } from "tslib";
|
|
1
|
+
import { __decorate, __metadata } from "tslib";
|
|
2
2
|
import '@material/web/icon/icon.js';
|
|
3
3
|
import '@material/web/button/text-button.js';
|
|
4
4
|
import '@operato/i18n/ox-i18n.js';
|
|
5
5
|
import '../viewparts/opensource-license';
|
|
6
6
|
import '../viewparts/license-info';
|
|
7
7
|
import { css, html } from 'lit';
|
|
8
|
-
import { customElement } from 'lit/decorators.js';
|
|
8
|
+
import { customElement, state } from 'lit/decorators.js';
|
|
9
9
|
import { openHelp } from '@operato/help';
|
|
10
10
|
import { i18next, localize } from '@operato/i18n';
|
|
11
11
|
import { openPopup } from '@operato/layout';
|
|
12
12
|
import { PageView } from '@operato/shell';
|
|
13
13
|
const ENV = process.env;
|
|
14
14
|
let SystemInfo = class SystemInfo extends localize(i18next)(PageView) {
|
|
15
|
+
constructor() {
|
|
16
|
+
super(...arguments);
|
|
17
|
+
this._swStatus = 'up-to-date';
|
|
18
|
+
this._swLastChecked = null;
|
|
19
|
+
this._swChecking = false;
|
|
20
|
+
this._swRegistration = null;
|
|
21
|
+
this._swStateChangeListener = null;
|
|
22
|
+
this._swUpdateFoundListener = null;
|
|
23
|
+
this._observedSW = null;
|
|
24
|
+
}
|
|
15
25
|
static { this.styles = css `
|
|
16
26
|
:host {
|
|
17
27
|
display: block;
|
|
@@ -85,6 +95,92 @@ let SystemInfo = class SystemInfo extends localize(i18next)(PageView) {
|
|
|
85
95
|
opacity: var(--help-icon-hover-opacity, 1);
|
|
86
96
|
color: var(--help-icon-hover-color);
|
|
87
97
|
}
|
|
98
|
+
|
|
99
|
+
/* Service Worker 상태 배너 */
|
|
100
|
+
.sw-status {
|
|
101
|
+
display: inline-flex;
|
|
102
|
+
align-items: center;
|
|
103
|
+
gap: 8px;
|
|
104
|
+
padding: 8px 14px;
|
|
105
|
+
margin: 8px auto;
|
|
106
|
+
border-radius: 6px;
|
|
107
|
+
font-size: 13px;
|
|
108
|
+
max-width: 420px;
|
|
109
|
+
}
|
|
110
|
+
.sw-status.downloading {
|
|
111
|
+
background: var(--sw-status-download-bg, #fef3c7);
|
|
112
|
+
color: var(--sw-status-download-color, #78350f);
|
|
113
|
+
border: 1px solid var(--sw-status-download-border, #fbbf24);
|
|
114
|
+
}
|
|
115
|
+
.sw-status.ready {
|
|
116
|
+
background: var(--sw-status-ready-bg, #dcfce7);
|
|
117
|
+
color: var(--sw-status-ready-color, #14532d);
|
|
118
|
+
border: 1px solid var(--sw-status-ready-border, #22c55e);
|
|
119
|
+
}
|
|
120
|
+
.sw-status.up-to-date {
|
|
121
|
+
background: var(--sw-status-uptodate-bg, #f0fdf4);
|
|
122
|
+
color: var(--sw-status-uptodate-color, #166534);
|
|
123
|
+
border: 1px solid var(--sw-status-uptodate-border, #86efac);
|
|
124
|
+
}
|
|
125
|
+
.sw-status.info {
|
|
126
|
+
background: var(--sw-status-info-bg, #eff6ff);
|
|
127
|
+
color: var(--sw-status-info-color, #1e3a8a);
|
|
128
|
+
border: 1px solid var(--sw-status-info-border, #93c5fd);
|
|
129
|
+
}
|
|
130
|
+
.sw-status-meta {
|
|
131
|
+
font-size: 11px;
|
|
132
|
+
opacity: 0.7;
|
|
133
|
+
}
|
|
134
|
+
.sw-check-btn {
|
|
135
|
+
margin-left: auto;
|
|
136
|
+
--md-text-button-label-text-size: 11px;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/* 개발 정보 블록 */
|
|
140
|
+
.dev-info {
|
|
141
|
+
max-width: 480px;
|
|
142
|
+
margin: 18px auto;
|
|
143
|
+
padding: 12px 16px;
|
|
144
|
+
border: 1px dashed #cbd5e1;
|
|
145
|
+
border-radius: 6px;
|
|
146
|
+
text-align: left;
|
|
147
|
+
background: rgba(148, 163, 184, 0.06);
|
|
148
|
+
}
|
|
149
|
+
.dev-info-title {
|
|
150
|
+
font-size: 11px;
|
|
151
|
+
font-weight: 700;
|
|
152
|
+
text-transform: uppercase;
|
|
153
|
+
letter-spacing: 1px;
|
|
154
|
+
color: #64748b;
|
|
155
|
+
margin-bottom: 8px;
|
|
156
|
+
}
|
|
157
|
+
.dev-info dl {
|
|
158
|
+
display: grid;
|
|
159
|
+
grid-template-columns: max-content 1fr;
|
|
160
|
+
gap: 4px 12px;
|
|
161
|
+
font-size: 12px;
|
|
162
|
+
margin: 0;
|
|
163
|
+
}
|
|
164
|
+
.dev-info dt {
|
|
165
|
+
color: #475569;
|
|
166
|
+
font-weight: 600;
|
|
167
|
+
}
|
|
168
|
+
.dev-info dd {
|
|
169
|
+
margin: 0;
|
|
170
|
+
color: #0f172a;
|
|
171
|
+
font-family: 'SF Mono', Consolas, monospace;
|
|
172
|
+
word-break: break-all;
|
|
173
|
+
}
|
|
174
|
+
.sw-status md-icon {
|
|
175
|
+
font-size: 18px;
|
|
176
|
+
}
|
|
177
|
+
.sw-status md-icon.spinning {
|
|
178
|
+
animation: sw-spin 1.4s linear infinite;
|
|
179
|
+
}
|
|
180
|
+
@keyframes sw-spin {
|
|
181
|
+
from { transform: rotate(0deg); }
|
|
182
|
+
to { transform: rotate(360deg); }
|
|
183
|
+
}
|
|
88
184
|
`; }
|
|
89
185
|
get context() {
|
|
90
186
|
return {
|
|
@@ -93,10 +189,10 @@ let SystemInfo = class SystemInfo extends localize(i18next)(PageView) {
|
|
|
93
189
|
}
|
|
94
190
|
get applicationMeta() {
|
|
95
191
|
if (!this._applicationMeta) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
192
|
+
const iconLink = document.querySelector('link[rel="application-icon"]');
|
|
193
|
+
const titleMeta = document.querySelector('meta[name="application-name"]');
|
|
194
|
+
const descriptionMeta = document.querySelector('meta[name="application-description"]');
|
|
195
|
+
const copyrightMeta = document.querySelector('meta[name="application-copyright"]');
|
|
100
196
|
this._applicationMeta = {
|
|
101
197
|
icon: iconLink?.href,
|
|
102
198
|
title: titleMeta ? titleMeta.content : 'Things Factory',
|
|
@@ -107,7 +203,7 @@ let SystemInfo = class SystemInfo extends localize(i18next)(PageView) {
|
|
|
107
203
|
return this._applicationMeta;
|
|
108
204
|
}
|
|
109
205
|
render() {
|
|
110
|
-
|
|
206
|
+
const { icon, title, description, copyright } = this.applicationMeta;
|
|
111
207
|
return html `
|
|
112
208
|
<img src=${icon} alt="system logo" />
|
|
113
209
|
|
|
@@ -119,15 +215,17 @@ let SystemInfo = class SystemInfo extends localize(i18next)(PageView) {
|
|
|
119
215
|
>
|
|
120
216
|
</div>
|
|
121
217
|
|
|
218
|
+
${this._renderSwStatus()}
|
|
219
|
+
|
|
122
220
|
<div>
|
|
123
221
|
<md-text-button
|
|
124
222
|
id="refresh-button"
|
|
125
|
-
@click=${
|
|
126
|
-
location.reload();
|
|
127
|
-
}}
|
|
223
|
+
@click=${() => this._applyUpdateOrReload()}
|
|
128
224
|
>
|
|
129
|
-
<md-icon
|
|
130
|
-
${String(
|
|
225
|
+
<md-icon>${this._swStatus === 'ready' ? 'system_update' : 'refresh'}</md-icon>
|
|
226
|
+
${String(this._swStatus === 'ready'
|
|
227
|
+
? i18next.t('label.apply update and reload')
|
|
228
|
+
: i18next.t('label.system reload'))}
|
|
131
229
|
</md-text-button>
|
|
132
230
|
</div>
|
|
133
231
|
|
|
@@ -136,19 +234,239 @@ let SystemInfo = class SystemInfo extends localize(i18next)(PageView) {
|
|
|
136
234
|
|
|
137
235
|
<span
|
|
138
236
|
>${title} is built on several
|
|
139
|
-
<a href="#" @click=${e => this.onClickOpenSourceLicense(e)}>open source software</a></span
|
|
237
|
+
<a href="#" @click=${(e) => this.onClickOpenSourceLicense(e)}>open source software</a></span
|
|
140
238
|
>
|
|
141
|
-
<span>We are using the <a href="#" @click=${e => this.onClickLicenseInfo(e)}>license</a> as permitted. </span>
|
|
239
|
+
<span>We are using the <a href="#" @click=${(e) => this.onClickLicenseInfo(e)}>license</a> as permitted. </span>
|
|
142
240
|
</div>
|
|
143
241
|
|
|
242
|
+
${this._renderDevInfo()}
|
|
243
|
+
|
|
144
244
|
<div class="poweredBy">
|
|
145
245
|
<span
|
|
146
246
|
>Powered by ™Things Factory <ox-i18n msgid="field.version"></ox-i18n> ${ENV['SHELL-VERSION']}
|
|
147
|
-
<md-icon @click=${
|
|
247
|
+
<md-icon @click=${() => openHelp('about/things-factory')}>help</md-icon>
|
|
148
248
|
</span>
|
|
149
249
|
</div>
|
|
150
250
|
`;
|
|
151
251
|
}
|
|
252
|
+
// ───────── Service Worker 상태 추적 ─────────
|
|
253
|
+
connectedCallback() {
|
|
254
|
+
super.connectedCallback();
|
|
255
|
+
this._setupSwObserver();
|
|
256
|
+
}
|
|
257
|
+
disconnectedCallback() {
|
|
258
|
+
super.disconnectedCallback();
|
|
259
|
+
this._teardownSwObserver();
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* SW registration 과 installing/waiting 상태를 관찰.
|
|
263
|
+
* `updatefound` 이벤트로 새 SW 감지 → statechange 로 전이 추적.
|
|
264
|
+
*
|
|
265
|
+
* 개발 미리보기: URL 에 `?sw-demo=downloading` 또는 `?sw-demo=ready` 를
|
|
266
|
+
* 붙이면 실제 SW 와 무관하게 해당 상태 배너를 고정 표시 (UI 검증용).
|
|
267
|
+
*/
|
|
268
|
+
async _setupSwObserver() {
|
|
269
|
+
// dev 미리보기 쇼트컷
|
|
270
|
+
const demo = new URLSearchParams(window.location.search).get('sw-demo');
|
|
271
|
+
if (demo === 'downloading' || demo === 'installing' || demo === 'ready' || demo === 'up-to-date') {
|
|
272
|
+
this._swStatus = demo;
|
|
273
|
+
this._swLastChecked = new Date();
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
if (!('serviceWorker' in navigator)) {
|
|
277
|
+
this._swStatus = 'unsupported';
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
const reg = await navigator.serviceWorker.getRegistration();
|
|
281
|
+
if (!reg) {
|
|
282
|
+
this._swStatus = 'unregistered';
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
this._swRegistration = reg;
|
|
286
|
+
// 이미 installing 중인 SW 가 있으면 즉시 관찰
|
|
287
|
+
if (reg.installing)
|
|
288
|
+
this._bindWorker(reg.installing);
|
|
289
|
+
// 이미 waiting (설치 완료, 적용 대기) 이면 ready
|
|
290
|
+
if (reg.waiting)
|
|
291
|
+
this._swStatus = 'ready';
|
|
292
|
+
// 새 SW 감지 핸들러
|
|
293
|
+
this._swUpdateFoundListener = () => {
|
|
294
|
+
if (reg.installing)
|
|
295
|
+
this._bindWorker(reg.installing);
|
|
296
|
+
};
|
|
297
|
+
reg.addEventListener('updatefound', this._swUpdateFoundListener);
|
|
298
|
+
// 아직 새 SW 가 없으면 "up-to-date" 로 표시
|
|
299
|
+
if (!reg.installing && !reg.waiting) {
|
|
300
|
+
this._swStatus = 'up-to-date';
|
|
301
|
+
}
|
|
302
|
+
// 강제 update check — 페이지 열린 시점의 최신 여부 확인
|
|
303
|
+
await this._checkForUpdates();
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* 수동 또는 초기 update check. 네트워크로 SW 파일 재확인 후
|
|
307
|
+
* 새 버전이 있으면 `updatefound` 이벤트가 발생해 기존 리스너가 상태 전이.
|
|
308
|
+
*/
|
|
309
|
+
async _checkForUpdates() {
|
|
310
|
+
if (!this._swRegistration)
|
|
311
|
+
return;
|
|
312
|
+
this._swChecking = true;
|
|
313
|
+
try {
|
|
314
|
+
await this._swRegistration.update();
|
|
315
|
+
}
|
|
316
|
+
catch {
|
|
317
|
+
/* 네트워크 불가 등 — 조용히 무시 */
|
|
318
|
+
}
|
|
319
|
+
finally {
|
|
320
|
+
this._swChecking = false;
|
|
321
|
+
this._swLastChecked = new Date();
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
_bindWorker(sw) {
|
|
325
|
+
this._observedSW = sw;
|
|
326
|
+
this._updateStatusFromWorker(sw);
|
|
327
|
+
this._swStateChangeListener = () => this._updateStatusFromWorker(sw);
|
|
328
|
+
sw.addEventListener('statechange', this._swStateChangeListener);
|
|
329
|
+
}
|
|
330
|
+
_updateStatusFromWorker(sw) {
|
|
331
|
+
switch (sw.state) {
|
|
332
|
+
case 'installing':
|
|
333
|
+
this._swStatus = 'downloading';
|
|
334
|
+
break;
|
|
335
|
+
case 'installed':
|
|
336
|
+
// 기존 controller 가 있으면 새 버전 대기 상태 (업데이트), 없으면 최초 설치
|
|
337
|
+
this._swStatus = navigator.serviceWorker.controller ? 'ready' : 'up-to-date';
|
|
338
|
+
break;
|
|
339
|
+
case 'activating':
|
|
340
|
+
this._swStatus = 'installing';
|
|
341
|
+
break;
|
|
342
|
+
case 'activated':
|
|
343
|
+
case 'redundant':
|
|
344
|
+
this._swStatus = 'up-to-date';
|
|
345
|
+
break;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
_teardownSwObserver() {
|
|
349
|
+
if (this._observedSW && this._swStateChangeListener) {
|
|
350
|
+
this._observedSW.removeEventListener('statechange', this._swStateChangeListener);
|
|
351
|
+
}
|
|
352
|
+
if (this._swRegistration && this._swUpdateFoundListener) {
|
|
353
|
+
this._swRegistration.removeEventListener('updatefound', this._swUpdateFoundListener);
|
|
354
|
+
}
|
|
355
|
+
this._observedSW = null;
|
|
356
|
+
this._swRegistration = null;
|
|
357
|
+
this._swStateChangeListener = null;
|
|
358
|
+
this._swUpdateFoundListener = null;
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* 상태에 따라 적절한 액션:
|
|
362
|
+
* - ready: waiting SW 에 SKIP_WAITING 메시지 → activate 유도 → reload
|
|
363
|
+
* - 기타: 단순 location.reload()
|
|
364
|
+
*/
|
|
365
|
+
_applyUpdateOrReload() {
|
|
366
|
+
if (this._swStatus === 'ready' && this._swRegistration?.waiting) {
|
|
367
|
+
this._swRegistration.waiting.postMessage({ type: 'SKIP_WAITING' });
|
|
368
|
+
navigator.serviceWorker.addEventListener('controllerchange', () => location.reload(), { once: true });
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
location.reload();
|
|
372
|
+
}
|
|
373
|
+
_renderSwStatus() {
|
|
374
|
+
const checkedAgo = this._swLastChecked
|
|
375
|
+
? this._formatAgo(this._swLastChecked)
|
|
376
|
+
: null;
|
|
377
|
+
// 각 상태별 스타일/아이콘/메시지
|
|
378
|
+
const statusConfig = {
|
|
379
|
+
downloading: {
|
|
380
|
+
tone: 'downloading',
|
|
381
|
+
icon: 'cached',
|
|
382
|
+
spin: true,
|
|
383
|
+
msg: i18next.t('text.new version downloading')
|
|
384
|
+
},
|
|
385
|
+
installing: {
|
|
386
|
+
tone: 'downloading',
|
|
387
|
+
icon: 'cached',
|
|
388
|
+
spin: true,
|
|
389
|
+
msg: i18next.t('text.new version installing')
|
|
390
|
+
},
|
|
391
|
+
ready: {
|
|
392
|
+
tone: 'ready',
|
|
393
|
+
icon: 'system_update',
|
|
394
|
+
msg: i18next.t('text.new version ready to apply')
|
|
395
|
+
},
|
|
396
|
+
'up-to-date': {
|
|
397
|
+
tone: 'up-to-date',
|
|
398
|
+
icon: 'check_circle',
|
|
399
|
+
msg: i18next.t('text.you are on the latest version')
|
|
400
|
+
},
|
|
401
|
+
unregistered: {
|
|
402
|
+
tone: 'info',
|
|
403
|
+
icon: 'info',
|
|
404
|
+
msg: i18next.t('text.service worker not registered (dev mode)')
|
|
405
|
+
},
|
|
406
|
+
unsupported: {
|
|
407
|
+
tone: 'info',
|
|
408
|
+
icon: 'info',
|
|
409
|
+
msg: i18next.t('text.service worker not supported')
|
|
410
|
+
}
|
|
411
|
+
};
|
|
412
|
+
const s = statusConfig[this._swStatus];
|
|
413
|
+
return html `
|
|
414
|
+
<div class="sw-status ${s.tone}">
|
|
415
|
+
<md-icon class=${s.spin ? 'spinning' : ''}>${s.icon}</md-icon>
|
|
416
|
+
<span>${s.msg}</span>
|
|
417
|
+
${checkedAgo
|
|
418
|
+
? html `<span class="sw-status-meta">· ${i18next.t('text.checked')} ${checkedAgo}</span>`
|
|
419
|
+
: ''}
|
|
420
|
+
${this._swStatus === 'up-to-date' || this._swStatus === 'unregistered'
|
|
421
|
+
? html `
|
|
422
|
+
<md-text-button
|
|
423
|
+
class="sw-check-btn"
|
|
424
|
+
?disabled=${this._swChecking}
|
|
425
|
+
@click=${() => this._checkForUpdates()}
|
|
426
|
+
>
|
|
427
|
+
<md-icon>refresh</md-icon>
|
|
428
|
+
${String(i18next.t('label.check now'))}
|
|
429
|
+
</md-text-button>
|
|
430
|
+
`
|
|
431
|
+
: ''}
|
|
432
|
+
</div>
|
|
433
|
+
`;
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* "5초 전" / "2분 전" 같은 상대 시간 문자열.
|
|
437
|
+
*/
|
|
438
|
+
_formatAgo(d) {
|
|
439
|
+
const sec = Math.max(0, Math.floor((Date.now() - d.getTime()) / 1000));
|
|
440
|
+
if (sec < 60)
|
|
441
|
+
return `${sec}${i18next.t('text.seconds ago suffix')}`;
|
|
442
|
+
const min = Math.floor(sec / 60);
|
|
443
|
+
if (min < 60)
|
|
444
|
+
return `${min}${i18next.t('text.minutes ago suffix')}`;
|
|
445
|
+
const hr = Math.floor(min / 60);
|
|
446
|
+
return `${hr}${i18next.t('text.hours ago suffix')}`;
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* 개발 정보 (NODE_ENV, SHELL-VERSION 등) — 개발 모드에서만 노출.
|
|
450
|
+
*/
|
|
451
|
+
_renderDevInfo() {
|
|
452
|
+
const nodeEnv = (ENV['NODE-ENV'] || '').toString();
|
|
453
|
+
if (nodeEnv !== 'development' && !new URLSearchParams(location.search).has('sw-demo')) {
|
|
454
|
+
return '';
|
|
455
|
+
}
|
|
456
|
+
return html `
|
|
457
|
+
<div class="dev-info">
|
|
458
|
+
<div class="dev-info-title">${i18next.t('text.development info')}</div>
|
|
459
|
+
<dl>
|
|
460
|
+
<dt>NODE_ENV</dt><dd>${nodeEnv || '(unset)'}</dd>
|
|
461
|
+
<dt>APP-VERSION</dt><dd>${ENV['APP-VERSION']}</dd>
|
|
462
|
+
<dt>SHELL-VERSION</dt><dd>${ENV['SHELL-VERSION']}</dd>
|
|
463
|
+
<dt>SHELL-LICENSE</dt><dd>${ENV['SHELL-LICENSE']}</dd>
|
|
464
|
+
<dt>Service Worker</dt><dd>${this._swStatus}</dd>
|
|
465
|
+
<dt>User Agent</dt><dd>${navigator.userAgent}</dd>
|
|
466
|
+
</dl>
|
|
467
|
+
</div>
|
|
468
|
+
`;
|
|
469
|
+
}
|
|
152
470
|
onClickOpenSourceLicense(e) {
|
|
153
471
|
e.preventDefault();
|
|
154
472
|
e.stopPropagation();
|
|
@@ -168,6 +486,18 @@ let SystemInfo = class SystemInfo extends localize(i18next)(PageView) {
|
|
|
168
486
|
});
|
|
169
487
|
}
|
|
170
488
|
};
|
|
489
|
+
__decorate([
|
|
490
|
+
state(),
|
|
491
|
+
__metadata("design:type", String)
|
|
492
|
+
], SystemInfo.prototype, "_swStatus", void 0);
|
|
493
|
+
__decorate([
|
|
494
|
+
state(),
|
|
495
|
+
__metadata("design:type", Object)
|
|
496
|
+
], SystemInfo.prototype, "_swLastChecked", void 0);
|
|
497
|
+
__decorate([
|
|
498
|
+
state(),
|
|
499
|
+
__metadata("design:type", Boolean)
|
|
500
|
+
], SystemInfo.prototype, "_swChecking", void 0);
|
|
171
501
|
SystemInfo = __decorate([
|
|
172
502
|
customElement('system-info')
|
|
173
503
|
], SystemInfo);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"system-info.js","sourceRoot":"","sources":["../../client/pages/system-info.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\nimport '@material/web/button/text-button.js'\nimport '@operato/i18n/ox-i18n.js'\nimport '../viewparts/opensource-license'\nimport '../viewparts/license-info'\n\nimport { css, html } from 'lit'\nimport { customElement } from 'lit/decorators.js'\n\nimport { openHelp } from '@operato/help'\nimport { i18next, localize } from '@operato/i18n'\nimport { openPopup } from '@operato/layout'\nimport { PageView } from '@operato/shell'\n\nconst ENV = process.env\n\n@customElement('system-info')\nexport class SystemInfo extends localize(i18next)(PageView) {\n static styles = css`\n :host {\n display: block;\n position: relative;\n text-align: center;\n\n background-color: var(--system-info-background-color);\n padding: var(--system-info-content-padding);\n color: var(--system-info-color);\n font: var(--system-info-font);\n }\n\n :host * {\n vertical-align: middle;\n }\n\n #refresh-button {\n border-radius: var(--border-radius);\n border: 1px solid rgba(0, 0, 0, 0.1);\n\n text-transform: capitalize;\n }\n\n div {\n padding: 10px;\n }\n\n strong {\n font: var(--system-info-appname-font);\n color: var(--system-info-appname-color);\n }\n\n span {\n display: block;\n }\n\n span.description {\n padding: 3px 10px;\n font: var(--system-info-description-font);\n color: var(--system-info-description-color);\n }\n\n span.version {\n display: inline-block;\n border-radius: 5px;\n opacity: 0.8;\n background-color: var(--system-info-version-background-color);\n padding: 3px 10px;\n font: var(--system-info-version-font);\n color: var(--system-info-version-color);\n }\n\n .poweredBy {\n background-color: #efefef;\n position: absolute;\n border-top: 1px solid rgba(0, 0, 0, 0.1);\n padding: 7px 0;\n width: 100%;\n bottom: 0;\n text-align: center;\n }\n\n .poweredBy md-icon {\n opacity: var(--help-icon-opacity, 0.4);\n font-size: var(--help-icon-size, 18px);\n color: var(--help-icon-color);\n }\n\n .poweredBy md-icon:hover {\n cursor: help;\n opacity: var(--help-icon-hover-opacity, 1);\n color: var(--help-icon-hover-color);\n }\n `\n\n private _applicationMeta?: { icon?: string; title: string; description: string; copyright: string }\n\n get context() {\n return {\n title: i18next.t('label.about-system')\n }\n }\n\n get applicationMeta() {\n if (!this._applicationMeta) {\n var iconLink = document.querySelector('link[rel=\"application-icon\"]') as HTMLLinkElement\n var titleMeta = document.querySelector('meta[name=\"application-name\"]') as HTMLMetaElement\n var descriptionMeta = document.querySelector('meta[name=\"application-description\"]') as HTMLMetaElement\n var copyrightMeta = document.querySelector('meta[name=\"application-copyright\"]') as HTMLMetaElement\n\n this._applicationMeta = {\n icon: iconLink?.href,\n title: titleMeta ? titleMeta.content : 'Things Factory',\n description: descriptionMeta ? descriptionMeta.content : 'Reimagining Software',\n copyright: copyrightMeta ? copyrightMeta.content : 'Copyright © hatiolab.com. All Rights Reserved.\" />'\n }\n }\n\n return this._applicationMeta\n }\n\n render() {\n var { icon, title, description, copyright } = this.applicationMeta\n\n return html`\n <img src=${icon!} alt=\"system logo\" />\n\n <div>\n <strong>${title}</strong>\n <span class=\"description\">${description}</span>\n <span class=\"version\"\n ><ox-i18n msgid=\"field.version\"></ox-i18n> : ${ENV['APP-VERSION']}-${ENV['NODE-ENV']}</span\n >\n </div>\n\n <div>\n <md-text-button\n id=\"refresh-button\"\n @click=${e => {\n location.reload()\n }}\n >\n <md-icon>refresh</md-icon>\n ${String(i18next.t('label.system reload'))}\n </md-text-button>\n </div>\n\n <div>\n <span>${copyright}</span>\n\n <span\n >${title} is built on several\n <a href=\"#\" @click=${e => this.onClickOpenSourceLicense(e)}>open source software</a></span\n >\n <span>We are using the <a href=\"#\" @click=${e => this.onClickLicenseInfo(e)}>license</a> as permitted. </span>\n </div>\n\n <div class=\"poweredBy\">\n <span\n >Powered by ™Things Factory <ox-i18n msgid=\"field.version\"></ox-i18n> ${ENV['SHELL-VERSION']}\n <md-icon @click=${e => openHelp('about/things-factory')}>help</md-icon>\n </span>\n </div>\n `\n }\n\n onClickOpenSourceLicense(e) {\n e.preventDefault()\n e.stopPropagation()\n\n openPopup(html` <opensource-license></opensource-license> `, {\n backdrop: true,\n size: 'large',\n title: 'Open Source Licenses'\n })\n }\n\n onClickLicenseInfo(e) {\n e.preventDefault()\n e.stopPropagation()\n\n openPopup(html` <license-info></license-info> `, {\n backdrop: true,\n size: 'large',\n title: 'License Information'\n })\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"system-info.js","sourceRoot":"","sources":["../../client/pages/system-info.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\nimport '@material/web/button/text-button.js'\nimport '@operato/i18n/ox-i18n.js'\nimport '../viewparts/opensource-license'\nimport '../viewparts/license-info'\n\nimport { css, html } from 'lit'\nimport { customElement, state } from 'lit/decorators.js'\n\nimport { openHelp } from '@operato/help'\nimport { i18next, localize } from '@operato/i18n'\nimport { openPopup } from '@operato/layout'\nimport { PageView } from '@operato/shell'\n\nconst ENV = process.env\n\n/**\n * Service Worker 상태. 다운로드/설치 중인지, 적용 대기 중인지, 조용한 상태인지 구분.\n * - 'unsupported': 브라우저가 SW 미지원\n * - 'unregistered': 등록된 SW 없음 (보통 개발 모드)\n * - 'up-to-date' : active SW 만 존재, 새 버전 없음 (최신)\n * - 'downloading': 새 SW 가 installing 상태 (네트워크에서 파일 받아오는 중)\n * - 'installing' : installing→installed 전이 직전 (마무리)\n * - 'ready' : installed + 기존 controller 있음 → 활성 대기 중 (update 존재)\n */\ntype SwStatus = 'unsupported' | 'unregistered' | 'up-to-date' | 'downloading' | 'installing' | 'ready'\n\n@customElement('system-info')\nexport class SystemInfo extends localize(i18next)(PageView) {\n static styles = css`\n :host {\n display: block;\n position: relative;\n text-align: center;\n\n background-color: var(--system-info-background-color);\n padding: var(--system-info-content-padding);\n color: var(--system-info-color);\n font: var(--system-info-font);\n }\n\n :host * {\n vertical-align: middle;\n }\n\n #refresh-button {\n border-radius: var(--border-radius);\n border: 1px solid rgba(0, 0, 0, 0.1);\n\n text-transform: capitalize;\n }\n\n div {\n padding: 10px;\n }\n\n strong {\n font: var(--system-info-appname-font);\n color: var(--system-info-appname-color);\n }\n\n span {\n display: block;\n }\n\n span.description {\n padding: 3px 10px;\n font: var(--system-info-description-font);\n color: var(--system-info-description-color);\n }\n\n span.version {\n display: inline-block;\n border-radius: 5px;\n opacity: 0.8;\n background-color: var(--system-info-version-background-color);\n padding: 3px 10px;\n font: var(--system-info-version-font);\n color: var(--system-info-version-color);\n }\n\n .poweredBy {\n background-color: #efefef;\n position: absolute;\n border-top: 1px solid rgba(0, 0, 0, 0.1);\n padding: 7px 0;\n width: 100%;\n bottom: 0;\n text-align: center;\n }\n\n .poweredBy md-icon {\n opacity: var(--help-icon-opacity, 0.4);\n font-size: var(--help-icon-size, 18px);\n color: var(--help-icon-color);\n }\n\n .poweredBy md-icon:hover {\n cursor: help;\n opacity: var(--help-icon-hover-opacity, 1);\n color: var(--help-icon-hover-color);\n }\n\n /* Service Worker 상태 배너 */\n .sw-status {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n padding: 8px 14px;\n margin: 8px auto;\n border-radius: 6px;\n font-size: 13px;\n max-width: 420px;\n }\n .sw-status.downloading {\n background: var(--sw-status-download-bg, #fef3c7);\n color: var(--sw-status-download-color, #78350f);\n border: 1px solid var(--sw-status-download-border, #fbbf24);\n }\n .sw-status.ready {\n background: var(--sw-status-ready-bg, #dcfce7);\n color: var(--sw-status-ready-color, #14532d);\n border: 1px solid var(--sw-status-ready-border, #22c55e);\n }\n .sw-status.up-to-date {\n background: var(--sw-status-uptodate-bg, #f0fdf4);\n color: var(--sw-status-uptodate-color, #166534);\n border: 1px solid var(--sw-status-uptodate-border, #86efac);\n }\n .sw-status.info {\n background: var(--sw-status-info-bg, #eff6ff);\n color: var(--sw-status-info-color, #1e3a8a);\n border: 1px solid var(--sw-status-info-border, #93c5fd);\n }\n .sw-status-meta {\n font-size: 11px;\n opacity: 0.7;\n }\n .sw-check-btn {\n margin-left: auto;\n --md-text-button-label-text-size: 11px;\n }\n\n /* 개발 정보 블록 */\n .dev-info {\n max-width: 480px;\n margin: 18px auto;\n padding: 12px 16px;\n border: 1px dashed #cbd5e1;\n border-radius: 6px;\n text-align: left;\n background: rgba(148, 163, 184, 0.06);\n }\n .dev-info-title {\n font-size: 11px;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 1px;\n color: #64748b;\n margin-bottom: 8px;\n }\n .dev-info dl {\n display: grid;\n grid-template-columns: max-content 1fr;\n gap: 4px 12px;\n font-size: 12px;\n margin: 0;\n }\n .dev-info dt {\n color: #475569;\n font-weight: 600;\n }\n .dev-info dd {\n margin: 0;\n color: #0f172a;\n font-family: 'SF Mono', Consolas, monospace;\n word-break: break-all;\n }\n .sw-status md-icon {\n font-size: 18px;\n }\n .sw-status md-icon.spinning {\n animation: sw-spin 1.4s linear infinite;\n }\n @keyframes sw-spin {\n from { transform: rotate(0deg); }\n to { transform: rotate(360deg); }\n }\n `\n\n @state() private _swStatus: SwStatus = 'up-to-date'\n @state() private _swLastChecked: Date | null = null\n @state() private _swChecking: boolean = false\n\n private _applicationMeta?: { icon?: string; title: string; description: string; copyright: string }\n private _swRegistration: ServiceWorkerRegistration | null = null\n private _swStateChangeListener: (() => void) | null = null\n private _swUpdateFoundListener: (() => void) | null = null\n private _observedSW: ServiceWorker | null = null\n\n get context() {\n return {\n title: i18next.t('label.about-system')\n }\n }\n\n get applicationMeta() {\n if (!this._applicationMeta) {\n const iconLink = document.querySelector('link[rel=\"application-icon\"]') as HTMLLinkElement\n const titleMeta = document.querySelector('meta[name=\"application-name\"]') as HTMLMetaElement\n const descriptionMeta = document.querySelector('meta[name=\"application-description\"]') as HTMLMetaElement\n const copyrightMeta = document.querySelector('meta[name=\"application-copyright\"]') as HTMLMetaElement\n\n this._applicationMeta = {\n icon: iconLink?.href,\n title: titleMeta ? titleMeta.content : 'Things Factory',\n description: descriptionMeta ? descriptionMeta.content : 'Reimagining Software',\n copyright: copyrightMeta ? copyrightMeta.content : 'Copyright © hatiolab.com. All Rights Reserved.\" />'\n }\n }\n\n return this._applicationMeta\n }\n\n render() {\n const { icon, title, description, copyright } = this.applicationMeta\n\n return html`\n <img src=${icon!} alt=\"system logo\" />\n\n <div>\n <strong>${title}</strong>\n <span class=\"description\">${description}</span>\n <span class=\"version\"\n ><ox-i18n msgid=\"field.version\"></ox-i18n> : ${ENV['APP-VERSION']}-${ENV['NODE-ENV']}</span\n >\n </div>\n\n ${this._renderSwStatus()}\n\n <div>\n <md-text-button\n id=\"refresh-button\"\n @click=${() => this._applyUpdateOrReload()}\n >\n <md-icon>${this._swStatus === 'ready' ? 'system_update' : 'refresh'}</md-icon>\n ${String(\n this._swStatus === 'ready'\n ? i18next.t('label.apply update and reload')\n : i18next.t('label.system reload')\n )}\n </md-text-button>\n </div>\n\n <div>\n <span>${copyright}</span>\n\n <span\n >${title} is built on several\n <a href=\"#\" @click=${(e: Event) => this.onClickOpenSourceLicense(e)}>open source software</a></span\n >\n <span>We are using the <a href=\"#\" @click=${(e: Event) => this.onClickLicenseInfo(e)}>license</a> as permitted. </span>\n </div>\n\n ${this._renderDevInfo()}\n\n <div class=\"poweredBy\">\n <span\n >Powered by ™Things Factory <ox-i18n msgid=\"field.version\"></ox-i18n> ${ENV['SHELL-VERSION']}\n <md-icon @click=${() => openHelp('about/things-factory')}>help</md-icon>\n </span>\n </div>\n `\n }\n\n // ───────── Service Worker 상태 추적 ─────────\n\n connectedCallback() {\n super.connectedCallback()\n this._setupSwObserver()\n }\n\n disconnectedCallback() {\n super.disconnectedCallback()\n this._teardownSwObserver()\n }\n\n /**\n * SW registration 과 installing/waiting 상태를 관찰.\n * `updatefound` 이벤트로 새 SW 감지 → statechange 로 전이 추적.\n *\n * 개발 미리보기: URL 에 `?sw-demo=downloading` 또는 `?sw-demo=ready` 를\n * 붙이면 실제 SW 와 무관하게 해당 상태 배너를 고정 표시 (UI 검증용).\n */\n private async _setupSwObserver() {\n // dev 미리보기 쇼트컷\n const demo = new URLSearchParams(window.location.search).get('sw-demo')\n if (demo === 'downloading' || demo === 'installing' || demo === 'ready' || demo === 'up-to-date') {\n this._swStatus = demo as SwStatus\n this._swLastChecked = new Date()\n return\n }\n\n if (!('serviceWorker' in navigator)) {\n this._swStatus = 'unsupported'\n return\n }\n const reg = await navigator.serviceWorker.getRegistration()\n if (!reg) {\n this._swStatus = 'unregistered'\n return\n }\n this._swRegistration = reg\n\n // 이미 installing 중인 SW 가 있으면 즉시 관찰\n if (reg.installing) this._bindWorker(reg.installing)\n // 이미 waiting (설치 완료, 적용 대기) 이면 ready\n if (reg.waiting) this._swStatus = 'ready'\n\n // 새 SW 감지 핸들러\n this._swUpdateFoundListener = () => {\n if (reg.installing) this._bindWorker(reg.installing)\n }\n reg.addEventListener('updatefound', this._swUpdateFoundListener)\n\n // 아직 새 SW 가 없으면 \"up-to-date\" 로 표시\n if (!reg.installing && !reg.waiting) {\n this._swStatus = 'up-to-date'\n }\n\n // 강제 update check — 페이지 열린 시점의 최신 여부 확인\n await this._checkForUpdates()\n }\n\n /**\n * 수동 또는 초기 update check. 네트워크로 SW 파일 재확인 후\n * 새 버전이 있으면 `updatefound` 이벤트가 발생해 기존 리스너가 상태 전이.\n */\n private async _checkForUpdates() {\n if (!this._swRegistration) return\n this._swChecking = true\n try {\n await this._swRegistration.update()\n } catch {\n /* 네트워크 불가 등 — 조용히 무시 */\n } finally {\n this._swChecking = false\n this._swLastChecked = new Date()\n }\n }\n\n private _bindWorker(sw: ServiceWorker) {\n this._observedSW = sw\n this._updateStatusFromWorker(sw)\n\n this._swStateChangeListener = () => this._updateStatusFromWorker(sw)\n sw.addEventListener('statechange', this._swStateChangeListener)\n }\n\n private _updateStatusFromWorker(sw: ServiceWorker) {\n switch (sw.state) {\n case 'installing':\n this._swStatus = 'downloading'\n break\n case 'installed':\n // 기존 controller 가 있으면 새 버전 대기 상태 (업데이트), 없으면 최초 설치\n this._swStatus = navigator.serviceWorker.controller ? 'ready' : 'up-to-date'\n break\n case 'activating':\n this._swStatus = 'installing'\n break\n case 'activated':\n case 'redundant':\n this._swStatus = 'up-to-date'\n break\n }\n }\n\n private _teardownSwObserver() {\n if (this._observedSW && this._swStateChangeListener) {\n this._observedSW.removeEventListener('statechange', this._swStateChangeListener)\n }\n if (this._swRegistration && this._swUpdateFoundListener) {\n this._swRegistration.removeEventListener('updatefound', this._swUpdateFoundListener)\n }\n this._observedSW = null\n this._swRegistration = null\n this._swStateChangeListener = null\n this._swUpdateFoundListener = null\n }\n\n /**\n * 상태에 따라 적절한 액션:\n * - ready: waiting SW 에 SKIP_WAITING 메시지 → activate 유도 → reload\n * - 기타: 단순 location.reload()\n */\n private _applyUpdateOrReload() {\n if (this._swStatus === 'ready' && this._swRegistration?.waiting) {\n this._swRegistration.waiting.postMessage({ type: 'SKIP_WAITING' })\n navigator.serviceWorker.addEventListener(\n 'controllerchange',\n () => location.reload(),\n { once: true }\n )\n return\n }\n location.reload()\n }\n\n private _renderSwStatus() {\n const checkedAgo = this._swLastChecked\n ? this._formatAgo(this._swLastChecked)\n : null\n\n // 각 상태별 스타일/아이콘/메시지\n const statusConfig: Record<\n SwStatus,\n { tone: string; icon: string; spin?: boolean; msg: string }\n > = {\n downloading: {\n tone: 'downloading',\n icon: 'cached',\n spin: true,\n msg: i18next.t('text.new version downloading')\n },\n installing: {\n tone: 'downloading',\n icon: 'cached',\n spin: true,\n msg: i18next.t('text.new version installing')\n },\n ready: {\n tone: 'ready',\n icon: 'system_update',\n msg: i18next.t('text.new version ready to apply')\n },\n 'up-to-date': {\n tone: 'up-to-date',\n icon: 'check_circle',\n msg: i18next.t('text.you are on the latest version')\n },\n unregistered: {\n tone: 'info',\n icon: 'info',\n msg: i18next.t('text.service worker not registered (dev mode)')\n },\n unsupported: {\n tone: 'info',\n icon: 'info',\n msg: i18next.t('text.service worker not supported')\n }\n }\n const s = statusConfig[this._swStatus]\n\n return html`\n <div class=\"sw-status ${s.tone}\">\n <md-icon class=${s.spin ? 'spinning' : ''}>${s.icon}</md-icon>\n <span>${s.msg}</span>\n ${checkedAgo\n ? html`<span class=\"sw-status-meta\">· ${i18next.t('text.checked')} ${checkedAgo}</span>`\n : ''}\n ${this._swStatus === 'up-to-date' || this._swStatus === 'unregistered'\n ? html`\n <md-text-button\n class=\"sw-check-btn\"\n ?disabled=${this._swChecking}\n @click=${() => this._checkForUpdates()}\n >\n <md-icon>refresh</md-icon>\n ${String(i18next.t('label.check now'))}\n </md-text-button>\n `\n : ''}\n </div>\n `\n }\n\n /**\n * \"5초 전\" / \"2분 전\" 같은 상대 시간 문자열.\n */\n private _formatAgo(d: Date): string {\n const sec = Math.max(0, Math.floor((Date.now() - d.getTime()) / 1000))\n if (sec < 60) return `${sec}${i18next.t('text.seconds ago suffix')}`\n const min = Math.floor(sec / 60)\n if (min < 60) return `${min}${i18next.t('text.minutes ago suffix')}`\n const hr = Math.floor(min / 60)\n return `${hr}${i18next.t('text.hours ago suffix')}`\n }\n\n /**\n * 개발 정보 (NODE_ENV, SHELL-VERSION 등) — 개발 모드에서만 노출.\n */\n private _renderDevInfo() {\n const nodeEnv = (ENV['NODE-ENV'] || '').toString()\n if (nodeEnv !== 'development' && !new URLSearchParams(location.search).has('sw-demo')) {\n return ''\n }\n return html`\n <div class=\"dev-info\">\n <div class=\"dev-info-title\">${i18next.t('text.development info')}</div>\n <dl>\n <dt>NODE_ENV</dt><dd>${nodeEnv || '(unset)'}</dd>\n <dt>APP-VERSION</dt><dd>${ENV['APP-VERSION']}</dd>\n <dt>SHELL-VERSION</dt><dd>${ENV['SHELL-VERSION']}</dd>\n <dt>SHELL-LICENSE</dt><dd>${ENV['SHELL-LICENSE']}</dd>\n <dt>Service Worker</dt><dd>${this._swStatus}</dd>\n <dt>User Agent</dt><dd>${navigator.userAgent}</dd>\n </dl>\n </div>\n `\n }\n\n onClickOpenSourceLicense(e: Event) {\n e.preventDefault()\n e.stopPropagation()\n\n openPopup(html` <opensource-license></opensource-license> `, {\n backdrop: true,\n size: 'large',\n title: 'Open Source Licenses'\n })\n }\n\n onClickLicenseInfo(e: Event) {\n e.preventDefault()\n e.stopPropagation()\n\n openPopup(html` <license-info></license-info> `, {\n backdrop: true,\n size: 'large',\n title: 'License Information'\n })\n }\n}\n"]}
|
package/dist-client/route.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export default function route(page:
|
|
1
|
+
export default function route(page: string): "system" | undefined;
|
package/dist-client/route.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route.js","sourceRoot":"","sources":["../client/route.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,OAAO,UAAU,KAAK,CAAC,
|
|
1
|
+
{"version":3,"file":"route.js","sourceRoot":"","sources":["../client/route.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,OAAO,UAAU,KAAK,CAAC,IAAY;IACxC,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,QAAQ;YACX,MAAM,CAAC,qBAAqB,CAAC,CAAA;YAC7B,OAAO,IAAI,CAAA;IACf,CAAC;AACH,CAAC","sourcesContent":["export default function route(page: string) {\n switch (page) {\n case 'system':\n import('./pages/system-info')\n return page\n }\n}\n"]}
|