@things-factory/figure-ui 10.1.160 → 10.1.161

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.
@@ -1,10 +1,12 @@
1
1
  import '@material/web/icon/icon.js';
2
2
  import { LitElement } from 'lit';
3
+ import '@operato/data-grist/ox-card-thumb.js';
3
4
  import '@things-factory/figure-viewer/dist-client/figure-thumb-mark.js';
4
5
  import type { Figure } from '../types.js';
5
6
  declare const FigureThumb_base: (new (...args: any[]) => LitElement) & typeof LitElement;
6
7
  /**
7
- * 카드의 그림 칸.
8
+ * 카드의 그림 칸. 그림 · 그림 없음 · 초안 표시는 모든 카드 카탈로그의 `ox-card-thumb`(@operato/data-grist)가
9
+ * 그리고, 여기서는 그 위에 이 카탈로그의 것(태그 · 삭제)만 얹는다(ADR-0117 개정 2026-10-13).
8
10
  *
9
11
  * ## 왜 컴포넌트인가
10
12
  *
@@ -3,6 +3,7 @@ import '@material/web/icon/icon.js';
3
3
  import { css, html, LitElement } from 'lit';
4
4
  import { customElement, property } from 'lit/decorators.js';
5
5
  import { i18next, localize } from '@operato/i18n';
6
+ import '@operato/data-grist/ox-card-thumb.js';
6
7
  import '@things-factory/figure-viewer/dist-client/figure-thumb-mark.js';
7
8
  import { getPathInfo } from '@operato/utils';
8
9
  /**
@@ -24,7 +25,8 @@ function thumbnailSrc(figure) {
24
25
  return `${contextPath}/figure-thumbnail/${figure.id}?v=${stamp}`;
25
26
  }
26
27
  /**
27
- * 카드의 그림 칸.
28
+ * 카드의 그림 칸. 그림 · 그림 없음 · 초안 표시는 모든 카드 카탈로그의 `ox-card-thumb`(@operato/data-grist)가
29
+ * 그리고, 여기서는 그 위에 이 카탈로그의 것(태그 · 삭제)만 얹는다(ADR-0117 개정 2026-10-13).
28
30
  *
29
31
  * ## 왜 컴포넌트인가
30
32
  *
@@ -45,39 +47,9 @@ function thumbnailSrc(figure) {
45
47
  let FigureThumb = class FigureThumb extends localize(i18next)(LitElement) {
46
48
  static { this.styles = css `
47
49
  :host {
48
- position: relative;
49
50
  display: block;
50
51
  width: 100%;
51
52
  height: 100%;
52
- overflow: hidden;
53
- }
54
-
55
- img {
56
- display: block;
57
- width: 100%;
58
- height: 100%;
59
- /* 형상을 자르지 않는다 — 긴 것(컨베이어)과 높은 것(시그널타워)이 한 줄에 선다 */
60
- object-fit: contain;
61
- }
62
-
63
- /*
64
- 초안 표시.
65
-
66
- 발행된 것에는 아무 말도 하지 않는다 — 카탈로그에 있는 것은 대개 발행된 것이라, 카드마다
67
- 「발행됨」이 붙으면 아무것도 가르지 않고 자리만 먹는다. 초안만 말하면 이 표시가 있는 카드가
68
- 곧 「아직 쓰면 안 되는 것」이 된다.
69
- */
70
- span[draft] {
71
- position: absolute;
72
- top: 6px;
73
- left: 6px;
74
- padding: 1px 7px;
75
- font: var(--label-font, inherit);
76
- font-size: 0.68rem;
77
- line-height: 1.5;
78
- color: var(--md-sys-color-on-secondary-container);
79
- background-color: var(--md-sys-color-secondary-container);
80
- border-radius: 4px;
81
53
  }
82
54
 
83
55
  /*
@@ -175,12 +147,12 @@ let FigureThumb = class FigureThumb extends localize(i18next)(LitElement) {
175
147
  }
176
148
  const src = thumbnailSrc(figure);
177
149
  return html `
178
- ${src
179
- ? html `<img src=${src} alt=${figure.name || ''} loading="lazy" />`
180
- : html `
181
- <figure-thumb-mark .label=${i18next.t('figure.text.thumbnail-not-made-yet')}></figure-thumb-mark>
182
- `}
183
- ${figure.state === 'released' ? '' : html `<span draft>${i18next.t('figure.label.draft')}</span>`}
150
+ <ox-card-thumb
151
+ src=${src || ''}
152
+ alt=${figure.name || ''}
153
+ badge=${figure.state === 'released' ? '' : i18next.t('figure.label.draft')}
154
+ >
155
+ <figure-thumb-mark slot="empty" .label=${i18next.t('figure.text.thumbnail-not-made-yet')}></figure-thumb-mark>
184
156
  ${figure.tags?.length
185
157
  ? html `<div tags>
186
158
  ${figure.tags.map(tag => html `
@@ -208,6 +180,7 @@ let FigureThumb = class FigureThumb extends localize(i18next)(LitElement) {
208
180
  >
209
181
  <md-icon>delete</md-icon>
210
182
  </button>
183
+ </ox-card-thumb>
211
184
  `;
212
185
  }
213
186
  };
@@ -1 +1 @@
1
- {"version":3,"file":"figure-thumb.js","sourceRoot":"","sources":["../../client/viewparts/figure-thumb.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\n\nimport { css, html, LitElement } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport '@things-factory/figure-viewer/dist-client/figure-thumb-mark.js'\nimport { getPathInfo } from '@operato/utils'\n\nimport type { Figure } from '../types.js'\n\n/**\n * 그림을 부르는 주소.\n *\n * **base64 를 목록 응답에 실어 오지 않는다.** data URL 은 브라우저가 캐시할 수 없어, 카탈로그를\n * 열 때마다 그림 전부가 다시 온다([확인함] 표본 14개 227KB). 주소로 부르면 캐시가 되고, 판을\n * 가리는 `v` 가 바뀐 것만 다시 받는다. 내주는 자리는 `figure-thumbnail-router` 다.\n *\n * 도메인 조각(`/domain/{subdomain}`)은 셸이 쓰는 것과 같은 함수로 읽는다 — 화면이 주소를\n * 손으로 짜맞추면 도메인이 바뀌는 배치에서 조용히 틀린다.\n */\nfunction thumbnailSrc(figure: Figure): string | undefined {\n if (!figure.thumbnailUpdatedAt) {\n return undefined\n }\n\n const { contextPath } = getPathInfo(location.pathname)\n const stamp = new Date(figure.thumbnailUpdatedAt).getTime()\n\n return `${contextPath}/figure-thumbnail/${figure.id}?v=${stamp}`\n}\n\n/**\n * 카드의 그림 칸.\n *\n * ## 왜 컴포넌트인가\n *\n * 카탈로그는 `ox-grist` 의 CARD 모드로 그린다. 격자에게 그림 칸은 컬럼 하나이고, 그 칸을 그리는\n * 것은 컬럼의 `record.renderer` 다. 그림 위에 얹히는 것들(초안 표시, 삭제)은 **그림 칸 안에**\n * 있어야 하므로 renderer 가 그려야 하는데, renderer 의 결과는 `ox-card-field` 의 shadow root\n * 안으로 들어간다 — 화면의 `static styles` 가 닿지 않고, 인라인 style 로는 `:hover` 를 쓸 수 없다.\n *\n * 그래서 그림 칸만 컴포넌트로 둔다. 자기 스타일을 갖고 있으니 hover 도 테마도 정상으로 돈다.\n * 격자로 옮기기 전 손으로 만들었던 카드의 위쪽 절반이 그대로 이 자리다.\n *\n * ## 삭제가 왜 여기 있나\n *\n * 격자의 gutter 줄에 두면 카드 아래에 단추 줄이 하나 생긴다. 감춰도 자리가 남고, 자리를 없애면\n * 눌릴 곳이 사라진다. 되돌릴 수 없는 조작은 **고르는 눈길에 걸리지 않아야** 하므로 그림 오른쪽\n * 위, 손이 갔을 때만 나오는 자리에 둔다.\n */\n@customElement('figure-thumb')\nexport class FigureThumb extends localize(i18next)(LitElement) {\n static styles = css`\n :host {\n position: relative;\n display: block;\n width: 100%;\n height: 100%;\n overflow: hidden;\n }\n\n img {\n display: block;\n width: 100%;\n height: 100%;\n /* 형상을 자르지 않는다 — 긴 것(컨베이어)과 높은 것(시그널타워)이 한 줄에 선다 */\n object-fit: contain;\n }\n\n /*\n 초안 표시.\n\n 발행된 것에는 아무 말도 하지 않는다 — 카탈로그에 있는 것은 대개 발행된 것이라, 카드마다\n 「발행됨」이 붙으면 아무것도 가르지 않고 자리만 먹는다. 초안만 말하면 이 표시가 있는 카드가\n 곧 「아직 쓰면 안 되는 것」이 된다.\n */\n span[draft] {\n position: absolute;\n top: 6px;\n left: 6px;\n padding: 1px 7px;\n font: var(--label-font, inherit);\n font-size: 0.68rem;\n line-height: 1.5;\n color: var(--md-sys-color-on-secondary-container);\n background-color: var(--md-sys-color-secondary-container);\n border-radius: 4px;\n }\n\n /*\n 태그 — **그림 위에 얹는다.**\n\n 카드 본문에 한 줄로 두었더니 태그가 있는 카드만 그만큼 높아져 격자가 들쭉날쭉해졌다.\n 태그는 도형마다 개수가 다르므로 본문에 두는 한 이 문제가 남는다. 그림 위는 이미 자리가\n 잡혀 있고(초안 표시·삭제), 얹어도 카드 높이가 변하지 않는다.\n\n 아래쪽에 둔다 — 위는 초안 표시와 삭제 단추가 쓴다.\n\n **투명한 알약이다.** 면을 꽉 채우면 그림 위에 흰 띠가 생겨 형상을 가린다 — 카탈로그에서\n 먼저 보아야 하는 것은 형상이다. 그래서 바탕은 거의 비우고, 읽히는 것은 뒤를 흐리는 것과\n 실낱 테두리가 맡는다. 흐림이 안 되는 환경에서도 테두리가 남아 글자가 형상에 묻히지 않는다.\n */\n div[tags] {\n position: absolute;\n right: 6px;\n bottom: 6px;\n left: 6px;\n display: flex;\n flex-wrap: wrap-reverse;\n justify-content: flex-end;\n gap: 3px;\n /* 태그가 많아도 그림을 덮지 않는다 — 넘치는 것은 위로 잘린다 */\n max-height: 44px;\n overflow: hidden;\n /* 알약 사이의 빈 자리는 그림이 받는다 — 거기서 누르면 저작면이 열린다 */\n pointer-events: none;\n }\n /*\n **알약은 누를 수 있다.** 누르면 그 태그로 목록을 거른다.\n\n 태그를 눈에 보이게 한 다음의 물음은 「그래서 이걸로 뭘 하나」다. 같은 태그가 붙은 것을\n 보려고 태그를 눈으로 읽어 검색창에 옮겨 적게 두면, 보여 준 뜻이 절반만 산다.\n\n 카드 자체가 저작면을 여는 자리이므로 알약에서 그 클릭을 멈춘다 — 안 멈추면 태그를 누른\n 사람이 편집기로 끌려간다.\n */\n div[tags] button {\n pointer-events: auto;\n cursor: pointer;\n padding: 0 7px;\n font: var(--label-font, inherit);\n font-size: 0.66rem;\n line-height: 1.7;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n background-color: color-mix(in srgb, var(--md-sys-color-surface, #fff) 34%, transparent);\n border: 1px solid color-mix(in srgb, var(--md-sys-color-outline, #888) 42%, transparent);\n backdrop-filter: blur(4px);\n border-radius: 999em;\n white-space: nowrap;\n }\n div[tags] button:hover,\n div[tags] button:focus-visible {\n color: var(--md-sys-color-on-surface);\n background-color: color-mix(in srgb, var(--md-sys-color-surface, #fff) 72%, transparent);\n border-color: var(--md-sys-color-primary);\n outline: none;\n }\n\n button[remove] {\n position: absolute;\n top: 5px;\n right: 5px;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 3px;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n /* 그림이 밝을 수도 어두울 수도 있다 — 옅은 면을 깔아 아이콘이 어디서나 읽히게 한다 */\n background-color: color-mix(in srgb, var(--md-sys-color-surface, #fff) 82%, transparent);\n border: none;\n border-radius: 4px;\n cursor: pointer;\n opacity: 0;\n transition: opacity 0.12s ease-out;\n }\n button[remove] md-icon {\n --md-icon-size: 18px;\n }\n :host(:hover) button[remove],\n button[remove]:focus-visible {\n opacity: 1;\n }\n button[remove]:hover {\n color: var(--md-sys-color-on-error, #fff);\n background-color: var(--md-sys-color-error);\n }\n `\n\n @property({ type: Object }) figure!: Figure\n\n render() {\n const figure = this.figure\n if (!figure) {\n return html``\n }\n\n const src = thumbnailSrc(figure)\n\n return html`\n ${src\n ? html`<img src=${src} alt=${figure.name || ''} loading=\"lazy\" />`\n : html`\n <figure-thumb-mark .label=${i18next.t('figure.text.thumbnail-not-made-yet')}></figure-thumb-mark>\n `}\n ${figure.state === 'released' ? '' : html`<span draft>${i18next.t('figure.label.draft')}</span>`}\n ${figure.tags?.length\n ? html`<div tags>\n ${figure.tags.map(\n tag => html`\n <button\n title=${i18next.t('figure.text.find-by-this-tag')}\n @click=${(e: Event) => {\n e.stopPropagation()\n this.dispatchEvent(\n new CustomEvent('search-tag', { detail: { tag }, bubbles: true, composed: true })\n )\n }}\n >\n ${tag}\n </button>\n `\n )}\n </div>`\n : ''}\n\n <button\n remove\n title=${i18next.t('figure.button.delete-figure')}\n @click=${(e: Event) => {\n /* 카드 클릭(=저작면 열기)까지 올라가지 않게 여기서 멈춘다 */\n e.stopPropagation()\n this.dispatchEvent(new CustomEvent('remove'))\n }}\n >\n <md-icon>delete</md-icon>\n </button>\n `\n }\n}\n"]}
1
+ {"version":3,"file":"figure-thumb.js","sourceRoot":"","sources":["../../client/viewparts/figure-thumb.ts"],"names":[],"mappings":";AAAA,OAAO,4BAA4B,CAAA;AAEnC,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,KAAK,CAAA;AAC3C,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAE3D,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACjD,OAAO,sCAAsC,CAAA;AAC7C,OAAO,gEAAgE,CAAA;AACvE,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAI5C;;;;;;;;;GASG;AACH,SAAS,YAAY,CAAC,MAAc;IAClC,IAAI,CAAC,MAAM,CAAC,kBAAkB,EAAE,CAAC;QAC/B,OAAO,SAAS,CAAA;IAClB,CAAC;IAED,MAAM,EAAE,WAAW,EAAE,GAAG,WAAW,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;IACtD,MAAM,KAAK,GAAG,IAAI,IAAI,CAAC,MAAM,CAAC,kBAAkB,CAAC,CAAC,OAAO,EAAE,CAAA;IAE3D,OAAO,GAAG,WAAW,qBAAqB,MAAM,CAAC,EAAE,MAAM,KAAK,EAAE,CAAA;AAClE,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AAEI,IAAM,WAAW,GAAjB,MAAM,WAAY,SAAQ,QAAQ,CAAC,OAAO,CAAC,CAAC,UAAU,CAAC;aACrD,WAAM,GAAG,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8FlB,AA9FY,CA8FZ;IAID,MAAM;QACJ,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAA;QAC1B,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,OAAO,IAAI,CAAA,EAAE,CAAA;QACf,CAAC;QAED,MAAM,GAAG,GAAG,YAAY,CAAC,MAAM,CAAC,CAAA;QAEhC,OAAO,IAAI,CAAA;;cAED,GAAG,IAAI,EAAE;cACT,MAAM,CAAC,IAAI,IAAI,EAAE;gBACf,MAAM,CAAC,KAAK,KAAK,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,oBAAoB,CAAC;;iDAEjC,OAAO,CAAC,CAAC,CAAC,oCAAoC,CAAC;QACxF,MAAM,CAAC,IAAI,EAAE,MAAM;YACnB,CAAC,CAAC,IAAI,CAAA;cACA,MAAM,CAAC,IAAI,CAAC,GAAG,CACf,GAAG,CAAC,EAAE,CAAC,IAAI,CAAA;;0BAEC,OAAO,CAAC,CAAC,CAAC,8BAA8B,CAAC;2BACxC,CAAC,CAAQ,EAAE,EAAE;gBACpB,CAAC,CAAC,eAAe,EAAE,CAAA;gBACnB,IAAI,CAAC,aAAa,CAChB,IAAI,WAAW,CAAC,YAAY,EAAE,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAClF,CAAA;YACH,CAAC;;oBAEC,GAAG;;eAER,CACF;iBACI;YACT,CAAC,CAAC,EAAE;;;;gBAII,OAAO,CAAC,CAAC,CAAC,6BAA6B,CAAC;iBACvC,CAAC,CAAQ,EAAE,EAAE;YACpB,sCAAsC;YACtC,CAAC,CAAC,eAAe,EAAE,CAAA;YACnB,IAAI,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,QAAQ,CAAC,CAAC,CAAA;QAC/C,CAAC;;;;;KAKJ,CAAA;IACH,CAAC;;AAlD2B;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;2CAAgB;AAjGhC,WAAW;IADvB,aAAa,CAAC,cAAc,CAAC;GACjB,WAAW,CAoJvB","sourcesContent":["import '@material/web/icon/icon.js'\n\nimport { css, html, LitElement } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport '@operato/data-grist/ox-card-thumb.js'\nimport '@things-factory/figure-viewer/dist-client/figure-thumb-mark.js'\nimport { getPathInfo } from '@operato/utils'\n\nimport type { Figure } from '../types.js'\n\n/**\n * 그림을 부르는 주소.\n *\n * **base64 를 목록 응답에 실어 오지 않는다.** data URL 은 브라우저가 캐시할 수 없어, 카탈로그를\n * 열 때마다 그림 전부가 다시 온다([확인함] 표본 14개 227KB). 주소로 부르면 캐시가 되고, 판을\n * 가리는 `v` 가 바뀐 것만 다시 받는다. 내주는 자리는 `figure-thumbnail-router` 다.\n *\n * 도메인 조각(`/domain/{subdomain}`)은 셸이 쓰는 것과 같은 함수로 읽는다 — 화면이 주소를\n * 손으로 짜맞추면 도메인이 바뀌는 배치에서 조용히 틀린다.\n */\nfunction thumbnailSrc(figure: Figure): string | undefined {\n if (!figure.thumbnailUpdatedAt) {\n return undefined\n }\n\n const { contextPath } = getPathInfo(location.pathname)\n const stamp = new Date(figure.thumbnailUpdatedAt).getTime()\n\n return `${contextPath}/figure-thumbnail/${figure.id}?v=${stamp}`\n}\n\n/**\n * 카드의 그림 칸. 그림 · 그림 없음 · 초안 표시는 모든 카드 카탈로그의 `ox-card-thumb`(@operato/data-grist)가\n * 그리고, 여기서는 그 위에 이 카탈로그의 것(태그 · 삭제)만 얹는다(ADR-0117 개정 2026-10-13).\n *\n * ## 왜 컴포넌트인가\n *\n * 카탈로그는 `ox-grist` 의 CARD 모드로 그린다. 격자에게 그림 칸은 컬럼 하나이고, 그 칸을 그리는\n * 것은 컬럼의 `record.renderer` 다. 그림 위에 얹히는 것들(초안 표시, 삭제)은 **그림 칸 안에**\n * 있어야 하므로 renderer 가 그려야 하는데, renderer 의 결과는 `ox-card-field` 의 shadow root\n * 안으로 들어간다 — 화면의 `static styles` 가 닿지 않고, 인라인 style 로는 `:hover` 를 쓸 수 없다.\n *\n * 그래서 그림 칸만 컴포넌트로 둔다. 자기 스타일을 갖고 있으니 hover 도 테마도 정상으로 돈다.\n * 격자로 옮기기 전 손으로 만들었던 카드의 위쪽 절반이 그대로 이 자리다.\n *\n * ## 삭제가 왜 여기 있나\n *\n * 격자의 gutter 줄에 두면 카드 아래에 단추 줄이 하나 생긴다. 감춰도 자리가 남고, 자리를 없애면\n * 눌릴 곳이 사라진다. 되돌릴 수 없는 조작은 **고르는 눈길에 걸리지 않아야** 하므로 그림 오른쪽\n * 위, 손이 갔을 때만 나오는 자리에 둔다.\n */\n@customElement('figure-thumb')\nexport class FigureThumb extends localize(i18next)(LitElement) {\n static styles = css`\n :host {\n display: block;\n width: 100%;\n height: 100%;\n }\n\n /*\n 태그 — **그림 위에 얹는다.**\n\n 카드 본문에 한 줄로 두었더니 태그가 있는 카드만 그만큼 높아져 격자가 들쭉날쭉해졌다.\n 태그는 도형마다 개수가 다르므로 본문에 두는 한 이 문제가 남는다. 그림 위는 이미 자리가\n 잡혀 있고(초안 표시·삭제), 얹어도 카드 높이가 변하지 않는다.\n\n 아래쪽에 둔다 — 위는 초안 표시와 삭제 단추가 쓴다.\n\n **투명한 알약이다.** 면을 꽉 채우면 그림 위에 흰 띠가 생겨 형상을 가린다 — 카탈로그에서\n 먼저 보아야 하는 것은 형상이다. 그래서 바탕은 거의 비우고, 읽히는 것은 뒤를 흐리는 것과\n 실낱 테두리가 맡는다. 흐림이 안 되는 환경에서도 테두리가 남아 글자가 형상에 묻히지 않는다.\n */\n div[tags] {\n position: absolute;\n right: 6px;\n bottom: 6px;\n left: 6px;\n display: flex;\n flex-wrap: wrap-reverse;\n justify-content: flex-end;\n gap: 3px;\n /* 태그가 많아도 그림을 덮지 않는다 — 넘치는 것은 위로 잘린다 */\n max-height: 44px;\n overflow: hidden;\n /* 알약 사이의 빈 자리는 그림이 받는다 — 거기서 누르면 저작면이 열린다 */\n pointer-events: none;\n }\n /*\n **알약은 누를 수 있다.** 누르면 그 태그로 목록을 거른다.\n\n 태그를 눈에 보이게 한 다음의 물음은 「그래서 이걸로 뭘 하나」다. 같은 태그가 붙은 것을\n 보려고 태그를 눈으로 읽어 검색창에 옮겨 적게 두면, 보여 준 뜻이 절반만 산다.\n\n 카드 자체가 저작면을 여는 자리이므로 알약에서 그 클릭을 멈춘다 — 안 멈추면 태그를 누른\n 사람이 편집기로 끌려간다.\n */\n div[tags] button {\n pointer-events: auto;\n cursor: pointer;\n padding: 0 7px;\n font: var(--label-font, inherit);\n font-size: 0.66rem;\n line-height: 1.7;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n background-color: color-mix(in srgb, var(--md-sys-color-surface, #fff) 34%, transparent);\n border: 1px solid color-mix(in srgb, var(--md-sys-color-outline, #888) 42%, transparent);\n backdrop-filter: blur(4px);\n border-radius: 999em;\n white-space: nowrap;\n }\n div[tags] button:hover,\n div[tags] button:focus-visible {\n color: var(--md-sys-color-on-surface);\n background-color: color-mix(in srgb, var(--md-sys-color-surface, #fff) 72%, transparent);\n border-color: var(--md-sys-color-primary);\n outline: none;\n }\n\n button[remove] {\n position: absolute;\n top: 5px;\n right: 5px;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 3px;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n /* 그림이 밝을 수도 어두울 수도 있다 — 옅은 면을 깔아 아이콘이 어디서나 읽히게 한다 */\n background-color: color-mix(in srgb, var(--md-sys-color-surface, #fff) 82%, transparent);\n border: none;\n border-radius: 4px;\n cursor: pointer;\n opacity: 0;\n transition: opacity 0.12s ease-out;\n }\n button[remove] md-icon {\n --md-icon-size: 18px;\n }\n :host(:hover) button[remove],\n button[remove]:focus-visible {\n opacity: 1;\n }\n button[remove]:hover {\n color: var(--md-sys-color-on-error, #fff);\n background-color: var(--md-sys-color-error);\n }\n `\n\n @property({ type: Object }) figure!: Figure\n\n render() {\n const figure = this.figure\n if (!figure) {\n return html``\n }\n\n const src = thumbnailSrc(figure)\n\n return html`\n <ox-card-thumb\n src=${src || ''}\n alt=${figure.name || ''}\n badge=${figure.state === 'released' ? '' : i18next.t('figure.label.draft')}\n >\n <figure-thumb-mark slot=\"empty\" .label=${i18next.t('figure.text.thumbnail-not-made-yet')}></figure-thumb-mark>\n ${figure.tags?.length\n ? html`<div tags>\n ${figure.tags.map(\n tag => html`\n <button\n title=${i18next.t('figure.text.find-by-this-tag')}\n @click=${(e: Event) => {\n e.stopPropagation()\n this.dispatchEvent(\n new CustomEvent('search-tag', { detail: { tag }, bubbles: true, composed: true })\n )\n }}\n >\n ${tag}\n </button>\n `\n )}\n </div>`\n : ''}\n\n <button\n remove\n title=${i18next.t('figure.button.delete-figure')}\n @click=${(e: Event) => {\n /* 카드 클릭(=저작면 열기)까지 올라가지 않게 여기서 멈춘다 */\n e.stopPropagation()\n this.dispatchEvent(new CustomEvent('remove'))\n }}\n >\n <md-icon>delete</md-icon>\n </button>\n </ox-card-thumb>\n `\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@things-factory/figure-ui",
3
- "version": "10.1.160",
3
+ "version": "10.1.161",
4
4
  "publishConfig": {
5
5
  "access": "public",
6
6
  "@things-factory:registry": "https://registry.npmjs.org"
@@ -41,11 +41,11 @@
41
41
  "@operato/slot-layout-editor": "^10.29.25",
42
42
  "@operato/styles": "^10.0.0",
43
43
  "@operato/utils": "^10.0.0",
44
- "@things-factory/ai-assistant": "^10.1.160",
45
- "@things-factory/figure-service": "^10.1.160",
44
+ "@things-factory/ai-assistant": "^10.1.161",
45
+ "@things-factory/figure-service": "^10.1.161",
46
46
  "@things-factory/figure-viewer": "^10.1.155",
47
47
  "@things-factory/setting-base": "^10.1.159",
48
48
  "three": "^0.185.1"
49
49
  },
50
- "gitHead": "b5e86be374e8b913a3f2b8459b6001e8f44e345b"
50
+ "gitHead": "d32fe0614ea40572787acd94e307cc18a681ab38"
51
51
  }