@operato/font 1.0.0-alpha.15

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/CHANGELOG.md ADDED
@@ -0,0 +1,11 @@
1
+ # Change Log
2
+
3
+ All notable changes to this project will be documented in this file.
4
+ See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
+
6
+ ## [1.0.0-alpha.15](https://github.com/hatiolab/operato/compare/v1.0.0-alpha.14...v1.0.0-alpha.15) (2022-03-13)
7
+
8
+
9
+ ### :rocket: New Features
10
+
11
+ * migrate input, property-editor from things-factory ([dbd5e73](https://github.com/hatiolab/operato/commit/dbd5e73eac328e72c4bb7abd113a819532bc894d))
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) Hatiolab Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@operato/font",
3
+ "description": "User interface to select font and create font.",
4
+ "license": "MIT",
5
+ "author": "heartyoh",
6
+ "version": "1.0.0-alpha.15",
7
+ "main": "src/index.js",
8
+ "module": "src/index.js",
9
+ "exports": {
10
+ ".": "./src/index.js",
11
+ "./font-selector": "./src/font-selector.js",
12
+ "./ox-font-selector": "./src/ox-font-selector.js",
13
+ "./ox-property-editor-font-selector": "./src/ox-property-editor-font-selector.js"
14
+ },
15
+ "publishConfig": {
16
+ "access": "public",
17
+ "@operato:registry": "https://registry.npmjs.org"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/hatiolab/operato.git",
22
+ "directory": "webcomponents/font"
23
+ },
24
+ "scripts": {},
25
+ "dependencies": {
26
+ "@material/mwc-icon": "^0.25.3",
27
+ "@operato/attachment": "^1.0.0-alpha.15",
28
+ "@operato/headroom": "^1.0.0-alpha.15",
29
+ "@operato/i18n": "^1.0.0-alpha.15",
30
+ "@operato/layout": "^1.0.0-alpha.15",
31
+ "@operato/property-editor": "^1.0.0-alpha.15",
32
+ "@operato/shell": "^1.0.0-alpha.15",
33
+ "@operato/utils": "^1.0.0-alpha.15",
34
+ "uuid": "^3.4.0"
35
+ },
36
+ "gitHead": "2f2d8a3d320ccce220e0c5edeb92d287f4b38af9"
37
+ }
@@ -0,0 +1,311 @@
1
+ import "@operato/attachment";
2
+
3
+ import { LitElement, css, html } from "lit";
4
+ import { i18next, localize } from "@operato/i18n";
5
+
6
+ import { FileDropHelper } from "@operato/utils";
7
+
8
+ export class FontCreationCard extends localize(i18next)(LitElement) {
9
+ static get properties() {
10
+ return {
11
+ provider: {
12
+ type: String,
13
+ },
14
+ googleFonts: {
15
+ type: Array,
16
+ },
17
+ _files: {
18
+ type: Array,
19
+ },
20
+ };
21
+ }
22
+
23
+ constructor() {
24
+ super();
25
+ this.provider = "google";
26
+ this.providers = [
27
+ { value: "google", display: "Google" },
28
+ // TODO 구글 외 폰트 서비스 구현
29
+ // { value: 'typekit', display: 'Typekit' },
30
+ { value: "custom", display: "Custom" },
31
+ ];
32
+ this.googleFonts = [];
33
+ }
34
+
35
+ static get styles() {
36
+ return [
37
+ css`
38
+ :host {
39
+ position: relative;
40
+
41
+ padding: 0;
42
+ margin: 0;
43
+ height: 100%;
44
+
45
+ -webkit-transform-style: preserve-3d;
46
+ transform-style: preserve-3d;
47
+ -webkit-transition: all 0.5s ease-in-out;
48
+ transition: all 0.5s ease-in-out;
49
+ }
50
+
51
+ :host(.candrop) [front],
52
+ :host(.candrop) [back] {
53
+ border-width: 2px;
54
+ background-color: #fffde9;
55
+ }
56
+
57
+ :host(.flipped) {
58
+ -webkit-transform: var(--card-list-flip-transform);
59
+ transform: var(--card-list-flip-transform);
60
+ }
61
+
62
+ [front],
63
+ [back] {
64
+ position: absolute;
65
+
66
+ width: 100%;
67
+ height: 100%;
68
+ margin: 0;
69
+ padding: 0;
70
+
71
+ border: var(--card-list-create-border);
72
+ border-radius: var(--card-list-create-border-radius);
73
+
74
+ background-color: #fff;
75
+
76
+ -webkit-backface-visibility: hidden;
77
+ backface-visibility: hidden;
78
+ }
79
+
80
+ [front] {
81
+ display: flex;
82
+ flex-direction: column;
83
+ align-items: center;
84
+ justify-content: center;
85
+
86
+ text-align: center;
87
+ font-size: 0.8em;
88
+ color: var(--card-list-create-color);
89
+ text-transform: capitalize;
90
+ }
91
+
92
+ [front] mwc-icon {
93
+ font-size: 3.5em;
94
+ color: var(--card-list-create-icon-color);
95
+ }
96
+
97
+ [back] {
98
+ -webkit-transform: var(--card-list-flip-transform);
99
+ transform: var(--card-list-flip-transform);
100
+ box-sizing: border-box;
101
+ display: grid;
102
+ }
103
+
104
+ [back] form {
105
+ padding: var(--card-list-create-form-padding);
106
+ width: 100%;
107
+ height: 100%;
108
+ box-sizing: border-box;
109
+ display: grid;
110
+ grid-template-columns: 1fr;
111
+ grid-template-rows: auto 1fr auto;
112
+ justify-content: center;
113
+ align-items: center;
114
+ }
115
+
116
+ [back] form .props {
117
+ width: 100%;
118
+ height: 100%;
119
+ box-sizing: border-box;
120
+ display: grid;
121
+ grid-template-columns: repeat(10, 1fr);
122
+ grid-row-gap: 7px;
123
+ justify-content: center;
124
+ align-items: center;
125
+ }
126
+
127
+ [back] form .props label {
128
+ grid-column: span 4;
129
+ font: var(--card-list-create-label-font);
130
+ color: var(--card-list-create-label-color);
131
+ }
132
+
133
+ [back] form .props input,
134
+ [back] form .props select {
135
+ grid-column: span 6;
136
+ background-color: #fff;
137
+ border: var(--card-list-create-input-border);
138
+ border-radius: var(--card-list-create-input-border-radius);
139
+ font: var(--card-list-create-input-font);
140
+ color: var(--card-list-create-input-color);
141
+ width: -moz-available;
142
+ }
143
+
144
+ file-selector {
145
+ grid-column: span 6;
146
+ font: var(--card-list-create-input-font);
147
+ border: none;
148
+ box-sizing: border-box;
149
+ padding: 0;
150
+ }
151
+
152
+ [back] input[type="submit"] {
153
+ background-color: var(--button-background-color) !important;
154
+ font: var(--button-font);
155
+ color: var(--button-color) !important;
156
+ border-radius: var(--button-radius);
157
+ border: var(--button-border);
158
+ grid-column: span 10;
159
+ grid-row: auto / -1;
160
+ }
161
+
162
+ .hidden {
163
+ display: none !important;
164
+ }
165
+ `,
166
+ ];
167
+ }
168
+
169
+ async firstUpdated() {
170
+ FileDropHelper.set(this);
171
+ }
172
+
173
+ render() {
174
+ let isProviderGoogle =
175
+ this.provider == "google" && this.googleFonts.length > 0;
176
+ let isFileAttached = this._files.length > 0 ? true : false;
177
+ return html`
178
+ <div @click=${(e) => this.onClickFlip(e)} front>
179
+ <mwc-icon>add_circle_outline</mwc-icon>create font
180
+ </div>
181
+
182
+ <div @click=${(e) => this.onClickFlip(e)} back>
183
+ <form @submit=${(e) => this.onClickSubmit(e)}>
184
+ <div class="props">
185
+ <label>${i18next.t("label.provider")}</label>
186
+ <select
187
+ name="provider"
188
+ @change=${(e) => {
189
+ this.provider = e.target.value;
190
+ if (e.target.value === "google") {
191
+ fetch(`/all-google-fonts`).then(async (response) => {
192
+ if (response.ok) this.googleFonts = await response.json();
193
+ else {
194
+ console.warn(
195
+ `(${response.url}) ${response.status} ${response.statusText}. Could not load Google fonts.`
196
+ );
197
+ }
198
+ });
199
+ }
200
+ }}
201
+ >
202
+ ${this.providers.map(
203
+ (p) =>
204
+ html`
205
+ <option
206
+ value=${p.value}
207
+ ?selected=${this.provider == p.value}
208
+ >
209
+ ${p.display}
210
+ </option>
211
+ `
212
+ )}
213
+ </select>
214
+
215
+ <label>${i18next.t("label.name")}</label>
216
+ <input
217
+ type="text"
218
+ name="${isProviderGoogle ? "" : "name"}"
219
+ ?hidden=${isProviderGoogle}
220
+ />
221
+ <select
222
+ name="${isProviderGoogle ? "name" : ""}"
223
+ ?hidden=${!isProviderGoogle}
224
+ >
225
+ ${isProviderGoogle &&
226
+ this.googleFonts.map(
227
+ (f) => html` <option value=${f}>${f}</option> `
228
+ )}
229
+ </select>
230
+
231
+ <label ?hidden=${this.provider != "custom"}
232
+ >${i18next.t("label.uri")}</label
233
+ >
234
+ <input
235
+ ?hidden=${this.provider != "custom"}
236
+ ?disabled=${isFileAttached}
237
+ .value=${isFileAttached ? this._files[0].name : ""}
238
+ type="text"
239
+ name="uri"
240
+ />
241
+ <!-- display when attachment module is imported -->
242
+ <label ?hidden=${this.provider != "custom"}
243
+ >${i18next.t("label.file")}</label
244
+ >
245
+ <file-selector
246
+ class="${this.provider != "custom" ? "hidden" : ""}"
247
+ name="file"
248
+ label="${i18next.t("label.select file")}"
249
+ accept=".ttf,.otf,.woff,.woff2,.eot,.svg,.svgz"
250
+ multiple
251
+ @file-change=${(e) => {
252
+ this._files = Array.from(e.detail.files);
253
+ }}
254
+ ></file-selector>
255
+ <!------------------------------------------------>
256
+
257
+ <label for="checkbox-active" @click=${(e) => e.stopPropagation()}>
258
+ ${i18next.t("label.active")}
259
+ </label>
260
+ <input id="checkbox-active" type="checkbox" name="active" checked />
261
+ </div>
262
+ <div></div>
263
+ <input type="submit" value=${i18next.t("button.create")} />
264
+ </form>
265
+ </div>
266
+ `;
267
+ }
268
+
269
+ onClickFlip(e) {
270
+ if (
271
+ !["INPUT", "SELECT", "OPTION"].find(
272
+ (tagName) => tagName === e.target.tagName
273
+ )
274
+ ) {
275
+ if (e.currentTarget.hasAttribute("front")) this.reset(); // 입력 폼으로 뒤집기 전에 한 번 리셋
276
+ this.classList.toggle("flipped");
277
+ }
278
+ }
279
+
280
+ async onClickSubmit(e) {
281
+ e.preventDefault();
282
+ e.stopPropagation();
283
+
284
+ var form = e.target;
285
+
286
+ var detail = {};
287
+ detail.name = form.elements["name"].value;
288
+ detail.provider = form.elements["provider"].value;
289
+ detail.active = form.elements["active"].checked;
290
+ if (this.provider === "custom") {
291
+ detail.uri = form.elements["uri"].value;
292
+ if (this._files?.length > 0) {
293
+ detail._files = this._files;
294
+ }
295
+ }
296
+
297
+ this.dispatchEvent(new CustomEvent("create-font", { detail }));
298
+ }
299
+
300
+ reset() {
301
+ var form = this.shadowRoot.querySelector("form");
302
+ if (form) {
303
+ form.reset();
304
+ }
305
+
306
+ this._files = [];
307
+ this.classList.remove("flipped");
308
+ }
309
+ }
310
+
311
+ customElements.define("font-creation-card", FontCreationCard);
@@ -0,0 +1,465 @@
1
+ import "./font-creation-card";
2
+
3
+ import { HeadroomStyles, ScrollbarStyles } from "@operato/styles";
4
+ import { LitElement, css, html } from "lit";
5
+ import {
6
+ createFont,
7
+ deleteFont,
8
+ fetchFontList,
9
+ updateFont,
10
+ } from "./graphql-client";
11
+ import { i18next, localize } from "@operato/i18n";
12
+
13
+ import Headroom from "@operato/headroom";
14
+ import { client } from "@operato/graphql";
15
+ import { connect } from "pwa-helpers/connect-mixin.js";
16
+ import gql from "graphql-tag";
17
+ import { pulltorefresh } from "@operato/utils";
18
+ import { store } from "@operato/shell";
19
+ import uuid from "uuid/v4";
20
+
21
+ export class FontSelector extends localize(i18next)(
22
+ connect(store)(LitElement)
23
+ ) {
24
+ static get styles() {
25
+ return [
26
+ ScrollbarStyles,
27
+ HeadroomStyles,
28
+ css`
29
+ :host {
30
+ display: flex;
31
+ flex-direction: column;
32
+ overflow: hidden;
33
+ background-color: var(--popup-content-background-color);
34
+
35
+ position: relative;
36
+ }
37
+
38
+ :host(.candrop) {
39
+ background: orange;
40
+ cursor: pointer;
41
+ }
42
+
43
+ #main {
44
+ overflow: auto;
45
+ padding: var(--popup-content-padding);
46
+ display: grid;
47
+ grid-template-columns: var(--card-list-template);
48
+ grid-auto-rows: var(--card-list-rows-height);
49
+ grid-gap: 20px;
50
+ box-sizing: border-box;
51
+ }
52
+
53
+ #main .card {
54
+ display: flex;
55
+ flex-direction: column;
56
+ align-items: center;
57
+ overflow: hidden;
58
+ border-radius: var(--card-list-border-radius);
59
+ border: var(--font-selector-border);
60
+ background-color: var(--card-list-background-color);
61
+
62
+ position: relative;
63
+ }
64
+
65
+ .card .button-container {
66
+ position: absolute;
67
+ right: 0;
68
+ height: 100%;
69
+ display: flex;
70
+ direction: rtl;
71
+ flex-direction: column;
72
+ flex-wrap: wrap;
73
+ }
74
+
75
+ .card .button-container > mwc-icon {
76
+ background-color: var(--font-selector-icon-background-color);
77
+ text-align: center;
78
+ width: var(--font-selector-icon-size);
79
+ height: var(--font-selector-icon-size);
80
+ font: var(--font-selector-icon-font);
81
+ color: var(--font-selector-icon-color);
82
+ }
83
+
84
+ .card .button-container > mwc-icon:last-child {
85
+ border-bottom-left-radius: 12px;
86
+ }
87
+
88
+ .card .button-container > mwc-icon:hover,
89
+ .card .button-container > mwc-icon:active {
90
+ background-color: var(--primary-color);
91
+ color: #fff;
92
+ }
93
+
94
+ #main .card.create {
95
+ overflow: visible;
96
+ background-color: initial;
97
+ }
98
+
99
+ #main .card:hover {
100
+ cursor: pointer;
101
+ }
102
+
103
+ [face] {
104
+ flex: 1;
105
+ }
106
+
107
+ [name] {
108
+ background-color: var(--board-renderer-name-background-color);
109
+ opacity: 0.8;
110
+ margin-top: -35px;
111
+ width: 100%;
112
+ color: #fff;
113
+ font-weight: bolder;
114
+ font-size: 13px;
115
+ text-indent: 7px;
116
+ }
117
+
118
+ [provider] {
119
+ background-color: rgba(0, 0, 0, 0.7);
120
+ width: 100%;
121
+ min-height: 15px;
122
+ font-size: 0.6rem;
123
+ color: #fff;
124
+ text-indent: 7px;
125
+ }
126
+
127
+ #filter {
128
+ padding: var(--popup-content-padding);
129
+ background-color: var(--font-tools-background-color);
130
+ box-shadow: var(--box-shadow);
131
+
132
+ position: absolute;
133
+ width: 100%;
134
+ box-sizing: border-box;
135
+ z-index: 1;
136
+ }
137
+
138
+ #filter * {
139
+ font-size: 15px;
140
+ }
141
+
142
+ select {
143
+ text-transform: capitalize;
144
+ float: right;
145
+ }
146
+ `,
147
+ ];
148
+ }
149
+
150
+ static get properties() {
151
+ return {
152
+ fonts: Array,
153
+ _page: Number,
154
+ _total: Number,
155
+ creatable: Boolean,
156
+ };
157
+ }
158
+
159
+ render() {
160
+ var fonts = this.fonts || [];
161
+
162
+ return html`
163
+ <div id="filter">
164
+ <select
165
+ @change=${(e) => {
166
+ this.provider = e.currentTarget.value;
167
+ this.requestUpdate();
168
+ }}
169
+ >
170
+ <option value="">
171
+ --${i18next.t("text.please choose a provider")}--
172
+ </option>
173
+ ${["google", "custom"].map(
174
+ (provider) => html` <option value=${provider}>${provider}</option> `
175
+ )}
176
+ </select>
177
+ </div>
178
+
179
+ <div id="main">
180
+ ${this.creatable
181
+ ? html`
182
+ <font-creation-card
183
+ class="card create"
184
+ @create-font=${(e) => this.onCreateFont(e)}
185
+ @file-drop=${(e) => this.onAttachmentDropped(e)}
186
+ ></font-creation-card>
187
+ `
188
+ : html``}
189
+ ${fonts.map(
190
+ (font) => html`
191
+ <div class="card" @click=${(e) => this.onClickSelect(font)}>
192
+ <div face>
193
+ <font .face=${font.name}>ABCDEFGHIJKLMN</font>
194
+ <font .face=${font.name}>abcdefghijklmn</font>
195
+ </div>
196
+ <div name>${font.name}</div>
197
+ <div provider>${font.provider}</div>
198
+ <div class="button-container">
199
+ <mwc-icon
200
+ @click=${(e) => {
201
+ e.stopPropagation();
202
+ this.toggleActive(font);
203
+ }}
204
+ >${font.active
205
+ ? "check_box"
206
+ : "check_box_outline_blank"}</mwc-icon
207
+ >
208
+ <mwc-icon
209
+ @click=${(e) => {
210
+ e.stopPropagation();
211
+ this.deleteFont(font);
212
+ }}
213
+ >delete</mwc-icon
214
+ >
215
+ </div>
216
+ </div>
217
+ `
218
+ )}
219
+ </div>
220
+ `;
221
+ }
222
+
223
+ async firstUpdated() {
224
+ var list = this.shadowRoot.querySelector("#main");
225
+
226
+ pulltorefresh({
227
+ container: this.shadowRoot,
228
+ scrollable: list,
229
+ refresh: () => {
230
+ return this.refresh();
231
+ },
232
+ });
233
+
234
+ /* for headroom */
235
+ var main = this.renderRoot.querySelector("#main");
236
+ main.addEventListener("scroll", (e) => {
237
+ this.showGotoTop = e.target.scrollTop !== 0;
238
+ });
239
+
240
+ var filter = this.renderRoot.querySelector("#filter");
241
+
242
+ await this.requestUpdate();
243
+
244
+ var originPaddingTop = parseFloat(
245
+ getComputedStyle(main, null).getPropertyValue("padding-top")
246
+ );
247
+ main.style.paddingTop = filter.clientHeight + originPaddingTop + "px";
248
+ var headroom = new Headroom(filter, {
249
+ scroller: main,
250
+ });
251
+ headroom.init();
252
+ }
253
+
254
+ get creationCard() {
255
+ return this.shadowRoot.querySelector("font-creation-card");
256
+ }
257
+
258
+ updated(changes) {
259
+ if (changes.has("fonts")) {
260
+ var creationCard = this.creationCard;
261
+ if (creationCard) {
262
+ creationCard.reset();
263
+ }
264
+ }
265
+ }
266
+
267
+ stateChanged(state) {
268
+ this.fonts = state.font;
269
+ }
270
+
271
+ refresh() {
272
+ return store.dispatch(fetchFontList());
273
+ }
274
+
275
+ toggleActive(font) {
276
+ store.dispatch(updateFont({ id: font.id, active: !font.active }));
277
+ }
278
+
279
+ onCreateFont(e) {
280
+ var font = e.detail;
281
+ this.createFont(font);
282
+ }
283
+
284
+ async createFont(font) {
285
+ if (font._files?.length > 0) {
286
+ let attachment = await this.attachFile(font._files[0], [
287
+ "fullpath",
288
+ "refBy",
289
+ ]);
290
+
291
+ //font.id = attachment?.refBy
292
+ font.uri = attachment?.fullpath;
293
+ delete font._files;
294
+ }
295
+
296
+ store.dispatch(createFont(font));
297
+ }
298
+
299
+ async onAttachmentDropped(e) {
300
+ var isNonFontIncluded = false;
301
+ var files = e.detail.filter((file) => {
302
+ var isFontFormat = !![
303
+ ".woff",
304
+ ".woff2",
305
+ ".eot",
306
+ ".svg",
307
+ ".svgz",
308
+ ".ttf",
309
+ ".otf",
310
+ ].find((ext) => file.name.endsWith(ext));
311
+ if (!isFontFormat) {
312
+ isNonFontIncluded = true;
313
+ return false;
314
+ }
315
+ var alreadyExist = !!this.fonts.find(
316
+ (font) =>
317
+ font.name == file.name.replace(/\.[^/.]+$/, "").replace(".", "_")
318
+ );
319
+ if (alreadyExist) return false;
320
+ return true;
321
+ });
322
+ // TODO alert if non-font file is included. ex) Non-font file is excluded in upload list.
323
+
324
+ if (files.length > 0) {
325
+ var attached = await this.attachFiles(files, [
326
+ "name",
327
+ "fullpath",
328
+ "refBy",
329
+ ]);
330
+ attached.forEach((attachment) => {
331
+ this.createFont({
332
+ id: attachment.refBy,
333
+ name: attachment.name.replace(/\.[^/.]+$/, "").replace(".", "_"), // cannot apply font correctly if '.' exists in name
334
+ provider: "custom",
335
+ active: true,
336
+ uri: attachment.fullpath,
337
+ });
338
+ });
339
+ }
340
+ }
341
+
342
+ /**
343
+ * attach a file
344
+ *
345
+ * @param { File } file file
346
+ * @param { Array<String> } fields fields to select from return
347
+ */
348
+ async attachFile(file, fields = []) {
349
+ var attaching = await client.mutate({
350
+ mutation: gql`
351
+ mutation ($attachment: NewAttachment!) {
352
+ createAttachment(attachment: $attachment) {
353
+ id
354
+ }
355
+ }
356
+ `,
357
+ variables: {
358
+ attachment: { refBy: uuid(), category: "font", file },
359
+ },
360
+ context: {
361
+ hasUpload: true,
362
+ },
363
+ });
364
+ // TODO mutation 이후 query 호출 안 해도 되도록 수정
365
+ // fullpath 값은 getter라서 그런지 뮤테이션에서 못 받아오는 듯
366
+ var attached = await client.query({
367
+ query: gql`
368
+ query($id: String!) {
369
+ attachment(id: $id) {
370
+ id
371
+ ${fields.join("\n")}
372
+ }
373
+ }
374
+ `,
375
+ variables: {
376
+ id: attaching.data.createAttachment?.id,
377
+ },
378
+ });
379
+ return attached.data.attachment;
380
+ }
381
+
382
+ /**
383
+ * attach multiple files
384
+ *
385
+ * @param { Array<File> } files files
386
+ * @param { Array<String> } fields fields to select from return
387
+ */
388
+ async attachFiles(files, fields = []) {
389
+ var attaching = await client.mutate({
390
+ mutation: gql`
391
+ mutation ($attachments: [NewAttachment!]!) {
392
+ createAttachments(attachments: $attachments) {
393
+ id
394
+ }
395
+ }
396
+ `,
397
+ variables: {
398
+ attachments: files.map((file) => ({
399
+ refBy: uuid(),
400
+ category: "",
401
+ file,
402
+ })),
403
+ },
404
+ context: {
405
+ hasUpload: true,
406
+ },
407
+ });
408
+ // TODO mutation 이후 query 호출 안 해도 되도록 수정
409
+ // fullpath 값은 getter라서 그런지 뮤테이션에서 못 받아오는 듯
410
+ var attached = await client.query({
411
+ query: gql`
412
+ query($filters: [Filter]) {
413
+ attachments(filters: $filters) {
414
+ items {
415
+ id
416
+ ${fields.join("\n")}
417
+ }
418
+ total
419
+ }
420
+ }
421
+ `,
422
+ variables: {
423
+ filters: {
424
+ name: "id",
425
+ operator: "in",
426
+ value: attaching.data.createAttachments.map(
427
+ (attachment) => attachment.id
428
+ ),
429
+ },
430
+ },
431
+ });
432
+ return attached.data.attachments.items;
433
+ }
434
+
435
+ async deleteFont(font) {
436
+ try {
437
+ client.mutate({
438
+ mutation: gql`
439
+ mutation ($refBys: [String!]!) {
440
+ deleteAttachmentsByRef(refBys: $refBys)
441
+ }
442
+ `,
443
+ variables: {
444
+ refBys: [font.id],
445
+ },
446
+ });
447
+ } catch (e) {}
448
+
449
+ store.dispatch(deleteFont(font));
450
+ }
451
+
452
+ onClickSelect(font) {
453
+ this.dispatchEvent(
454
+ new CustomEvent("font-selected", {
455
+ composed: true,
456
+ bubbles: true,
457
+ detail: {
458
+ font,
459
+ },
460
+ })
461
+ );
462
+ }
463
+ }
464
+
465
+ customElements.define("font-selector", FontSelector);
@@ -0,0 +1,108 @@
1
+ import { client } from "@operato/graphql";
2
+ import gql from "graphql-tag";
3
+
4
+ /**
5
+ * @param {Object} listParam {filters, pagination, sortings}
6
+ */
7
+ export async function fetchFontList(listParam) {
8
+ const response = await client.query({
9
+ query: gql`
10
+ query (
11
+ $filters: [Filter!]
12
+ $pagination: Pagination
13
+ $sortings: [Sorting!]
14
+ ) {
15
+ fonts(filters: $filters, pagination: $pagination, sortings: $sortings) {
16
+ items {
17
+ id
18
+ name
19
+ provider
20
+ uri
21
+ path
22
+ active
23
+ createdAt
24
+ updatedAt
25
+ }
26
+ total
27
+ }
28
+ }
29
+ `,
30
+ variables: listParam,
31
+ });
32
+
33
+ return response.data && response.data.fonts;
34
+ }
35
+
36
+ /**
37
+ * @param {Object} font Font patch
38
+ */
39
+ export async function createFont(font) {
40
+ const response = await client.mutate({
41
+ mutation: gql`
42
+ mutation CreateFont($font: NewFont!) {
43
+ createFont(font: $font) {
44
+ name
45
+ provider
46
+ uri
47
+ path
48
+ active
49
+ createdAt
50
+ updatedAt
51
+ }
52
+ }
53
+ `,
54
+ variables: {
55
+ font: { active: false, ...font },
56
+ },
57
+ });
58
+
59
+ return response.data;
60
+ }
61
+
62
+ /**
63
+ * @param {Object} font Font patch
64
+ */
65
+ export async function updateFont(font) {
66
+ var { id, ...patch } = font;
67
+
68
+ const response = await client.mutate({
69
+ mutation: gql`
70
+ mutation UpdateFont($id: String!, $patch: FontPatch!) {
71
+ updateFont(id: $id, patch: $patch) {
72
+ id
73
+ name
74
+ provider
75
+ uri
76
+ path
77
+ active
78
+ createdAt
79
+ updatedAt
80
+ }
81
+ }
82
+ `,
83
+ variables: {
84
+ id,
85
+ patch,
86
+ },
87
+ });
88
+
89
+ return response.data;
90
+ }
91
+
92
+ /**
93
+ * @param {String} id Font id
94
+ */
95
+ export async function deleteFont(id) {
96
+ const response = await client.mutate({
97
+ mutation: gql`
98
+ mutation ($id: String!) {
99
+ deleteFont(id: $id)
100
+ }
101
+ `,
102
+ variables: {
103
+ id,
104
+ },
105
+ });
106
+
107
+ return response.data;
108
+ }
package/src/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export * from "./font-selector";
2
+ export * from "./ox-font-selector";
3
+ export * from "./ox-property-editor-font-selector";
@@ -0,0 +1,102 @@
1
+ /**
2
+ * @license Copyright © HatioLab Inc. All rights reserved.
3
+ */
4
+
5
+ import "@material/mwc-icon";
6
+ import "./font-selector";
7
+
8
+ import { LitElement, css, html } from "lit";
9
+
10
+ import { i18next } from "@operato/i18n";
11
+ import { openPopup } from "@operato/layout";
12
+
13
+ export default class OxFontSelector extends LitElement {
14
+ static get properties() {
15
+ return {
16
+ value: String,
17
+ properties: Object,
18
+ };
19
+ }
20
+
21
+ static get styles() {
22
+ return [
23
+ css`
24
+ :host {
25
+ position: relative;
26
+ display: inline-block;
27
+ }
28
+
29
+ input[type="text"] {
30
+ box-sizing: border-box;
31
+ width: 100%;
32
+ height: 100%;
33
+ border: 1px solid rgba(0, 0, 0, 0.2);
34
+ }
35
+
36
+ mwc-icon {
37
+ position: absolute;
38
+ top: 0;
39
+ right: 0;
40
+ }
41
+ `,
42
+ ];
43
+ }
44
+
45
+ render() {
46
+ return html`
47
+ <input
48
+ id="text"
49
+ type="text"
50
+ .value=${this.value || ""}
51
+ @change=${(e) => this._onInputChanged(e)}
52
+ .placeholder=${this.getAttribute("placeholder") || ""}
53
+ />
54
+
55
+ <mwc-icon @click=${(e) => this.openSelector(e)}>font_download</mwc-icon>
56
+ `;
57
+ }
58
+
59
+ _onInputChanged(e) {
60
+ this.value = e.target.value;
61
+ this.dispatchEvent(
62
+ new CustomEvent("change", { bubbles: true, composed: true })
63
+ );
64
+ }
65
+
66
+ openSelector() {
67
+ if (this.popup) {
68
+ delete this.popup;
69
+ }
70
+
71
+ /*
72
+ * 기존 설정된 보드가 선택된 상태가 되게 하기 위해서는 selector에 value를 전달해줄 필요가 있음.
73
+ * 주의. value는 object일 수도 있고, string일 수도 있다.
74
+ * string인 경우에는 해당 보드의 id로 해석한다.
75
+ */
76
+ var value = this.value || {};
77
+
78
+ var template = html`
79
+ <font-selector
80
+ .creatable=${true}
81
+ @font-selected=${async (e) => {
82
+ var font = e.detail.font;
83
+ this.value = font.name;
84
+
85
+ this.dispatchEvent(
86
+ new CustomEvent("change", { bubbles: true, composed: true })
87
+ );
88
+
89
+ this.popup && this.popup.close();
90
+ }}
91
+ ></font-selector>
92
+ `;
93
+
94
+ this.popup = openPopup(template, {
95
+ backdrop: true,
96
+ size: "large",
97
+ title: i18next.t("title.select font"),
98
+ });
99
+ }
100
+ }
101
+
102
+ customElements.define("ox-font-selector", OxFontSelector);
@@ -0,0 +1,25 @@
1
+ /**
2
+ * @license Copyright © HatioLab Inc. All rights reserved.
3
+ */
4
+
5
+ import "./ox-font-selector";
6
+
7
+ import { OxPropertyEditor } from "@operato/property-editor";
8
+ import { html } from "lit";
9
+
10
+ export class OxPropertyEditorFontSelector extends OxPropertyEditor {
11
+ editorTemplate() {
12
+ return html`
13
+ <ox-font-selector
14
+ id="editor"
15
+ .value=${this.value}
16
+ .properties=${this.property}
17
+ ></ox-font-selector>
18
+ `;
19
+ }
20
+ }
21
+
22
+ customElements.define(
23
+ "ox-property-editor-font-selector",
24
+ OxPropertyEditorFontSelector
25
+ );
@@ -0,0 +1,14 @@
1
+ body {
2
+ --font-tools-background-color: var(--primary-color);
3
+ --font-selector-background-color: #fff;
4
+ --font-selector-name-font: bold 14px var(--theme-font);
5
+ --font-selector-border: 1px solid rgba(0, 0, 0, 0.2);
6
+ --font-selector-icon-size: 35px;
7
+ --font-selector-icon-font: normal 20px/35px var(--mdc-icon-font, 'Material Icons');
8
+ --font-selector-icon-color: var(--primary-color);
9
+ --font-selector-icon-background-color: rgba(255, 255, 255, 0.85);
10
+ --font-selector-etc-background-color: var(--primary-color);
11
+ --font-selector-etc-font: bold 9px var(--theme-font);
12
+ --font-selector-etc-icon-font: normal 72px var(--mdc-icon-font, 'Material Icons');
13
+ --font-selector-etc-icon-color: var(--secondary-color);
14
+ }
@@ -0,0 +1,5 @@
1
+ import bootstrap from './client/bootstrap'
2
+
3
+ export default {
4
+ bootstrap
5
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "label.module": "module",
3
+ "label.uri": "URI",
4
+ "text.please choose a provider": "Please choose a provider"
5
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "label.module": "모듈",
3
+ "label.uri": "URI",
4
+ "text.please choose a provider": "서비스 제공자를 선택하세요"
5
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "label.module": "module",
3
+ "label.uri": "URI",
4
+ "text.please choose a provider": "[ms]Please choose a provider"
5
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "label.module": "模块",
3
+ "label.uri": "URI",
4
+ "text.please choose a provider": "Please choose a provider"
5
+ }