@things-factory/dashboard 10.0.13 → 10.0.18

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.
@@ -0,0 +1,2 @@
1
+ export declare const UPDATE_BOARD_SETTINGS = "UPDATE_BOARD_SETTINGS";
2
+ export declare const CLEAR_BOARD_SETTINGS = "CLEAR_BOARD_SETTINGS";
@@ -0,0 +1,11 @@
1
+ /*
2
+ * 액션 이름만 담는 모듈.
3
+ *
4
+ * 리듀서가 `board-settings.ts` 에서 이름을 가져오면 그 파일의 apollo client 까지 함께 딸려 온다.
5
+ * 리듀서는 순수 함수인데도 시험에서 GraphQL 클라이언트를 로드하다 죽는다(실제로 그랬다).
6
+ * 이름은 문자열일 뿐이므로 여기 따로 둔다 — `board-settings.ts` 가 그대로 다시 내보내므로
7
+ * 기존 import 자리는 바뀌지 않는다.
8
+ */
9
+ export const UPDATE_BOARD_SETTINGS = 'UPDATE_BOARD_SETTINGS';
10
+ export const CLEAR_BOARD_SETTINGS = 'CLEAR_BOARD_SETTINGS';
11
+ //# sourceMappingURL=board-settings-types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"board-settings-types.js","sourceRoot":"","sources":["../../client/actions/board-settings-types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,uBAAuB,CAAA;AAC5D,MAAM,CAAC,MAAM,oBAAoB,GAAG,sBAAsB,CAAA","sourcesContent":["/*\n * 액션 이름만 담는 모듈.\n *\n * 리듀서가 `board-settings.ts` 에서 이름을 가져오면 그 파일의 apollo client 까지 함께 딸려 온다.\n * 리듀서는 순수 함수인데도 시험에서 GraphQL 클라이언트를 로드하다 죽는다(실제로 그랬다).\n * 이름은 문자열일 뿐이므로 여기 따로 둔다 — `board-settings.ts` 가 그대로 다시 내보내므로\n * 기존 import 자리는 바뀌지 않는다.\n */\nexport const UPDATE_BOARD_SETTINGS = 'UPDATE_BOARD_SETTINGS'\nexport const CLEAR_BOARD_SETTINGS = 'CLEAR_BOARD_SETTINGS'\n"]}
@@ -1,3 +1,2 @@
1
- export declare const UPDATE_BOARD_SETTINGS = "UPDATE_BOARD_SETTINGS";
2
- export declare const CLEAR_BOARD_SETTINGS = "CLEAR_BOARD_SETTINGS";
1
+ export { UPDATE_BOARD_SETTINGS, CLEAR_BOARD_SETTINGS } from './board-settings-types.js';
3
2
  export declare function fetchBoardSettings(names: any): Promise<any>;
@@ -1,7 +1,6 @@
1
1
  import gql from 'graphql-tag';
2
2
  import { client } from '@operato/graphql';
3
- export const UPDATE_BOARD_SETTINGS = 'UPDATE_BOARD_SETTINGS';
4
- export const CLEAR_BOARD_SETTINGS = 'CLEAR_BOARD_SETTINGS';
3
+ export { UPDATE_BOARD_SETTINGS, CLEAR_BOARD_SETTINGS } from './board-settings-types.js';
5
4
  export async function fetchBoardSettings(names) {
6
5
  var response = await client.query({
7
6
  query: gql `
@@ -1 +1 @@
1
- {"version":3,"file":"board-settings.js","sourceRoot":"","sources":["../../client/actions/board-settings.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,aAAa,CAAA;AAE7B,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAEzC,MAAM,CAAC,MAAM,qBAAqB,GAAG,uBAAuB,CAAA;AAC5D,MAAM,CAAC,MAAM,oBAAoB,GAAG,sBAAsB,CAAA;AAE1D,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,KAAK;IAC5C,IAAI,QAAQ,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC;QAChC,KAAK,EAAE,GAAG,CAAA;;;;;;;;;;;;;KAaT;QACD,SAAS,EAAE;YACT,KAAK;SACN;KACF,CAAC,CAAA;IAEF,IAAI,CAAC,QAAQ,IAAI,CAAC,QAAQ,CAAC,IAAI;QAAE,OAAM;IACvC,OAAO,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAA;AACpC,CAAC","sourcesContent":["import gql from 'graphql-tag'\n\nimport { client } from '@operato/graphql'\n\nexport const UPDATE_BOARD_SETTINGS = 'UPDATE_BOARD_SETTINGS'\nexport const CLEAR_BOARD_SETTINGS = 'CLEAR_BOARD_SETTINGS'\n\nexport async function fetchBoardSettings(names) {\n var response = await client.query({\n query: gql`\n query ($names: [String!]) {\n boardSettings(names: $names) {\n name\n value\n board {\n id\n name\n description\n thumbnail\n }\n }\n }\n `,\n variables: {\n names\n }\n })\n\n if (!response || !response.data) return\n return response.data.boardSettings\n}\n"]}
1
+ {"version":3,"file":"board-settings.js","sourceRoot":"","sources":["../../client/actions/board-settings.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,aAAa,CAAA;AAE7B,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAEzC,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAA;AAEvF,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,KAAK;IAC5C,IAAI,QAAQ,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC;QAChC,KAAK,EAAE,GAAG,CAAA;;;;;;;;;;;;;KAaT;QACD,SAAS,EAAE;YACT,KAAK;SACN;KACF,CAAC,CAAA;IAEF,IAAI,CAAC,QAAQ,IAAI,CAAC,QAAQ,CAAC,IAAI;QAAE,OAAM;IACvC,OAAO,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAA;AACpC,CAAC","sourcesContent":["import gql from 'graphql-tag'\n\nimport { client } from '@operato/graphql'\n\nexport { UPDATE_BOARD_SETTINGS, CLEAR_BOARD_SETTINGS } from './board-settings-types.js'\n\nexport async function fetchBoardSettings(names) {\n var response = await client.query({\n query: gql`\n query ($names: [String!]) {\n boardSettings(names: $names) {\n name\n value\n board {\n id\n name\n description\n thumbnail\n }\n }\n }\n `,\n variables: {\n names\n }\n })\n\n if (!response || !response.data) return\n return response.data.boardSettings\n}\n"]}
@@ -1,16 +1,50 @@
1
1
  import '@operato/board/ox-board-selector.js';
2
+ import '@material/web/button/filled-button.js';
3
+ import '@material/web/icon/icon.js';
4
+ import { TemplateResult } from 'lit';
2
5
  import { BoardViewerPage } from '@things-factory/board-ui/dist-client/pages/board-viewer-page.js';
3
- export declare class DashboardHome extends BoardViewerPage {
6
+ declare const DashboardHome_base: (new (...args: any[]) => import("lit").LitElement) & typeof BoardViewerPage;
7
+ /**
8
+ * things-factory 의 랜딩 화면.
9
+ *
10
+ * 앱이 따로 정하지 않으면 모든 things-factory 앱이 `/dashboard` 로 들어온다. 관리자가 홈 대시보드로
11
+ * 지정한 보드를 그대로 보여주는 자리다.
12
+ *
13
+ * ── 이 화면이 갈라야 하는 상태 넷 ───────────────────────────────────────────
14
+ *
15
+ * ```
16
+ * 아직 모른다 설정을 받아오는 중 → 기다리는 표시
17
+ * 지정 없음 받아왔는데 지정된 보드 없음 → 무엇을 하면 되는지, 누가 할 수 있는지
18
+ * 열지 못함 지정은 있는데 보드가 안 열림 → 서버가 준 이유를 그대로
19
+ * 정상 보드 표시
20
+ * ```
21
+ *
22
+ * 넷 중 앞의 셋을 하나로 뭉치면 화면이 거짓말을 한다. 실제로 그랬다 — 설정을 받아오는 동안
23
+ * 「지정된 대시보드가 없습니다」가 떴다가 보드로 바뀌었고, 지정할 권한이 없는 사용자에게도
24
+ * 「눌러서 지정하십시오」라고 안내했다(게다가 그 문구가 가리키는 누를 곳이 없었다).
25
+ */
26
+ export declare class DashboardHome extends DashboardHome_base {
27
+ static styles: import("lit").CSSResult[];
28
+ /** 설정을 한 번이라도 받아왔는지. 받아오기 전에는 「지정된 것이 없다」고 말하지 않는다. */
29
+ private settingsLoaded;
4
30
  private _unsubscribe?;
5
31
  connectedCallback(): void;
6
32
  disconnectedCallback(): void;
33
+ /**
34
+ * 지정된 보드를 열지 못했을 때. 「지정이 없다」와는 다른 상태다.
35
+ *
36
+ * 이유는 `_loadError` 에 서버 문장이 그대로 들어 있다. 비어 있으면 이유를 모르는 것이고,
37
+ * 그때는 모른다고 둔다 — 여기서 원인을 지어내지 않는다.
38
+ */
7
39
  get oopsNote(): {
8
40
  icon: string;
9
41
  title: string;
10
42
  description: string;
11
- click: (e: any) => void;
12
43
  };
13
44
  get context(): any;
45
+ render(): TemplateResult<1>;
46
+ /** 지정 버튼 — 권한이 없으면 버튼 대신 누가 지정할 수 있는지 알린다. */
47
+ private renderSettingAction;
14
48
  onClickDashboardSetting(name: any, description: any): void;
15
49
  saveSettings({ name, value, category, description }: {
16
50
  name: any;
@@ -19,3 +53,4 @@ export declare class DashboardHome extends BoardViewerPage {
19
53
  description: any;
20
54
  }): Promise<void>;
21
55
  }
56
+ export {};
@@ -1,27 +1,127 @@
1
- import { __decorate } from "tslib";
1
+ import { __decorate, __metadata } from "tslib";
2
2
  import '@operato/board/ox-board-selector.js';
3
+ import '@material/web/button/filled-button.js';
4
+ import '@material/web/icon/icon.js';
3
5
  import gql from 'graphql-tag';
4
- import { html } from 'lit';
5
- import { customElement } from 'lit/decorators.js';
6
+ import { css, html } from 'lit';
7
+ import { customElement, state } from 'lit/decorators.js';
6
8
  import { store } from '@operato/shell';
7
9
  import { client } from '@operato/graphql';
8
- import { i18next } from '@operato/i18n';
10
+ import { i18next, localize } from '@operato/i18n';
9
11
  import { openPopup } from '@operato/layout';
10
12
  import { isMobileDevice } from '@operato/utils';
13
+ import { privileged } from '@things-factory/auth-base/dist-client/directive/privileged.js';
11
14
  import { BoardViewerPage } from '@things-factory/board-ui/dist-client/pages/board-viewer-page.js';
12
15
  import { fetchBoardSettings, UPDATE_BOARD_SETTINGS } from '../actions/board-settings';
16
+ import { isBoardSettingsLoaded } from '../reducers/board-settings';
13
17
  import { DASHBOARD, DASHBOARD_MOBILE, FALLBACK_HOME_BOARD } from '../const';
14
18
  const HOME_DESCRIPTION = 'home dashboard';
15
19
  const FALLBACK_ORDER = isMobileDevice()
16
20
  ? [DASHBOARD_MOBILE, DASHBOARD, FALLBACK_HOME_BOARD]
17
21
  : [DASHBOARD, FALLBACK_HOME_BOARD];
18
22
  const HOME_BOARD = isMobileDevice() ? DASHBOARD_MOBILE : DASHBOARD;
19
- let DashboardHome = class DashboardHome extends BoardViewerPage {
23
+ /**
24
+ * 홈 대시보드를 바꿀 권한 — 서버 지시자와 글자까지 같게 둔다.
25
+ *
26
+ * 서버: `@privilege(category: "setting", privilege: "mutation")` (setting-mutation.ts 의 updateSetting)
27
+ *
28
+ * 소유자·슈퍼유저 통과가 **선언되어 있지 않다.** 즉 도메인 소유자도 `setting/mutation` 을 따로
29
+ * 부여받아야 한다. 화면에서 임의로 넓히면 버튼은 보이는데 누르면 거절당한다.
30
+ *
31
+ * 화면 게이팅은 UX 이지 보안이 아니다 — 집행은 서버가 한다. 여기서 감추는 것은 할 수 없는 일을
32
+ * 권하지 않기 위한 것이다.
33
+ */
34
+ const SETTING_MUTATION = { category: 'setting', privilege: 'mutation' };
35
+ /**
36
+ * things-factory 의 랜딩 화면.
37
+ *
38
+ * 앱이 따로 정하지 않으면 모든 things-factory 앱이 `/dashboard` 로 들어온다. 관리자가 홈 대시보드로
39
+ * 지정한 보드를 그대로 보여주는 자리다.
40
+ *
41
+ * ── 이 화면이 갈라야 하는 상태 넷 ───────────────────────────────────────────
42
+ *
43
+ * ```
44
+ * 아직 모른다 설정을 받아오는 중 → 기다리는 표시
45
+ * 지정 없음 받아왔는데 지정된 보드 없음 → 무엇을 하면 되는지, 누가 할 수 있는지
46
+ * 열지 못함 지정은 있는데 보드가 안 열림 → 서버가 준 이유를 그대로
47
+ * 정상 보드 표시
48
+ * ```
49
+ *
50
+ * 넷 중 앞의 셋을 하나로 뭉치면 화면이 거짓말을 한다. 실제로 그랬다 — 설정을 받아오는 동안
51
+ * 「지정된 대시보드가 없습니다」가 떴다가 보드로 바뀌었고, 지정할 권한이 없는 사용자에게도
52
+ * 「눌러서 지정하십시오」라고 안내했다(게다가 그 문구가 가리키는 누를 곳이 없었다).
53
+ */
54
+ let DashboardHome = class DashboardHome extends localize(i18next)(BoardViewerPage) {
55
+ constructor() {
56
+ super(...arguments);
57
+ /** 설정을 한 번이라도 받아왔는지. 받아오기 전에는 「지정된 것이 없다」고 말하지 않는다. */
58
+ this.settingsLoaded = false;
59
+ }
60
+ static { this.styles = [
61
+ ...(Array.isArray(BoardViewerPage.styles) ? BoardViewerPage.styles : [BoardViewerPage.styles]),
62
+ css `
63
+ .empty {
64
+ position: absolute;
65
+ left: 50%;
66
+ top: 50%;
67
+ transform: translate(-50%, -50%);
68
+
69
+ display: flex;
70
+ flex-direction: column;
71
+ align-items: center;
72
+ gap: var(--spacing-medium, 8px);
73
+
74
+ max-width: 34rem;
75
+ padding: var(--spacing-huge, 24px);
76
+ text-align: center;
77
+ }
78
+
79
+ .empty md-icon {
80
+ font: normal var(--icon-size-huge, 48px) var(--md-icon-font, 'Material Symbols Outlined');
81
+ width: var(--icon-size-huge, 48px);
82
+ height: var(--icon-size-huge, 48px);
83
+ color: var(--md-sys-color-outline);
84
+ }
85
+
86
+ .empty h2 {
87
+ margin: 0;
88
+ font: var(--subtitle-font, bold 18px var(--theme-font));
89
+ color: var(--md-sys-color-on-surface);
90
+ }
91
+
92
+ .empty p {
93
+ margin: 0;
94
+ font: normal var(--fontsize-default, 14px) / 1.6 var(--theme-font);
95
+ color: var(--md-sys-color-on-surface-variant);
96
+ }
97
+
98
+ /* 서버가 준 문장. 길 수 있고 줄바꿈이 들어 있을 수 있어 그대로 흘린다. */
99
+ .empty .reason {
100
+ margin-top: var(--spacing-small, 4px);
101
+ padding: var(--spacing-medium, 8px) var(--spacing-large, 12px);
102
+ border: var(--border-dim-color, 1px solid var(--md-sys-color-outline-variant));
103
+ border-radius: var(--border-radius, 4px);
104
+
105
+ max-width: 100%;
106
+ overflow-wrap: anywhere;
107
+ white-space: pre-wrap;
108
+
109
+ font: normal var(--fontsize-small, 13px) / 1.5 var(--theme-font);
110
+ color: var(--md-sys-color-on-surface-variant);
111
+ text-align: left;
112
+ }
113
+
114
+ .empty md-filled-button {
115
+ margin-top: var(--spacing-medium, 8px);
116
+ }
117
+ `
118
+ ]; }
20
119
  connectedCallback() {
21
120
  super.connectedCallback();
22
121
  const apply = () => {
23
122
  const state = store.getState();
24
123
  const dashboard = state?.boardSetting;
124
+ this.settingsLoaded = isBoardSettingsLoaded(dashboard);
25
125
  const homeBoard = dashboard && dashboard[FALLBACK_ORDER.find(name => dashboard[name]) || 'maybe notfound'];
26
126
  this._boardId = homeBoard?.value;
27
127
  };
@@ -32,19 +132,60 @@ let DashboardHome = class DashboardHome extends BoardViewerPage {
32
132
  super.disconnectedCallback();
33
133
  this._unsubscribe?.();
34
134
  }
135
+ /**
136
+ * 지정된 보드를 열지 못했을 때. 「지정이 없다」와는 다른 상태다.
137
+ *
138
+ * 이유는 `_loadError` 에 서버 문장이 그대로 들어 있다. 비어 있으면 이유를 모르는 것이고,
139
+ * 그때는 모른다고 둔다 — 여기서 원인을 지어내지 않는다.
140
+ */
35
141
  get oopsNote() {
36
142
  return {
37
- icon: 'insert_chart',
38
- title: 'HOME DASHBOARD',
39
- description: 'There are no home dashboard setting. Pls, click to setting home dashboard.',
40
- click: e => this.onClickDashboardSetting(HOME_BOARD, HOME_DESCRIPTION)
143
+ icon: 'error_outline',
144
+ title: i18next.t('title.home dashboard unavailable'),
145
+ description: i18next.t('text.home dashboard unavailable')
41
146
  };
42
147
  }
43
148
  get context() {
44
149
  return {
45
- title: HOME_DESCRIPTION
150
+ title: i18next.t('title.home dashboard')
46
151
  };
47
152
  }
153
+ render() {
154
+ if (!this.settingsLoaded) {
155
+ return html `<ox-oops-spinner show></ox-oops-spinner>`;
156
+ }
157
+ /* 지정은 되어 있는데 열지 못했다 — 「지정이 없다」와 다른 말을 해야 한다. */
158
+ if (this._boardId && this._loadError) {
159
+ return html `
160
+ <div class="empty">
161
+ <md-icon>error_outline</md-icon>
162
+ <h2>${i18next.t('title.home dashboard unavailable')}</h2>
163
+ <p>${i18next.t('text.home dashboard unavailable')}</p>
164
+ <div class="reason" role="status">${this._loadError}</div>
165
+ ${this.renderSettingAction()}
166
+ </div>
167
+ `;
168
+ }
169
+ if (this._boardId) {
170
+ return super.render();
171
+ }
172
+ return html `
173
+ <div class="empty">
174
+ <md-icon>dashboard_customize</md-icon>
175
+ <h2>${i18next.t('title.no home dashboard')}</h2>
176
+ <p>${i18next.t('text.no home dashboard')}</p>
177
+ ${this.renderSettingAction()}
178
+ </div>
179
+ `;
180
+ }
181
+ /** 지정 버튼 — 권한이 없으면 버튼 대신 누가 지정할 수 있는지 알린다. */
182
+ renderSettingAction() {
183
+ return privileged(SETTING_MUTATION, html `
184
+ <md-filled-button @click=${() => this.onClickDashboardSetting(HOME_BOARD, HOME_DESCRIPTION)}>
185
+ ${i18next.t('button.set home dashboard')}
186
+ </md-filled-button>
187
+ `, html `<p>${i18next.t('text.home dashboard ask administrator')}</p>`);
188
+ }
48
189
  onClickDashboardSetting(name, description) {
49
190
  var popup = openPopup(html `
50
191
  <ox-board-selector
@@ -101,6 +242,10 @@ let DashboardHome = class DashboardHome extends BoardViewerPage {
101
242
  });
102
243
  }
103
244
  };
245
+ __decorate([
246
+ state(),
247
+ __metadata("design:type", Object)
248
+ ], DashboardHome.prototype, "settingsLoaded", void 0);
104
249
  DashboardHome = __decorate([
105
250
  customElement('dashboard-home')
106
251
  ], DashboardHome);
@@ -1 +1 @@
1
- {"version":3,"file":"dashboard.js","sourceRoot":"","sources":["../../client/pages/dashboard.ts"],"names":[],"mappings":"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","sourcesContent":["import '@operato/board/ox-board-selector.js'\n\nimport gql from 'graphql-tag'\nimport { html } from 'lit'\nimport { customElement } from 'lit/decorators.js'\n\nimport { store } from '@operato/shell'\nimport { client } from '@operato/graphql'\nimport { i18next } from '@operato/i18n'\nimport { openPopup } from '@operato/layout'\nimport { isMobileDevice } from '@operato/utils'\nimport { BoardViewerPage } from '@things-factory/board-ui/dist-client/pages/board-viewer-page.js'\n\nimport { fetchBoardSettings, UPDATE_BOARD_SETTINGS } from '../actions/board-settings'\nimport { DASHBOARD, DASHBOARD_MOBILE, FALLBACK_HOME_BOARD } from '../const'\n\nconst HOME_DESCRIPTION = 'home dashboard'\n\nconst FALLBACK_ORDER = isMobileDevice()\n ? [DASHBOARD_MOBILE, DASHBOARD, FALLBACK_HOME_BOARD]\n : [DASHBOARD, FALLBACK_HOME_BOARD]\nconst HOME_BOARD = isMobileDevice() ? DASHBOARD_MOBILE : DASHBOARD\n\n@customElement('dashboard-home')\nexport class DashboardHome extends BoardViewerPage {\n private _unsubscribe?: () => void\n\n connectedCallback() {\n super.connectedCallback()\n\n const apply = () => {\n const state = store.getState() as any\n const dashboard = state?.boardSetting\n const homeBoard = dashboard && dashboard[FALLBACK_ORDER.find(name => dashboard[name]) || 'maybe notfound']\n this._boardId = homeBoard?.value\n }\n\n apply()\n this._unsubscribe = store.subscribe(apply)\n }\n\n disconnectedCallback() {\n super.disconnectedCallback()\n this._unsubscribe?.()\n }\n\n get oopsNote() {\n return {\n icon: 'insert_chart',\n title: 'HOME DASHBOARD',\n description: 'There are no home dashboard setting. Pls, click to setting home dashboard.',\n click: e => this.onClickDashboardSetting(HOME_BOARD, HOME_DESCRIPTION)\n }\n }\n\n get context() {\n return {\n title: HOME_DESCRIPTION\n } as any\n }\n\n onClickDashboardSetting(name, description) {\n var popup = openPopup(\n html`\n <ox-board-selector\n .creatable=${true}\n .value=${this._boardId}\n @board-selected=${async e => {\n var board = e.detail.board\n\n await this.saveSettings({\n name,\n value: board.id,\n category: 'board',\n description\n })\n\n var settings = await fetchBoardSettings([FALLBACK_HOME_BOARD, DASHBOARD, DASHBOARD_MOBILE])\n store.dispatch({\n type: UPDATE_BOARD_SETTINGS,\n settings: settings.reduce((settings, setting) => {\n settings[setting.name] = setting\n return settings\n }, {})\n })\n\n popup.close()\n this.requestUpdate()\n }}\n ></ox-board-selector>\n `,\n {\n backdrop: true,\n size: 'large',\n title: i18next.t('title.dashboard setting')\n }\n )\n }\n\n async saveSettings({ name, value, category, description }) {\n if (!(name && value)) {\n return\n }\n\n await client.query({\n query: gql`\n mutation UpdateSetting($patch: SettingPatch!, $name: String!) {\n updateSetting(patch: $patch, name: $name) {\n name\n value\n }\n }\n `,\n variables: {\n name,\n patch: {\n name,\n description,\n category,\n value\n }\n }\n })\n }\n}\n"]}
1
+ {"version":3,"file":"dashboard.js","sourceRoot":"","sources":["../../client/pages/dashboard.ts"],"names":[],"mappings":"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","sourcesContent":["import '@operato/board/ox-board-selector.js'\nimport '@material/web/button/filled-button.js'\nimport '@material/web/icon/icon.js'\n\nimport gql from 'graphql-tag'\nimport { css, html, TemplateResult } from 'lit'\nimport { customElement, state } from 'lit/decorators.js'\n\nimport { store } from '@operato/shell'\nimport { client } from '@operato/graphql'\nimport { i18next, localize } from '@operato/i18n'\nimport { openPopup } from '@operato/layout'\nimport { isMobileDevice } from '@operato/utils'\nimport { privileged } from '@things-factory/auth-base/dist-client/directive/privileged.js'\nimport { BoardViewerPage } from '@things-factory/board-ui/dist-client/pages/board-viewer-page.js'\n\nimport { fetchBoardSettings, UPDATE_BOARD_SETTINGS } from '../actions/board-settings'\nimport { isBoardSettingsLoaded } from '../reducers/board-settings'\nimport { DASHBOARD, DASHBOARD_MOBILE, FALLBACK_HOME_BOARD } from '../const'\n\nconst HOME_DESCRIPTION = 'home dashboard'\n\nconst FALLBACK_ORDER = isMobileDevice()\n ? [DASHBOARD_MOBILE, DASHBOARD, FALLBACK_HOME_BOARD]\n : [DASHBOARD, FALLBACK_HOME_BOARD]\nconst HOME_BOARD = isMobileDevice() ? DASHBOARD_MOBILE : DASHBOARD\n\n/**\n * 홈 대시보드를 바꿀 권한 — 서버 지시자와 글자까지 같게 둔다.\n *\n * 서버: `@privilege(category: \"setting\", privilege: \"mutation\")` (setting-mutation.ts 의 updateSetting)\n *\n * 소유자·슈퍼유저 통과가 **선언되어 있지 않다.** 즉 도메인 소유자도 `setting/mutation` 을 따로\n * 부여받아야 한다. 화면에서 임의로 넓히면 버튼은 보이는데 누르면 거절당한다.\n *\n * 화면 게이팅은 UX 이지 보안이 아니다 — 집행은 서버가 한다. 여기서 감추는 것은 할 수 없는 일을\n * 권하지 않기 위한 것이다.\n */\nconst SETTING_MUTATION = { category: 'setting', privilege: 'mutation' }\n\n/**\n * things-factory 의 랜딩 화면.\n *\n * 앱이 따로 정하지 않으면 모든 things-factory 앱이 `/dashboard` 로 들어온다. 관리자가 홈 대시보드로\n * 지정한 보드를 그대로 보여주는 자리다.\n *\n * ── 이 화면이 갈라야 하는 상태 넷 ───────────────────────────────────────────\n *\n * ```\n * 아직 모른다 설정을 받아오는 중 → 기다리는 표시\n * 지정 없음 받아왔는데 지정된 보드 없음 → 무엇을 하면 되는지, 누가 할 수 있는지\n * 열지 못함 지정은 있는데 보드가 안 열림 → 서버가 준 이유를 그대로\n * 정상 보드 표시\n * ```\n *\n * 넷 중 앞의 셋을 하나로 뭉치면 화면이 거짓말을 한다. 실제로 그랬다 — 설정을 받아오는 동안\n * 「지정된 대시보드가 없습니다」가 떴다가 보드로 바뀌었고, 지정할 권한이 없는 사용자에게도\n * 「눌러서 지정하십시오」라고 안내했다(게다가 그 문구가 가리키는 누를 곳이 없었다).\n */\n@customElement('dashboard-home')\nexport class DashboardHome extends localize(i18next)(BoardViewerPage) {\n static styles = [\n ...(Array.isArray(BoardViewerPage.styles) ? BoardViewerPage.styles : [BoardViewerPage.styles]),\n css`\n .empty {\n position: absolute;\n left: 50%;\n top: 50%;\n transform: translate(-50%, -50%);\n\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: var(--spacing-medium, 8px);\n\n max-width: 34rem;\n padding: var(--spacing-huge, 24px);\n text-align: center;\n }\n\n .empty md-icon {\n font: normal var(--icon-size-huge, 48px) var(--md-icon-font, 'Material Symbols Outlined');\n width: var(--icon-size-huge, 48px);\n height: var(--icon-size-huge, 48px);\n color: var(--md-sys-color-outline);\n }\n\n .empty h2 {\n margin: 0;\n font: var(--subtitle-font, bold 18px var(--theme-font));\n color: var(--md-sys-color-on-surface);\n }\n\n .empty p {\n margin: 0;\n font: normal var(--fontsize-default, 14px) / 1.6 var(--theme-font);\n color: var(--md-sys-color-on-surface-variant);\n }\n\n /* 서버가 준 문장. 길 수 있고 줄바꿈이 들어 있을 수 있어 그대로 흘린다. */\n .empty .reason {\n margin-top: var(--spacing-small, 4px);\n padding: var(--spacing-medium, 8px) var(--spacing-large, 12px);\n border: var(--border-dim-color, 1px solid var(--md-sys-color-outline-variant));\n border-radius: var(--border-radius, 4px);\n\n max-width: 100%;\n overflow-wrap: anywhere;\n white-space: pre-wrap;\n\n font: normal var(--fontsize-small, 13px) / 1.5 var(--theme-font);\n color: var(--md-sys-color-on-surface-variant);\n text-align: left;\n }\n\n .empty md-filled-button {\n margin-top: var(--spacing-medium, 8px);\n }\n `\n ]\n\n /** 설정을 한 번이라도 받아왔는지. 받아오기 전에는 「지정된 것이 없다」고 말하지 않는다. */\n @state() private settingsLoaded = false\n\n private _unsubscribe?: () => void\n\n connectedCallback() {\n super.connectedCallback()\n\n const apply = () => {\n const state = store.getState() as any\n const dashboard = state?.boardSetting\n\n this.settingsLoaded = isBoardSettingsLoaded(dashboard)\n\n const homeBoard = dashboard && dashboard[FALLBACK_ORDER.find(name => dashboard[name]) || 'maybe notfound']\n this._boardId = homeBoard?.value\n }\n\n apply()\n this._unsubscribe = store.subscribe(apply)\n }\n\n disconnectedCallback() {\n super.disconnectedCallback()\n this._unsubscribe?.()\n }\n\n /**\n * 지정된 보드를 열지 못했을 때. 「지정이 없다」와는 다른 상태다.\n *\n * 이유는 `_loadError` 에 서버 문장이 그대로 들어 있다. 비어 있으면 이유를 모르는 것이고,\n * 그때는 모른다고 둔다 — 여기서 원인을 지어내지 않는다.\n */\n get oopsNote() {\n return {\n icon: 'error_outline',\n title: i18next.t('title.home dashboard unavailable'),\n description: i18next.t('text.home dashboard unavailable')\n }\n }\n\n get context() {\n return {\n title: i18next.t('title.home dashboard')\n } as any\n }\n\n render(): TemplateResult<1> {\n if (!this.settingsLoaded) {\n return html`<ox-oops-spinner show></ox-oops-spinner>`\n }\n\n /* 지정은 되어 있는데 열지 못했다 — 「지정이 없다」와 다른 말을 해야 한다. */\n if (this._boardId && this._loadError) {\n return html`\n <div class=\"empty\">\n <md-icon>error_outline</md-icon>\n <h2>${i18next.t('title.home dashboard unavailable')}</h2>\n <p>${i18next.t('text.home dashboard unavailable')}</p>\n <div class=\"reason\" role=\"status\">${this._loadError}</div>\n ${this.renderSettingAction()}\n </div>\n `\n }\n\n if (this._boardId) {\n return super.render() as TemplateResult<1>\n }\n\n return html`\n <div class=\"empty\">\n <md-icon>dashboard_customize</md-icon>\n <h2>${i18next.t('title.no home dashboard')}</h2>\n <p>${i18next.t('text.no home dashboard')}</p>\n ${this.renderSettingAction()}\n </div>\n `\n }\n\n /** 지정 버튼 — 권한이 없으면 버튼 대신 누가 지정할 수 있는지 알린다. */\n private renderSettingAction() {\n return privileged(\n SETTING_MUTATION,\n html`\n <md-filled-button @click=${() => this.onClickDashboardSetting(HOME_BOARD, HOME_DESCRIPTION)}>\n ${i18next.t('button.set home dashboard')}\n </md-filled-button>\n `,\n html`<p>${i18next.t('text.home dashboard ask administrator')}</p>`\n )\n }\n\n onClickDashboardSetting(name, description) {\n var popup = openPopup(\n html`\n <ox-board-selector\n .creatable=${true}\n .value=${this._boardId}\n @board-selected=${async e => {\n var board = e.detail.board\n\n await this.saveSettings({\n name,\n value: board.id,\n category: 'board',\n description\n })\n\n var settings = await fetchBoardSettings([FALLBACK_HOME_BOARD, DASHBOARD, DASHBOARD_MOBILE])\n store.dispatch({\n type: UPDATE_BOARD_SETTINGS,\n settings: settings.reduce((settings, setting) => {\n settings[setting.name] = setting\n return settings\n }, {})\n })\n\n popup.close()\n this.requestUpdate()\n }}\n ></ox-board-selector>\n `,\n {\n backdrop: true,\n size: 'large',\n title: i18next.t('title.dashboard setting')\n }\n )\n }\n\n async saveSettings({ name, value, category, description }) {\n if (!(name && value)) {\n return\n }\n\n await client.query({\n query: gql`\n mutation UpdateSetting($patch: SettingPatch!, $name: String!) {\n updateSetting(patch: $patch, name: $name) {\n name\n value\n }\n }\n `,\n variables: {\n name,\n patch: {\n name,\n description,\n category,\n value\n }\n }\n })\n }\n}\n"]}
@@ -1,2 +1,4 @@
1
+ /** 설정을 한 번이라도 받아왔는지. 화면은 이것으로 「없다」와 「아직 모른다」를 가른다. */
2
+ export declare function isBoardSettingsLoaded(boardSetting: any): boolean;
1
3
  declare const boardSetting: (state: {} | undefined, action: any) => any;
2
4
  export default boardSetting;
@@ -1,5 +1,19 @@
1
- import { UPDATE_BOARD_SETTINGS, CLEAR_BOARD_SETTINGS } from '../actions/board-settings';
1
+ import { UPDATE_BOARD_SETTINGS, CLEAR_BOARD_SETTINGS } from '../actions/board-settings-types.js';
2
+ /*
3
+ * 초기값의 **참조 자체**가 「아직 받아오지 않았다」는 표시다.
4
+ *
5
+ * 이 맵의 키는 설정 이름이라서, 「받아왔는데 지정된 것이 하나도 없다」도 빈 객체가 된다. 두 경우를
6
+ * 값으로는 구별할 수 없다. 그래서 초기값 객체를 하나 두고, 받아온 뒤에는 반드시 새 객체를 만든다
7
+ * — 참조가 그대로면 아직 안 받아온 것이다.
8
+ *
9
+ * 구별이 필요한 이유: 홈 화면이 이 둘을 같게 다루면, 받아오는 동안 「지정된 대시보드가 없습니다」가
10
+ * 잠깐 떴다가 보드로 바뀐다. 사용자에게는 없다고 말했다가 번복하는 화면이다.
11
+ */
2
12
  const INITIAL_STATE = {};
13
+ /** 설정을 한 번이라도 받아왔는지. 화면은 이것으로 「없다」와 「아직 모른다」를 가른다. */
14
+ export function isBoardSettingsLoaded(boardSetting) {
15
+ return !!boardSetting && boardSetting !== INITIAL_STATE;
16
+ }
3
17
  const boardSetting = (state = INITIAL_STATE, action) => {
4
18
  switch (action.type) {
5
19
  case UPDATE_BOARD_SETTINGS:
@@ -7,8 +21,9 @@ const boardSetting = (state = INITIAL_STATE, action) => {
7
21
  ...state,
8
22
  ...action.settings
9
23
  };
24
+ /* 로그아웃·도메인 전환 — 받아온 적 없는 상태로 되돌린다(빈 객체가 아니라 초기값 그 참조로). */
10
25
  case CLEAR_BOARD_SETTINGS:
11
- return {};
26
+ return INITIAL_STATE;
12
27
  default:
13
28
  return state;
14
29
  }
@@ -1 +1 @@
1
- {"version":3,"file":"board-settings.js","sourceRoot":"","sources":["../../client/reducers/board-settings.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAA;AAEvF,MAAM,aAAa,GAAG,EAAE,CAAA;AAExB,MAAM,YAAY,GAAG,CAAC,KAAK,GAAG,aAAa,EAAE,MAAM,EAAE,EAAE;IACrD,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;QACpB,KAAK,qBAAqB;YACxB,OAAO;gBACL,GAAG,KAAK;gBACR,GAAG,MAAM,CAAC,QAAQ;aACnB,CAAA;QAEH,KAAK,oBAAoB;YACvB,OAAO,EAAE,CAAA;QAEX;YACE,OAAO,KAAK,CAAA;IAChB,CAAC;AACH,CAAC,CAAA;AAED,eAAe,YAAY,CAAA","sourcesContent":["import { UPDATE_BOARD_SETTINGS, CLEAR_BOARD_SETTINGS } from '../actions/board-settings'\n\nconst INITIAL_STATE = {}\n\nconst boardSetting = (state = INITIAL_STATE, action) => {\n switch (action.type) {\n case UPDATE_BOARD_SETTINGS:\n return {\n ...state,\n ...action.settings\n }\n\n case CLEAR_BOARD_SETTINGS:\n return {}\n\n default:\n return state\n }\n}\n\nexport default boardSetting\n"]}
1
+ {"version":3,"file":"board-settings.js","sourceRoot":"","sources":["../../client/reducers/board-settings.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,oCAAoC,CAAA;AAEhG;;;;;;;;;GASG;AACH,MAAM,aAAa,GAAG,EAAE,CAAA;AAExB,sDAAsD;AACtD,MAAM,UAAU,qBAAqB,CAAC,YAAiB;IACrD,OAAO,CAAC,CAAC,YAAY,IAAI,YAAY,KAAK,aAAa,CAAA;AACzD,CAAC;AAED,MAAM,YAAY,GAAG,CAAC,KAAK,GAAG,aAAa,EAAE,MAAM,EAAE,EAAE;IACrD,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;QACpB,KAAK,qBAAqB;YACxB,OAAO;gBACL,GAAG,KAAK;gBACR,GAAG,MAAM,CAAC,QAAQ;aACnB,CAAA;QAEH,2DAA2D;QAC3D,KAAK,oBAAoB;YACvB,OAAO,aAAa,CAAA;QAEtB;YACE,OAAO,KAAK,CAAA;IAChB,CAAC;AACH,CAAC,CAAA;AAED,eAAe,YAAY,CAAA","sourcesContent":["import { UPDATE_BOARD_SETTINGS, CLEAR_BOARD_SETTINGS } from '../actions/board-settings-types.js'\n\n/*\n * 초기값의 **참조 자체**가 「아직 받아오지 않았다」는 표시다.\n *\n * 이 맵의 키는 설정 이름이라서, 「받아왔는데 지정된 것이 하나도 없다」도 빈 객체가 된다. 두 경우를\n * 값으로는 구별할 수 없다. 그래서 초기값 객체를 하나 두고, 받아온 뒤에는 반드시 새 객체를 만든다\n * — 참조가 그대로면 아직 안 받아온 것이다.\n *\n * 구별이 필요한 이유: 홈 화면이 이 둘을 같게 다루면, 받아오는 동안 「지정된 대시보드가 없습니다」가\n * 잠깐 떴다가 보드로 바뀐다. 사용자에게는 없다고 말했다가 번복하는 화면이다.\n */\nconst INITIAL_STATE = {}\n\n/** 설정을 한 번이라도 받아왔는지. 화면은 이것으로 「없다」와 「아직 모른다」를 가른다. */\nexport function isBoardSettingsLoaded(boardSetting: any): boolean {\n return !!boardSetting && boardSetting !== INITIAL_STATE\n}\n\nconst boardSetting = (state = INITIAL_STATE, action) => {\n switch (action.type) {\n case UPDATE_BOARD_SETTINGS:\n return {\n ...state,\n ...action.settings\n }\n\n /* 로그아웃·도메인 전환 — 받아온 적 없는 상태로 되돌린다(빈 객체가 아니라 초기값 그 참조로). */\n case CLEAR_BOARD_SETTINGS:\n return INITIAL_STATE\n\n default:\n return state\n }\n}\n\nexport default boardSetting\n"]}