@things-factory/auth-ui 10.1.4 → 10.1.7
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/components/change-password.d.ts +11 -0
- package/dist-client/components/change-password.js +103 -6
- package/dist-client/components/change-password.js.map +1 -1
- package/dist-client/components/role-privilege-picker.d.ts +37 -0
- package/dist-client/components/role-privilege-picker.js +58 -0
- package/dist-client/components/role-privilege-picker.js.map +1 -0
- package/dist-client/components/role-template-picker.d.ts +83 -0
- package/dist-client/components/role-template-picker.js +487 -0
- package/dist-client/components/role-template-picker.js.map +1 -0
- package/dist-client/components/user-detail.d.ts +151 -0
- package/dist-client/components/user-detail.js +631 -0
- package/dist-client/components/user-detail.js.map +1 -0
- package/dist-client/components/user-invite.d.ts +80 -0
- package/dist-client/components/user-invite.js +422 -0
- package/dist-client/components/user-invite.js.map +1 -0
- package/dist-client/entries/auth/activate.d.ts +0 -1
- package/dist-client/entries/auth/activate.js +0 -8
- package/dist-client/entries/auth/activate.js.map +1 -1
- package/dist-client/pages/role/role-list-page.d.ts +203 -0
- package/dist-client/pages/role/role-list-page.js +838 -0
- package/dist-client/pages/role/role-list-page.js.map +1 -0
- package/dist-client/pages/role/role-privilege-page.d.ts +181 -0
- package/dist-client/pages/role/role-privilege-page.js +980 -0
- package/dist-client/pages/role/role-privilege-page.js.map +1 -0
- package/dist-client/pages/user/user-list-page.d.ts +88 -0
- package/dist-client/pages/user/user-list-page.js +474 -0
- package/dist-client/pages/user/user-list-page.js.map +1 -0
- package/dist-client/route.js +5 -2
- package/dist-client/route.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/things-factory.config.js +3 -2
- package/translations/en.json +72 -3
- package/translations/ja.json +14 -3
- package/translations/ko.json +73 -4
- package/translations/ms.json +15 -4
- package/translations/zh.json +14 -3
- package/dist-client/components/contact-us.d.ts +0 -20
- package/dist-client/components/contact-us.js +0 -121
- package/dist-client/components/contact-us.js.map +0 -1
- package/dist-client/components/create-role.d.ts +0 -1
- package/dist-client/components/create-role.js +0 -117
- package/dist-client/components/create-role.js.map +0 -1
- package/dist-client/components/credential-manager.d.ts +0 -11
- package/dist-client/components/credential-manager.js +0 -64
- package/dist-client/components/credential-manager.js.map +0 -1
- package/dist-client/components/role-edit-form.d.ts +0 -1
- package/dist-client/components/role-edit-form.js +0 -95
- package/dist-client/components/role-edit-form.js.map +0 -1
- package/dist-client/components/role-privilege-editor.d.ts +0 -1
- package/dist-client/components/role-privilege-editor.js +0 -368
- package/dist-client/components/role-privilege-editor.js.map +0 -1
- package/dist-client/pages/role/role-management.d.ts +0 -4
- package/dist-client/pages/role/role-management.js +0 -137
- package/dist-client/pages/role/role-management.js.map +0 -1
- package/dist-client/pages/user/user-management.d.ts +0 -35
- package/dist-client/pages/user/user-management.js +0 -223
- package/dist-client/pages/user/user-management.js.map +0 -1
|
@@ -14,6 +14,17 @@ export declare class ChangePassword extends ChangePassword_base {
|
|
|
14
14
|
looseCharacterLength?: number;
|
|
15
15
|
};
|
|
16
16
|
form: HTMLFormElement;
|
|
17
|
+
/**
|
|
18
|
+
* Set once the change went through.
|
|
19
|
+
*
|
|
20
|
+
* The server answers with a message and the shell shows it as a toast, but a toast leaves and
|
|
21
|
+
* the same three empty boxes stay behind. A person who has just changed their password reads
|
|
22
|
+
* that as "nothing happened" — the report that brought this in said exactly that.
|
|
23
|
+
*
|
|
24
|
+
* So the form is replaced by a statement that stays. It also stops the boxes from inviting a
|
|
25
|
+
* second attempt at something already done.
|
|
26
|
+
*/
|
|
27
|
+
private changed;
|
|
17
28
|
private passwordPattern;
|
|
18
29
|
private passwordHelp;
|
|
19
30
|
render(): import("lit-html").TemplateResult<1>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { __decorate, __metadata } from "tslib";
|
|
2
2
|
import { css, html, LitElement } from 'lit';
|
|
3
|
-
import { customElement, property, query } from 'lit/decorators.js';
|
|
3
|
+
import { customElement, property, query, state } from 'lit/decorators.js';
|
|
4
4
|
import { i18next, localize } from '@operato/i18n';
|
|
5
5
|
import { FormFieldStyles } from '@operato/styles';
|
|
6
6
|
import { auth } from '@things-factory/auth-base/dist-client/auth.js';
|
|
@@ -9,6 +9,17 @@ let ChangePassword = class ChangePassword extends localize(i18next)(LitElement)
|
|
|
9
9
|
constructor() {
|
|
10
10
|
super(...arguments);
|
|
11
11
|
this.passwordRule = {};
|
|
12
|
+
/**
|
|
13
|
+
* Set once the change went through.
|
|
14
|
+
*
|
|
15
|
+
* The server answers with a message and the shell shows it as a toast, but a toast leaves and
|
|
16
|
+
* the same three empty boxes stay behind. A person who has just changed their password reads
|
|
17
|
+
* that as "nothing happened" — the report that brought this in said exactly that.
|
|
18
|
+
*
|
|
19
|
+
* So the form is replaced by a statement that stays. It also stops the boxes from inviting a
|
|
20
|
+
* second attempt at something already done.
|
|
21
|
+
*/
|
|
22
|
+
this.changed = false;
|
|
12
23
|
this.passwordPattern = '';
|
|
13
24
|
this.passwordHelp = '';
|
|
14
25
|
}
|
|
@@ -73,6 +84,40 @@ let ChangePassword = class ChangePassword extends localize(i18next)(LitElement)
|
|
|
73
84
|
}
|
|
74
85
|
|
|
75
86
|
/* 비밀번호 규칙 안내문 — 본문보다 낮은 강조. 라이트/다크 모두 읽히도록 토큰 파생. */
|
|
87
|
+
/* The statement that replaces the form once the change took. */
|
|
88
|
+
.changed {
|
|
89
|
+
display: flex;
|
|
90
|
+
align-items: center;
|
|
91
|
+
gap: 8px;
|
|
92
|
+
/*
|
|
93
|
+
* 같은 여백을 위쪽 인풋과 나눠 쓴다. 폼 상태에서는 첫 인풋의 margin 이 「비밀번호」
|
|
94
|
+
* 라벨과의 간격을 만드는데, 이 문장은 margin 이 없어 라벨에 붙어 있었다 — 같은 자리에
|
|
95
|
+
* 두 가지 간격이 생겼다. 값을 새로 정하지 않고 인풋과 같은 토큰을 쓴다.
|
|
96
|
+
*/
|
|
97
|
+
margin: var(--change-password-field-margin, var(--spacing-small) 0);
|
|
98
|
+
padding: 12px 14px;
|
|
99
|
+
border: 1px solid var(--md-sys-color-outline-variant);
|
|
100
|
+
border-left: 3px solid var(--md-sys-color-primary);
|
|
101
|
+
border-radius: 7px;
|
|
102
|
+
background: var(--md-sys-color-surface-variant);
|
|
103
|
+
color: var(--md-sys-color-on-surface-variant);
|
|
104
|
+
line-height: 1.5;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.changed md-icon {
|
|
108
|
+
color: var(--md-sys-color-primary);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.changed button {
|
|
112
|
+
margin-left: auto;
|
|
113
|
+
border: 1px solid var(--md-sys-color-outline-variant);
|
|
114
|
+
background: transparent;
|
|
115
|
+
color: inherit;
|
|
116
|
+
border-radius: 7px;
|
|
117
|
+
padding: 5px 11px;
|
|
118
|
+
cursor: pointer;
|
|
119
|
+
}
|
|
120
|
+
|
|
76
121
|
.helper-text {
|
|
77
122
|
font-size: 12px;
|
|
78
123
|
color: var(--md-sys-color-on-surface-variant);
|
|
@@ -83,12 +128,32 @@ let ChangePassword = class ChangePassword extends localize(i18next)(LitElement)
|
|
|
83
128
|
`
|
|
84
129
|
]; }
|
|
85
130
|
render() {
|
|
131
|
+
if (this.changed) {
|
|
132
|
+
return html `
|
|
133
|
+
<div class="changed" role="status">
|
|
134
|
+
<md-icon>check_circle</md-icon>
|
|
135
|
+
<span>${i18next.t('text.password changed successfully')}</span>
|
|
136
|
+
<button type="button" @click=${() => (this.changed = false)}>
|
|
137
|
+
${i18next.t('text.change password')}
|
|
138
|
+
</button>
|
|
139
|
+
</div>
|
|
140
|
+
`;
|
|
141
|
+
}
|
|
86
142
|
return html `
|
|
87
143
|
<form>
|
|
88
144
|
<div class="field">
|
|
89
145
|
<input type="password" name="current_pass" placeholder=${i18next.t('text.current password')} required />
|
|
90
146
|
</div>
|
|
91
|
-
|
|
147
|
+
<!--
|
|
148
|
+
The rule belongs under the field the rule applies to.
|
|
149
|
+
|
|
150
|
+
It used to sit between the two, directly beneath the current password, so a sighted
|
|
151
|
+
reader took it as the rule for that field — and the current password has no reason to
|
|
152
|
+
satisfy today's rule. It may predate it. The server only checks that it matches
|
|
153
|
+
(change-pwd.ts verifies the current one and validates the new one against the rule).
|
|
154
|
+
aria-describedby always pointed at the new field, so a screen reader had it right and
|
|
155
|
+
only the eye was misled.
|
|
156
|
+
-->
|
|
92
157
|
<div class="field">
|
|
93
158
|
<input
|
|
94
159
|
type="password"
|
|
@@ -99,11 +164,25 @@ let ChangePassword = class ChangePassword extends localize(i18next)(LitElement)
|
|
|
99
164
|
aria-describedby="password-helper"
|
|
100
165
|
/>
|
|
101
166
|
</div>
|
|
167
|
+
<span id="password-helper" class="helper-text">${this.passwordHelp}</span>
|
|
102
168
|
<div class="field">
|
|
103
169
|
<input type="password" name="confirm_pass" placeholder=${i18next.t('text.confirm password')} required />
|
|
104
170
|
</div>
|
|
105
171
|
|
|
106
|
-
|
|
172
|
+
<!--
|
|
173
|
+
type="button", the way abstract-sign and abstract-auth-page already declare it.
|
|
174
|
+
|
|
175
|
+
A Material button inside a form defaults to type="submit" and submits unless the click
|
|
176
|
+
is default-prevented. So one click ran submit() and then let the browser submit the
|
|
177
|
+
form as well. submit() had already called form.reset(), so constraint validation found
|
|
178
|
+
three empty required fields, rejected the first one, and put the caret back on the
|
|
179
|
+
current password with an English "Please fill out this field." bubble on a Korean
|
|
180
|
+
screen. It read as the current password being refused.
|
|
181
|
+
[확인함] 2026-09-09 · 사용자 보고 · 화면 사진
|
|
182
|
+
-->
|
|
183
|
+
<md-elevated-button type="button" @click=${this.submit.bind(this)}
|
|
184
|
+
>${i18next.t('text.change password')}</md-elevated-button
|
|
185
|
+
>
|
|
107
186
|
</form>
|
|
108
187
|
`;
|
|
109
188
|
}
|
|
@@ -130,8 +209,11 @@ let ChangePassword = class ChangePassword extends localize(i18next)(LitElement)
|
|
|
130
209
|
if (params['new_pass'] !== params['confirm_pass']) {
|
|
131
210
|
return this.showToast(i18next.t('error.new-password-and-confirm-password-do-not-match'));
|
|
132
211
|
}
|
|
133
|
-
|
|
134
|
-
|
|
212
|
+
/* Clear only once it took — a refused change used to wipe all three fields. */
|
|
213
|
+
if (await auth.changePassword(params)) {
|
|
214
|
+
this.form.reset();
|
|
215
|
+
this.changed = true;
|
|
216
|
+
}
|
|
135
217
|
}
|
|
136
218
|
showToast(message) {
|
|
137
219
|
document.dispatchEvent(new CustomEvent('notify', { detail: { message } }));
|
|
@@ -143,8 +225,23 @@ __decorate([
|
|
|
143
225
|
], ChangePassword.prototype, "passwordRule", void 0);
|
|
144
226
|
__decorate([
|
|
145
227
|
query('form'),
|
|
146
|
-
__metadata("design:type", HTMLFormElement
|
|
228
|
+
__metadata("design:type", HTMLFormElement
|
|
229
|
+
/**
|
|
230
|
+
* Set once the change went through.
|
|
231
|
+
*
|
|
232
|
+
* The server answers with a message and the shell shows it as a toast, but a toast leaves and
|
|
233
|
+
* the same three empty boxes stay behind. A person who has just changed their password reads
|
|
234
|
+
* that as "nothing happened" — the report that brought this in said exactly that.
|
|
235
|
+
*
|
|
236
|
+
* So the form is replaced by a statement that stays. It also stops the boxes from inviting a
|
|
237
|
+
* second attempt at something already done.
|
|
238
|
+
*/
|
|
239
|
+
)
|
|
147
240
|
], ChangePassword.prototype, "form", void 0);
|
|
241
|
+
__decorate([
|
|
242
|
+
state(),
|
|
243
|
+
__metadata("design:type", Object)
|
|
244
|
+
], ChangePassword.prototype, "changed", void 0);
|
|
148
245
|
ChangePassword = __decorate([
|
|
149
246
|
customElement('change-password')
|
|
150
247
|
], ChangePassword);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"change-password.js","sourceRoot":"","sources":["../../client/components/change-password.ts"],"names":[],"mappings":"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","sourcesContent":["import { css, html, LitElement, PropertyValues } from 'lit'\nimport { customElement, property, query } from 'lit/decorators.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport { FormFieldStyles } from '@operato/styles'\nimport { auth } from '@things-factory/auth-base/dist-client/auth.js'\nimport { generatePasswordPatternHelp, generatePasswordPatternRegExp } from '../utils/password-rule.js'\n\n@customElement('change-password')\nexport class ChangePassword extends localize(i18next)(LitElement) {\n static styles = [\n FormFieldStyles,\n css`\n * {\n box-sizing: border-box;\n }\n\n *:focus {\n outline: none;\n }\n\n form {\n display: flex;\n flex-direction: column;\n }\n\n /*\n * 색·테두리·모서리·포커스·placeholder 색은 FormFieldStyles 가 담당한다.\n * 여기서는 이 화면의 치수만 둔다 — 같은 프로필 화면의 위쪽 인풋(profile-component)과\n * 같은 값을 써서 한 화면에 두 가지 인상이 생기지 않게 한다.\n */\n input {\n border-radius: var(--change-password-field-border-radius, var(--border-radius));\n margin: var(--change-password-field-margin, var(--spacing-small) 0);\n padding: var(--change-password-field-padding, 9px);\n font: var(--change-password-field-font, var(--auth-input-field-font));\n width: var(--change-password-field-width, var(--auth-input-field-width));\n }\n\n ::placeholder {\n /*\n * 크기를 줄이지 않는다. 이전에는 0.8rem 이라 위쪽 인풋의 값(16px)보다 작게 보여\n * 같은 화면에서 패스워드 칸만 다른 스타일처럼 느껴졌다.\n */\n text-transform: capitalize;\n }\n\n md-elevated-button {\n margin: var(--spacing-small) auto var(--spacing-medium) auto;\n text-transform: capitalize;\n }\n\n button {\n /* fallback 도 테마 토큰으로 — 하드코딩 색은 다크에서 어긋난다 */\n background-color: var(--secondary-color, var(--md-sys-color-secondary));\n margin: 2px 2px 10px 2px;\n height: var(--button-height, 28px);\n color: var(--button-color, var(--md-sys-color-on-secondary));\n font: var(--button-font);\n border-radius: var(--button-radius, var(--md-sys-shape-corner-small, 5px));\n border: var(--button-border, 1px solid transparent);\n line-height: 1.5;\n }\n\n button:hover,\n button:active {\n background-color: var(--button-active-background-color, var(--md-sys-color-primary));\n border: var(--button-active-border);\n }\n\n /* 비밀번호 규칙 안내문 — 본문보다 낮은 강조. 라이트/다크 모두 읽히도록 토큰 파생. */\n .helper-text {\n font-size: 12px;\n color: var(--md-sys-color-on-surface-variant);\n margin-top: 4px;\n display: block; /* 텍스트를 입력 필드 아래에 배치 */\n line-height: 1.5; /* 텍스트 줄 간격 조절 */\n }\n `\n ]\n\n @property({ type: Object }) passwordRule: {\n lowerCase?: boolean\n upperCase?: boolean\n digit?: boolean\n specialCharacter?: boolean\n allowRepeat?: boolean\n useTightPattern?: boolean\n useLoosePattern?: boolean\n tightCharacterLength?: number\n looseCharacterLength?: number\n } = {}\n\n @query('form') form!: HTMLFormElement\n\n private passwordPattern: string = ''\n private passwordHelp: string = ''\n\n render() {\n return html`\n <form>\n <div class=\"field\">\n <input type=\"password\" name=\"current_pass\" placeholder=${i18next.t('text.current password')} required />\n </div>\n <span id=\"password-helper\" class=\"helper-text\">${this.passwordHelp}</span>\n <div class=\"field\">\n <input\n type=\"password\"\n name=\"new_pass\"\n placeholder=${i18next.t('text.new password')}\n required\n pattern=${this.passwordPattern}\n aria-describedby=\"password-helper\"\n />\n </div>\n <div class=\"field\">\n <input type=\"password\" name=\"confirm_pass\" placeholder=${i18next.t('text.confirm password')} required />\n </div>\n\n <md-elevated-button @click=${this.submit.bind(this)}>${i18next.t('text.change password')}</md-elevated-button>\n </form>\n `\n }\n\n updated(changes: PropertyValues<this>) {\n if (changes.has('passwordRule')) {\n this.passwordPattern = generatePasswordPatternRegExp(this.passwordRule).source\n this.passwordHelp = generatePasswordPatternHelp(this.passwordRule)\n }\n }\n\n languageUpdated() {\n this.passwordPattern = generatePasswordPatternRegExp(this.passwordRule).source\n this.passwordHelp = generatePasswordPatternHelp(this.passwordRule)\n }\n\n async submit() {\n const formData = new FormData(this.form)\n let params = {}\n for (const [key, value] of formData.entries()) {\n if (!value) {\n const placeholder = (this.form.querySelector(`[name=${key}]`) as HTMLInputElement)?.placeholder\n return this.showToast(i18next.t('error.value is empty', { value: placeholder || key }))\n }\n params[key] = value\n }\n\n if (params['new_pass'] !== params['confirm_pass']) {\n return this.showToast(i18next.t('error.new-password-and-confirm-password-do-not-match'))\n }\n\n auth.changePassword(params)\n this.form.reset()\n }\n\n showToast(message) {\n document.dispatchEvent(new CustomEvent('notify', { detail: { message } }))\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"change-password.js","sourceRoot":"","sources":["../../client/components/change-password.ts"],"names":[],"mappings":"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","sourcesContent":["import { css, html, LitElement, PropertyValues } from 'lit'\nimport { customElement, property, query, state } from 'lit/decorators.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport { FormFieldStyles } from '@operato/styles'\nimport { auth } from '@things-factory/auth-base/dist-client/auth.js'\nimport { generatePasswordPatternHelp, generatePasswordPatternRegExp } from '../utils/password-rule.js'\n\n@customElement('change-password')\nexport class ChangePassword extends localize(i18next)(LitElement) {\n static styles = [\n FormFieldStyles,\n css`\n * {\n box-sizing: border-box;\n }\n\n *:focus {\n outline: none;\n }\n\n form {\n display: flex;\n flex-direction: column;\n }\n\n /*\n * 색·테두리·모서리·포커스·placeholder 색은 FormFieldStyles 가 담당한다.\n * 여기서는 이 화면의 치수만 둔다 — 같은 프로필 화면의 위쪽 인풋(profile-component)과\n * 같은 값을 써서 한 화면에 두 가지 인상이 생기지 않게 한다.\n */\n input {\n border-radius: var(--change-password-field-border-radius, var(--border-radius));\n margin: var(--change-password-field-margin, var(--spacing-small) 0);\n padding: var(--change-password-field-padding, 9px);\n font: var(--change-password-field-font, var(--auth-input-field-font));\n width: var(--change-password-field-width, var(--auth-input-field-width));\n }\n\n ::placeholder {\n /*\n * 크기를 줄이지 않는다. 이전에는 0.8rem 이라 위쪽 인풋의 값(16px)보다 작게 보여\n * 같은 화면에서 패스워드 칸만 다른 스타일처럼 느껴졌다.\n */\n text-transform: capitalize;\n }\n\n md-elevated-button {\n margin: var(--spacing-small) auto var(--spacing-medium) auto;\n text-transform: capitalize;\n }\n\n button {\n /* fallback 도 테마 토큰으로 — 하드코딩 색은 다크에서 어긋난다 */\n background-color: var(--secondary-color, var(--md-sys-color-secondary));\n margin: 2px 2px 10px 2px;\n height: var(--button-height, 28px);\n color: var(--button-color, var(--md-sys-color-on-secondary));\n font: var(--button-font);\n border-radius: var(--button-radius, var(--md-sys-shape-corner-small, 5px));\n border: var(--button-border, 1px solid transparent);\n line-height: 1.5;\n }\n\n button:hover,\n button:active {\n background-color: var(--button-active-background-color, var(--md-sys-color-primary));\n border: var(--button-active-border);\n }\n\n /* 비밀번호 규칙 안내문 — 본문보다 낮은 강조. 라이트/다크 모두 읽히도록 토큰 파생. */\n /* The statement that replaces the form once the change took. */\n .changed {\n display: flex;\n align-items: center;\n gap: 8px;\n /*\n * 같은 여백을 위쪽 인풋과 나눠 쓴다. 폼 상태에서는 첫 인풋의 margin 이 「비밀번호」\n * 라벨과의 간격을 만드는데, 이 문장은 margin 이 없어 라벨에 붙어 있었다 — 같은 자리에\n * 두 가지 간격이 생겼다. 값을 새로 정하지 않고 인풋과 같은 토큰을 쓴다.\n */\n margin: var(--change-password-field-margin, var(--spacing-small) 0);\n padding: 12px 14px;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-left: 3px solid var(--md-sys-color-primary);\n border-radius: 7px;\n background: var(--md-sys-color-surface-variant);\n color: var(--md-sys-color-on-surface-variant);\n line-height: 1.5;\n }\n\n .changed md-icon {\n color: var(--md-sys-color-primary);\n }\n\n .changed button {\n margin-left: auto;\n border: 1px solid var(--md-sys-color-outline-variant);\n background: transparent;\n color: inherit;\n border-radius: 7px;\n padding: 5px 11px;\n cursor: pointer;\n }\n\n .helper-text {\n font-size: 12px;\n color: var(--md-sys-color-on-surface-variant);\n margin-top: 4px;\n display: block; /* 텍스트를 입력 필드 아래에 배치 */\n line-height: 1.5; /* 텍스트 줄 간격 조절 */\n }\n `\n ]\n\n @property({ type: Object }) passwordRule: {\n lowerCase?: boolean\n upperCase?: boolean\n digit?: boolean\n specialCharacter?: boolean\n allowRepeat?: boolean\n useTightPattern?: boolean\n useLoosePattern?: boolean\n tightCharacterLength?: number\n looseCharacterLength?: number\n } = {}\n\n @query('form') form!: HTMLFormElement\n\n /**\n * Set once the change went through.\n *\n * The server answers with a message and the shell shows it as a toast, but a toast leaves and\n * the same three empty boxes stay behind. A person who has just changed their password reads\n * that as \"nothing happened\" — the report that brought this in said exactly that.\n *\n * So the form is replaced by a statement that stays. It also stops the boxes from inviting a\n * second attempt at something already done.\n */\n @state() private changed = false\n\n private passwordPattern: string = ''\n private passwordHelp: string = ''\n\n render() {\n if (this.changed) {\n return html`\n <div class=\"changed\" role=\"status\">\n <md-icon>check_circle</md-icon>\n <span>${i18next.t('text.password changed successfully')}</span>\n <button type=\"button\" @click=${() => (this.changed = false)}>\n ${i18next.t('text.change password')}\n </button>\n </div>\n `\n }\n\n return html`\n <form>\n <div class=\"field\">\n <input type=\"password\" name=\"current_pass\" placeholder=${i18next.t('text.current password')} required />\n </div>\n <!--\n The rule belongs under the field the rule applies to.\n \n It used to sit between the two, directly beneath the current password, so a sighted\n reader took it as the rule for that field — and the current password has no reason to\n satisfy today's rule. It may predate it. The server only checks that it matches\n (change-pwd.ts verifies the current one and validates the new one against the rule).\n aria-describedby always pointed at the new field, so a screen reader had it right and\n only the eye was misled.\n -->\n <div class=\"field\">\n <input\n type=\"password\"\n name=\"new_pass\"\n placeholder=${i18next.t('text.new password')}\n required\n pattern=${this.passwordPattern}\n aria-describedby=\"password-helper\"\n />\n </div>\n <span id=\"password-helper\" class=\"helper-text\">${this.passwordHelp}</span>\n <div class=\"field\">\n <input type=\"password\" name=\"confirm_pass\" placeholder=${i18next.t('text.confirm password')} required />\n </div>\n\n <!--\n type=\"button\", the way abstract-sign and abstract-auth-page already declare it.\n \n A Material button inside a form defaults to type=\"submit\" and submits unless the click\n is default-prevented. So one click ran submit() and then let the browser submit the\n form as well. submit() had already called form.reset(), so constraint validation found\n three empty required fields, rejected the first one, and put the caret back on the\n current password with an English \"Please fill out this field.\" bubble on a Korean\n screen. It read as the current password being refused.\n [확인함] 2026-09-09 · 사용자 보고 · 화면 사진\n -->\n <md-elevated-button type=\"button\" @click=${this.submit.bind(this)}\n >${i18next.t('text.change password')}</md-elevated-button\n >\n </form>\n `\n }\n\n updated(changes: PropertyValues<this>) {\n if (changes.has('passwordRule')) {\n this.passwordPattern = generatePasswordPatternRegExp(this.passwordRule).source\n this.passwordHelp = generatePasswordPatternHelp(this.passwordRule)\n }\n }\n\n languageUpdated() {\n this.passwordPattern = generatePasswordPatternRegExp(this.passwordRule).source\n this.passwordHelp = generatePasswordPatternHelp(this.passwordRule)\n }\n\n async submit() {\n const formData = new FormData(this.form)\n let params = {}\n for (const [key, value] of formData.entries()) {\n if (!value) {\n const placeholder = (this.form.querySelector(`[name=${key}]`) as HTMLInputElement)?.placeholder\n return this.showToast(i18next.t('error.value is empty', { value: placeholder || key }))\n }\n params[key] = value\n }\n\n if (params['new_pass'] !== params['confirm_pass']) {\n return this.showToast(i18next.t('error.new-password-and-confirm-password-do-not-match'))\n }\n\n /* Clear only once it took — a refused change used to wipe all three fields. */\n if (await auth.changePassword(params)) {\n this.form.reset()\n this.changed = true\n }\n }\n\n showToast(message) {\n document.dispatchEvent(new CustomEvent('notify', { detail: { message } }))\n }\n}\n"]}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import '@material/web/icon/icon.js';
|
|
2
|
+
import '@material/web/checkbox/checkbox.js';
|
|
3
|
+
import '@operato/input/ox-input-search.js';
|
|
4
|
+
import { LitElement } from 'lit';
|
|
5
|
+
import type { CSSResultGroup } from 'lit';
|
|
6
|
+
/**
|
|
7
|
+
* 「할 수 있는 일 더하기」의 **그릇**.
|
|
8
|
+
*
|
|
9
|
+
* ── 왜 팝업인가 (사용자, 2026-09-11) ────────────────────────────────────────
|
|
10
|
+
* 「'할 수 있는 일 더하기' 작동을 팝업으로 넓게 할 수 있었으면 어떤가? 지금은 좁아보이고」
|
|
11
|
+
*
|
|
12
|
+
* 페이지 안에 펼치면 본문(할 수 있는 일)이 아래로 밀리고, 남은 폭에서 모듈 46개와 축 목록을
|
|
13
|
+
* 함께 봐야 한다. 고르는 동안은 **고르는 것만** 보는 것이 맞다.
|
|
14
|
+
*
|
|
15
|
+
* ── 이 부품은 아무것도 안 그린다 ────────────────────────────────────────────
|
|
16
|
+
* 모듈 접기·축 줄·유령 규칙이 전부 페이지의 상태(`modules`·`assigned`·`selected`·`keyword`)를
|
|
17
|
+
* 본다. 그것을 여기로 옮기면 **상태가 두 벌**이 되고, 그 순간 어느 쪽이 저장 범위인지
|
|
18
|
+
* 흐려진다 — 옛 편집기가 `querySelectorAll` 로 범위를 만들던 결함이 정확히 그 모양이었다.
|
|
19
|
+
*
|
|
20
|
+
* 그래서 그리기는 페이지가 하고(`renderPickerBody`), 여기는 담아서 띄우기만 한다. 대신
|
|
21
|
+
* 페이지가 토글할 때 이 부품에게 다시 그리라고 알린다(`page.picker`).
|
|
22
|
+
*
|
|
23
|
+
* ── 스타일도 한 벌이다 ──────────────────────────────────────────────────────
|
|
24
|
+
* 팝업은 페이지의 shadow root **밖**에 뜨므로 페이지 스타일이 여기까지 안 온다. 베껴 두면
|
|
25
|
+
* 한쪽만 고쳐지는 날이 오고, 오늘 이 화면에서 그 부류를 이미 만났다 — 칩 시절 CSS 가 남아
|
|
26
|
+
* 새 규칙을 이겼다. 그래서 페이지의 `styles` 를 그대로 가져간다.
|
|
27
|
+
*
|
|
28
|
+
* 페이지가 이 파일을 **동적으로** 들이기 때문에 이 import 가 순환이 되지 않는다.
|
|
29
|
+
*/
|
|
30
|
+
export declare class RolePrivilegePicker extends LitElement {
|
|
31
|
+
static styles: CSSResultGroup;
|
|
32
|
+
/** 담는 페이지. 이 부품은 그것의 `renderPickerBody()` 를 그린다. */
|
|
33
|
+
page?: any;
|
|
34
|
+
connectedCallback(): void;
|
|
35
|
+
disconnectedCallback(): void;
|
|
36
|
+
render(): any;
|
|
37
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { __decorate, __metadata } from "tslib";
|
|
2
|
+
import '@material/web/icon/icon.js';
|
|
3
|
+
import '@material/web/checkbox/checkbox.js';
|
|
4
|
+
import '@operato/input/ox-input-search.js';
|
|
5
|
+
import { html, LitElement } from 'lit';
|
|
6
|
+
import { customElement, property } from 'lit/decorators.js';
|
|
7
|
+
import { RolePrivilegePage } from '../pages/role/role-privilege-page.js';
|
|
8
|
+
/**
|
|
9
|
+
* 「할 수 있는 일 더하기」의 **그릇**.
|
|
10
|
+
*
|
|
11
|
+
* ── 왜 팝업인가 (사용자, 2026-09-11) ────────────────────────────────────────
|
|
12
|
+
* 「'할 수 있는 일 더하기' 작동을 팝업으로 넓게 할 수 있었으면 어떤가? 지금은 좁아보이고」
|
|
13
|
+
*
|
|
14
|
+
* 페이지 안에 펼치면 본문(할 수 있는 일)이 아래로 밀리고, 남은 폭에서 모듈 46개와 축 목록을
|
|
15
|
+
* 함께 봐야 한다. 고르는 동안은 **고르는 것만** 보는 것이 맞다.
|
|
16
|
+
*
|
|
17
|
+
* ── 이 부품은 아무것도 안 그린다 ────────────────────────────────────────────
|
|
18
|
+
* 모듈 접기·축 줄·유령 규칙이 전부 페이지의 상태(`modules`·`assigned`·`selected`·`keyword`)를
|
|
19
|
+
* 본다. 그것을 여기로 옮기면 **상태가 두 벌**이 되고, 그 순간 어느 쪽이 저장 범위인지
|
|
20
|
+
* 흐려진다 — 옛 편집기가 `querySelectorAll` 로 범위를 만들던 결함이 정확히 그 모양이었다.
|
|
21
|
+
*
|
|
22
|
+
* 그래서 그리기는 페이지가 하고(`renderPickerBody`), 여기는 담아서 띄우기만 한다. 대신
|
|
23
|
+
* 페이지가 토글할 때 이 부품에게 다시 그리라고 알린다(`page.picker`).
|
|
24
|
+
*
|
|
25
|
+
* ── 스타일도 한 벌이다 ──────────────────────────────────────────────────────
|
|
26
|
+
* 팝업은 페이지의 shadow root **밖**에 뜨므로 페이지 스타일이 여기까지 안 온다. 베껴 두면
|
|
27
|
+
* 한쪽만 고쳐지는 날이 오고, 오늘 이 화면에서 그 부류를 이미 만났다 — 칩 시절 CSS 가 남아
|
|
28
|
+
* 새 규칙을 이겼다. 그래서 페이지의 `styles` 를 그대로 가져간다.
|
|
29
|
+
*
|
|
30
|
+
* 페이지가 이 파일을 **동적으로** 들이기 때문에 이 import 가 순환이 되지 않는다.
|
|
31
|
+
*/
|
|
32
|
+
let RolePrivilegePicker = class RolePrivilegePicker extends LitElement {
|
|
33
|
+
static { this.styles = RolePrivilegePage.styles; }
|
|
34
|
+
connectedCallback() {
|
|
35
|
+
super.connectedCallback();
|
|
36
|
+
if (this.page) {
|
|
37
|
+
this.page.picker = this;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
disconnectedCallback() {
|
|
41
|
+
super.disconnectedCallback();
|
|
42
|
+
if (this.page?.picker === this) {
|
|
43
|
+
this.page.picker = undefined;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
render() {
|
|
47
|
+
return this.page?.renderPickerBody?.() ?? html ``;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
__decorate([
|
|
51
|
+
property({ type: Object }),
|
|
52
|
+
__metadata("design:type", Object)
|
|
53
|
+
], RolePrivilegePicker.prototype, "page", void 0);
|
|
54
|
+
RolePrivilegePicker = __decorate([
|
|
55
|
+
customElement('role-privilege-picker')
|
|
56
|
+
], RolePrivilegePicker);
|
|
57
|
+
export { RolePrivilegePicker };
|
|
58
|
+
//# sourceMappingURL=role-privilege-picker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"role-privilege-picker.js","sourceRoot":"","sources":["../../client/components/role-privilege-picker.ts"],"names":[],"mappings":";AAAA,OAAO,4BAA4B,CAAA;AACnC,OAAO,oCAAoC,CAAA;AAC3C,OAAO,mCAAmC,CAAA;AAE1C,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,KAAK,CAAA;AAEtC,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAE3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,sCAAsC,CAAA;AAExE;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEI,IAAM,mBAAmB,GAAzB,MAAM,mBAAoB,SAAQ,UAAU;aAC1C,WAAM,GAAmB,iBAAiB,CAAC,MAAwB,AAA7D,CAA6D;IAK1E,iBAAiB;QACf,KAAK,CAAC,iBAAiB,EAAE,CAAA;QACzB,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YACd,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAA;QACzB,CAAC;IACH,CAAC;IAED,oBAAoB;QAClB,KAAK,CAAC,oBAAoB,EAAE,CAAA;QAC5B,IAAI,IAAI,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,EAAE,CAAC;YAC/B,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,SAAS,CAAA;QAC9B,CAAC;IACH,CAAC;IAED,MAAM;QACJ,OAAO,IAAI,CAAC,IAAI,EAAE,gBAAgB,EAAE,EAAE,IAAI,IAAI,CAAA,EAAE,CAAA;IAClD,CAAC;;AAlB2B;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;iDAAW;AAJ3B,mBAAmB;IAD/B,aAAa,CAAC,uBAAuB,CAAC;GAC1B,mBAAmB,CAuB/B","sourcesContent":["import '@material/web/icon/icon.js'\nimport '@material/web/checkbox/checkbox.js'\nimport '@operato/input/ox-input-search.js'\n\nimport { html, LitElement } from 'lit'\nimport type { CSSResultGroup } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\n\nimport { RolePrivilegePage } from '../pages/role/role-privilege-page.js'\n\n/**\n * 「할 수 있는 일 더하기」의 **그릇**.\n *\n * ── 왜 팝업인가 (사용자, 2026-09-11) ────────────────────────────────────────\n * 「'할 수 있는 일 더하기' 작동을 팝업으로 넓게 할 수 있었으면 어떤가? 지금은 좁아보이고」\n *\n * 페이지 안에 펼치면 본문(할 수 있는 일)이 아래로 밀리고, 남은 폭에서 모듈 46개와 축 목록을\n * 함께 봐야 한다. 고르는 동안은 **고르는 것만** 보는 것이 맞다.\n *\n * ── 이 부품은 아무것도 안 그린다 ────────────────────────────────────────────\n * 모듈 접기·축 줄·유령 규칙이 전부 페이지의 상태(`modules`·`assigned`·`selected`·`keyword`)를\n * 본다. 그것을 여기로 옮기면 **상태가 두 벌**이 되고, 그 순간 어느 쪽이 저장 범위인지\n * 흐려진다 — 옛 편집기가 `querySelectorAll` 로 범위를 만들던 결함이 정확히 그 모양이었다.\n *\n * 그래서 그리기는 페이지가 하고(`renderPickerBody`), 여기는 담아서 띄우기만 한다. 대신\n * 페이지가 토글할 때 이 부품에게 다시 그리라고 알린다(`page.picker`).\n *\n * ── 스타일도 한 벌이다 ──────────────────────────────────────────────────────\n * 팝업은 페이지의 shadow root **밖**에 뜨므로 페이지 스타일이 여기까지 안 온다. 베껴 두면\n * 한쪽만 고쳐지는 날이 오고, 오늘 이 화면에서 그 부류를 이미 만났다 — 칩 시절 CSS 가 남아\n * 새 규칙을 이겼다. 그래서 페이지의 `styles` 를 그대로 가져간다.\n *\n * 페이지가 이 파일을 **동적으로** 들이기 때문에 이 import 가 순환이 되지 않는다.\n */\n@customElement('role-privilege-picker')\nexport class RolePrivilegePicker extends LitElement {\n static styles: CSSResultGroup = RolePrivilegePage.styles as CSSResultGroup\n\n /** 담는 페이지. 이 부품은 그것의 `renderPickerBody()` 를 그린다. */\n @property({ type: Object }) page?: any\n\n connectedCallback() {\n super.connectedCallback()\n if (this.page) {\n this.page.picker = this\n }\n }\n\n disconnectedCallback() {\n super.disconnectedCallback()\n if (this.page?.picker === this) {\n this.page.picker = undefined\n }\n }\n\n render() {\n return this.page?.renderPickerBody?.() ?? html``\n }\n}\n"]}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import '@material/web/icon/icon.js';
|
|
2
|
+
import { LitElement } from 'lit';
|
|
3
|
+
import type { CSSResultGroup } from 'lit';
|
|
4
|
+
declare const RoleTemplatePicker_base: (new (...args: any[]) => LitElement) & typeof LitElement;
|
|
5
|
+
/**
|
|
6
|
+
* Starting a role.
|
|
7
|
+
*
|
|
8
|
+
* ── Why this stands where the blank row used to ──────────────────────────────
|
|
9
|
+
* A new plant has no roles, and the old answer to "make one" was an empty line waiting for
|
|
10
|
+
* a name. Naming it is the easy half; the hard half is which of 84 privileges belong to a
|
|
11
|
+
* quality inspector, and that is a judgement the product has made and the plant has not.
|
|
12
|
+
*
|
|
13
|
+
* miratek is the case: two roles (Administrator, TWIN-CONNECTOR), no MES seats, and eleven
|
|
14
|
+
* approval doors standing empty because there is nothing to route them to. Read from
|
|
15
|
+
* inside, that looks like a plant that has not decided. It is a plant with nothing to
|
|
16
|
+
* decide from.
|
|
17
|
+
*
|
|
18
|
+
* ── Both doors, in one place ─────────────────────────────────────────────────
|
|
19
|
+
* The templates come first and the blank name last, because the order is what teaches: a
|
|
20
|
+
* plant that sees "quality inspector, 7 privileges" learns what a seat looks like before
|
|
21
|
+
* it is asked to invent one. Hiding the blank door would be worse, though — the seats a
|
|
22
|
+
* factory actually has ("night shift lead", "outside inspector") are its own, and the
|
|
23
|
+
* product does not know them.
|
|
24
|
+
*
|
|
25
|
+
* ── Why the name is editable ─────────────────────────────────────────────────
|
|
26
|
+
* The template knows the seat exists; it does not know what the people there call it. And
|
|
27
|
+
* because a role is also what an approval line points at, the same seat is sometimes
|
|
28
|
+
* needed twice — a day and a night inspector hold the same privileges and take different
|
|
29
|
+
* approvals. A fixed name would allow each template exactly once per domain.
|
|
30
|
+
*/
|
|
31
|
+
export declare class RoleTemplatePicker extends RoleTemplatePicker_base {
|
|
32
|
+
static styles: CSSResultGroup;
|
|
33
|
+
page?: any;
|
|
34
|
+
private templates;
|
|
35
|
+
private selected?;
|
|
36
|
+
/** `undefined` means "the blank door" — name the seat yourself. */
|
|
37
|
+
private scratch;
|
|
38
|
+
private name;
|
|
39
|
+
private description;
|
|
40
|
+
connectedCallback(): Promise<void>;
|
|
41
|
+
private fetchTemplates;
|
|
42
|
+
render(): import("lit-html").TemplateResult<1>;
|
|
43
|
+
/**
|
|
44
|
+
* What the seat opens, in one line — **counted, not interpreted.**
|
|
45
|
+
*
|
|
46
|
+
* This briefly said "changes 6 · only sees 8", which read well and was not something the
|
|
47
|
+
* data supports: the privilege axis is an unconstrained string and its name does not
|
|
48
|
+
* determine what the resolver behind it does. Counting is safe; grouping by meaning was
|
|
49
|
+
* not. See `renderOpens`.
|
|
50
|
+
*
|
|
51
|
+
* "0 privileges" is still two different facts and must not be merged: granting nothing
|
|
52
|
+
* on purpose is an approval seat, and a template whose grants are all missing here is a
|
|
53
|
+
* defect (`missingCount`).
|
|
54
|
+
*/
|
|
55
|
+
private summarize;
|
|
56
|
+
private renderDetail;
|
|
57
|
+
/**
|
|
58
|
+
* 이 자리가 무엇을 여나 — **선언된 것만.**
|
|
59
|
+
*
|
|
60
|
+
* ⚠ 여기 「바꿀 수 있는 것 / 볼 수만 있는 것」 두 묶음이 있었다. 사용자가 물었다(2026-09-11):
|
|
61
|
+
* 「바꿀 수 있고, 볼 수만 있는 것을 너가 어떻게 알아? 논리적이지 않은 것을 우겨넣지 말고.」
|
|
62
|
+
*
|
|
63
|
+
* 몰랐다. 지어낸 것이다. 근거가 없는 이유가 둘이다.
|
|
64
|
+
*
|
|
65
|
+
* ① `@privilege(privilege:)` 는 **제약 없는 문자열**이다(지시자 선언이 `privilege: String`
|
|
66
|
+
* 이고 enum 이 아니다). 지금 쓰인 값이 mutation 484 · query 314 로 둘뿐인 것은 **오늘
|
|
67
|
+
* 그렇게 쓴 것**이지 둘로 한정된 것이 아니다. 내 코드는 `axis !== 'query'` 로 갈랐으므로,
|
|
68
|
+
* 처음 보는 값은 **알아서가 아니라 query 가 아니라서** 「바꿀 수 있는 것」으로 떨어졌다.
|
|
69
|
+
* ② 이름이 뜻을 보장하지 않는다. 축 이름과 실제 GraphQL 연산이 어긋나는 자리가 6곳이고,
|
|
70
|
+
* 그중 둘은 이 기능의 내 파일이다(`role-template-query.ts` — 템플릿을 **읽는** 질의에
|
|
71
|
+
* `privilege: "mutation"` 을 달았다. 역할을 만드는 문이라서 그게 맞다). 축은 연산 종류가
|
|
72
|
+
* 아니라 **어느 문을 지나야 하나**의 이름이다.
|
|
73
|
+
*
|
|
74
|
+
* 그래서 화면은 선언된 것을 그대로 낸다 — 어느 모듈에, 어느 권한이. 읽히게 만드는 것
|
|
75
|
+
* (굵기·줄 간격·가운뎃점)은 아무것도 주장하지 않으므로 그대로 둔다.
|
|
76
|
+
*/
|
|
77
|
+
private renderOpens;
|
|
78
|
+
private pick;
|
|
79
|
+
private pickScratch;
|
|
80
|
+
private canCreate;
|
|
81
|
+
private create;
|
|
82
|
+
}
|
|
83
|
+
export {};
|