@things-factory/figure-ui 10.1.6 → 10.1.8
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/client/graphql/index.ts +25 -0
- package/client/modeller/figure-animations.ts +769 -0
- package/client/modeller/figure-ask.ts +6 -1
- package/client/modeller/figure-history-panel.ts +52 -1
- package/client/modeller/figure-inspector.ts +520 -120
- package/client/modeller/figure-side.ts +65 -17
- package/client/modeller/proposal-session.ts +92 -0
- package/client/pages/figure-list-page.ts +56 -4
- package/client/pages/figure-modeller-page.ts +204 -179
- package/client/types.ts +39 -0
- package/client/viewparts/figure-thumb.ts +78 -0
- package/dist-client/graphql/index.d.ts +3 -1
- package/dist-client/graphql/index.js +22 -0
- package/dist-client/graphql/index.js.map +1 -1
- package/dist-client/modeller/figure-animations.d.ts +96 -0
- package/dist-client/modeller/figure-animations.js +708 -0
- package/dist-client/modeller/figure-animations.js.map +1 -0
- package/dist-client/modeller/figure-ask.d.ts +3 -0
- package/dist-client/modeller/figure-ask.js +9 -0
- package/dist-client/modeller/figure-ask.js.map +1 -1
- package/dist-client/modeller/figure-history-panel.d.ts +7 -0
- package/dist-client/modeller/figure-history-panel.js +50 -0
- package/dist-client/modeller/figure-history-panel.js.map +1 -1
- package/dist-client/modeller/figure-inspector.d.ts +17 -0
- package/dist-client/modeller/figure-inspector.js +506 -107
- package/dist-client/modeller/figure-inspector.js.map +1 -1
- package/dist-client/modeller/figure-side.d.ts +23 -0
- package/dist-client/modeller/figure-side.js +68 -16
- package/dist-client/modeller/figure-side.js.map +1 -1
- package/dist-client/modeller/proposal-session.d.ts +23 -0
- package/dist-client/modeller/proposal-session.js +65 -0
- package/dist-client/modeller/proposal-session.js.map +1 -0
- package/dist-client/pages/figure-list-page.d.ts +10 -0
- package/dist-client/pages/figure-list-page.js +55 -4
- package/dist-client/pages/figure-list-page.js.map +1 -1
- package/dist-client/pages/figure-modeller-page.d.ts +50 -28
- package/dist-client/pages/figure-modeller-page.js +197 -176
- package/dist-client/pages/figure-modeller-page.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/types.d.ts +44 -0
- package/dist-client/types.js.map +1 -1
- package/dist-client/viewparts/figure-thumb.js +74 -0
- package/dist-client/viewparts/figure-thumb.js.map +1 -1
- package/package.json +4 -5
- package/test/ai-proposal-contract.test.ts +169 -0
- package/test/i18n-prefix-guard.test.ts +42 -0
- package/translations/en.json +82 -6
- package/translations/ja.json +34 -1
- package/translations/ko.json +92 -6
- package/translations/ms.json +34 -1
- package/translations/zh.json +34 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"figure-animations.js","sourceRoot":"","sources":["../../client/modeller/figure-animations.ts"],"names":[],"mappings":"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","sourcesContent":["import { css, html, LitElement, nothing, type TemplateResult } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { live } from 'lit/directives/live.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport { ScrollbarStyles } from '@operato/styles'\n\nimport { AXES, CHANNEL_PATHS, CLIP_DRIVES, INTERPOLATIONS, LIMITS } from '@hatiolab/figure-model'\nimport type { ChannelPath, Vec3 } from '@hatiolab/figure-model'\n\nimport type { BoardModel, PartModel } from './figure-source.js'\n\n/**\n * 이 도형이 **어떻게 움직이는가.**\n *\n * ## 왜 탭 하나가 필요한가\n *\n * 움직임은 형식에 있고 런타임까지 서 있었다 — 검증이 촘촘히 보고, 청사진이 움직이는 부품을\n * 단독 묶음으로 빼고(합치면 따로 못 움직인다), 씬이 frameClock 을 구독해 실제로 돌린다.\n * 미리보기에도 clip 마다 슬라이더가 있었다.\n *\n * **만들 자리만 없었다.** 그래서 움직이는 도형을 얻는 길이 「AI 가 후보로 내놓는 것」 하나였고,\n * 채널 하나를 고치려면 손댈 화면이 없었다.\n *\n * ## 부품이 아니라 도형의 것이다\n *\n * clip 하나가 부품 여럿을 함께 움직인다 — 문 열림은 왼짝과 오른짝이 반대로 미끄러지는 것\n * 하나다. 그래서 부품을 고르고 「이 부품의 움직임」을 적는 형태가 될 수 없고, 속성 탭이 아니라\n * 자기 탭에 산다.\n *\n * ## 트윈은 타임라인을 재생하지 않는다\n *\n * 「문 열기 clip 을 재생하라」가 아니라 **「지금 40% 열려 있다」**다. 그래서 clip 마다 그 값을\n * 어떻게 받을지(`drive`)를 저작 시점에 정한다 — 도는 롤러는 배속을 받고, 열린 문은 0~1 위치를\n * 받는다. 이 선택이 화면에서 가장 설명이 필요한 자리다.\n */\n\ntype Clip = NonNullable<BoardModel['animations']>[number]\ntype Channel = Clip['channels'][number]\ntype Keyframe = Channel['keys'][number]\n\n/** A first-success motion: intent first, raw clip/channel editing second. */\ninterface MotionRecipe {\n id: 'conveyor' | 'gate' | 'pusher' | 'lift'\n name: string\n drive: Clip['drive']\n path: ChannelPath\n end: Vec3\n pivot?: Vec3\n}\n\nconst MOTION_RECIPES: readonly MotionRecipe[] = [\n { id: 'conveyor', name: 'conveyor-running', drive: 'loop', path: 'rotation', end: { x: 360, y: 0, z: 0 } },\n { id: 'gate', name: 'gate-open', drive: 'hold', path: 'rotation', end: { x: 0, y: 90, z: 0 }, pivot: { x: 0, y: 0, z: 0 } },\n { id: 'pusher', name: 'pusher-position', drive: 'hold', path: 'translation', end: { x: 100, y: 0, z: 0 } },\n { id: 'lift', name: 'lift-height', drive: 'hold', path: 'translation', end: { x: 0, y: 100, z: 0 } }\n]\n\n/** 새 clip 의 키 둘. **하나로 만들지 않는다** — 키 하나는 움직임이 아니라 자세다. */\nconst NEW_KEYS: Keyframe[] = [\n { at: 0, value: { x: 0, y: 0, z: 0 } },\n { at: 1, value: { x: 0, y: 0, z: 0 } }\n]\n\n/** `scale` 만 1 에서 시작한다 — 0 은 부품을 지운다. */\nfunction startValue(path: ChannelPath): Vec3 {\n return path === 'scale' ? { x: 1, y: 1, z: 1 } : { x: 0, y: 0, z: 0 }\n}\n\nfunction keysFor(path: ChannelPath): Keyframe[] {\n return NEW_KEYS.map(key => ({ at: key.at, value: startValue(path) }))\n}\n\n/** 이미 쓴 이름을 비켜 간다. `type` 처럼 저장되는 식별자는 아니지만 인스턴스가 이 이름으로 구동한다. */\nfunction freeName(taken: readonly Clip[]): string {\n const used = new Set(taken.map(clip => clip.name))\n for (let n = 1; ; n++) {\n const name = `clip-${n}`\n if (!used.has(name)) return name\n }\n}\n\nfunction freeRecipeName(taken: readonly Clip[], base: string): string {\n const used = new Set(taken.map(clip => clip.name))\n if (!used.has(base)) return base\n for (let n = 2; ; n++) if (!used.has(`${base}-${n}`)) return `${base}-${n}`\n}\n\n@customElement('figure-animations')\nexport class FigureAnimations extends localize(i18next)(LitElement) {\n static styles = [\n ScrollbarStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n min-height: 0;\n overflow-y: auto;\n background-color: var(--md-sys-color-surface-container-lowest);\n color: var(--md-sys-color-on-surface);\n font: var(--label-font, inherit);\n font-size: 0.76rem;\n }\n\n section {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-medium, 8px);\n padding: var(--spacing-large, 12px);\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n }\n\n /* 다른 탭들과 같은 눈금이다 */\n h3 {\n margin: 0 0 2px 0;\n font-size: var(--ui-t-micro, 11px);\n font-weight: 700;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n\n p[quiet] {\n margin: 0;\n font-size: 0.72rem;\n line-height: 1.5;\n color: var(--md-sys-color-on-surface-variant);\n }\n\n div[recipes] { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; }\n button[recipe] { min-height: 54px; align-items: flex-start; flex-direction: column; text-align: left; }\n button[recipe] strong { font-size: 0.74rem; }\n button[recipe] span { color: var(--md-sys-color-on-surface-variant); font-size: 0.68rem; line-height: 1.35; }\n\n /* ── clip ─────────────────────────────────────────────────────── */\n\n div[clip] {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-medium, 8px);\n padding: 8px;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 4px;\n }\n div[clip][on] {\n border-color: var(--md-sys-color-primary);\n background-color: var(--md-sys-color-surface-container-low);\n }\n\n div[head] {\n display: flex;\n align-items: center;\n gap: 6px;\n }\n div[head] input[name] {\n flex: 1;\n min-width: 0;\n }\n\n /* ── channel ──────────────────────────────────────────────────── */\n\n div[channel] {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 6px 8px;\n border-left: 2px solid var(--md-sys-color-outline-variant);\n background-color: var(--md-sys-color-surface-container-lowest);\n }\n div[channel][orphan] {\n border-left-color: var(--md-sys-color-error);\n }\n\n div[row] {\n display: flex;\n align-items: center;\n gap: 6px;\n }\n div[row] > label {\n flex: none;\n min-width: 3.2em;\n color: var(--md-sys-color-on-surface-variant);\n }\n\n /* ── keyframe ─────────────────────────────────────────────────── */\n\n table[keys] {\n width: 100%;\n border-collapse: collapse;\n }\n table[keys] th {\n padding: 0 2px 2px;\n font-size: 0.68rem;\n font-weight: 600;\n text-align: left;\n color: var(--md-sys-color-on-surface-variant);\n }\n table[keys] td {\n padding: 1px 2px;\n }\n table[keys] input {\n width: 100%;\n min-width: 0;\n }\n table[keys] td[drop] {\n width: 1.6em;\n }\n\n input,\n select {\n box-sizing: border-box;\n padding: 3px 5px;\n font: inherit;\n font-size: 0.74rem;\n color: var(--md-sys-color-on-surface);\n background-color: var(--md-sys-color-surface-container-lowest);\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 3px;\n }\n input:focus,\n select:focus {\n outline: none;\n border-color: var(--md-sys-color-primary);\n }\n input[bad] {\n border-color: var(--md-sys-color-error);\n }\n\n button {\n display: inline-flex;\n align-items: center;\n gap: 3px;\n padding: 3px 8px;\n font: inherit;\n font-size: 0.72rem;\n color: var(--md-sys-color-on-surface);\n background-color: var(--md-sys-color-surface-container);\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 3px;\n cursor: pointer;\n }\n button:hover:not(:disabled) {\n background-color: var(--md-sys-color-surface-container-high);\n }\n button:disabled {\n opacity: 0.4;\n cursor: default;\n }\n button[plain] {\n padding: 1px 4px;\n background: none;\n border: none;\n color: var(--md-sys-color-on-surface-variant);\n }\n button[plain]:hover:not(:disabled) {\n color: var(--md-sys-color-error);\n background: none;\n }\n button md-icon {\n font-size: 14px;\n width: 14px;\n height: 14px;\n }\n\n /* ── 예산 ─────────────────────────────────────────────────────── */\n\n div[budget] {\n display: flex;\n align-items: baseline;\n gap: 6px;\n }\n div[budget] strong {\n font-size: 0.86rem;\n font-weight: 700;\n }\n div[budget][over] strong {\n color: var(--md-sys-color-error);\n }\n\n p[warn] {\n margin: 0;\n font-size: 0.7rem;\n line-height: 1.5;\n color: var(--md-sys-color-error);\n }\n `\n ]\n\n @property({ type: Object }) board?: BoardModel\n @property({ type: Array }) parts: PartModel[] = []\n\n /** 펼쳐 둔 clip. 이름이 아니라 자리로 잡는다 — 이름은 편집 중에 바뀐다. */\n @state() private open = 0\n @state() private recipeTarget = ''\n\n private get clips(): Clip[] {\n return this.board?.animations ?? []\n }\n\n /** 부품 이름 — 채널이 이것을 가리킨다. */\n private get partNames(): string[] {\n return this.parts.map(part => part.name).filter(name => !!name)\n }\n\n /**\n * 몇 부품이 움직이나 — 채널이 가리키는 **서로 다른 부품**의 수.\n *\n * clip 수가 아니다. 한 부품을 세 clip 이 움직여도 그 부품 하나가 단독 묶음이 될 뿐이다.\n * 청사진이 묶음을 가르는 기준과 같은 셈이어야 화면의 수와 판정의 수가 맞는다.\n */\n private get movingParts(): number {\n const moving = new Set<string>()\n for (const clip of this.clips) for (const channel of clip.channels) moving.add(channel.target)\n return moving.size\n }\n\n render(): TemplateResult {\n if (!this.board) return html``\n\n return html`\n <section>\n <h3>${i18next.t('figure.label.animation')}</h3>\n <p quiet>${i18next.t('figure.text.animation-is-driven-by-a-fact')}</p>\n </section>\n\n ${this.renderRecipes()}\n ${this.clips.length === 0 ? nothing : this.renderClips()}\n\n <section>\n <button @click=${() => this.addClip()} ?disabled=${this.partNames.length === 0}>\n <md-icon>add</md-icon>${i18next.t('figure.button.add-clip')}\n </button>\n ${this.partNames.length === 0\n ? html`<p quiet>${i18next.t('figure.text.animation-needs-a-named-part')}</p>`\n : nothing}\n </section>\n\n ${this.clips.length === 0 ? nothing : this.renderBudget()}\n `\n }\n\n /**\n * 빈 화면 — **이 탭이 무엇인지 가르치는 자리다.**\n *\n * 「clip 을 추가하세요」라고만 쓰면 처음 여는 사람은 clip 이 무엇인지 모른 채 단추를 누른다.\n * 그래서 무엇을 만들 수 있는지 예를 들고, 그것이 어떻게 움직이는지(사실이 들어온다)를 적는다.\n */\n private renderRecipes(): TemplateResult {\n return html`\n <section>\n <h3>${i18next.t('figure.label.motion-intent')}</h3>\n <p quiet>${i18next.t('figure.text.motion-intent-help')}</p>\n ${this.partNames.length\n ? html`<select .value=${this.recipeTarget} @change=${(e: Event) => (this.recipeTarget = (e.target as HTMLSelectElement).value)}>\n ${this.partNames.map(name => html`<option value=${name}>${name}</option>`)}\n </select>\n <div recipes>\n ${MOTION_RECIPES.map(recipe => html`\n <button recipe @click=${() => this.addRecipe(recipe)}>\n <strong>${i18next.t('figure.label.recipe-' + recipe.id)}</strong>\n <span>${i18next.t('figure.text.recipe-' + recipe.id)}</span>\n </button>\n `)}\n </div>`\n : html`<p quiet>${i18next.t('figure.text.animation-needs-a-named-part')}</p>`}\n </section>\n `\n }\n\n private renderClips(): TemplateResult {\n return html`\n <section>\n ${this.clips.map((clip, at) => this.renderClip(clip, at))}\n </section>\n `\n }\n\n private renderClip(clip: Clip, at: number): TemplateResult {\n const on = this.open === at\n const duplicate = this.clips.some((other, i) => i !== at && other.name === clip.name)\n const nameless = !clip.name?.trim()\n\n return html`\n <div clip ?on=${on}>\n <div head>\n <button plain @click=${() => (this.open = on ? -1 : at)} title=${i18next.t('figure.label.channels')}>\n <md-icon>${on ? 'expand_more' : 'chevron_right'}</md-icon>\n </button>\n <input\n name\n .value=${live(clip.name ?? '')}\n ?bad=${duplicate || nameless}\n placeholder=${i18next.t('figure.label.clip-name')}\n title=${duplicate ? i18next.t('figure.text.clip-name-taken') : i18next.t('figure.text.clip-name-is-how-it-is-driven')}\n @change=${(e: Event) => this.renameClip(at, (e.target as HTMLInputElement).value)}\n />\n <select\n .value=${live(clip.drive ?? 'loop')}\n title=${i18next.t('figure.text.drive-explained')}\n @change=${(e: Event) => this.setDrive(at, (e.target as HTMLSelectElement).value as Clip['drive'])}\n >\n ${CLIP_DRIVES.map(\n drive => html`<option value=${drive}>${i18next.t('figure.label.drive-' + drive)}</option>`\n )}\n </select>\n <button plain @click=${() => this.removeClip(at)} title=${i18next.t('figure.button.remove')}>\n <md-icon>close</md-icon>\n </button>\n </div>\n\n ${duplicate ? html`<p warn>${i18next.t('figure.text.clip-name-taken')}</p>` : nothing}\n ${nameless ? html`<p warn>${i18next.t('figure.text.clip-needs-a-name')}</p>` : nothing}\n ${on ? this.renderChannels(clip, at) : nothing}\n </div>\n `\n }\n\n private renderChannels(clip: Clip, at: number): TemplateResult {\n return html`\n <p quiet>${i18next.t('figure.text.drive-' + (clip.drive ?? 'loop') + '-note')}</p>\n\n ${clip.channels.length === 0 ? html`<p warn>${i18next.t('figure.text.clip-needs-a-channel')}</p>` : nothing}\n ${clip.channels.map((channel, j) => this.renderChannel(channel, at, j))}\n\n <button @click=${() => this.addChannel(at)}>\n <md-icon>add</md-icon>${i18next.t('figure.button.add-channel')}\n </button>\n `\n }\n\n /**\n * 채널 하나 — 어느 부품의 어느 속성을 움직이나.\n *\n * 없는 부품을 가리키는 채널은 붉게 낸다. 부품 이름을 바꾸면 그 순간 이렇게 되는데,\n * 검증은 저장할 때 말하고 여기서는 **그 자리에서** 보여 준다.\n */\n private renderChannel(channel: Channel, at: number, j: number): TemplateResult {\n const orphan = !this.partNames.includes(channel.target)\n const turning = channel.path === 'rotation'\n\n return html`\n <div channel ?orphan=${orphan}>\n <div row>\n <label>${i18next.t('figure.label.channel-target')}</label>\n <select\n .value=${live(channel.target ?? '')}\n @change=${(e: Event) => this.setChannel(at, j, { target: (e.target as HTMLSelectElement).value })}\n >\n ${orphan ? html`<option value=${channel.target}>${channel.target}</option>` : nothing}\n ${this.partNames.map(name => html`<option value=${name}>${name}</option>`)}\n </select>\n <button plain @click=${() => this.removeChannel(at, j)} title=${i18next.t('figure.button.remove')}>\n <md-icon>close</md-icon>\n </button>\n </div>\n\n ${orphan ? html`<p warn>${i18next.t('figure.text.channel-target-is-gone')}</p>` : nothing}\n\n <div row>\n <label>${i18next.t('figure.label.channel-path')}</label>\n <select\n .value=${live(channel.path)}\n @change=${(e: Event) => this.setPath(at, j, (e.target as HTMLSelectElement).value as ChannelPath)}\n >\n ${CHANNEL_PATHS.map(path => html`<option value=${path}>${i18next.t('figure.label.path-' + path)}</option>`)}\n </select>\n <select\n .value=${live(channel.interpolation ?? 'linear')}\n title=${i18next.t('figure.text.interpolation-explained')}\n @change=${(e: Event) =>\n this.setChannel(at, j, { interpolation: (e.target as HTMLSelectElement).value as Channel['interpolation'] })}\n >\n ${INTERPOLATIONS.map(\n one => html`<option value=${one}>${i18next.t('figure.label.interpolation-' + one)}</option>`\n )}\n </select>\n </div>\n\n <p quiet>${i18next.t('figure.text.path-' + channel.path + '-unit')}</p>\n\n ${turning ? this.renderPivot(channel, at, j) : nothing}\n ${this.renderKeys(channel, at, j)}\n </div>\n `\n }\n\n /**\n * 회전 중심 — **`rotation` 에서만 뜻이 있다.**\n *\n * 그래서 경로가 rotation 일 때만 낸다. 언제나 내면 저작자가 옮김·배율에도 적고, 검증이\n * 저장할 때 「쓰이지 않는다」고 알린다. 안 보이면 적을 일이 없다.\n */\n private renderPivot(channel: Channel, at: number, j: number): TemplateResult {\n const pivot = channel.pivot\n const on = pivot !== undefined\n\n return html`\n <div row>\n <label>${i18next.t('figure.label.channel-pivot')}</label>\n <button\n @click=${() => this.setChannel(at, j, { pivot: on ? undefined : { x: 0, y: 0, z: 0 } })}\n title=${i18next.t('figure.text.pivot-defaults-to-the-part-centre')}\n >\n ${i18next.t(on ? 'figure.button.pivot-clear' : 'figure.button.pivot-set')}\n </button>\n ${on\n ? AXES.map(\n axis => html`\n <input\n type=\"number\"\n step=\"1\"\n .value=${live(String(pivot![axis]))}\n aria-label=${axis}\n @change=${(e: Event) =>\n this.setChannel(at, j, {\n pivot: { ...pivot!, [axis]: Number((e.target as HTMLInputElement).value) || 0 }\n })}\n />\n `\n )\n : nothing}\n </div>\n `\n }\n\n /**\n * 키프레임 표.\n *\n * **둘 미만으로 못 내려간다.** 하나뿐이면 움직이는 것이 아니라 자세 하나이고, 형식이\n * 오류로 막는다. 저장하고 나서 알려 주는 것보다 지우는 단추를 죽이는 것이 낫다.\n *\n * 시각이 오름차순이어야 하는 것도 형식의 규칙이다. 여기서는 어긋난 줄을 붉게 내고 값을\n * 고치지는 않는다 — 저작자가 3 을 1 로 고치던 중일 수 있다.\n */\n private renderKeys(channel: Channel, at: number, j: number): TemplateResult {\n const keys = channel.keys ?? []\n\n return html`\n <table keys>\n <tr>\n <th>${i18next.t('figure.label.key-at')}</th>\n ${AXES.map(axis => html`<th>${axis}</th>`)}\n <th></th>\n </tr>\n ${keys.map((key, k) => {\n const backwards = k > 0 && key.at <= (keys[k - 1]!.at ?? 0)\n return html`\n <tr>\n <td>\n <input\n type=\"number\"\n step=\"0.1\"\n min=\"0\"\n ?bad=${backwards || key.at < 0}\n .value=${live(String(key.at))}\n title=${backwards ? i18next.t('figure.text.keys-must-rise') : ''}\n @change=${(e: Event) =>\n this.setKey(at, j, k, { at: Math.max(0, Number((e.target as HTMLInputElement).value) || 0) })}\n />\n </td>\n ${AXES.map(\n axis => html`\n <td>\n <input\n type=\"number\"\n step=${channel.path === 'scale' ? '0.1' : '1'}\n .value=${live(String(key.value[axis]))}\n aria-label=${axis}\n @change=${(e: Event) =>\n this.setKey(at, j, k, {\n value: { ...key.value, [axis]: Number((e.target as HTMLInputElement).value) || 0 }\n })}\n />\n </td>\n `\n )}\n <td drop>\n <button\n plain\n ?disabled=${keys.length <= 2}\n title=${keys.length <= 2 ? i18next.t('figure.text.two-keys-at-least') : i18next.t('figure.button.remove')}\n @click=${() => this.removeKey(at, j, k)}\n >\n <md-icon>close</md-icon>\n </button>\n </td>\n </tr>\n `\n })}\n </table>\n\n ${keys.some((key, k) => k > 0 && key.at <= (keys[k - 1]!.at ?? 0))\n ? html`<p warn>${i18next.t('figure.text.keys-must-rise')}</p>`\n : nothing}\n\n <button @click=${() => this.addKey(at, j)}>\n <md-icon>add</md-icon>${i18next.t('figure.button.add-key')}\n </button>\n `\n }\n\n /**\n * 움직이는 부품 예산.\n *\n * **넘어도 막지 않는다.** 움직이는 부품은 저마다 단독 묶음이라 draw call 이 늘고 프레임마다\n * 갱신이 는다 — 그 대가가 수로 보이면 저작자가 아낀다. 랜드마크 설비 하나는 정교해도 되고,\n * 그 값이 도면 예산에 드러나면 된다.\n */\n private renderBudget(): TemplateResult {\n const used = this.movingParts\n const over = used > LIMITS.animatedParts\n\n return html`\n <section>\n <h3>${i18next.t('figure.label.budget-animations')}</h3>\n <div budget ?over=${over}>\n <strong>${used}</strong>\n <span quiet>/ ${LIMITS.animatedParts}</span>\n </div>\n <p quiet>${i18next.t('figure.text.animated-parts-cost')}</p>\n ${over ? html`<p warn>${i18next.t('figure.text.animated-parts-over')}</p>` : nothing}\n </section>\n `\n }\n\n // ─── 고치기 ───────────────────────────────────────────────────────────────\n\n /**\n * 보드에 실어 올려보낸다.\n *\n * `animations` 는 도형 전체의 것이므로 부품 편집(`update-part`)이 아니라 `update-board` 로\n * 간다. 빈 배열은 칸을 지운다 — clip 을 다 지운 도형은 `animations: []` 가 아니라 없는 것이다.\n */\n private tell(animations: Clip[]): void {\n if (!this.board) return\n\n const board: BoardModel = { ...this.board }\n if (animations.length === 0) delete board.animations\n else board.animations = animations\n\n this.dispatchEvent(new CustomEvent('update-board', { detail: { board }, bubbles: true, composed: true }))\n }\n\n private patchClip(at: number, patch: Partial<Clip>): void {\n this.tell(this.clips.map((clip, i) => (i === at ? { ...clip, ...patch } : clip)))\n }\n\n private patchChannel(at: number, j: number, next: Channel): void {\n const clip = this.clips[at]\n if (!clip) return\n this.patchClip(at, { channels: clip.channels.map((channel, i) => (i === j ? next : channel)) })\n }\n\n private addClip(): void {\n const target = this.partNames[0]\n if (!target) return\n\n const clip: Clip = {\n name: freeName(this.clips),\n drive: 'loop',\n channels: [{ target, path: 'translation', keys: keysFor('translation') }]\n }\n\n this.open = this.clips.length\n this.tell([...this.clips, clip])\n }\n\n /** Recipe creates a valid, editable two-pose clip; it never saves or publishes the Figure. */\n private addRecipe(recipe: MotionRecipe): void {\n const target = this.recipeTarget || this.partNames[0]\n if (!target) return\n const channel: Channel = {\n target,\n path: recipe.path,\n keys: [{ at: 0, value: startValue(recipe.path) }, { at: 1, value: { ...recipe.end } }]\n }\n if (recipe.pivot) channel.pivot = { ...recipe.pivot }\n this.open = this.clips.length\n this.tell([...this.clips, { name: freeRecipeName(this.clips, recipe.name), drive: recipe.drive, channels: [channel] }])\n }\n\n private renameClip(at: number, name: string): void {\n this.patchClip(at, { name: name.trim() })\n }\n\n private setDrive(at: number, drive: Clip['drive']): void {\n this.patchClip(at, { drive })\n }\n\n private removeClip(at: number): void {\n if (this.open >= at) this.open = Math.max(0, this.open - 1)\n this.tell(this.clips.filter((_, i) => i !== at))\n }\n\n private addChannel(at: number): void {\n const clip = this.clips[at]\n const target = this.partNames[0]\n if (!clip || !target) return\n\n this.patchClip(at, {\n channels: [...clip.channels, { target, path: 'translation', keys: keysFor('translation') }]\n })\n }\n\n private setChannel(at: number, j: number, patch: Partial<Channel>): void {\n const channel = this.clips[at]?.channels[j]\n if (!channel) return\n\n const next: Channel = { ...channel, ...patch }\n /* `undefined` 를 얹는 것은 지우는 뜻이다. 남겨 두면 저장 형식에 빈 칸이 실린다. */\n if ('pivot' in patch && patch.pivot === undefined) delete next.pivot\n this.patchChannel(at, j, next)\n }\n\n /**\n * 경로를 바꾼다 — **값까지 함께 옮긴다.**\n *\n * 옮김·회전의 0 은 「제자리」인데 배율의 0 은 **부품을 지운다.** 경로만 바꾸고 값을 그대로\n * 두면 저작자는 부품이 사라진 것을 보고 도구가 고장 난 줄 안다. 그래서 옮김·회전 ↔ 배율을\n * 건널 때 시작값을 그 경로의 제자리로 되돌린다. 시각은 지킨다 — 저작자가 짠 박자다.\n */\n private setPath(at: number, j: number, path: ChannelPath): void {\n const channel = this.clips[at]?.channels[j]\n if (!channel) return\n\n const crossing = (channel.path === 'scale') !== (path === 'scale')\n const next: Channel = {\n ...channel,\n path,\n keys: crossing ? channel.keys.map(key => ({ at: key.at, value: startValue(path) })) : channel.keys\n }\n /* 회전이 아니면 회전 중심은 쓰이지 않는다. 남겨 두면 검증이 「쓰이지 않는다」고 알린다. */\n if (path !== 'rotation') delete next.pivot\n\n this.patchChannel(at, j, next)\n }\n\n private removeChannel(at: number, j: number): void {\n const clip = this.clips[at]\n if (!clip) return\n this.patchClip(at, { channels: clip.channels.filter((_, i) => i !== j) })\n }\n\n /** 마지막 키에서 1 초 뒤. 값은 그 키를 그대로 받는다 — 붙여 놓고 고치는 것이 짜기 쉽다. */\n private addKey(at: number, j: number): void {\n const channel = this.clips[at]?.channels[j]\n if (!channel) return\n\n const last = channel.keys[channel.keys.length - 1]\n const key: Keyframe = { at: (last?.at ?? 0) + 1, value: { ...(last?.value ?? startValue(channel.path)) } }\n this.patchChannel(at, j, { ...channel, keys: [...channel.keys, key] })\n }\n\n private setKey(at: number, j: number, k: number, patch: Partial<Keyframe>): void {\n const channel = this.clips[at]?.channels[j]\n if (!channel) return\n this.patchChannel(at, j, {\n ...channel,\n keys: channel.keys.map((key, i) => (i === k ? { ...key, ...patch } : key))\n })\n }\n\n /** 둘 미만으로 내려가지 않는다 — 단추가 이미 죽어 있지만 밖에서 불릴 수 있다. */\n private removeKey(at: number, j: number, k: number): void {\n const channel = this.clips[at]?.channels[j]\n if (!channel || channel.keys.length <= 2) return\n this.patchChannel(at, j, { ...channel, keys: channel.keys.filter((_, i) => i !== k) })\n }\n}\n"]}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import '@material/web/icon/icon.js';
|
|
2
2
|
import { LitElement } from 'lit';
|
|
3
3
|
import type { FigureSource } from '@hatiolab/figure-model';
|
|
4
|
+
import type { ProposalFeedback } from '../types.js';
|
|
4
5
|
declare const FigureAsk_base: (new (...args: any[]) => LitElement) & typeof LitElement;
|
|
5
6
|
/**
|
|
6
7
|
* 말로 시키는 줄.
|
|
@@ -26,6 +27,8 @@ export declare class FigureAsk extends FigureAsk_base {
|
|
|
26
27
|
source?: FigureSource;
|
|
27
28
|
/** 새로 만들 때 쓸 타입 이름. */
|
|
28
29
|
type: string;
|
|
30
|
+
/** 이 모델러 세션에서 사람이 명시적으로 남긴 이전 후보 반응. */
|
|
31
|
+
feedback: ProposalFeedback[];
|
|
29
32
|
private asking;
|
|
30
33
|
private failure;
|
|
31
34
|
private picture?;
|
|
@@ -28,6 +28,8 @@ let FigureAsk = class FigureAsk extends localize(i18next)(LitElement) {
|
|
|
28
28
|
super(...arguments);
|
|
29
29
|
/** 새로 만들 때 쓸 타입 이름. */
|
|
30
30
|
this.type = '';
|
|
31
|
+
/** 이 모델러 세션에서 사람이 명시적으로 남긴 이전 후보 반응. */
|
|
32
|
+
this.feedback = [];
|
|
31
33
|
this.asking = false;
|
|
32
34
|
this.failure = '';
|
|
33
35
|
}
|
|
@@ -224,6 +226,9 @@ let FigureAsk = class FigureAsk extends localize(i18next)(LitElement) {
|
|
|
224
226
|
type: this.source ? undefined : this.type,
|
|
225
227
|
// 그리는 쪽이 풀 수 있는 토큰만 보낸다. 서버는 이 목록 밖의 색을 거절한다.
|
|
226
228
|
palette: Object.keys(activePalette()),
|
|
229
|
+
// 구조적으로는 맞지만 품질 근거가 남으면 한 번 더 개선 후보를 시킨다.
|
|
230
|
+
refine: true,
|
|
231
|
+
feedback: this.feedback,
|
|
227
232
|
image: this.picture
|
|
228
233
|
});
|
|
229
234
|
this.dispatchEvent(new CustomEvent('proposed', { detail: { proposal }, bubbles: true, composed: true }));
|
|
@@ -247,6 +252,10 @@ __decorate([
|
|
|
247
252
|
property({ type: String }),
|
|
248
253
|
__metadata("design:type", Object)
|
|
249
254
|
], FigureAsk.prototype, "type", void 0);
|
|
255
|
+
__decorate([
|
|
256
|
+
property({ type: Array }),
|
|
257
|
+
__metadata("design:type", Array)
|
|
258
|
+
], FigureAsk.prototype, "feedback", void 0);
|
|
250
259
|
__decorate([
|
|
251
260
|
state(),
|
|
252
261
|
__metadata("design:type", Object)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"figure-ask.js","sourceRoot":"","sources":["../../client/modeller/figure-ask.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\n\nimport { css, html, LitElement, nothing } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport type { FigureSource } from '@hatiolab/figure-model'\nimport { activePalette } from '@hatiolab/things-scene'\n\nimport { proposeFigure } from '../graphql/index.js'\nimport type { FigureProposal } from '../types.js'\n\n/**\n * 말로 시키는 줄.\n *\n * ## 무엇을 시키는지가 화면에 보여야 한다\n *\n * 정본이 있으면 **고치는 것**이고, 없으면 **만드는 것**이다. 같은 칸으로 두 가지를\n * 하면 저작자가 「이게 지금 것을 덮어쓰나」를 매번 걱정한다. 그래서 무엇을 할지\n * 칸 옆에 적어 둔다.\n *\n * ## 후보를 여기서 받지 않는다\n *\n * 이 컴포넌트는 시키고 결과를 **알릴 뿐**이다. 받을지 버릴지는 저작면이 정한다 —\n * 정본은 한 벌이고 그 한 벌을 가진 자리가 정해야 한다.\n *\n * ## 팔레트를 함께 보낸다\n *\n * 서버는 팔레트를 모른다. 그리는 쪽이 어떤 토큰을 풀 수 있는지 아는 유일한 자리다.\n */\n@customElement('figure-ask')\nexport class FigureAsk extends localize(i18next)(LitElement) {\n static styles = css`\n /*\n 이 부품은 **자기 줄을 갖지 않는다.**\n\n 전에는 테두리와 여백으로 스스로 한 줄이 되었다. 저작면의 머리줄 바로 아래에 놓이니\n 화면 위쪽이 두 줄이 되었고, 위 줄은 이름을 페이지 제목과 겹쳐 적고 있었다. 캔버스가\n 주인공인 화면에서 그 두 줄은 비싸다.\n\n 이제 머리줄 안에 들어가 그 줄의 남는 폭을 쓴다. 테두리·여백은 담는 줄이 갖는다.\n */\n :host {\n display: flex;\n flex: 1;\n min-width: 0;\n font: var(--label-font, inherit);\n /*\n 이 줄의 글자 크기를 여기서 정한다.\n\n 안 적어 두면 브라우저 기본 크기(16px)를 물려받는다. 아래 패널들은 전부 12px\n 언저리라 위 두 줄만 커 보이고, 단추가 화면 전체와 따로 논다.\n\n form 요소(input · button)는 글꼴을 물려받지 않으므로 각자 적어야 한다.\n */\n font-size: 0.76rem;\n }\n\n form {\n display: flex;\n flex: 1;\n min-width: 0;\n align-items: center;\n gap: var(--spacing-medium, 8px);\n }\n\n md-icon[lead] {\n --md-icon-size: 18px;\n flex: none;\n color: var(--md-sys-color-primary);\n }\n\n input[ask] {\n flex: 1;\n min-width: 0;\n padding: 6px 10px;\n font: var(--input-field-font, inherit);\n font-size: 0.78rem;\n color: var(--md-sys-color-on-surface);\n background-color: var(--md-sys-color-surface-container-lowest);\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n }\n input[ask]:focus {\n outline: 2px solid var(--md-sys-color-primary);\n outline-offset: -1px;\n }\n input[ask][disabled] {\n opacity: 0.5;\n }\n\n /* 무엇을 시키는 것인지 — 만드나 고치나 */\n span[mode] {\n flex: none;\n font-size: 0.72rem;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n\n /* 그림 붙이기. 붙인 것이 있으면 이름을 보여 준다 */\n label[picture] {\n display: flex;\n align-items: center;\n gap: 4px;\n flex: none;\n padding: 5px 9px;\n font-size: 0.74rem;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n cursor: pointer;\n }\n label[picture]:hover {\n background-color: var(--md-sys-color-surface-container);\n }\n label[picture][has] {\n color: var(--md-sys-color-on-primary-container);\n background-color: var(--md-sys-color-primary-container);\n border-color: transparent;\n }\n label[picture] md-icon {\n --md-icon-size: 16px;\n }\n label[picture] input {\n display: none;\n }\n span[filename] {\n max-width: 8em;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n /*\n 채운 파랑 단추를 저장과 나눠 갖지 않는다.\n\n 머리줄의 저장이 이 화면의 주 동작이다. 바로 아래 줄에 같은 파랑 단추가 하나 더\n 있으면 어느 쪽이 주인지 눈이 못 고른다. 이쪽은 톤 단추로 한 단계 내린다.\n */\n button[go] {\n flex: none;\n padding: 6px 14px;\n font: var(--label-font, inherit);\n font-size: 0.76rem;\n font-weight: 600;\n color: var(--md-sys-color-on-primary-container);\n background-color: var(--md-sys-color-primary-container);\n border: none;\n border-radius: 6px;\n cursor: pointer;\n }\n button[go]:hover:not([disabled]) {\n background-color: color-mix(in srgb, var(--md-sys-color-primary) 24%, var(--md-sys-color-primary-container));\n }\n button[go][disabled] {\n opacity: 0.4;\n cursor: default;\n }\n\n /* 못 만들었을 때. 사유를 삼키지 않는다 — 말을 고쳐 다시 시킬 단서다 */\n div[failed] {\n display: flex;\n gap: 6px;\n padding: 0 var(--spacing-large, 12px) var(--spacing-medium, 8px) var(--spacing-large, 12px);\n font-size: 0.74rem;\n line-height: 1.5;\n color: var(--md-sys-color-error);\n }\n div[failed] md-icon {\n --md-icon-size: 16px;\n flex: none;\n }\n `\n\n /** 지금 정본. 있으면 고치는 것이고 없으면 만드는 것이다. */\n @property({ type: Object }) source?: FigureSource\n /** 새로 만들 때 쓸 타입 이름. */\n @property({ type: String }) type = ''\n\n @state() private asking = false\n @state() private failure = ''\n @state() private picture?: File\n\n render() {\n const revising = !!this.source\n\n return html`\n <form @submit=${(e: Event) => this.submit(e)}>\n <md-icon lead>auto_awesome</md-icon>\n <span mode>${i18next.t(revising ? 'figure.text.ask-to-revise' : 'figure.text.ask-to-create')}</span>\n <input\n ask\n ?disabled=${this.asking}\n placeholder=${i18next.t(revising ? 'figure.text.ask-revise-example' : 'figure.text.ask-create-example')}\n />\n <label picture ?has=${!!this.picture} title=${i18next.t('figure.text.picture-is-for-proportions')}>\n <md-icon>image</md-icon>\n ${this.picture\n ? html`<span filename>${this.picture.name}</span>`\n : i18next.t('figure.button.attach-a-picture')}\n <input type=\"file\" accept=\"image/*\" @change=${(e: Event) => this.pick(e)} />\n </label>\n <button go ?disabled=${this.asking}>\n ${this.asking ? i18next.t('figure.text.asking') : i18next.t('figure.button.ask')}\n </button>\n </form>\n\n ${this.failure\n ? html`\n <div failed>\n <md-icon>error</md-icon>\n <span>${this.failure}</span>\n </div>\n `\n : nothing}\n `\n }\n\n private pick(e: Event) {\n const input = e.target as HTMLInputElement\n this.picture = input.files?.[0]\n }\n\n private async submit(e: Event) {\n e.preventDefault()\n\n const input = this.renderRoot.querySelector('input[ask]') as HTMLInputElement\n const prompt = input?.value?.trim()\n if (!prompt || this.asking) {\n return\n }\n\n this.asking = true\n this.failure = ''\n\n try {\n const proposal = await proposeFigure({\n prompt,\n base: this.source ? JSON.stringify(this.source) : undefined,\n type: this.source ? undefined : this.type,\n // 그리는 쪽이 풀 수 있는 토큰만 보낸다. 서버는 이 목록 밖의 색을 거절한다.\n palette: Object.keys(activePalette() as Record<string, string>),\n image: this.picture\n })\n\n this.dispatchEvent(\n new CustomEvent('proposed', { detail: { proposal }, bubbles: true, composed: true })\n )\n input.value = ''\n this.picture = undefined\n } catch (err) {\n // 사유를 그대로 보여 준다. 「실패했습니다」로 뭉개면 말을 어떻게 고칠지 모른다.\n this.failure = (err as Error).message\n } finally {\n this.asking = false\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"figure-ask.js","sourceRoot":"","sources":["../../client/modeller/figure-ask.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\n\nimport { css, html, LitElement, nothing } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport type { FigureSource } from '@hatiolab/figure-model'\nimport { activePalette } from '@hatiolab/things-scene'\n\nimport { proposeFigure } from '../graphql/index.js'\nimport type { FigureProposal, ProposalFeedback } from '../types.js'\n\n/**\n * 말로 시키는 줄.\n *\n * ## 무엇을 시키는지가 화면에 보여야 한다\n *\n * 정본이 있으면 **고치는 것**이고, 없으면 **만드는 것**이다. 같은 칸으로 두 가지를\n * 하면 저작자가 「이게 지금 것을 덮어쓰나」를 매번 걱정한다. 그래서 무엇을 할지\n * 칸 옆에 적어 둔다.\n *\n * ## 후보를 여기서 받지 않는다\n *\n * 이 컴포넌트는 시키고 결과를 **알릴 뿐**이다. 받을지 버릴지는 저작면이 정한다 —\n * 정본은 한 벌이고 그 한 벌을 가진 자리가 정해야 한다.\n *\n * ## 팔레트를 함께 보낸다\n *\n * 서버는 팔레트를 모른다. 그리는 쪽이 어떤 토큰을 풀 수 있는지 아는 유일한 자리다.\n */\n@customElement('figure-ask')\nexport class FigureAsk extends localize(i18next)(LitElement) {\n static styles = css`\n /*\n 이 부품은 **자기 줄을 갖지 않는다.**\n\n 전에는 테두리와 여백으로 스스로 한 줄이 되었다. 저작면의 머리줄 바로 아래에 놓이니\n 화면 위쪽이 두 줄이 되었고, 위 줄은 이름을 페이지 제목과 겹쳐 적고 있었다. 캔버스가\n 주인공인 화면에서 그 두 줄은 비싸다.\n\n 이제 머리줄 안에 들어가 그 줄의 남는 폭을 쓴다. 테두리·여백은 담는 줄이 갖는다.\n */\n :host {\n display: flex;\n flex: 1;\n min-width: 0;\n font: var(--label-font, inherit);\n /*\n 이 줄의 글자 크기를 여기서 정한다.\n\n 안 적어 두면 브라우저 기본 크기(16px)를 물려받는다. 아래 패널들은 전부 12px\n 언저리라 위 두 줄만 커 보이고, 단추가 화면 전체와 따로 논다.\n\n form 요소(input · button)는 글꼴을 물려받지 않으므로 각자 적어야 한다.\n */\n font-size: 0.76rem;\n }\n\n form {\n display: flex;\n flex: 1;\n min-width: 0;\n align-items: center;\n gap: var(--spacing-medium, 8px);\n }\n\n md-icon[lead] {\n --md-icon-size: 18px;\n flex: none;\n color: var(--md-sys-color-primary);\n }\n\n input[ask] {\n flex: 1;\n min-width: 0;\n padding: 6px 10px;\n font: var(--input-field-font, inherit);\n font-size: 0.78rem;\n color: var(--md-sys-color-on-surface);\n background-color: var(--md-sys-color-surface-container-lowest);\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n }\n input[ask]:focus {\n outline: 2px solid var(--md-sys-color-primary);\n outline-offset: -1px;\n }\n input[ask][disabled] {\n opacity: 0.5;\n }\n\n /* 무엇을 시키는 것인지 — 만드나 고치나 */\n span[mode] {\n flex: none;\n font-size: 0.72rem;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n\n /* 그림 붙이기. 붙인 것이 있으면 이름을 보여 준다 */\n label[picture] {\n display: flex;\n align-items: center;\n gap: 4px;\n flex: none;\n padding: 5px 9px;\n font-size: 0.74rem;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n cursor: pointer;\n }\n label[picture]:hover {\n background-color: var(--md-sys-color-surface-container);\n }\n label[picture][has] {\n color: var(--md-sys-color-on-primary-container);\n background-color: var(--md-sys-color-primary-container);\n border-color: transparent;\n }\n label[picture] md-icon {\n --md-icon-size: 16px;\n }\n label[picture] input {\n display: none;\n }\n span[filename] {\n max-width: 8em;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n /*\n 채운 파랑 단추를 저장과 나눠 갖지 않는다.\n\n 머리줄의 저장이 이 화면의 주 동작이다. 바로 아래 줄에 같은 파랑 단추가 하나 더\n 있으면 어느 쪽이 주인지 눈이 못 고른다. 이쪽은 톤 단추로 한 단계 내린다.\n */\n button[go] {\n flex: none;\n padding: 6px 14px;\n font: var(--label-font, inherit);\n font-size: 0.76rem;\n font-weight: 600;\n color: var(--md-sys-color-on-primary-container);\n background-color: var(--md-sys-color-primary-container);\n border: none;\n border-radius: 6px;\n cursor: pointer;\n }\n button[go]:hover:not([disabled]) {\n background-color: color-mix(in srgb, var(--md-sys-color-primary) 24%, var(--md-sys-color-primary-container));\n }\n button[go][disabled] {\n opacity: 0.4;\n cursor: default;\n }\n\n /* 못 만들었을 때. 사유를 삼키지 않는다 — 말을 고쳐 다시 시킬 단서다 */\n div[failed] {\n display: flex;\n gap: 6px;\n padding: 0 var(--spacing-large, 12px) var(--spacing-medium, 8px) var(--spacing-large, 12px);\n font-size: 0.74rem;\n line-height: 1.5;\n color: var(--md-sys-color-error);\n }\n div[failed] md-icon {\n --md-icon-size: 16px;\n flex: none;\n }\n `\n\n /** 지금 정본. 있으면 고치는 것이고 없으면 만드는 것이다. */\n @property({ type: Object }) source?: FigureSource\n /** 새로 만들 때 쓸 타입 이름. */\n @property({ type: String }) type = ''\n /** 이 모델러 세션에서 사람이 명시적으로 남긴 이전 후보 반응. */\n @property({ type: Array }) feedback: ProposalFeedback[] = []\n\n @state() private asking = false\n @state() private failure = ''\n @state() private picture?: File\n\n render() {\n const revising = !!this.source\n\n return html`\n <form @submit=${(e: Event) => this.submit(e)}>\n <md-icon lead>auto_awesome</md-icon>\n <span mode>${i18next.t(revising ? 'figure.text.ask-to-revise' : 'figure.text.ask-to-create')}</span>\n <input\n ask\n ?disabled=${this.asking}\n placeholder=${i18next.t(revising ? 'figure.text.ask-revise-example' : 'figure.text.ask-create-example')}\n />\n <label picture ?has=${!!this.picture} title=${i18next.t('figure.text.picture-is-for-proportions')}>\n <md-icon>image</md-icon>\n ${this.picture\n ? html`<span filename>${this.picture.name}</span>`\n : i18next.t('figure.button.attach-a-picture')}\n <input type=\"file\" accept=\"image/*\" @change=${(e: Event) => this.pick(e)} />\n </label>\n <button go ?disabled=${this.asking}>\n ${this.asking ? i18next.t('figure.text.asking') : i18next.t('figure.button.ask')}\n </button>\n </form>\n\n ${this.failure\n ? html`\n <div failed>\n <md-icon>error</md-icon>\n <span>${this.failure}</span>\n </div>\n `\n : nothing}\n `\n }\n\n private pick(e: Event) {\n const input = e.target as HTMLInputElement\n this.picture = input.files?.[0]\n }\n\n private async submit(e: Event) {\n e.preventDefault()\n\n const input = this.renderRoot.querySelector('input[ask]') as HTMLInputElement\n const prompt = input?.value?.trim()\n if (!prompt || this.asking) {\n return\n }\n\n this.asking = true\n this.failure = ''\n\n try {\n const proposal = await proposeFigure({\n prompt,\n base: this.source ? JSON.stringify(this.source) : undefined,\n type: this.source ? undefined : this.type,\n // 그리는 쪽이 풀 수 있는 토큰만 보낸다. 서버는 이 목록 밖의 색을 거절한다.\n palette: Object.keys(activePalette() as Record<string, string>),\n // 구조적으로는 맞지만 품질 근거가 남으면 한 번 더 개선 후보를 시킨다.\n refine: true,\n feedback: this.feedback,\n image: this.picture\n })\n\n this.dispatchEvent(\n new CustomEvent('proposed', { detail: { proposal }, bubbles: true, composed: true })\n )\n input.value = ''\n this.picture = undefined\n } catch (err) {\n // 사유를 그대로 보여 준다. 「실패했습니다」로 뭉개면 말을 어떻게 고칠지 모른다.\n this.failure = (err as Error).message\n } finally {\n this.asking = false\n }\n }\n}\n"]}
|
|
@@ -49,6 +49,13 @@ export declare class FigureHistoryPanel extends FigureHistoryPanel_base {
|
|
|
49
49
|
* 준다 — 화면이 문장을 지으면 서버가 거절할 때와 다른 말을 하게 된다.
|
|
50
50
|
*/
|
|
51
51
|
private renderFinding;
|
|
52
|
+
/**
|
|
53
|
+
* 게이트는 수정을 저장하지 않는다. 부모 모델러가 현재 정본과 이 한 수정안을 나란히 렌더하고,
|
|
54
|
+
* 저작자가 '변경 받기'를 눌렀을 때에만 자기 작업 사본에 적용한다.
|
|
55
|
+
*/
|
|
56
|
+
private previewFix;
|
|
57
|
+
/** 서버가 전수 재검사해 모든 차단 split이 사라진 후보만 작업 사본에 원클릭 적용한다. 저장은 하지 않는다. */
|
|
58
|
+
private applyFix;
|
|
52
59
|
private renderVersion;
|
|
53
60
|
private release;
|
|
54
61
|
private revert;
|
|
@@ -257,6 +257,33 @@ let FigureHistoryPanel = class FigureHistoryPanel extends localize(i18next)(LitE
|
|
|
257
257
|
margin-top: 1px;
|
|
258
258
|
}
|
|
259
259
|
|
|
260
|
+
/* 설명으로 끝내지 않는다. 이 단추는 원본을 바꾸지 않고 모델러에 후보만 연다. */
|
|
261
|
+
li[finding] button[preview-fix] {
|
|
262
|
+
margin-top: 3px;
|
|
263
|
+
padding: 4px 7px;
|
|
264
|
+
font-weight: 700;
|
|
265
|
+
color: var(--md-sys-color-on-primary-container, var(--md-sys-color-on-surface));
|
|
266
|
+
background-color: var(--md-sys-color-primary-container, transparent);
|
|
267
|
+
border: 0;
|
|
268
|
+
border-radius: 5px;
|
|
269
|
+
cursor: pointer;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
li[finding] button[preview-fix]:hover {
|
|
273
|
+
filter: brightness(1.08);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
li[finding] button[apply-fix] {
|
|
277
|
+
margin-top: 3px;
|
|
278
|
+
padding: 4px 7px;
|
|
279
|
+
font-weight: 700;
|
|
280
|
+
color: var(--ui-on-accent, #fff);
|
|
281
|
+
background-color: var(--md-sys-color-primary);
|
|
282
|
+
border: 0;
|
|
283
|
+
border-radius: 5px;
|
|
284
|
+
cursor: pointer;
|
|
285
|
+
}
|
|
286
|
+
|
|
260
287
|
p[quiet] {
|
|
261
288
|
margin: 0;
|
|
262
289
|
line-height: 1.6;
|
|
@@ -386,9 +413,32 @@ let FigureHistoryPanel = class FigureHistoryPanel extends localize(i18next)(LitE
|
|
|
386
413
|
${finding.at ? html `<code where>${finding.at}</code>` : nothing}
|
|
387
414
|
${finding.why ? html `<span why-line>${finding.why}</span>` : nothing}
|
|
388
415
|
${finding.how ? html `<span how-line><md-icon>build</md-icon>${finding.how}</span>` : nothing}
|
|
416
|
+
${(finding.fixes ?? []).map(fix => html `
|
|
417
|
+
${fix.safe ? html `
|
|
418
|
+
<button apply-fix @click=${() => this.applyFix(fix)}>
|
|
419
|
+
안전하게 수정 적용 · ${fix.part}.anchor.${fix.axis}: ${fix.from ?? 'auto'} → ${fix.to}
|
|
420
|
+
</button>
|
|
421
|
+
` : nothing}
|
|
422
|
+
<button preview-fix @click=${() => this.previewFix(fix)}>
|
|
423
|
+
수정안 미리보기 · ${fix.part}.anchor.${fix.axis}: ${fix.from ?? 'auto'} → ${fix.to}
|
|
424
|
+
</button>
|
|
425
|
+
`)}
|
|
389
426
|
</li>
|
|
390
427
|
`;
|
|
391
428
|
}
|
|
429
|
+
/**
|
|
430
|
+
* 게이트는 수정을 저장하지 않는다. 부모 모델러가 현재 정본과 이 한 수정안을 나란히 렌더하고,
|
|
431
|
+
* 저작자가 '변경 받기'를 눌렀을 때에만 자기 작업 사본에 적용한다.
|
|
432
|
+
*/
|
|
433
|
+
previewFix(fix) {
|
|
434
|
+
this.dispatchEvent(new CustomEvent('preview-sizing-fix', { detail: fix, bubbles: true, composed: true }));
|
|
435
|
+
}
|
|
436
|
+
/** 서버가 전수 재검사해 모든 차단 split이 사라진 후보만 작업 사본에 원클릭 적용한다. 저장은 하지 않는다. */
|
|
437
|
+
applyFix(fix) {
|
|
438
|
+
if (!fix.safe)
|
|
439
|
+
return;
|
|
440
|
+
this.dispatchEvent(new CustomEvent('apply-sizing-fix', { detail: fix, bubbles: true, composed: true }));
|
|
441
|
+
}
|
|
392
442
|
renderVersion(version) {
|
|
393
443
|
return html `
|
|
394
444
|
<li>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"figure-history-panel.js","sourceRoot":"","sources":["../../client/modeller/figure-history-panel.ts"],"names":[],"mappings":";AAAA,OAAO,4BAA4B,CAAA;AAEnC,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,KAAK,CAAA;AACpD,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAElE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAEjD,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;AAG5G;;;;;;;;;;;;;;;;;;GAkBG;AAEI,IAAM,kBAAkB,GAAxB,MAAM,kBAAmB,SAAQ,QAAQ,CAAC,OAAO,CAAC,CAAC,UAAU,CAAC;IAA9D;;QA2OuB,kBAAa,GAAG,CAAC,CAAA;QAC7C,mCAAmC;QACN,UAAK,GAAG,KAAK,CAAA;QAEzB,aAAQ,GAAoB,EAAE,CAAA;QAC9B,SAAI,GAAG,KAAK,CAAA;QACZ,YAAO,GAAG,EAAE,CAAA;IA4M/B,CAAC;aA5bQ,WAAM,GAAG;QACd,eAAe;QACf,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkOJ;KACA,AArOY,CAqOZ;IAkBD,OAAO,CAAC,OAA6B;QACnC,IAAI,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,EAAE,CAAC;YAC5D,KAAK,IAAI,CAAC,MAAM,EAAE,CAAA;QACpB,CAAC;QAED;;;UAGE;QACF,IAAI,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;YACpD,KAAK,IAAI,CAAC,OAAO,EAAE,CAAA;QACrB,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,OAAO;QACnB,IAAI,CAAC,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACjC,IAAI,CAAC,UAAU,GAAG,SAAS,CAAA;YAC3B,OAAM;QACR,CAAC;QAED,IAAI,CAAC;YACH,IAAI,CAAC,UAAU,GAAG,MAAM,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;QACtD,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,sDAAsD;YACtD,IAAI,CAAC,UAAU,GAAG,SAAS,CAAA;YAC3B,IAAI,CAAC,OAAO,GAAI,CAAW,CAAC,OAAO,CAAA;QACrC,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,MAAM;QAClB,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnB,IAAI,CAAC,QAAQ,GAAG,EAAE,CAAA;YAClB,OAAM;QACR,CAAC;QAED,IAAI,CAAC;YACH,IAAI,CAAC,QAAQ,GAAG,MAAM,mBAAmB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;YACxD,IAAI,CAAC,OAAO,GAAG,EAAE,CAAA;QACnB,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,IAAI,CAAC,OAAO,GAAI,CAAW,CAAC,OAAO,CAAA;QACrC,CAAC;IACH,CAAC;IAED,MAAM;QACJ,OAAO,IAAI,CAAA;;cAED,OAAO,CAAC,CAAC,CAAC,sBAAsB,CAAC;UACrC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC,IAAI,CAAA,YAAY,OAAO,CAAC,CAAC,CAAC,8CAA8C,CAAC,MAAM;;;;cAIlH,OAAO,CAAC,CAAC,CAAC,gCAAgC,CAAC;UAC/C,IAAI,CAAC,QAAQ,CAAC,MAAM;YACpB,CAAC,CAAC,IAAI,CAAA;gBACA,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;kBACvD;YACR,CAAC,CAAC,IAAI,CAAA,YAAY,OAAO,CAAC,CAAC,CAAC,kCAAkC,CAAC,MAAM;;KAE1E,CAAA;IACH,CAAC;IAEO,aAAa;QACnB,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW,KAAK,UAAU,CAAA;QAEhD;;;UAGE;QACF,MAAM,QAAQ,GAAG,CAAC,IAAI,CAAC,UAAU,EAAE,QAAQ,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAA;QACtF,MAAM,QAAQ,GAAG,CAAC,IAAI,CAAC,UAAU,EAAE,QAAQ,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAA;QAEvF;;;;;;;;UAQE;QACF,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK;YACpB,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,wCAAwC,CAAC;YACrD,CAAC,CAAC,QAAQ;gBACR,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,oDAAoD,CAAC;gBACjE,CAAC,CAAC,QAAQ,CAAC,MAAM;oBACf,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,gCAAgC,CAAC;oBAC7C,CAAC,CAAC,EAAE,CAAA;QAEV,OAAO,IAAI,CAAA;;gCAEiB,QAAQ,IAAI,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,8BAA8B,CAAC,CAAC,CAAC,oBAAoB,CAAC;oBACnG,OAAO,CAAC,CAAC,CAAC,4BAA4B,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,aAAa,IAAI,CAAC,EAAE,CAAC;;;gCAGjE,OAAO,CAAC,CAAC,CAAC,0CAA0C,CAAC,cAAc,CAAC,CAAC,GAAG,IAAI,IAAI,CAAC,IAAI;;kCAEnF,CAAC,CAAC,GAAG,IAAI,IAAI,CAAC,IAAI,WAAW,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE;UACzE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,uBAAuB,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,8BAA8B,CAAC;;;QAG5F,GAAG,CAAC,CAAC,CAAC,IAAI,CAAA,UAAU,GAAG,MAAM,CAAC,CAAC,CAAC,OAAO;QACvC,QAAQ,CAAC,MAAM;YACf,CAAC,CAAC,IAAI,CAAA;uBACS,OAAO,CAAC,CAAC,CAAC,iCAAiC,CAAC;cACrD,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;gBACxD;YACR,CAAC,CAAC,OAAO;QACT,QAAQ,CAAC,MAAM;YACf,CAAC,CAAC,IAAI,CAAA;6BACe,OAAO,CAAC,CAAC,CAAC,4BAA4B,CAAC;cACtD,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;gBACzD;YACR,CAAC,CAAC,OAAO;QACT,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAA,aAAa,IAAI,CAAC,OAAO,MAAM,CAAC,CAAC,CAAC,OAAO;KAC/D,CAAA;IACH,CAAC;IAED;;;;;;OAMG;IACK,aAAa,CAAC,OAA0B,EAAE,QAAiB;QACjE,OAAO,IAAI,CAAA;8BACe,QAAQ;oBAClB,OAAO,CAAC,OAAO;UACzB,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAA,eAAe,OAAO,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC,OAAO;UAC7D,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAA,kBAAkB,OAAO,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,OAAO;UAClE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAA,0CAA0C,OAAO,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,OAAO;;KAE/F,CAAA;IACH,CAAC;IAEO,aAAa,CAAC,OAAsB;QAC1C,OAAO,IAAI,CAAA;;mBAEI,OAAO,CAAC,CAAC,CAAC,4BAA4B,EAAE,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC;;YAE5E,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE;YACrE,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,MAAM,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE;;mCAElC,IAAI,CAAC,IAAI,WAAW,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC;YAC7E,OAAO,CAAC,CAAC,CAAC,sBAAsB,CAAC;;UAEnC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAA,iBAAiB,OAAO,CAAC,OAAO,SAAS,CAAC,CAAC,CAAC,OAAO;;KAE9E,CAAA;IACH,CAAC;IAEO,KAAK,CAAC,OAAO;QACnB,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,aAAa,CAAqB,CAAA;QAC9E,MAAM,OAAO,GAAG,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,CAAA;QAEpC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;QAChB,IAAI,CAAC,OAAO,GAAG,EAAE,CAAA;QAEjB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,IAAI,CAAC,QAAS,EAAE,OAAO,IAAI,SAAS,CAAC,CAAA;YACxE,IAAI,KAAK;gBAAE,KAAK,CAAC,KAAK,GAAG,EAAE,CAAA;YAC3B,6CAA6C;YAC7C,IAAI,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,iBAAiB,EAAE,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;YAC7G,MAAM,IAAI,CAAC,MAAM,EAAE,CAAA;YACnB,MAAM,IAAI,CAAC,OAAO,EAAE,CAAA;QACtB,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,IAAI,CAAC,OAAO,GAAI,CAAW,CAAC,OAAO,CAAA;QACrC,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,IAAI,GAAG,KAAK,CAAA;QACnB,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,MAAM,CAAC,OAAe;QAClC,6DAA6D;QAC7D,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,uCAAuC,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC;YAC9E,OAAM;QACR,CAAC;QAED,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;QAChB,IAAI,CAAC,OAAO,GAAG,EAAE,CAAA;QAEjB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,mBAAmB,CAAC,IAAI,CAAC,QAAS,EAAE,OAAO,CAAC,CAAA;YACjE,IAAI,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,iBAAiB,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;YACzG,MAAM,IAAI,CAAC,MAAM,EAAE,CAAA;YACnB;;;;cAIE;YACF,MAAM,IAAI,CAAC,OAAO,EAAE,CAAA;QACtB,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,IAAI,CAAC,OAAO,GAAI,CAAW,CAAC,OAAO,CAAA;QACrC,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,IAAI,GAAG,KAAK,CAAA;QACnB,CAAC;IACH,CAAC;;AAnN2B;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;oDAAkB;AACjB;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;uDAAmC;AAClC;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;yDAAkB;AAEhB;IAA5B,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;;iDAAc;AAEzB;IAAhB,KAAK,EAAE;;oDAAuC;AAC9B;IAAhB,KAAK,EAAE;;gDAAqB;AACZ;IAAhB,KAAK,EAAE;;mDAAqB;AAKZ;IAAhB,KAAK,EAAE;;sDAAsC;AAtPnC,kBAAkB;IAD9B,aAAa,CAAC,sBAAsB,CAAC;GACzB,kBAAkB,CA6b9B","sourcesContent":["import '@material/web/icon/icon.js'\n\nimport { css, html, LitElement, nothing } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport { ScrollbarStyles } from '@operato/styles'\n\nimport { fetchFigureVersions, inspectFigure, releaseFigure, revertFigureVersion } from '../graphql/index.js'\nimport type { FigureGateFinding, FigureInspection, FigureVersion } from '../types.js'\n\n/**\n * 이력 — **발행하는 자리이자, 발행된 판들을 보는 자리.**\n *\n * ## 왜 저장 옆이 아니라 여기인가\n *\n * 저장은 하루에 수십 번이고 발행은 드물다. 두 단추를 나란히 두면 저장하려다 발행을 누른다.\n * 그리고 발행은 **소비처에게 하는 약속**이라, 무엇을 약속하는지(지금 몇 판인가 · 저장 안 한 것이\n * 있나 · 지난 판이 무엇이었나)를 함께 보여 주는 자리에 있어야 한다.\n *\n * ## 저장하지 않은 것이 있으면 발행하지 않는다\n *\n * 발행은 **저장된 것**을 내보낸다. 화면에 고친 것이 남아 있는 채로 발행하면, 저작자가 보고 있는\n * 것과 약속한 것이 다르다. 그래서 막고, 왜 막혔는지 적는다.\n *\n * ## 되돌리기는 초안으로 돌아온다\n *\n * 옛 판의 정본·속성·그림이 돌아오고 이름·설명은 그대로다. 되살렸다고 그 판이 다시 발행되지는\n * 않는다 — 다시 내보내려면 발행을 한 번 더 하고, 그때 번호는 이미 쓴 다음 번호를 받는다.\n */\n@customElement('figure-history-panel')\nexport class FigureHistoryPanel extends localize(i18next)(LitElement) {\n static styles = [\n ScrollbarStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n min-height: 0;\n overflow-y: auto;\n background-color: var(--md-sys-color-surface-container-lowest);\n color: var(--md-sys-color-on-surface);\n font: var(--label-font, inherit);\n font-size: 0.76rem;\n }\n\n :host([off]) {\n display: none;\n }\n\n section {\n padding: var(--spacing-medium, 8px) var(--spacing-large, 12px);\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n }\n\n h3 {\n margin: 0 0 6px 0;\n font-size: var(--ui-t-micro, 11px);\n font-weight: 700;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n\n /* 지금 무엇인가 — 발행을 누르기 전에 읽는 한 줄 */\n div[now] {\n display: flex;\n align-items: center;\n gap: 6px;\n margin-bottom: 8px;\n }\n\n span[state] {\n padding: 2px 8px;\n border-radius: 10px;\n font-size: 0.7rem;\n font-weight: 700;\n color: var(--md-sys-color-on-secondary-container);\n background-color: var(--md-sys-color-secondary-container);\n }\n\n span[state][released] {\n color: var(--md-sys-color-on-primary);\n background-color: var(--md-sys-color-primary);\n }\n\n span[ver] {\n font-family: var(--mono-font, monospace);\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n\n input,\n button {\n font: var(--input-field-font, inherit);\n font-size: 0.76rem;\n }\n\n input[note] {\n width: 100%;\n box-sizing: border-box;\n padding: 5px 8px;\n color: var(--md-sys-color-on-surface);\n background-color: var(--md-sys-color-surface-container-lowest);\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n }\n\n button[release] {\n width: 100%;\n margin-top: 6px;\n padding: 6px 0;\n font-weight: 700;\n color: var(--ui-on-accent, #fff);\n background-color: var(--md-sys-color-primary);\n border: 0;\n border-radius: 6px;\n cursor: pointer;\n }\n\n button[release][disabled] {\n opacity: 0.45;\n cursor: default;\n }\n\n /* 왜 못 누르는지 — 눌러 보고 알게 하지 않는다 */\n p[why] {\n margin: 6px 0 0 0;\n font-size: 0.7rem;\n line-height: 1.5;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n\n p[failed] {\n margin: 6px 0 0 0;\n font-size: 0.7rem;\n line-height: 1.5;\n color: var(--md-sys-color-error);\n }\n\n ul {\n margin: 0;\n padding: 0;\n list-style: none;\n }\n\n li {\n display: grid;\n grid-template-columns: auto 1fr auto;\n gap: 4px 8px;\n align-items: baseline;\n padding: 7px 0;\n border-bottom: 1px dashed var(--md-sys-color-outline-variant);\n }\n\n li:last-child {\n border-bottom: 0;\n }\n\n li span[no] {\n font-family: var(--mono-font, monospace);\n font-weight: 700;\n }\n\n li span[when] {\n grid-column: 2 / 4;\n font-size: 0.7rem;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n\n li span[comment] {\n grid-column: 1 / 4;\n line-height: 1.5;\n }\n\n li button[revert] {\n padding: 2px 8px;\n color: var(--md-sys-color-on-surface);\n background: none;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n cursor: pointer;\n }\n\n li button[revert]:hover {\n color: var(--md-sys-color-on-primary-container);\n background-color: var(--md-sys-color-primary-container);\n border-color: transparent;\n }\n\n /*\n 게이트 사유. 막는 것은 색으로도 말하고, 알리는 것은 조용히 둔다 — 둘을 같은 톤으로 두면\n 「이 중 어느 것이 발행을 막았나」를 사람이 다시 세어야 한다.\n */\n ul[gate] {\n margin: var(--spacing-small, 6px) 0 0;\n padding: 0;\n list-style: none;\n display: flex;\n flex-direction: column;\n gap: 3px;\n }\n\n li[head] {\n font: var(--label-font, inherit);\n font-size: 0.72rem;\n color: var(--md-sys-color-error);\n }\n\n li[head][quiet] {\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n\n li[finding] {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 3px;\n padding: 3px 6px;\n font-size: 0.72rem;\n line-height: 1.5;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n background-color: var(--md-sys-color-surface-container);\n border-radius: 4px;\n }\n\n li[finding][blocking] {\n color: var(--md-sys-color-on-error-container, var(--md-sys-color-on-surface));\n background-color: var(--md-sys-color-error-container);\n }\n\n li[finding] code[where] {\n font-family: var(--mono-font, monospace);\n font-size: 0.68rem;\n opacity: 0.85;\n }\n\n /* 왜 나쁜지는 곁글로 — 읽어야 하지만 잰 것보다 먼저 눈에 들 필요는 없다 */\n li[finding] span[why-line] {\n opacity: 0.8;\n }\n\n /* 무엇을 바꾸면 되나 — 이 줄이 실제로 사람이 할 일이다. 연장 표를 달아 눈에 걸리게 한다 */\n li[finding] span[how-line] {\n display: flex;\n align-items: flex-start;\n gap: 4px;\n padding-top: 2px;\n font-weight: 600;\n }\n li[finding] span[how-line] md-icon {\n --md-icon-size: 13px;\n flex: none;\n margin-top: 1px;\n }\n\n p[quiet] {\n margin: 0;\n line-height: 1.6;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n `\n ]\n\n /** 지금 열려 있는 도형. 없으면(새로 만드는 중) 발행할 것이 없다. */\n @property({ type: String }) figureId?: string\n @property({ type: String }) figureState?: 'draft' | 'released'\n @property({ type: Number }) figureVersion = 0\n /** 저장하지 않은 것이 있나 — 있으면 발행을 막는다. */\n @property({ type: Boolean }) dirty = false\n\n @state() private versions: FigureVersion[] = []\n @state() private busy = false\n @state() private failure = ''\n /**\n * 발행 판정. **저장된 정본에 대한 것**이라 저장할 때마다 다시 묻는다(`figureVersion` 이 아니라\n * `dirty` 가 풀리는 순간이 그때다).\n */\n @state() private inspection?: FigureInspection\n\n updated(changes: Map<string, unknown>) {\n if (changes.has('figureId') || changes.has('figureVersion')) {\n void this.reload()\n }\n\n /*\n 저장이 끝난 순간(dirty 가 풀림) 정본이 바뀌었으니 판정을 다시 묻는다. 저작 중(dirty)에는\n 묻지 않는다 — 서버에 있는 것은 저장 전 정본이라, 그 답은 화면에 있는 것과 다른 것을 말한다.\n */\n if (changes.has('figureId') || changes.has('dirty')) {\n void this.inspect()\n }\n }\n\n private async inspect() {\n if (!this.figureId || this.dirty) {\n this.inspection = undefined\n return\n }\n\n try {\n this.inspection = await inspectFigure(this.figureId)\n } catch (e) {\n /* 판정을 못 받은 것을 「통과」로 두지 않는다 — 모르는 것이다. 단추는 서버가 막는다. */\n this.inspection = undefined\n this.failure = (e as Error).message\n }\n }\n\n private async reload() {\n if (!this.figureId) {\n this.versions = []\n return\n }\n\n try {\n this.versions = await fetchFigureVersions(this.figureId)\n this.failure = ''\n } catch (e) {\n this.failure = (e as Error).message\n }\n }\n\n render() {\n return html`\n <section>\n <h3>${i18next.t('figure.label.release')}</h3>\n ${this.figureId ? this.renderRelease() : html`<p quiet>${i18next.t('figure.text.save-the-figure-before-releasing')}</p>`}\n </section>\n\n <section>\n <h3>${i18next.t('figure.label.released-versions')}</h3>\n ${this.versions.length\n ? html`<ul>\n ${this.versions.map(version => this.renderVersion(version))}\n </ul>`\n : html`<p quiet>${i18next.t('figure.text.nothing-released-yet')}</p>`}\n </section>\n `\n }\n\n private renderRelease() {\n const released = this.figureState === 'released'\n\n /*\n 게이트가 막는 것과 알리는 것을 함께 낸다. **막는 것은 단추를 잠근다** — 눌러 보고 서버에게\n 거절당하는 것으로 알게 하지 않는다. 알리는 것은 잠그지 않고 그대로 보여 준다.\n */\n const blocking = (this.inspection?.findings ?? []).filter(finding => finding.blocking)\n const advisory = (this.inspection?.findings ?? []).filter(finding => !finding.blocking)\n\n /*\n ── 「발행되나」에 **한 번만** 답한다 ─────────────────────────────────────\n 전에는 막는 목록의 머리가 「이것들이 발행을 막습니다」, 알리는 목록의 머리가 「발행은 되지만\n 확인해 보십시오」였다. 두 줄이 나란히 서서 화면이 「막힌다」와 「된다」를 함께 말했고, 사람이\n 「그래서 되는 건가」를 되물었다.\n\n 답은 이 한 줄이 하고, 아래 목록들은 **이유**일 뿐이다 — 그래서 목록 머리는 문장이 아니라\n 이름씨다(「고쳐야 하는 것」·「막지는 않는 것」).\n */\n const why = this.dirty\n ? i18next.t('figure.text.release-needs-a-save-first')\n : released\n ? i18next.t('figure.text.already-released-edit-to-release-again')\n : blocking.length\n ? i18next.t('figure.text.cannot-release-now')\n : ''\n\n return html`\n <div now>\n <span state ?released=${released}>${i18next.t(released ? 'figure.label.figure-released' : 'figure.label.draft')}</span>\n <span ver>${i18next.t('figure.text.version-number', { version: this.figureVersion ?? 0 })}</span>\n </div>\n\n <input note placeholder=${i18next.t('figure.text.what-changed-in-this-release')} ?disabled=${!!why || this.busy} />\n\n <button release ?disabled=${!!why || this.busy} @click=${() => this.release()}>\n ${this.busy ? i18next.t('figure.text.releasing') : i18next.t('figure.button.release-figure')}\n </button>\n\n ${why ? html`<p why>${why}</p>` : nothing}\n ${blocking.length\n ? html`<ul gate>\n <li head>${i18next.t('figure.text.must-fix-to-release')}</li>\n ${blocking.map(finding => this.renderFinding(finding, true))}\n </ul>`\n : nothing}\n ${advisory.length\n ? html`<ul gate>\n <li head quiet>${i18next.t('figure.text.does-not-block')}</li>\n ${advisory.map(finding => this.renderFinding(finding, false))}\n </ul>`\n : nothing}\n ${this.failure ? html`<p failed>${this.failure}</p>` : nothing}\n `\n }\n\n /**\n * 사유 한 줄 — **잰 것 · 왜 · 어떻게** 셋을 함께 말한다.\n *\n * 잰 것만 보여 줬더니 「1 조각이 2 조각으로 갈라진다」에서 사람이 멈췄다. 왜 나쁜지도, 무엇을\n * 바꾸면 되는지도 알 수 없었다. 그 셋은 형식(`figure-model` 의 gate)이 만들고 여기서는 자리만\n * 준다 — 화면이 문장을 지으면 서버가 거절할 때와 다른 말을 하게 된다.\n */\n private renderFinding(finding: FigureGateFinding, blocking: boolean) {\n return html`\n <li finding ?blocking=${blocking}>\n <span msg>${finding.message}</span>\n ${finding.at ? html`<code where>${finding.at}</code>` : nothing}\n ${finding.why ? html`<span why-line>${finding.why}</span>` : nothing}\n ${finding.how ? html`<span how-line><md-icon>build</md-icon>${finding.how}</span>` : nothing}\n </li>\n `\n }\n\n private renderVersion(version: FigureVersion) {\n return html`\n <li>\n <span no>${i18next.t('figure.text.version-number', { version: version.version })}</span>\n <span when>\n ${version.updatedAt ? new Date(version.updatedAt).toLocaleString() : ''}\n ${version.updater?.name ? ` · ${version.updater.name}` : ''}\n </span>\n <button revert ?disabled=${this.busy} @click=${() => this.revert(version.version)}>\n ${i18next.t('figure.button.revert')}\n </button>\n ${version.comment ? html`<span comment>${version.comment}</span>` : nothing}\n </li>\n `\n }\n\n private async release() {\n const input = this.renderRoot.querySelector('input[note]') as HTMLInputElement\n const comment = input?.value?.trim()\n\n this.busy = true\n this.failure = ''\n\n try {\n const figure = await releaseFigure(this.figureId!, comment || undefined)\n if (input) input.value = ''\n /* 정본을 든 자리가 갱신한다 — 여기서 페이지 상태를 고쳐 쓰지 않는다. */\n this.dispatchEvent(new CustomEvent('figure-released', { detail: { figure }, bubbles: true, composed: true }))\n await this.reload()\n await this.inspect()\n } catch (e) {\n this.failure = (e as Error).message\n } finally {\n this.busy = false\n }\n }\n\n private async revert(version: number) {\n /* 되돌아갈 수 없는 조작은 아니지만(판본은 남는다), 지금 화면의 것이 덮인다 — 이름으로 확인한다. */\n if (!confirm(i18next.t('figure.text.revert-to-version-confirm', { version }))) {\n return\n }\n\n this.busy = true\n this.failure = ''\n\n try {\n const result = await revertFigureVersion(this.figureId!, version)\n this.dispatchEvent(new CustomEvent('figure-reverted', { detail: result, bubbles: true, composed: true }))\n await this.reload()\n /*\n **되돌리면 정본이 바뀌므로 판정도 바뀐다.** 이것을 안 하면 옛 판의 사유가 새 정본 위에\n 남아 화면이 거짓말한다 — 되돌리기는 `dirty` 도 `figureVersion` 도 건드리지 않으므로\n `updated` 의 감시로는 다시 묻지 않는다.\n */\n await this.inspect()\n } catch (e) {\n this.failure = (e as Error).message\n } finally {\n this.busy = false\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"figure-history-panel.js","sourceRoot":"","sources":["../../client/modeller/figure-history-panel.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\n\nimport { css, html, LitElement, nothing } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport { ScrollbarStyles } from '@operato/styles'\n\nimport { fetchFigureVersions, inspectFigure, releaseFigure, revertFigureVersion } from '../graphql/index.js'\nimport type { FigureGateFinding, FigureGateFix, FigureInspection, FigureVersion } from '../types.js'\n\n/**\n * 이력 — **발행하는 자리이자, 발행된 판들을 보는 자리.**\n *\n * ## 왜 저장 옆이 아니라 여기인가\n *\n * 저장은 하루에 수십 번이고 발행은 드물다. 두 단추를 나란히 두면 저장하려다 발행을 누른다.\n * 그리고 발행은 **소비처에게 하는 약속**이라, 무엇을 약속하는지(지금 몇 판인가 · 저장 안 한 것이\n * 있나 · 지난 판이 무엇이었나)를 함께 보여 주는 자리에 있어야 한다.\n *\n * ## 저장하지 않은 것이 있으면 발행하지 않는다\n *\n * 발행은 **저장된 것**을 내보낸다. 화면에 고친 것이 남아 있는 채로 발행하면, 저작자가 보고 있는\n * 것과 약속한 것이 다르다. 그래서 막고, 왜 막혔는지 적는다.\n *\n * ## 되돌리기는 초안으로 돌아온다\n *\n * 옛 판의 정본·속성·그림이 돌아오고 이름·설명은 그대로다. 되살렸다고 그 판이 다시 발행되지는\n * 않는다 — 다시 내보내려면 발행을 한 번 더 하고, 그때 번호는 이미 쓴 다음 번호를 받는다.\n */\n@customElement('figure-history-panel')\nexport class FigureHistoryPanel extends localize(i18next)(LitElement) {\n static styles = [\n ScrollbarStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n min-height: 0;\n overflow-y: auto;\n background-color: var(--md-sys-color-surface-container-lowest);\n color: var(--md-sys-color-on-surface);\n font: var(--label-font, inherit);\n font-size: 0.76rem;\n }\n\n :host([off]) {\n display: none;\n }\n\n section {\n padding: var(--spacing-medium, 8px) var(--spacing-large, 12px);\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n }\n\n h3 {\n margin: 0 0 6px 0;\n font-size: var(--ui-t-micro, 11px);\n font-weight: 700;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n\n /* 지금 무엇인가 — 발행을 누르기 전에 읽는 한 줄 */\n div[now] {\n display: flex;\n align-items: center;\n gap: 6px;\n margin-bottom: 8px;\n }\n\n span[state] {\n padding: 2px 8px;\n border-radius: 10px;\n font-size: 0.7rem;\n font-weight: 700;\n color: var(--md-sys-color-on-secondary-container);\n background-color: var(--md-sys-color-secondary-container);\n }\n\n span[state][released] {\n color: var(--md-sys-color-on-primary);\n background-color: var(--md-sys-color-primary);\n }\n\n span[ver] {\n font-family: var(--mono-font, monospace);\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n\n input,\n button {\n font: var(--input-field-font, inherit);\n font-size: 0.76rem;\n }\n\n input[note] {\n width: 100%;\n box-sizing: border-box;\n padding: 5px 8px;\n color: var(--md-sys-color-on-surface);\n background-color: var(--md-sys-color-surface-container-lowest);\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n }\n\n button[release] {\n width: 100%;\n margin-top: 6px;\n padding: 6px 0;\n font-weight: 700;\n color: var(--ui-on-accent, #fff);\n background-color: var(--md-sys-color-primary);\n border: 0;\n border-radius: 6px;\n cursor: pointer;\n }\n\n button[release][disabled] {\n opacity: 0.45;\n cursor: default;\n }\n\n /* 왜 못 누르는지 — 눌러 보고 알게 하지 않는다 */\n p[why] {\n margin: 6px 0 0 0;\n font-size: 0.7rem;\n line-height: 1.5;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n\n p[failed] {\n margin: 6px 0 0 0;\n font-size: 0.7rem;\n line-height: 1.5;\n color: var(--md-sys-color-error);\n }\n\n ul {\n margin: 0;\n padding: 0;\n list-style: none;\n }\n\n li {\n display: grid;\n grid-template-columns: auto 1fr auto;\n gap: 4px 8px;\n align-items: baseline;\n padding: 7px 0;\n border-bottom: 1px dashed var(--md-sys-color-outline-variant);\n }\n\n li:last-child {\n border-bottom: 0;\n }\n\n li span[no] {\n font-family: var(--mono-font, monospace);\n font-weight: 700;\n }\n\n li span[when] {\n grid-column: 2 / 4;\n font-size: 0.7rem;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n\n li span[comment] {\n grid-column: 1 / 4;\n line-height: 1.5;\n }\n\n li button[revert] {\n padding: 2px 8px;\n color: var(--md-sys-color-on-surface);\n background: none;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n cursor: pointer;\n }\n\n li button[revert]:hover {\n color: var(--md-sys-color-on-primary-container);\n background-color: var(--md-sys-color-primary-container);\n border-color: transparent;\n }\n\n /*\n 게이트 사유. 막는 것은 색으로도 말하고, 알리는 것은 조용히 둔다 — 둘을 같은 톤으로 두면\n 「이 중 어느 것이 발행을 막았나」를 사람이 다시 세어야 한다.\n */\n ul[gate] {\n margin: var(--spacing-small, 6px) 0 0;\n padding: 0;\n list-style: none;\n display: flex;\n flex-direction: column;\n gap: 3px;\n }\n\n li[head] {\n font: var(--label-font, inherit);\n font-size: 0.72rem;\n color: var(--md-sys-color-error);\n }\n\n li[head][quiet] {\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n\n li[finding] {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 3px;\n padding: 3px 6px;\n font-size: 0.72rem;\n line-height: 1.5;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n background-color: var(--md-sys-color-surface-container);\n border-radius: 4px;\n }\n\n li[finding][blocking] {\n color: var(--md-sys-color-on-error-container, var(--md-sys-color-on-surface));\n background-color: var(--md-sys-color-error-container);\n }\n\n li[finding] code[where] {\n font-family: var(--mono-font, monospace);\n font-size: 0.68rem;\n opacity: 0.85;\n }\n\n /* 왜 나쁜지는 곁글로 — 읽어야 하지만 잰 것보다 먼저 눈에 들 필요는 없다 */\n li[finding] span[why-line] {\n opacity: 0.8;\n }\n\n /* 무엇을 바꾸면 되나 — 이 줄이 실제로 사람이 할 일이다. 연장 표를 달아 눈에 걸리게 한다 */\n li[finding] span[how-line] {\n display: flex;\n align-items: flex-start;\n gap: 4px;\n padding-top: 2px;\n font-weight: 600;\n }\n li[finding] span[how-line] md-icon {\n --md-icon-size: 13px;\n flex: none;\n margin-top: 1px;\n }\n\n /* 설명으로 끝내지 않는다. 이 단추는 원본을 바꾸지 않고 모델러에 후보만 연다. */\n li[finding] button[preview-fix] {\n margin-top: 3px;\n padding: 4px 7px;\n font-weight: 700;\n color: var(--md-sys-color-on-primary-container, var(--md-sys-color-on-surface));\n background-color: var(--md-sys-color-primary-container, transparent);\n border: 0;\n border-radius: 5px;\n cursor: pointer;\n }\n\n li[finding] button[preview-fix]:hover {\n filter: brightness(1.08);\n }\n\n li[finding] button[apply-fix] {\n margin-top: 3px;\n padding: 4px 7px;\n font-weight: 700;\n color: var(--ui-on-accent, #fff);\n background-color: var(--md-sys-color-primary);\n border: 0;\n border-radius: 5px;\n cursor: pointer;\n }\n\n p[quiet] {\n margin: 0;\n line-height: 1.6;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n `\n ]\n\n /** 지금 열려 있는 도형. 없으면(새로 만드는 중) 발행할 것이 없다. */\n @property({ type: String }) figureId?: string\n @property({ type: String }) figureState?: 'draft' | 'released'\n @property({ type: Number }) figureVersion = 0\n /** 저장하지 않은 것이 있나 — 있으면 발행을 막는다. */\n @property({ type: Boolean }) dirty = false\n\n @state() private versions: FigureVersion[] = []\n @state() private busy = false\n @state() private failure = ''\n /**\n * 발행 판정. **저장된 정본에 대한 것**이라 저장할 때마다 다시 묻는다(`figureVersion` 이 아니라\n * `dirty` 가 풀리는 순간이 그때다).\n */\n @state() private inspection?: FigureInspection\n\n updated(changes: Map<string, unknown>) {\n if (changes.has('figureId') || changes.has('figureVersion')) {\n void this.reload()\n }\n\n /*\n 저장이 끝난 순간(dirty 가 풀림) 정본이 바뀌었으니 판정을 다시 묻는다. 저작 중(dirty)에는\n 묻지 않는다 — 서버에 있는 것은 저장 전 정본이라, 그 답은 화면에 있는 것과 다른 것을 말한다.\n */\n if (changes.has('figureId') || changes.has('dirty')) {\n void this.inspect()\n }\n }\n\n private async inspect() {\n if (!this.figureId || this.dirty) {\n this.inspection = undefined\n return\n }\n\n try {\n this.inspection = await inspectFigure(this.figureId)\n } catch (e) {\n /* 판정을 못 받은 것을 「통과」로 두지 않는다 — 모르는 것이다. 단추는 서버가 막는다. */\n this.inspection = undefined\n this.failure = (e as Error).message\n }\n }\n\n private async reload() {\n if (!this.figureId) {\n this.versions = []\n return\n }\n\n try {\n this.versions = await fetchFigureVersions(this.figureId)\n this.failure = ''\n } catch (e) {\n this.failure = (e as Error).message\n }\n }\n\n render() {\n return html`\n <section>\n <h3>${i18next.t('figure.label.release')}</h3>\n ${this.figureId ? this.renderRelease() : html`<p quiet>${i18next.t('figure.text.save-the-figure-before-releasing')}</p>`}\n </section>\n\n <section>\n <h3>${i18next.t('figure.label.released-versions')}</h3>\n ${this.versions.length\n ? html`<ul>\n ${this.versions.map(version => this.renderVersion(version))}\n </ul>`\n : html`<p quiet>${i18next.t('figure.text.nothing-released-yet')}</p>`}\n </section>\n `\n }\n\n private renderRelease() {\n const released = this.figureState === 'released'\n\n /*\n 게이트가 막는 것과 알리는 것을 함께 낸다. **막는 것은 단추를 잠근다** — 눌러 보고 서버에게\n 거절당하는 것으로 알게 하지 않는다. 알리는 것은 잠그지 않고 그대로 보여 준다.\n */\n const blocking = (this.inspection?.findings ?? []).filter(finding => finding.blocking)\n const advisory = (this.inspection?.findings ?? []).filter(finding => !finding.blocking)\n\n /*\n ── 「발행되나」에 **한 번만** 답한다 ─────────────────────────────────────\n 전에는 막는 목록의 머리가 「이것들이 발행을 막습니다」, 알리는 목록의 머리가 「발행은 되지만\n 확인해 보십시오」였다. 두 줄이 나란히 서서 화면이 「막힌다」와 「된다」를 함께 말했고, 사람이\n 「그래서 되는 건가」를 되물었다.\n\n 답은 이 한 줄이 하고, 아래 목록들은 **이유**일 뿐이다 — 그래서 목록 머리는 문장이 아니라\n 이름씨다(「고쳐야 하는 것」·「막지는 않는 것」).\n */\n const why = this.dirty\n ? i18next.t('figure.text.release-needs-a-save-first')\n : released\n ? i18next.t('figure.text.already-released-edit-to-release-again')\n : blocking.length\n ? i18next.t('figure.text.cannot-release-now')\n : ''\n\n return html`\n <div now>\n <span state ?released=${released}>${i18next.t(released ? 'figure.label.figure-released' : 'figure.label.draft')}</span>\n <span ver>${i18next.t('figure.text.version-number', { version: this.figureVersion ?? 0 })}</span>\n </div>\n\n <input note placeholder=${i18next.t('figure.text.what-changed-in-this-release')} ?disabled=${!!why || this.busy} />\n\n <button release ?disabled=${!!why || this.busy} @click=${() => this.release()}>\n ${this.busy ? i18next.t('figure.text.releasing') : i18next.t('figure.button.release-figure')}\n </button>\n\n ${why ? html`<p why>${why}</p>` : nothing}\n ${blocking.length\n ? html`<ul gate>\n <li head>${i18next.t('figure.text.must-fix-to-release')}</li>\n ${blocking.map(finding => this.renderFinding(finding, true))}\n </ul>`\n : nothing}\n ${advisory.length\n ? html`<ul gate>\n <li head quiet>${i18next.t('figure.text.does-not-block')}</li>\n ${advisory.map(finding => this.renderFinding(finding, false))}\n </ul>`\n : nothing}\n ${this.failure ? html`<p failed>${this.failure}</p>` : nothing}\n `\n }\n\n /**\n * 사유 한 줄 — **잰 것 · 왜 · 어떻게** 셋을 함께 말한다.\n *\n * 잰 것만 보여 줬더니 「1 조각이 2 조각으로 갈라진다」에서 사람이 멈췄다. 왜 나쁜지도, 무엇을\n * 바꾸면 되는지도 알 수 없었다. 그 셋은 형식(`figure-model` 의 gate)이 만들고 여기서는 자리만\n * 준다 — 화면이 문장을 지으면 서버가 거절할 때와 다른 말을 하게 된다.\n */\n private renderFinding(finding: FigureGateFinding, blocking: boolean) {\n return html`\n <li finding ?blocking=${blocking}>\n <span msg>${finding.message}</span>\n ${finding.at ? html`<code where>${finding.at}</code>` : nothing}\n ${finding.why ? html`<span why-line>${finding.why}</span>` : nothing}\n ${finding.how ? html`<span how-line><md-icon>build</md-icon>${finding.how}</span>` : nothing}\n ${(finding.fixes ?? []).map(fix => html`\n ${fix.safe ? html`\n <button apply-fix @click=${() => this.applyFix(fix)}>\n 안전하게 수정 적용 · ${fix.part}.anchor.${fix.axis}: ${fix.from ?? 'auto'} → ${fix.to}\n </button>\n ` : nothing}\n <button preview-fix @click=${() => this.previewFix(fix)}>\n 수정안 미리보기 · ${fix.part}.anchor.${fix.axis}: ${fix.from ?? 'auto'} → ${fix.to}\n </button>\n `)}\n </li>\n `\n }\n\n /**\n * 게이트는 수정을 저장하지 않는다. 부모 모델러가 현재 정본과 이 한 수정안을 나란히 렌더하고,\n * 저작자가 '변경 받기'를 눌렀을 때에만 자기 작업 사본에 적용한다.\n */\n private previewFix(fix: FigureGateFix) {\n this.dispatchEvent(new CustomEvent('preview-sizing-fix', { detail: fix, bubbles: true, composed: true }))\n }\n\n /** 서버가 전수 재검사해 모든 차단 split이 사라진 후보만 작업 사본에 원클릭 적용한다. 저장은 하지 않는다. */\n private applyFix(fix: FigureGateFix) {\n if (!fix.safe) return\n this.dispatchEvent(new CustomEvent('apply-sizing-fix', { detail: fix, bubbles: true, composed: true }))\n }\n\n private renderVersion(version: FigureVersion) {\n return html`\n <li>\n <span no>${i18next.t('figure.text.version-number', { version: version.version })}</span>\n <span when>\n ${version.updatedAt ? new Date(version.updatedAt).toLocaleString() : ''}\n ${version.updater?.name ? ` · ${version.updater.name}` : ''}\n </span>\n <button revert ?disabled=${this.busy} @click=${() => this.revert(version.version)}>\n ${i18next.t('figure.button.revert')}\n </button>\n ${version.comment ? html`<span comment>${version.comment}</span>` : nothing}\n </li>\n `\n }\n\n private async release() {\n const input = this.renderRoot.querySelector('input[note]') as HTMLInputElement\n const comment = input?.value?.trim()\n\n this.busy = true\n this.failure = ''\n\n try {\n const figure = await releaseFigure(this.figureId!, comment || undefined)\n if (input) input.value = ''\n /* 정본을 든 자리가 갱신한다 — 여기서 페이지 상태를 고쳐 쓰지 않는다. */\n this.dispatchEvent(new CustomEvent('figure-released', { detail: { figure }, bubbles: true, composed: true }))\n await this.reload()\n await this.inspect()\n } catch (e) {\n this.failure = (e as Error).message\n } finally {\n this.busy = false\n }\n }\n\n private async revert(version: number) {\n /* 되돌아갈 수 없는 조작은 아니지만(판본은 남는다), 지금 화면의 것이 덮인다 — 이름으로 확인한다. */\n if (!confirm(i18next.t('figure.text.revert-to-version-confirm', { version }))) {\n return\n }\n\n this.busy = true\n this.failure = ''\n\n try {\n const result = await revertFigureVersion(this.figureId!, version)\n this.dispatchEvent(new CustomEvent('figure-reverted', { detail: result, bubbles: true, composed: true }))\n await this.reload()\n /*\n **되돌리면 정본이 바뀌므로 판정도 바뀐다.** 이것을 안 하면 옛 판의 사유가 새 정본 위에\n 남아 화면이 거짓말한다 — 되돌리기는 `dirty` 도 `figureVersion` 도 건드리지 않으므로\n `updated` 의 감시로는 다시 묻지 않는다.\n */\n await this.inspect()\n } catch (e) {\n this.failure = (e as Error).message\n } finally {\n this.busy = false\n }\n }\n}\n"]}
|
|
@@ -47,6 +47,10 @@ export declare class FigureInspector extends FigureInspector_base {
|
|
|
47
47
|
/** 보드. 위치를 보드 중심 기준으로 바꾸려면 이것이 있어야 한다. */
|
|
48
48
|
/** 도형의 표시 이름. 정본은 저작면이 들고, 여기서는 보여 주고 바꾼다(`figure-rename` 으로 알린다). */
|
|
49
49
|
figureName: string;
|
|
50
|
+
/** 분류 하나 — 팔레트에서 묶는 기준. */
|
|
51
|
+
figureCategory?: string;
|
|
52
|
+
/** 태그 여럿 — 분류로는 못 하는 찾기. */
|
|
53
|
+
figureTags?: string[];
|
|
50
54
|
/** 펴 둔 도움말. 부품을 바꿔도 유지된다 — 매번 다시 펴게 하지 않는다. */
|
|
51
55
|
private opened;
|
|
52
56
|
board?: BoardModel;
|
|
@@ -93,6 +97,16 @@ export declare class FigureInspector extends FigureInspector_base {
|
|
|
93
97
|
* 저장되는 식별자다. 배치된 보드가 이 이름으로 타입을 찾으므로 만든 뒤에는 못 바꾼다.
|
|
94
98
|
*/
|
|
95
99
|
private renderFigure;
|
|
100
|
+
/**
|
|
101
|
+
* 태그 줄을 목록으로.
|
|
102
|
+
*
|
|
103
|
+
* 쉼표로 가르고 `#` 은 떼어 낸다 — 사람은 `#반출` 이라고 적고, 저장되는 것은 `반출` 이다.
|
|
104
|
+
* 겹친 것은 한 번만 남긴다. 빈 줄은 **빈 배열**이지 `undefined` 가 아니다 — 전부 지우려는
|
|
105
|
+
* 저작자가 지울 방법을 잃지 않게.
|
|
106
|
+
*/
|
|
107
|
+
private tellTags;
|
|
108
|
+
/** 카탈로그 값이 바뀌었다고 알린다. 정본은 저작면이 든다 — 여기서는 알리기만 한다. */
|
|
109
|
+
private tellCatalog;
|
|
96
110
|
private renderIdentity;
|
|
97
111
|
private renderPlacement;
|
|
98
112
|
/** 분할 수와 모서리 굴림 — 해당하는 도형에만 낸다. */
|
|
@@ -129,6 +143,7 @@ export declare class FigureInspector extends FigureInspector_base {
|
|
|
129
143
|
*/
|
|
130
144
|
private silhouette;
|
|
131
145
|
/** 견본 한 줄. 무리마다 부른다. */
|
|
146
|
+
private palettePurpose;
|
|
132
147
|
private swatches;
|
|
133
148
|
private renderMaterial;
|
|
134
149
|
/**
|
|
@@ -207,6 +222,8 @@ export declare class FigureInspector extends FigureInspector_base {
|
|
|
207
222
|
* 계속 고치는 사람이 매번 다시 펴야 하면 접은 것이 되레 방해가 된다.
|
|
208
223
|
*/
|
|
209
224
|
private help;
|
|
225
|
+
private helpButton;
|
|
226
|
+
private helpHint;
|
|
210
227
|
private renderAnchors;
|
|
211
228
|
/**
|
|
212
229
|
* 숫자 칸.
|