@things-factory/figure-ui 10.1.180 → 10.1.183
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/modeller/editor-styles.ts +28 -9
- package/client/modeller/editor.ts +105 -12
- package/client/modeller/fasten-form.ts +207 -0
- package/client/modeller/fasten-geometry.ts +5 -1
- package/client/modeller/figure-fasten-dialog.ts +442 -0
- package/client/modeller/figure-fasten-drawing.ts +32 -11
- package/client/modeller/figure-links.ts +56 -300
- package/client/modeller/modeller-keys.ts +4 -4
- package/client/pages/figure-modeller-page.ts +17 -11
- package/dist-client/modeller/editor-styles.js +28 -9
- package/dist-client/modeller/editor-styles.js.map +1 -1
- package/dist-client/modeller/editor.d.ts +23 -2
- package/dist-client/modeller/editor.js +121 -12
- package/dist-client/modeller/editor.js.map +1 -1
- package/dist-client/modeller/fasten-form.d.ts +69 -0
- package/dist-client/modeller/fasten-form.js +131 -0
- package/dist-client/modeller/fasten-form.js.map +1 -0
- package/dist-client/modeller/fasten-geometry.d.ts +3 -1
- package/dist-client/modeller/fasten-geometry.js +6 -1
- package/dist-client/modeller/fasten-geometry.js.map +1 -1
- package/dist-client/modeller/figure-fasten-dialog.d.ts +64 -0
- package/dist-client/modeller/figure-fasten-dialog.js +418 -0
- package/dist-client/modeller/figure-fasten-dialog.js.map +1 -0
- package/dist-client/modeller/figure-fasten-drawing.d.ts +6 -3
- package/dist-client/modeller/figure-fasten-drawing.js +36 -10
- package/dist-client/modeller/figure-fasten-drawing.js.map +1 -1
- package/dist-client/modeller/figure-links.d.ts +15 -39
- package/dist-client/modeller/figure-links.js +58 -301
- package/dist-client/modeller/figure-links.js.map +1 -1
- package/dist-client/modeller/modeller-keys.js +4 -4
- package/dist-client/modeller/modeller-keys.js.map +1 -1
- package/dist-client/pages/figure-modeller-page.d.ts +5 -1
- package/dist-client/pages/figure-modeller-page.js +14 -12
- package/dist-client/pages/figure-modeller-page.js.map +1 -1
- package/dist-client/route.d.ts +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/package.json +14 -14
- package/test/browser/links-page.js +4 -4
- package/test/browser/links.check.mjs +184 -46
- package/test/browser/modeller-page.js +23 -0
- package/test/browser/modeller.check.mjs +19 -20
- package/test/browser/namespace-page.js +2 -29
- package/test/browser/popup-stand-in.js +48 -0
- package/test/browser/product-page.check.mjs +4 -3
- package/test/browser/product-page.js +19 -32
- package/test/browser/props.check.mjs +28 -7
- package/test/browser/screen.check.mjs +28 -23
- package/test/browser/section-page.js +13 -41
- package/test/browser/section.check.mjs +39 -30
- package/test/fasten-form.test.ts +77 -0
- package/test/fasten-geometry.test.ts +2 -0
- package/test/first-minute.test.ts +7 -4
- package/translations/en.json +4 -3
- package/translations/es.json +4 -3
- package/translations/ja.json +4 -3
- package/translations/ko.json +4 -3
- package/translations/ms.json +4 -3
- package/translations/zh.json +4 -3
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
import { __decorate, __metadata } from "tslib";
|
|
2
|
+
import '@material/web/button/filled-button.js';
|
|
3
|
+
import '@material/web/button/text-button.js';
|
|
4
|
+
import { css, html, LitElement, nothing } from 'lit';
|
|
5
|
+
import { customElement, property, state } from 'lit/decorators.js';
|
|
6
|
+
import { i18next, localize } from '@operato/i18n';
|
|
7
|
+
import '@operato/input/ox-input-select-buttons.js';
|
|
8
|
+
import '@operato/popup/ox-popup-actions.js';
|
|
9
|
+
import { CommonPopupStyles } from '@operato/styles';
|
|
10
|
+
import { SidePanelStyles } from '@things-factory/figure-viewer/dist-client/side-panel-styles.js';
|
|
11
|
+
import './figure-fasten-drawing.js';
|
|
12
|
+
import { turnsInPlace } from './fasten-geometry.js';
|
|
13
|
+
import { aimFasten, fastenChanged, fastenDetailWordsOf, fastenFormOf, fastenPlanOf, fastenSentenceOf, fastenTargetsOf } from './fasten-form.js';
|
|
14
|
+
import { fieldWordsOf, hasInstanceSize, heldBy } from './links.js';
|
|
15
|
+
/** The target list's entry for the plane the figure stands by. */
|
|
16
|
+
const PLANE = '__plane';
|
|
17
|
+
/**
|
|
18
|
+
* 붙이기 창: what the selected part is fastened to, on which face, how -- chosen with room to see it, in the standard
|
|
19
|
+
* popup the editor opens (`openPopup`: its title, its close, moved by its head) (user 2026-10-17: 「속성에는 심플하고
|
|
20
|
+
* 직관적인 결과 위주로, 편집은 팝업에서 넓은 공간을 써서」; 2026-10-18: the standard popup, not a window of its own).
|
|
21
|
+
*
|
|
22
|
+
* Everything chosen here is tried, not done: the command it comes to is told to the editor as it changes
|
|
23
|
+
* (`preview-command`), and the canvas draws it. 「적용」 sends it once (`fasten-apply`), one step of the history;
|
|
24
|
+
* 「취소」 and Esc close it with nothing written (`fasten-cancel`). A row of the target list under the pointer or the
|
|
25
|
+
* arrows asks the canvas to light that part (`fasten-hover`). The window is the only one telling the preview while it
|
|
26
|
+
* is open; the editor keeps the panel quiet.
|
|
27
|
+
*/
|
|
28
|
+
let FigureFastenDialog = class FigureFastenDialog extends localize(i18next)(LitElement) {
|
|
29
|
+
constructor() {
|
|
30
|
+
super(...arguments);
|
|
31
|
+
/** The part being fastened. */
|
|
32
|
+
this.partId = '';
|
|
33
|
+
this.locked = false;
|
|
34
|
+
/** What the command would do, in words (the editor works it out: `command-preview.ts`). */
|
|
35
|
+
this.previewWords = [];
|
|
36
|
+
/** Why the last 「적용」 was refused, in the editor's words. */
|
|
37
|
+
this.error = '';
|
|
38
|
+
this.told = '';
|
|
39
|
+
this.hovered = null;
|
|
40
|
+
this.onKey = async (e) => {
|
|
41
|
+
if (e.key === 'Escape') {
|
|
42
|
+
e.stopPropagation();
|
|
43
|
+
e.preventDefault();
|
|
44
|
+
this.cancel();
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (e.key !== 'Enter' || e.isComposing)
|
|
48
|
+
return;
|
|
49
|
+
const path = e.composedPath();
|
|
50
|
+
const target = path[0];
|
|
51
|
+
/* On a chip of the list Enter applies, as it does anywhere in the window; a chip is chosen by Space or a press. */
|
|
52
|
+
const onChip = path.some(n => n.localName === 'ox-input-select-buttons');
|
|
53
|
+
/* A button, a fold or a list of options does what Enter does to it. */
|
|
54
|
+
if (!onChip &&
|
|
55
|
+
(target.localName === 'button' ||
|
|
56
|
+
target.localName === 'summary' ||
|
|
57
|
+
target.localName === 'select' ||
|
|
58
|
+
/^md-.*button$/.test(target.localName)))
|
|
59
|
+
return;
|
|
60
|
+
e.preventDefault();
|
|
61
|
+
e.stopPropagation();
|
|
62
|
+
/* A number typed and Enter: the number first, as leaving the field would take it. */
|
|
63
|
+
if (target.localName === 'input')
|
|
64
|
+
target.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
|
65
|
+
await this.updateComplete;
|
|
66
|
+
this.apply();
|
|
67
|
+
};
|
|
68
|
+
this.apply = () => {
|
|
69
|
+
const plan = this.revision && this.form ? fastenPlanOf(this.revision, this.partId, this.form) : undefined;
|
|
70
|
+
if (this.locked || !plan?.action)
|
|
71
|
+
return;
|
|
72
|
+
this.dispatchEvent(new CustomEvent('fasten-apply', { detail: { action: plan.action }, bubbles: true, composed: true }));
|
|
73
|
+
};
|
|
74
|
+
this.cancel = () => {
|
|
75
|
+
this.dispatchEvent(new CustomEvent('fasten-cancel', { bubbles: true, composed: true }));
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
static { this.styles = [
|
|
79
|
+
SidePanelStyles,
|
|
80
|
+
CommonPopupStyles,
|
|
81
|
+
css `
|
|
82
|
+
:host {
|
|
83
|
+
display: grid;
|
|
84
|
+
gap: 16px;
|
|
85
|
+
padding: var(--popup-padding, 16px);
|
|
86
|
+
overflow: auto;
|
|
87
|
+
}
|
|
88
|
+
/* The result in one line, at the top: it changes with every choice below it. */
|
|
89
|
+
p[fasten-sentence] {
|
|
90
|
+
margin: 0;
|
|
91
|
+
font-size: var(--ui-t-body, 13px);
|
|
92
|
+
font-weight: 600;
|
|
93
|
+
}
|
|
94
|
+
div[question] {
|
|
95
|
+
display: grid;
|
|
96
|
+
align-content: start;
|
|
97
|
+
gap: 6px;
|
|
98
|
+
}
|
|
99
|
+
h4 {
|
|
100
|
+
margin: 0;
|
|
101
|
+
font-size: var(--ui-t-small, 11.5px);
|
|
102
|
+
font-weight: 600;
|
|
103
|
+
color: var(--md-sys-color-on-surface-variant);
|
|
104
|
+
}
|
|
105
|
+
p[loops] {
|
|
106
|
+
margin: 0;
|
|
107
|
+
}
|
|
108
|
+
details[fasten-detail] {
|
|
109
|
+
margin: 0;
|
|
110
|
+
}
|
|
111
|
+
details[fasten-detail] > div[form] {
|
|
112
|
+
margin-top: 8px;
|
|
113
|
+
max-width: 320px;
|
|
114
|
+
}
|
|
115
|
+
span[fasten-summary] {
|
|
116
|
+
opacity: 0.8;
|
|
117
|
+
}
|
|
118
|
+
ul[preview-words] {
|
|
119
|
+
margin: 0;
|
|
120
|
+
padding: 8px 12px 8px 26px;
|
|
121
|
+
border-radius: 6px;
|
|
122
|
+
background: var(--md-sys-color-surface-container, #8881);
|
|
123
|
+
display: grid;
|
|
124
|
+
gap: 4px;
|
|
125
|
+
}
|
|
126
|
+
p[preview-words] {
|
|
127
|
+
margin: 0;
|
|
128
|
+
padding: 8px 12px;
|
|
129
|
+
border-radius: 6px;
|
|
130
|
+
background: var(--md-sys-color-surface-container, #8881);
|
|
131
|
+
}
|
|
132
|
+
p[error] {
|
|
133
|
+
margin: 0;
|
|
134
|
+
color: var(--md-sys-color-error);
|
|
135
|
+
}
|
|
136
|
+
`
|
|
137
|
+
]; }
|
|
138
|
+
willUpdate() {
|
|
139
|
+
/* Opened on what the part has now; another part is another window's. */
|
|
140
|
+
if (this.revision && this.partId && this.partId !== this.loadedFor) {
|
|
141
|
+
this.loadedFor = this.partId;
|
|
142
|
+
this.form = fastenFormOf(this.revision, this.partId);
|
|
143
|
+
this.opened = JSON.stringify(fastenPlanOf(this.revision, this.partId, this.form).action ?? null);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
async firstUpdated() {
|
|
147
|
+
/* Into the list, on what is chosen, so the arrows go from there. */
|
|
148
|
+
const list = this.renderRoot.querySelector('ox-input-select-buttons');
|
|
149
|
+
await list?.updateComplete;
|
|
150
|
+
const chips = list?.shadowRoot?.querySelectorAll('md-filter-chip');
|
|
151
|
+
([...(chips ?? [])].find(c => c.hasAttribute('selected')) ?? chips?.[0])?.focus();
|
|
152
|
+
}
|
|
153
|
+
updated() {
|
|
154
|
+
const plan = this.revision && this.form ? fastenPlanOf(this.revision, this.partId, this.form) : undefined;
|
|
155
|
+
const candidate = plan?.action ? { action: plan.action, ...(plan.face ? { face: plan.face } : {}) } : null;
|
|
156
|
+
const said = JSON.stringify(candidate);
|
|
157
|
+
if (said === this.told)
|
|
158
|
+
return;
|
|
159
|
+
this.told = said;
|
|
160
|
+
this.dispatchEvent(new CustomEvent('preview-command', { detail: candidate, bubbles: true, composed: true }));
|
|
161
|
+
}
|
|
162
|
+
disconnectedCallback() {
|
|
163
|
+
super.disconnectedCallback();
|
|
164
|
+
/* Closed: what it showed goes with it. */
|
|
165
|
+
if (this.told && this.told !== 'null')
|
|
166
|
+
this.dispatchEvent(new CustomEvent('preview-command', { detail: null, bubbles: true, composed: true }));
|
|
167
|
+
this.told = '';
|
|
168
|
+
this.hover(null);
|
|
169
|
+
}
|
|
170
|
+
render() {
|
|
171
|
+
const revision = this.revision;
|
|
172
|
+
const f = this.form;
|
|
173
|
+
if (!revision || !f)
|
|
174
|
+
return nothing;
|
|
175
|
+
const asset = revision.asset;
|
|
176
|
+
const held = !!heldBy(asset, this.partId);
|
|
177
|
+
const plan = fastenPlanOf(revision, this.partId, f);
|
|
178
|
+
const planeWord = i18next.t('figure.label.resize-place-' + (asset.placement ?? 'floor'));
|
|
179
|
+
const modes = f.toPlane
|
|
180
|
+
? ['meet']
|
|
181
|
+
: ['meet', 'flush', ...(turnsInPlace(asset, this.partId) ? ['centre'] : [])];
|
|
182
|
+
const detail = fastenDetailWordsOf(f, (key, params) => i18next.t(key, params));
|
|
183
|
+
const drawing = (show) => html `<figure-fasten-drawing
|
|
184
|
+
show=${show}
|
|
185
|
+
.target=${f.toPlane ? '' : f.target}
|
|
186
|
+
.planeWord=${planeWord}
|
|
187
|
+
.faces=${plan.theirFaces}
|
|
188
|
+
.face=${plan.targetFace}
|
|
189
|
+
.modes=${modes}
|
|
190
|
+
.mode=${plan.mode}
|
|
191
|
+
.view=${plan.view}
|
|
192
|
+
.align=${f.align}
|
|
193
|
+
.alignMm=${f.alignMm}
|
|
194
|
+
.gap=${f.gap}
|
|
195
|
+
.partName=${this.partId}
|
|
196
|
+
?locked=${this.locked}
|
|
197
|
+
@fasten-change=${(e) => (this.form = fastenChanged(revision, this.partId, this.form, e.detail))}
|
|
198
|
+
></figure-fasten-drawing>`;
|
|
199
|
+
return html `
|
|
200
|
+
${plan.aim
|
|
201
|
+
? html `<p fasten-sentence role="status">
|
|
202
|
+
${fastenSentenceOf(this.partId, f, plan, planeWord, (key, params) => i18next.t(key, params))}
|
|
203
|
+
</p>`
|
|
204
|
+
: html `<p hint fasten-sentence>${i18next.t('figure.text.fasten-target-first')}</p>`}
|
|
205
|
+
<div question="what">
|
|
206
|
+
<h4 id="what">${i18next.t('figure.label.fasten-what')}</h4>
|
|
207
|
+
${this.renderTargets(revision, f, planeWord)}
|
|
208
|
+
</div>
|
|
209
|
+
${plan.aim
|
|
210
|
+
? html `<div question="face">
|
|
211
|
+
<h4>
|
|
212
|
+
${
|
|
213
|
+
/* The face asked is the target's, named: the part's own face follows from it. */ f.toPlane
|
|
214
|
+
? i18next.t('figure.label.fasten-face-on-plane', { plane: planeWord })
|
|
215
|
+
: i18next.t('figure.label.fasten-face-on-part', { part: f.target })}
|
|
216
|
+
</h4>
|
|
217
|
+
${drawing('face')}
|
|
218
|
+
</div>
|
|
219
|
+
${modes.length > 1
|
|
220
|
+
? html `<div question="how">
|
|
221
|
+
<h4>${i18next.t('figure.label.fasten-how')}</h4>
|
|
222
|
+
${drawing('how')}
|
|
223
|
+
</div>`
|
|
224
|
+
: nothing}
|
|
225
|
+
<div question="detail">
|
|
226
|
+
<details fasten-detail>
|
|
227
|
+
<summary>
|
|
228
|
+
${i18next.t('figure.label.fasten-detail')}${detail.length ? html ` · <span fasten-summary>${detail.join(' · ')}</span>` : nothing}
|
|
229
|
+
</summary>
|
|
230
|
+
${plan.alignRows.length ? this.renderAlignment(plan.alignRows) : nothing}
|
|
231
|
+
<div form>
|
|
232
|
+
<span>${i18next.t('figure.label.gap')}</span>
|
|
233
|
+
<input
|
|
234
|
+
gap
|
|
235
|
+
type="number"
|
|
236
|
+
step="any"
|
|
237
|
+
.value=${f.gapKept !== undefined ? '' : String(f.gap)}
|
|
238
|
+
placeholder=${f.gapKept !== undefined ? i18next.t('figure.label.follows-expression') : ''}
|
|
239
|
+
@change=${(e) => this.set({ gap: Number(e.target.value) || 0, gapKept: undefined })}
|
|
240
|
+
/>
|
|
241
|
+
<span>${i18next.t('figure.label.twist')}</span>
|
|
242
|
+
<input
|
|
243
|
+
twist
|
|
244
|
+
type="number"
|
|
245
|
+
step="any"
|
|
246
|
+
.value=${String(f.twist)}
|
|
247
|
+
@change=${(e) => this.set({ twist: Number(e.target.value) || 0 })}
|
|
248
|
+
/>
|
|
249
|
+
</div>
|
|
250
|
+
</details>
|
|
251
|
+
</div>
|
|
252
|
+
${ /* The one it has, tried unchanged: that it stays where it is goes without saying. */JSON.stringify(plan.action ?? null) === this.opened ? nothing : this.renderPreviewWords()}`
|
|
253
|
+
: nothing}
|
|
254
|
+
${this.error ? html `<p error role="alert">${this.error}</p>` : nothing}
|
|
255
|
+
<ox-popup-actions>
|
|
256
|
+
<md-text-button slot="cancel" cancel @click=${this.cancel}>${i18next.t('figure.button.cancel')}</md-text-button>
|
|
257
|
+
<md-filled-button slot="confirm" apply ?disabled=${this.locked || !plan.action} @click=${this.apply}>
|
|
258
|
+
${i18next.t(held ? 'figure.button.apply' : 'figure.button.fasten')}
|
|
259
|
+
</md-filled-button>
|
|
260
|
+
</ox-popup-actions>
|
|
261
|
+
`;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* What to fasten to: the plane and the other parts, as the standard choice chips. The arrows move among them and
|
|
265
|
+
* the canvas lights the part under the focus or the pointer; a part hanging from this one is not offered, and why is
|
|
266
|
+
* said under them.
|
|
267
|
+
*/
|
|
268
|
+
renderTargets(revision, f, planeWord) {
|
|
269
|
+
const chosen = f.toPlane ? PLANE : f.target;
|
|
270
|
+
const targets = fastenTargetsOf(revision, this.partId);
|
|
271
|
+
const chipOf = (e) => e.composedPath().find(n => n.localName === 'md-filter-chip')?.getAttribute('data-value') ?? null;
|
|
272
|
+
return html `<ox-input-select-buttons
|
|
273
|
+
targets
|
|
274
|
+
aria-labelledby="what"
|
|
275
|
+
.options=${[{ value: PLANE, display: planeWord }, ...targets.filter(t => !t.loops).map(t => ({ value: t.part, display: t.part }))]}
|
|
276
|
+
.value=${chosen || undefined}
|
|
277
|
+
?disabled=${this.locked}
|
|
278
|
+
@change=${(e) => {
|
|
279
|
+
/* A chip pressed again lets go of it; what to fasten to is always one thing once chosen. */
|
|
280
|
+
if (e.detail)
|
|
281
|
+
this.aim(e.detail);
|
|
282
|
+
else
|
|
283
|
+
e.target.value = chosen || undefined;
|
|
284
|
+
}}
|
|
285
|
+
@pointerover=${(e) => this.hover(chipOf(e))}
|
|
286
|
+
@pointerleave=${() => this.hover(null)}
|
|
287
|
+
@focusin=${(e) => this.hover(chipOf(e))}
|
|
288
|
+
@keyup=${(e) => {
|
|
289
|
+
/* The arrows move the focus among the chips inside the list, which tells nothing outside it: read where it is. */
|
|
290
|
+
const at = e.currentTarget.shadowRoot?.activeElement;
|
|
291
|
+
if (at?.localName === 'md-filter-chip')
|
|
292
|
+
this.hover(at.getAttribute('data-value'));
|
|
293
|
+
}}
|
|
294
|
+
></ox-input-select-buttons>
|
|
295
|
+
${targets.filter(t => t.loops).map(t => html `<p hint loops target=${t.part}>${i18next.t('figure.text.target-would-loop', { part: t.part, mine: this.partId })}</p>`)}`;
|
|
296
|
+
}
|
|
297
|
+
aim(part) {
|
|
298
|
+
if (this.locked || !this.revision || !this.form)
|
|
299
|
+
return;
|
|
300
|
+
const chosen = this.form.toPlane ? PLANE : this.form.target;
|
|
301
|
+
if (part === chosen)
|
|
302
|
+
return;
|
|
303
|
+
this.form = aimFasten(this.revision, this.partId, this.form, part === PLANE ? { plane: true } : { part });
|
|
304
|
+
}
|
|
305
|
+
/** The part under the pointer or the focus in the list, for the canvas to light; the plane lights nothing. */
|
|
306
|
+
hover(part) {
|
|
307
|
+
const lit = part && part !== PLANE ? part : null;
|
|
308
|
+
if (lit === this.hovered)
|
|
309
|
+
return;
|
|
310
|
+
this.hovered = lit;
|
|
311
|
+
const face = lit && this.revision && this.form
|
|
312
|
+
? fastenPlanOf(this.revision, this.partId, aimFasten(this.revision, this.partId, this.form, { part: lit }))
|
|
313
|
+
.targetFace
|
|
314
|
+
: undefined;
|
|
315
|
+
this.dispatchEvent(new CustomEvent('fasten-hover', { detail: lit ? { part: lit, face } : null, bubbles: true, composed: true }));
|
|
316
|
+
}
|
|
317
|
+
set(change) {
|
|
318
|
+
this.form = { ...this.form, ...change };
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Where on the target face the part sits, on the axes the drawing cannot set: centred, level with the target's edge on
|
|
322
|
+
* either side, a distance off centre, a share of the figure's size, or the position field it follows.
|
|
323
|
+
*/
|
|
324
|
+
renderAlignment(axes) {
|
|
325
|
+
const f = this.form;
|
|
326
|
+
const set = (axis, how) => this.set({ align: { ...f.align, [axis]: how } });
|
|
327
|
+
const setMm = (axis, mm) => this.set({ alignMm: { ...f.alignMm, [axis]: mm } });
|
|
328
|
+
return html `<div form>
|
|
329
|
+
${axes.map(({ axis, edges }) => {
|
|
330
|
+
const how = f.align[axis] === 'min' || f.align[axis] === 'max'
|
|
331
|
+
? edges
|
|
332
|
+
? f.align[axis]
|
|
333
|
+
: 'centre'
|
|
334
|
+
: (f.align[axis] ?? 'centre');
|
|
335
|
+
return html `<span>${i18next.t('figure.label.align-' + axis)}</span>
|
|
336
|
+
<select @change=${(e) => set(axis, e.target.value)}>
|
|
337
|
+
<option value="centre" ?selected=${how === 'centre'}>${i18next.t('figure.label.align-centre')}</option>
|
|
338
|
+
${edges
|
|
339
|
+
? html `<option value="min" ?selected=${how === 'min'}>
|
|
340
|
+
${i18next.t(`figure.label.align-${axis}-min`)}
|
|
341
|
+
</option>
|
|
342
|
+
<option value="max" ?selected=${how === 'max'}>
|
|
343
|
+
${i18next.t(`figure.label.align-${axis}-max`)}
|
|
344
|
+
</option>`
|
|
345
|
+
: nothing}
|
|
346
|
+
${f.alignInput?.[axis]
|
|
347
|
+
? html `<option value="input" ?selected=${how === 'input'}>
|
|
348
|
+
${i18next.t('figure.label.align-input', { field: fieldWordsOf(f.alignInput[axis], key => i18next.t(key)) })}
|
|
349
|
+
</option>`
|
|
350
|
+
: nothing}
|
|
351
|
+
<option value="mm" ?selected=${how === 'mm'}>${i18next.t('figure.label.align-mm')}</option>
|
|
352
|
+
${this.revision && hasInstanceSize(this.revision.asset, axis)
|
|
353
|
+
? html `<option value="share" ?selected=${how === 'share'}>
|
|
354
|
+
${i18next.t('figure.label.align-share')}
|
|
355
|
+
</option>`
|
|
356
|
+
: nothing}
|
|
357
|
+
</select>
|
|
358
|
+
${how === 'mm' || how === 'share'
|
|
359
|
+
? html `<span></span
|
|
360
|
+
><input
|
|
361
|
+
type="number"
|
|
362
|
+
step="any"
|
|
363
|
+
aria-label=${i18next.t('figure.label.align-mm')}
|
|
364
|
+
.value=${String(f.alignMm[axis] ?? 0)}
|
|
365
|
+
@change=${(e) => setMm(axis, Number(e.target.value) || 0)}
|
|
366
|
+
/>`
|
|
367
|
+
: nothing}`;
|
|
368
|
+
})}
|
|
369
|
+
</div>`;
|
|
370
|
+
}
|
|
371
|
+
renderPreviewWords() {
|
|
372
|
+
if (this.previewWords.length === 1)
|
|
373
|
+
return html `<p preview-words role="status">${this.previewWords[0]}</p>`;
|
|
374
|
+
return this.previewWords.length
|
|
375
|
+
? html `<ul preview-words role="status">
|
|
376
|
+
${this.previewWords.map(line => html `<li>${line}</li>`)}
|
|
377
|
+
</ul>`
|
|
378
|
+
: nothing;
|
|
379
|
+
}
|
|
380
|
+
/** Enter applies what is chosen, Esc closes with nothing written; a key the window does not take goes on. */
|
|
381
|
+
connectedCallback() {
|
|
382
|
+
super.connectedCallback();
|
|
383
|
+
this.setAttribute('role', 'dialog');
|
|
384
|
+
this.setAttribute('aria-label', i18next.t('figure.label.fastening'));
|
|
385
|
+
this.addEventListener('keydown', this.onKey);
|
|
386
|
+
/* The popup's fitting width is the overlay's own setting; the window asks it for room for the face and the way side by side. */
|
|
387
|
+
this.closest('ox-floating-overlay')?.style.setProperty('--overlay-center-fit-width', '640px');
|
|
388
|
+
}
|
|
389
|
+
};
|
|
390
|
+
__decorate([
|
|
391
|
+
property({ attribute: false }),
|
|
392
|
+
__metadata("design:type", Object)
|
|
393
|
+
], FigureFastenDialog.prototype, "revision", void 0);
|
|
394
|
+
__decorate([
|
|
395
|
+
property(),
|
|
396
|
+
__metadata("design:type", Object)
|
|
397
|
+
], FigureFastenDialog.prototype, "partId", void 0);
|
|
398
|
+
__decorate([
|
|
399
|
+
property({ type: Boolean }),
|
|
400
|
+
__metadata("design:type", Object)
|
|
401
|
+
], FigureFastenDialog.prototype, "locked", void 0);
|
|
402
|
+
__decorate([
|
|
403
|
+
property({ attribute: false }),
|
|
404
|
+
__metadata("design:type", Array)
|
|
405
|
+
], FigureFastenDialog.prototype, "previewWords", void 0);
|
|
406
|
+
__decorate([
|
|
407
|
+
property(),
|
|
408
|
+
__metadata("design:type", Object)
|
|
409
|
+
], FigureFastenDialog.prototype, "error", void 0);
|
|
410
|
+
__decorate([
|
|
411
|
+
state(),
|
|
412
|
+
__metadata("design:type", Object)
|
|
413
|
+
], FigureFastenDialog.prototype, "form", void 0);
|
|
414
|
+
FigureFastenDialog = __decorate([
|
|
415
|
+
customElement('figure-fasten-dialog')
|
|
416
|
+
], FigureFastenDialog);
|
|
417
|
+
export { FigureFastenDialog };
|
|
418
|
+
//# sourceMappingURL=figure-fasten-dialog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"figure-fasten-dialog.js","sourceRoot":"","sources":["../../client/modeller/figure-fasten-dialog.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/button/filled-button.js'\nimport '@material/web/button/text-button.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 '@operato/input/ox-input-select-buttons.js'\nimport '@operato/popup/ox-popup-actions.js'\nimport { CommonPopupStyles } from '@operato/styles'\nimport { SidePanelStyles } from '@things-factory/figure-viewer/dist-client/side-panel-styles.js'\n\nimport './figure-fasten-drawing.js'\nimport type { FastenChange } from './figure-fasten-drawing.js'\nimport { turnsInPlace, type FastenMode } from './fasten-geometry.js'\nimport {\n aimFasten,\n fastenChanged,\n fastenDetailWordsOf,\n fastenFormOf,\n fastenPlanOf,\n fastenSentenceOf,\n fastenTargetsOf,\n type FastenForm\n} from './fasten-form.js'\nimport { fieldWordsOf, hasInstanceSize, heldBy, type Alignment, type Axis } from './links.js'\nimport type { FigureRevision } from './revision.js'\n\n/** The target list's entry for the plane the figure stands by. */\nconst PLANE = '__plane'\n\n/**\n * 붙이기 창: what the selected part is fastened to, on which face, how -- chosen with room to see it, in the standard\n * popup the editor opens (`openPopup`: its title, its close, moved by its head) (user 2026-10-17: 「속성에는 심플하고\n * 직관적인 결과 위주로, 편집은 팝업에서 넓은 공간을 써서」; 2026-10-18: the standard popup, not a window of its own).\n *\n * Everything chosen here is tried, not done: the command it comes to is told to the editor as it changes\n * (`preview-command`), and the canvas draws it. 「적용」 sends it once (`fasten-apply`), one step of the history;\n * 「취소」 and Esc close it with nothing written (`fasten-cancel`). A row of the target list under the pointer or the\n * arrows asks the canvas to light that part (`fasten-hover`). The window is the only one telling the preview while it\n * is open; the editor keeps the panel quiet.\n */\n@customElement('figure-fasten-dialog')\nexport class FigureFastenDialog extends localize(i18next)(LitElement) {\n static styles = [\n SidePanelStyles,\n CommonPopupStyles,\n css`\n :host {\n display: grid;\n gap: 16px;\n padding: var(--popup-padding, 16px);\n overflow: auto;\n }\n /* The result in one line, at the top: it changes with every choice below it. */\n p[fasten-sentence] {\n margin: 0;\n font-size: var(--ui-t-body, 13px);\n font-weight: 600;\n }\n div[question] {\n display: grid;\n align-content: start;\n gap: 6px;\n }\n h4 {\n margin: 0;\n font-size: var(--ui-t-small, 11.5px);\n font-weight: 600;\n color: var(--md-sys-color-on-surface-variant);\n }\n p[loops] {\n margin: 0;\n }\n details[fasten-detail] {\n margin: 0;\n }\n details[fasten-detail] > div[form] {\n margin-top: 8px;\n max-width: 320px;\n }\n span[fasten-summary] {\n opacity: 0.8;\n }\n ul[preview-words] {\n margin: 0;\n padding: 8px 12px 8px 26px;\n border-radius: 6px;\n background: var(--md-sys-color-surface-container, #8881);\n display: grid;\n gap: 4px;\n }\n p[preview-words] {\n margin: 0;\n padding: 8px 12px;\n border-radius: 6px;\n background: var(--md-sys-color-surface-container, #8881);\n }\n p[error] {\n margin: 0;\n color: var(--md-sys-color-error);\n }\n `\n ]\n\n /** The figure at the version on screen. */\n @property({ attribute: false }) revision?: FigureRevision\n /** The part being fastened. */\n @property() partId = ''\n @property({ type: Boolean }) locked = false\n /** What the command would do, in words (the editor works it out: `command-preview.ts`). */\n @property({ attribute: false }) previewWords: string[] = []\n /** Why the last 「적용」 was refused, in the editor's words. */\n @property() error = ''\n\n @state() private form?: FastenForm\n /** The command the part's own fastening comes to, as opened: tried unchanged, nothing new is said of it. */\n private opened?: string\n private told = ''\n private loadedFor?: string\n private hovered: string | null = null\n\n willUpdate() {\n /* Opened on what the part has now; another part is another window's. */\n if (this.revision && this.partId && this.partId !== this.loadedFor) {\n this.loadedFor = this.partId\n this.form = fastenFormOf(this.revision, this.partId)\n this.opened = JSON.stringify(fastenPlanOf(this.revision, this.partId, this.form).action ?? null)\n }\n }\n\n async firstUpdated() {\n /* Into the list, on what is chosen, so the arrows go from there. */\n const list = this.renderRoot.querySelector<HTMLElement & { updateComplete?: Promise<unknown> }>(\n 'ox-input-select-buttons'\n )\n await list?.updateComplete\n const chips = list?.shadowRoot?.querySelectorAll<HTMLElement>('md-filter-chip')\n ;([...(chips ?? [])].find(c => c.hasAttribute('selected')) ?? chips?.[0])?.focus()\n }\n\n updated() {\n const plan = this.revision && this.form ? fastenPlanOf(this.revision, this.partId, this.form) : undefined\n const candidate = plan?.action ? { action: plan.action, ...(plan.face ? { face: plan.face } : {}) } : null\n const said = JSON.stringify(candidate)\n if (said === this.told) return\n this.told = said\n this.dispatchEvent(new CustomEvent('preview-command', { detail: candidate, bubbles: true, composed: true }))\n }\n\n disconnectedCallback() {\n super.disconnectedCallback()\n /* Closed: what it showed goes with it. */\n if (this.told && this.told !== 'null')\n this.dispatchEvent(new CustomEvent('preview-command', { detail: null, bubbles: true, composed: true }))\n this.told = ''\n this.hover(null)\n }\n\n render() {\n const revision = this.revision\n const f = this.form\n if (!revision || !f) return nothing\n const asset = revision.asset\n const held = !!heldBy(asset, this.partId)\n const plan = fastenPlanOf(revision, this.partId, f)\n const planeWord = i18next.t('figure.label.resize-place-' + (asset.placement ?? 'floor'))\n const modes: FastenMode[] = f.toPlane\n ? ['meet']\n : ['meet', 'flush', ...(turnsInPlace(asset, this.partId) ? (['centre'] as FastenMode[]) : [])]\n const detail = fastenDetailWordsOf(f, (key, params) => i18next.t(key, params))\n const drawing = (show: 'face' | 'how') =>\n html`<figure-fasten-drawing\n show=${show}\n .target=${f.toPlane ? '' : f.target}\n .planeWord=${planeWord}\n .faces=${plan.theirFaces}\n .face=${plan.targetFace}\n .modes=${modes}\n .mode=${plan.mode}\n .view=${plan.view}\n .align=${f.align}\n .alignMm=${f.alignMm}\n .gap=${f.gap}\n .partName=${this.partId}\n ?locked=${this.locked}\n @fasten-change=${(e: CustomEvent<FastenChange>) => (this.form = fastenChanged(revision, this.partId, this.form!, e.detail))}\n ></figure-fasten-drawing>`\n return html`\n ${\n plan.aim\n ? html`<p fasten-sentence role=\"status\">\n ${fastenSentenceOf(this.partId, f, plan, planeWord, (key, params) => i18next.t(key, params))}\n </p>`\n : html`<p hint fasten-sentence>${i18next.t('figure.text.fasten-target-first')}</p>`\n }\n <div question=\"what\">\n <h4 id=\"what\">${i18next.t('figure.label.fasten-what')}</h4>\n ${this.renderTargets(revision, f, planeWord)}\n </div>\n ${\n plan.aim\n ? html`<div question=\"face\">\n <h4>\n ${\n /* The face asked is the target's, named: the part's own face follows from it. */ f.toPlane\n ? i18next.t('figure.label.fasten-face-on-plane', { plane: planeWord })\n : i18next.t('figure.label.fasten-face-on-part', { part: f.target })\n }\n </h4>\n ${drawing('face')}\n </div>\n ${\n modes.length > 1\n ? html`<div question=\"how\">\n <h4>${i18next.t('figure.label.fasten-how')}</h4>\n ${drawing('how')}\n </div>`\n : nothing\n }\n <div question=\"detail\">\n <details fasten-detail>\n <summary>\n ${i18next.t('figure.label.fasten-detail')}${detail.length ? html` · <span fasten-summary>${detail.join(' · ')}</span>` : nothing}\n </summary>\n ${plan.alignRows.length ? this.renderAlignment(plan.alignRows) : nothing}\n <div form>\n <span>${i18next.t('figure.label.gap')}</span>\n <input\n gap\n type=\"number\"\n step=\"any\"\n .value=${f.gapKept !== undefined ? '' : String(f.gap)}\n placeholder=${f.gapKept !== undefined ? i18next.t('figure.label.follows-expression') : ''}\n @change=${(e: Event) => this.set({ gap: Number((e.target as HTMLInputElement).value) || 0, gapKept: undefined })}\n />\n <span>${i18next.t('figure.label.twist')}</span>\n <input\n twist\n type=\"number\"\n step=\"any\"\n .value=${String(f.twist)}\n @change=${(e: Event) => this.set({ twist: Number((e.target as HTMLInputElement).value) || 0 })}\n />\n </div>\n </details>\n </div>\n ${/* The one it has, tried unchanged: that it stays where it is goes without saying. */ JSON.stringify(plan.action ?? null) === this.opened ? nothing : this.renderPreviewWords()}`\n : nothing\n }\n ${this.error ? html`<p error role=\"alert\">${this.error}</p>` : nothing}\n <ox-popup-actions>\n <md-text-button slot=\"cancel\" cancel @click=${this.cancel}>${i18next.t('figure.button.cancel')}</md-text-button>\n <md-filled-button slot=\"confirm\" apply ?disabled=${this.locked || !plan.action} @click=${this.apply}>\n ${i18next.t(held ? 'figure.button.apply' : 'figure.button.fasten')}\n </md-filled-button>\n </ox-popup-actions>\n `\n }\n\n /**\n * What to fasten to: the plane and the other parts, as the standard choice chips. The arrows move among them and\n * the canvas lights the part under the focus or the pointer; a part hanging from this one is not offered, and why is\n * said under them.\n */\n private renderTargets(revision: FigureRevision, f: FastenForm, planeWord: string) {\n const chosen = f.toPlane ? PLANE : f.target\n const targets = fastenTargetsOf(revision, this.partId)\n const chipOf = (e: Event) =>\n (e.composedPath().find(n => (n as Element).localName === 'md-filter-chip') as Element | undefined)?.getAttribute(\n 'data-value'\n ) ?? null\n return html`<ox-input-select-buttons\n targets\n aria-labelledby=\"what\"\n .options=${[{ value: PLANE, display: planeWord }, ...targets.filter(t => !t.loops).map(t => ({ value: t.part, display: t.part }))]}\n .value=${chosen || undefined}\n ?disabled=${this.locked}\n @change=${(e: CustomEvent) => {\n /* A chip pressed again lets go of it; what to fasten to is always one thing once chosen. */\n if (e.detail) this.aim(e.detail)\n else (e.target as HTMLElement & { value?: string }).value = chosen || undefined\n }}\n @pointerover=${(e: Event) => this.hover(chipOf(e))}\n @pointerleave=${() => this.hover(null)}\n @focusin=${(e: Event) => this.hover(chipOf(e))}\n @keyup=${(e: KeyboardEvent) => {\n /* The arrows move the focus among the chips inside the list, which tells nothing outside it: read where it is. */\n const at = (e.currentTarget as Element).shadowRoot?.activeElement\n if (at?.localName === 'md-filter-chip') this.hover(at.getAttribute('data-value'))\n }}\n ></ox-input-select-buttons>\n ${targets.filter(t => t.loops).map(t => html`<p hint loops target=${t.part}>${i18next.t('figure.text.target-would-loop', { part: t.part, mine: this.partId })}</p>`)}`\n }\n\n private aim(part: string) {\n if (this.locked || !this.revision || !this.form) return\n const chosen = this.form.toPlane ? PLANE : this.form.target\n if (part === chosen) return\n this.form = aimFasten(this.revision, this.partId, this.form, part === PLANE ? { plane: true } : { part })\n }\n\n /** The part under the pointer or the focus in the list, for the canvas to light; the plane lights nothing. */\n private hover(part: string | null) {\n const lit = part && part !== PLANE ? part : null\n if (lit === this.hovered) return\n this.hovered = lit\n const face =\n lit && this.revision && this.form\n ? fastenPlanOf(this.revision, this.partId, aimFasten(this.revision, this.partId, this.form, { part: lit }))\n .targetFace\n : undefined\n this.dispatchEvent(\n new CustomEvent('fasten-hover', { detail: lit ? { part: lit, face } : null, bubbles: true, composed: true })\n )\n }\n\n private set(change: Partial<FastenForm>) {\n this.form = { ...this.form!, ...change }\n }\n\n /**\n * Where on the target face the part sits, on the axes the drawing cannot set: centred, level with the target's edge on\n * either side, a distance off centre, a share of the figure's size, or the position field it follows.\n */\n private renderAlignment(axes: { axis: Axis; edges: boolean }[]) {\n const f = this.form!\n const set = (axis: Axis, how: Alignment) => this.set({ align: { ...f.align, [axis]: how } })\n const setMm = (axis: Axis, mm: number) => this.set({ alignMm: { ...f.alignMm, [axis]: mm } })\n return html`<div form>\n ${axes.map(({ axis, edges }) => {\n const how =\n f.align[axis] === 'min' || f.align[axis] === 'max'\n ? edges\n ? f.align[axis]!\n : 'centre'\n : (f.align[axis] ?? 'centre')\n return html`<span>${i18next.t('figure.label.align-' + axis)}</span>\n <select @change=${(e: Event) => set(axis, (e.target as HTMLSelectElement).value as Alignment)}>\n <option value=\"centre\" ?selected=${how === 'centre'}>${i18next.t('figure.label.align-centre')}</option>\n ${\n edges\n ? html`<option value=\"min\" ?selected=${how === 'min'}>\n ${i18next.t(`figure.label.align-${axis}-min`)}\n </option>\n <option value=\"max\" ?selected=${how === 'max'}>\n ${i18next.t(`figure.label.align-${axis}-max`)}\n </option>`\n : nothing\n }\n ${\n f.alignInput?.[axis]\n ? html`<option value=\"input\" ?selected=${how === 'input'}>\n ${i18next.t('figure.label.align-input', { field: fieldWordsOf(f.alignInput[axis]!, key => i18next.t(key)) })}\n </option>`\n : nothing\n }\n <option value=\"mm\" ?selected=${how === 'mm'}>${i18next.t('figure.label.align-mm')}</option>\n ${\n this.revision && hasInstanceSize(this.revision.asset, axis)\n ? html`<option value=\"share\" ?selected=${how === 'share'}>\n ${i18next.t('figure.label.align-share')}\n </option>`\n : nothing\n }\n </select>\n ${\n how === 'mm' || how === 'share'\n ? html`<span></span\n ><input\n type=\"number\"\n step=\"any\"\n aria-label=${i18next.t('figure.label.align-mm')}\n .value=${String(f.alignMm[axis] ?? 0)}\n @change=${(e: Event) => setMm(axis, Number((e.target as HTMLInputElement).value) || 0)}\n />`\n : nothing\n }`\n })}\n </div>`\n }\n\n private renderPreviewWords() {\n if (this.previewWords.length === 1) return html`<p preview-words role=\"status\">${this.previewWords[0]}</p>`\n return this.previewWords.length\n ? html`<ul preview-words role=\"status\">\n ${this.previewWords.map(line => html`<li>${line}</li>`)}\n </ul>`\n : nothing\n }\n\n /** Enter applies what is chosen, Esc closes with nothing written; a key the window does not take goes on. */\n connectedCallback() {\n super.connectedCallback()\n this.setAttribute('role', 'dialog')\n this.setAttribute('aria-label', i18next.t('figure.label.fastening'))\n this.addEventListener('keydown', this.onKey)\n /* The popup's fitting width is the overlay's own setting; the window asks it for room for the face and the way side by side. */\n this.closest<HTMLElement>('ox-floating-overlay')?.style.setProperty('--overlay-center-fit-width', '640px')\n }\n\n private onKey = async (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n e.stopPropagation()\n e.preventDefault()\n this.cancel()\n return\n }\n if (e.key !== 'Enter' || e.isComposing) return\n const path = e.composedPath() as Element[]\n const target = path[0] as HTMLElement\n /* On a chip of the list Enter applies, as it does anywhere in the window; a chip is chosen by Space or a press. */\n const onChip = path.some(n => n.localName === 'ox-input-select-buttons')\n /* A button, a fold or a list of options does what Enter does to it. */\n if (\n !onChip &&\n (target.localName === 'button' ||\n target.localName === 'summary' ||\n target.localName === 'select' ||\n /^md-.*button$/.test(target.localName))\n )\n return\n e.preventDefault()\n e.stopPropagation()\n /* A number typed and Enter: the number first, as leaving the field would take it. */\n if (target.localName === 'input') target.dispatchEvent(new Event('change', { bubbles: true, composed: true }))\n await this.updateComplete\n this.apply()\n }\n\n private apply = () => {\n const plan = this.revision && this.form ? fastenPlanOf(this.revision, this.partId, this.form) : undefined\n if (this.locked || !plan?.action) return\n this.dispatchEvent(\n new CustomEvent('fasten-apply', { detail: { action: plan.action }, bubbles: true, composed: true })\n )\n }\n\n private cancel = () => {\n this.dispatchEvent(new CustomEvent('fasten-cancel', { bubbles: true, composed: true }))\n }\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { LitElement } from 'lit';
|
|
1
|
+
import { LitElement, nothing } from 'lit';
|
|
2
2
|
import { type FaceView, type FastenMode } from './fasten-geometry.js';
|
|
3
3
|
import type { Alignment } from './links.js';
|
|
4
4
|
type Axis = 'x' | 'y' | 'z';
|
|
@@ -25,7 +25,8 @@ declare const FigureFastenDrawing_base: (new (...args: any[]) => LitElement) & t
|
|
|
25
25
|
* footprint on it: dragged, caught at the centre and at the edges from inside and outside, a guide drawn where it is
|
|
26
26
|
* caught and a dimension where it is not, which is pressed to type the number. It may go past the edges: the part that
|
|
27
27
|
* does is shaded, and a part off the face altogether is said so. Nothing is decided here: every change goes to the form
|
|
28
|
-
* (`fasten-change`), and a press or a drag let go says so (`fasten-release`)
|
|
28
|
+
* (`fasten-change`), and a press or a drag let go says so (`fasten-release`). The window draws it twice, under its own
|
|
29
|
+
* questions: the face (`show="face"`: the box and the face seen from above, side by side) and the way (`show="how"`).
|
|
29
30
|
*/
|
|
30
31
|
export declare class FigureFastenDrawing extends FigureFastenDrawing_base {
|
|
31
32
|
static styles: import("lit").CSSResult;
|
|
@@ -46,6 +47,8 @@ export declare class FigureFastenDrawing extends FigureFastenDrawing_base {
|
|
|
46
47
|
locked: boolean;
|
|
47
48
|
/** The form already says how the fastening it has lies on the face: the drawing says it only while dragging. */
|
|
48
49
|
said: boolean;
|
|
50
|
+
/** Which of its questions it draws: the face, or the way. */
|
|
51
|
+
show: 'face' | 'how';
|
|
49
52
|
/** Where the footprint is while dragging, and what it is caught by; undefined when nothing is dragged. */
|
|
50
53
|
private dragged?;
|
|
51
54
|
private typing?;
|
|
@@ -53,7 +56,7 @@ export declare class FigureFastenDrawing extends FigureFastenDrawing_base {
|
|
|
53
56
|
private gapFrom?;
|
|
54
57
|
private change;
|
|
55
58
|
private released;
|
|
56
|
-
render(): import("lit-html").TemplateResult<1>;
|
|
59
|
+
render(): typeof nothing | import("lit-html").TemplateResult<1>;
|
|
57
60
|
/** The target box, its faces pressed; or the plane. */
|
|
58
61
|
private renderTarget;
|
|
59
62
|
/** How: three side views; the chosen one's gap is dragged. */
|
|
@@ -16,6 +16,9 @@ const HIDDEN = [
|
|
|
16
16
|
['back', 236, 14]
|
|
17
17
|
];
|
|
18
18
|
const PANEL_W = 300;
|
|
19
|
+
/* The face seen from above, beside the box in the fastening window: taller than the panel's was, for a long face's parts. */
|
|
20
|
+
const WHERE_W = 360;
|
|
21
|
+
const WHERE_H = 200;
|
|
19
22
|
/**
|
|
20
23
|
* 붙이기 그림 (user 2026-10-01) — which face of what the part is fastened to, how, and where on it; each of them chosen
|
|
21
24
|
* by pressing or dragging the drawing, none by a list of faces.
|
|
@@ -26,7 +29,8 @@ const PANEL_W = 300;
|
|
|
26
29
|
* footprint on it: dragged, caught at the centre and at the edges from inside and outside, a guide drawn where it is
|
|
27
30
|
* caught and a dimension where it is not, which is pressed to type the number. It may go past the edges: the part that
|
|
28
31
|
* does is shaded, and a part off the face altogether is said so. Nothing is decided here: every change goes to the form
|
|
29
|
-
* (`fasten-change`), and a press or a drag let go says so (`fasten-release`)
|
|
32
|
+
* (`fasten-change`), and a press or a drag let go says so (`fasten-release`). The window draws it twice, under its own
|
|
33
|
+
* questions: the face (`show="face"`: the box and the face seen from above, side by side) and the way (`show="how"`).
|
|
30
34
|
*/
|
|
31
35
|
let FigureFastenDrawing = class FigureFastenDrawing extends localize(i18next)(LitElement) {
|
|
32
36
|
constructor() {
|
|
@@ -47,6 +51,8 @@ let FigureFastenDrawing = class FigureFastenDrawing extends localize(i18next)(Li
|
|
|
47
51
|
this.locked = false;
|
|
48
52
|
/** The form already says how the fastening it has lies on the face: the drawing says it only while dragging. */
|
|
49
53
|
this.said = false;
|
|
54
|
+
/** Which of its questions it draws: the face, or the way. */
|
|
55
|
+
this.show = 'face';
|
|
50
56
|
this.scale = 1;
|
|
51
57
|
}
|
|
52
58
|
static { this.styles = css `
|
|
@@ -66,31 +72,45 @@ let FigureFastenDrawing = class FigureFastenDrawing extends localize(i18next)(Li
|
|
|
66
72
|
}
|
|
67
73
|
div[modes] {
|
|
68
74
|
display: grid;
|
|
69
|
-
grid-template-columns: repeat(auto-fit, minmax(
|
|
75
|
+
grid-template-columns: repeat(auto-fit, minmax(72px, 1fr));
|
|
70
76
|
gap: 6px;
|
|
71
77
|
}
|
|
72
78
|
div[modes] svg {
|
|
73
|
-
width:
|
|
74
|
-
height:
|
|
79
|
+
width: 48px;
|
|
80
|
+
height: 33px;
|
|
75
81
|
}
|
|
82
|
+
/* A choice drawn, not a box to press (the side's rule: no boxed buttons): the chosen one on the primary's ground. */
|
|
76
83
|
div[modes] button {
|
|
77
84
|
display: grid;
|
|
78
85
|
justify-items: center;
|
|
79
86
|
align-content: center;
|
|
80
87
|
gap: 2px;
|
|
81
|
-
padding:
|
|
88
|
+
padding: 4px 4px 6px;
|
|
82
89
|
font: inherit;
|
|
83
90
|
color: var(--md-sys-color-on-surface);
|
|
84
91
|
background: none;
|
|
85
|
-
border:
|
|
86
|
-
border-radius:
|
|
92
|
+
border: none;
|
|
93
|
+
border-radius: 6px;
|
|
87
94
|
cursor: pointer;
|
|
88
95
|
}
|
|
96
|
+
div[modes] button:hover:not([on]):not(:disabled) {
|
|
97
|
+
background: var(--md-sys-color-surface-container);
|
|
98
|
+
}
|
|
89
99
|
div[modes] button[on] {
|
|
90
|
-
border: 1.5px solid var(--md-sys-color-primary);
|
|
91
100
|
background: var(--md-sys-color-primary-container);
|
|
92
101
|
color: var(--md-sys-color-on-primary-container);
|
|
93
102
|
}
|
|
103
|
+
/* The box and the face seen from above side by side, one under the other where the window is narrow. */
|
|
104
|
+
div[face-row] {
|
|
105
|
+
display: grid;
|
|
106
|
+
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
|
107
|
+
gap: 12px;
|
|
108
|
+
align-items: start;
|
|
109
|
+
}
|
|
110
|
+
div[face-row] > div {
|
|
111
|
+
display: grid;
|
|
112
|
+
gap: 8px;
|
|
113
|
+
}
|
|
94
114
|
div[where] {
|
|
95
115
|
position: relative;
|
|
96
116
|
}
|
|
@@ -113,7 +133,9 @@ let FigureFastenDrawing = class FigureFastenDrawing extends localize(i18next)(Li
|
|
|
113
133
|
this.dispatchEvent(new CustomEvent('fasten-release', { bubbles: true, composed: true }));
|
|
114
134
|
}
|
|
115
135
|
render() {
|
|
116
|
-
|
|
136
|
+
if (this.show === 'how')
|
|
137
|
+
return this.renderModes();
|
|
138
|
+
return html `<div face-row>${this.target ? html `<div>${this.renderTarget()}</div>` : nothing}<div>${this.renderWhere()}</div></div>`;
|
|
117
139
|
}
|
|
118
140
|
/** The target box, its faces pressed; or the plane. */
|
|
119
141
|
renderTarget() {
|
|
@@ -235,7 +257,7 @@ let FigureFastenDrawing = class FigureFastenDrawing extends localize(i18next)(Li
|
|
|
235
257
|
if (!view)
|
|
236
258
|
return nothing;
|
|
237
259
|
const { h, v, sign } = screenOf(view.axes);
|
|
238
|
-
const W =
|
|
260
|
+
const W = WHERE_W, H = WHERE_H;
|
|
239
261
|
/* Room around the face for the part to go past its edges by about half itself; a plane with no size, three parts wide. */
|
|
240
262
|
const span = (a) => (view.face[a] !== undefined ? view.face[a] + view.part[a] : view.part[a] * 3);
|
|
241
263
|
const k = Math.min((W - 40) / span(h), (H - 40) / span(v));
|
|
@@ -444,6 +466,10 @@ __decorate([
|
|
|
444
466
|
property({ type: Boolean }),
|
|
445
467
|
__metadata("design:type", Object)
|
|
446
468
|
], FigureFastenDrawing.prototype, "said", void 0);
|
|
469
|
+
__decorate([
|
|
470
|
+
property(),
|
|
471
|
+
__metadata("design:type", String)
|
|
472
|
+
], FigureFastenDrawing.prototype, "show", void 0);
|
|
447
473
|
__decorate([
|
|
448
474
|
state(),
|
|
449
475
|
__metadata("design:type", Object)
|