@things-factory/setting-ui 10.1.34 → 10.1.38
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/setting.d.ts +25 -0
- package/dist-client/pages/setting.js +80 -8
- package/dist-client/pages/setting.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -5
- package/translations/en.json +3 -1
- package/translations/ja.json +4 -2
- package/translations/ko.json +4 -2
- package/translations/ms.json +3 -1
- package/translations/zh.json +1 -1
|
@@ -3,6 +3,31 @@ declare const SettingPage_base: (new (...args: any[]) => import("lit").LitElemen
|
|
|
3
3
|
export declare class SettingPage extends SettingPage_base {
|
|
4
4
|
static styles: import("lit").CSSResult[];
|
|
5
5
|
private _settings;
|
|
6
|
+
private _tab;
|
|
7
|
+
/**
|
|
8
|
+
* 설정은 둘로 갈린다 — **내 것과 이 테넌트의 것.**
|
|
9
|
+
*
|
|
10
|
+
* 주소가 `/domain/<이곳>/setting` 인데 안에 있던 것은 전부 그 사람 브라우저 취향(테마 · 알림 ·
|
|
11
|
+
* MDI · 메뉴)이었다. 테넌트 단위로 정하는 것이 들어오면 둘이 섞여서, 사람은 자기 취향을 고치는
|
|
12
|
+
* 줄 알고 그 테넌트 전체를 바꾼다.
|
|
13
|
+
*
|
|
14
|
+
* ⚠ **이 화면은 모든 앱이 쓴다** — plant · twin · warehouse · figure.
|
|
15
|
+
*
|
|
16
|
+
* ── 부르는 말은 앱이 바꾼다 ────────────────────────────────────────────
|
|
17
|
+
* 기본은 **사업장**이다. 「도메인」은 보통 사람에게 웹 주소이고 「테넌트」는 업계 말이라 현장에서
|
|
18
|
+
* 안 읽힌다. 사용자가 겪는 것은 로그인해서 자기 회사·현장을 고르는 일이다.
|
|
19
|
+
*
|
|
20
|
+
* ⚠ **키는 뜻이고 값은 그 앱의 말이다.** 번역이 한 평면에 병합되고 마지막이 이기므로, 앱이
|
|
21
|
+
* `label.tenant-setting` 을 자기 번역에 적으면 그 말로 뜬다 — plant 는 「공장」, warehouse 는
|
|
22
|
+
* 「창고」로 둘 수 있다. 그래서 **기본값은 앱을 안 가리는 말**이어야 한다.
|
|
23
|
+
*
|
|
24
|
+
* ⚠ 로그인 화면은 아직 같은 것을 「도메인 선택」이라 부른다. 한 가지를 두 이름으로 부르는
|
|
25
|
+
* 것이라 언젠가 맞춰야 한다(2026-09-22).
|
|
26
|
+
*
|
|
27
|
+
* 등록하는 쪽이 `scope: 'tenant'` 라고 말하지 않으면 개인 것으로 본다 — 지금까지 붙은 것들이
|
|
28
|
+
* 그래서 그대로 돈다.
|
|
29
|
+
*/
|
|
30
|
+
private scopeOf;
|
|
6
31
|
render(): import("lit-html").TemplateResult<1>;
|
|
7
32
|
private _unsubscribe?;
|
|
8
33
|
connectedCallback(): void;
|
|
@@ -8,6 +8,7 @@ let SettingPage = class SettingPage extends localize(i18next)(PageView) {
|
|
|
8
8
|
constructor() {
|
|
9
9
|
super(...arguments);
|
|
10
10
|
this._settings = [];
|
|
11
|
+
this._tab = 'personal';
|
|
11
12
|
}
|
|
12
13
|
static { this.styles = [
|
|
13
14
|
DocumentPageStyles,
|
|
@@ -24,19 +25,86 @@ let SettingPage = class SettingPage extends localize(i18next)(PageView) {
|
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
/*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
28
|
+
* 탭은 page 안에 둔다. 밖에 두면 page 의 max-width 와 좌우 여백을 안 받아서 카드들과
|
|
29
|
+
* 기준선이 어긋난다 — 처음에 그렇게 만들었다.
|
|
29
30
|
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
31
|
+
* ── 모양은 밑줄이다 ─────────────────────────────────────────────────
|
|
32
|
+
* 이 제품의 탭은 대부분 밑줄이다 — board-ui 의 board-info, figure-ui 의 figure-side,
|
|
33
|
+
* ai-assistant 의 세션 탭. 알약으로 채우는 것은 성숙도 화면 하나뿐이고, 그것은 점수를 단
|
|
34
|
+
* 필터 조각이라 성격이 다르다.
|
|
35
|
+
*
|
|
36
|
+
* 고른 것은 on-surface 글자에 primary 밑줄, 안 고른 것은 on-surface-variant 에 투명 밑줄이다.
|
|
37
|
+
* 자리가 안 흔들리게 안 고른 쪽도 같은 두께의 투명 선을 든다.
|
|
32
38
|
*/
|
|
39
|
+
.tabs {
|
|
40
|
+
display: flex;
|
|
41
|
+
gap: var(--spacing-large, 16px);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.tabs button {
|
|
45
|
+
padding: var(--spacing-small, 4px) 0;
|
|
46
|
+
border: none;
|
|
47
|
+
border-bottom: 2px solid transparent;
|
|
48
|
+
background: none;
|
|
49
|
+
color: var(--md-sys-color-on-surface-variant);
|
|
50
|
+
font: inherit;
|
|
51
|
+
cursor: pointer;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.tabs button.on {
|
|
55
|
+
color: var(--md-sys-color-on-surface);
|
|
56
|
+
border-bottom-color: var(--md-sys-color-primary);
|
|
57
|
+
}
|
|
33
58
|
`
|
|
34
59
|
]; }
|
|
60
|
+
/**
|
|
61
|
+
* 설정은 둘로 갈린다 — **내 것과 이 테넌트의 것.**
|
|
62
|
+
*
|
|
63
|
+
* 주소가 `/domain/<이곳>/setting` 인데 안에 있던 것은 전부 그 사람 브라우저 취향(테마 · 알림 ·
|
|
64
|
+
* MDI · 메뉴)이었다. 테넌트 단위로 정하는 것이 들어오면 둘이 섞여서, 사람은 자기 취향을 고치는
|
|
65
|
+
* 줄 알고 그 테넌트 전체를 바꾼다.
|
|
66
|
+
*
|
|
67
|
+
* ⚠ **이 화면은 모든 앱이 쓴다** — plant · twin · warehouse · figure.
|
|
68
|
+
*
|
|
69
|
+
* ── 부르는 말은 앱이 바꾼다 ────────────────────────────────────────────
|
|
70
|
+
* 기본은 **사업장**이다. 「도메인」은 보통 사람에게 웹 주소이고 「테넌트」는 업계 말이라 현장에서
|
|
71
|
+
* 안 읽힌다. 사용자가 겪는 것은 로그인해서 자기 회사·현장을 고르는 일이다.
|
|
72
|
+
*
|
|
73
|
+
* ⚠ **키는 뜻이고 값은 그 앱의 말이다.** 번역이 한 평면에 병합되고 마지막이 이기므로, 앱이
|
|
74
|
+
* `label.tenant-setting` 을 자기 번역에 적으면 그 말로 뜬다 — plant 는 「공장」, warehouse 는
|
|
75
|
+
* 「창고」로 둘 수 있다. 그래서 **기본값은 앱을 안 가리는 말**이어야 한다.
|
|
76
|
+
*
|
|
77
|
+
* ⚠ 로그인 화면은 아직 같은 것을 「도메인 선택」이라 부른다. 한 가지를 두 이름으로 부르는
|
|
78
|
+
* 것이라 언젠가 맞춰야 한다(2026-09-22).
|
|
79
|
+
*
|
|
80
|
+
* 등록하는 쪽이 `scope: 'tenant'` 라고 말하지 않으면 개인 것으로 본다 — 지금까지 붙은 것들이
|
|
81
|
+
* 그래서 그대로 돈다.
|
|
82
|
+
*/
|
|
83
|
+
scopeOf(setting) {
|
|
84
|
+
return setting?.scope === 'tenant' ? 'tenant' : 'personal';
|
|
85
|
+
}
|
|
35
86
|
render() {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
87
|
+
const sorted = [...this._settings].sort((a, b) => a.seq - b.seq);
|
|
88
|
+
const personal = sorted.filter(one => this.scopeOf(one) === 'personal');
|
|
89
|
+
const forTenant = sorted.filter(one => this.scopeOf(one) === 'tenant');
|
|
90
|
+
/* 테넌트 설정이 하나도 없으면 탭을 안 만든다 — 탭 하나짜리 탭줄은 자리만 먹는다. */
|
|
91
|
+
if (!forTenant.length) {
|
|
92
|
+
return html `<div class="page">${personal.map(one => html `${one.template}`)}</div>`;
|
|
93
|
+
}
|
|
94
|
+
const showing = this._tab === 'tenant' ? forTenant : personal;
|
|
95
|
+
return html `
|
|
96
|
+
<div class="page">
|
|
97
|
+
<div class="tabs">
|
|
98
|
+
<button class=${this._tab === 'personal' ? 'on' : ''} @click=${() => (this._tab = 'personal')}>
|
|
99
|
+
${i18next.t('label.personal-setting')}
|
|
100
|
+
</button>
|
|
101
|
+
<button class=${this._tab === 'tenant' ? 'on' : ''} @click=${() => (this._tab = 'tenant')}>
|
|
102
|
+
${i18next.t('label.tenant-setting')}
|
|
103
|
+
</button>
|
|
104
|
+
</div>
|
|
105
|
+
${showing.map(one => html `${one.template}`)}
|
|
106
|
+
</div>
|
|
107
|
+
`;
|
|
40
108
|
}
|
|
41
109
|
connectedCallback() {
|
|
42
110
|
super.connectedCallback();
|
|
@@ -66,6 +134,10 @@ __decorate([
|
|
|
66
134
|
state(),
|
|
67
135
|
__metadata("design:type", Array)
|
|
68
136
|
], SettingPage.prototype, "_settings", void 0);
|
|
137
|
+
__decorate([
|
|
138
|
+
state(),
|
|
139
|
+
__metadata("design:type", String)
|
|
140
|
+
], SettingPage.prototype, "_tab", void 0);
|
|
69
141
|
SettingPage = __decorate([
|
|
70
142
|
customElement('setting-page')
|
|
71
143
|
], SettingPage);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setting.js","sourceRoot":"","sources":["../../client/pages/setting.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,KAAK,CAAA;AAC/B,OAAO,EAAE,aAAa,EAAmB,KAAK,EAAE,MAAM,mBAAmB,CAAA;AACzE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAChD,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACjD,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AAG7C,IAAM,WAAW,GAAjB,MAAM,WAAY,SAAQ,QAAQ,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC;IAArD;;
|
|
1
|
+
{"version":3,"file":"setting.js","sourceRoot":"","sources":["../../client/pages/setting.ts"],"names":[],"mappings":"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","sourcesContent":["import { html, css } from 'lit'\nimport { customElement, property, query, state } from 'lit/decorators.js'\nimport { store, PageView } from '@operato/shell'\nimport { i18next, localize } from '@operato/i18n'\nimport { DocumentPageStyles } from '@operato/styles'\n\n@customElement('setting-page')\nexport class SettingPage extends localize(i18next)(PageView) {\n static styles = [\n DocumentPageStyles,\n css`\n /*\n * Settings are read and edited, so this is a document (ADR-0060 decision 8): one column, a\n * readable width, and the spacing between cards decided by the frame.\n *\n * The card's own outer margin goes to zero so that spacing is decided in one place. With both\n * alive, every screen that holds cards comes out with a different gap.\n */\n :host {\n --setting-let-margin: 0;\n }\n\n /*\n * 탭은 page 안에 둔다. 밖에 두면 page 의 max-width 와 좌우 여백을 안 받아서 카드들과\n * 기준선이 어긋난다 — 처음에 그렇게 만들었다.\n *\n * ── 모양은 밑줄이다 ─────────────────────────────────────────────────\n * 이 제품의 탭은 대부분 밑줄이다 — board-ui 의 board-info, figure-ui 의 figure-side,\n * ai-assistant 의 세션 탭. 알약으로 채우는 것은 성숙도 화면 하나뿐이고, 그것은 점수를 단\n * 필터 조각이라 성격이 다르다.\n *\n * 고른 것은 on-surface 글자에 primary 밑줄, 안 고른 것은 on-surface-variant 에 투명 밑줄이다.\n * 자리가 안 흔들리게 안 고른 쪽도 같은 두께의 투명 선을 든다.\n */\n .tabs {\n display: flex;\n gap: var(--spacing-large, 16px);\n }\n\n .tabs button {\n padding: var(--spacing-small, 4px) 0;\n border: none;\n border-bottom: 2px solid transparent;\n background: none;\n color: var(--md-sys-color-on-surface-variant);\n font: inherit;\n cursor: pointer;\n }\n\n .tabs button.on {\n color: var(--md-sys-color-on-surface);\n border-bottom-color: var(--md-sys-color-primary);\n }\n `\n ]\n\n @state() private _settings: any[] = []\n @state() private _tab: 'personal' | 'tenant' = 'personal'\n\n /**\n * 설정은 둘로 갈린다 — **내 것과 이 테넌트의 것.**\n *\n * 주소가 `/domain/<이곳>/setting` 인데 안에 있던 것은 전부 그 사람 브라우저 취향(테마 · 알림 ·\n * MDI · 메뉴)이었다. 테넌트 단위로 정하는 것이 들어오면 둘이 섞여서, 사람은 자기 취향을 고치는\n * 줄 알고 그 테넌트 전체를 바꾼다.\n *\n * ⚠ **이 화면은 모든 앱이 쓴다** — plant · twin · warehouse · figure.\n *\n * ── 부르는 말은 앱이 바꾼다 ────────────────────────────────────────────\n * 기본은 **사업장**이다. 「도메인」은 보통 사람에게 웹 주소이고 「테넌트」는 업계 말이라 현장에서\n * 안 읽힌다. 사용자가 겪는 것은 로그인해서 자기 회사·현장을 고르는 일이다.\n *\n * ⚠ **키는 뜻이고 값은 그 앱의 말이다.** 번역이 한 평면에 병합되고 마지막이 이기므로, 앱이\n * `label.tenant-setting` 을 자기 번역에 적으면 그 말로 뜬다 — plant 는 「공장」, warehouse 는\n * 「창고」로 둘 수 있다. 그래서 **기본값은 앱을 안 가리는 말**이어야 한다.\n *\n * ⚠ 로그인 화면은 아직 같은 것을 「도메인 선택」이라 부른다. 한 가지를 두 이름으로 부르는\n * 것이라 언젠가 맞춰야 한다(2026-09-22).\n *\n * 등록하는 쪽이 `scope: 'tenant'` 라고 말하지 않으면 개인 것으로 본다 — 지금까지 붙은 것들이\n * 그래서 그대로 돈다.\n */\n private scopeOf(setting: any): 'personal' | 'tenant' {\n return setting?.scope === 'tenant' ? 'tenant' : 'personal'\n }\n\n render() {\n const sorted = [...this._settings].sort((a, b) => a.seq - b.seq)\n const personal = sorted.filter(one => this.scopeOf(one) === 'personal')\n const forTenant = sorted.filter(one => this.scopeOf(one) === 'tenant')\n\n /* 테넌트 설정이 하나도 없으면 탭을 안 만든다 — 탭 하나짜리 탭줄은 자리만 먹는다. */\n if (!forTenant.length) {\n return html`<div class=\"page\">${personal.map(one => html`${one.template}`)}</div>`\n }\n\n const showing = this._tab === 'tenant' ? forTenant : personal\n\n return html`\n <div class=\"page\">\n <div class=\"tabs\">\n <button class=${this._tab === 'personal' ? 'on' : ''} @click=${() => (this._tab = 'personal')}>\n ${i18next.t('label.personal-setting')}\n </button>\n <button class=${this._tab === 'tenant' ? 'on' : ''} @click=${() => (this._tab = 'tenant')}>\n ${i18next.t('label.tenant-setting')}\n </button>\n </div>\n ${showing.map(one => html`${one.template}`)}\n </div>\n `\n }\n\n private _unsubscribe?: () => void\n\n connectedCallback() {\n super.connectedCallback()\n\n this._applyState()\n\n this._unsubscribe = store.subscribe(() => this._applyState())\n }\n\n disconnectedCallback() {\n super.disconnectedCallback()\n\n this._unsubscribe?.()\n }\n\n private _applyState() {\n const __state__ = store.getState() as any\n this._settings = __state__.setting.settings\n }\n\n get context() {\n return {\n /*\n * 이 화면이 어떤 틀인가 — 동작 줄이 이것을 읽고 자기 오른쪽 끝을 내용에 맞춘다\n * (ADR-0060 결정 9 보탬). 안 적으면 창 폭이고, 그것은 작업면의 모양이다.\n */\n frame: 'document',\ntitle: i18next.t('title.setting')\n }\n }\n}\n"]}
|