@things-factory/station 10.1.42 → 10.1.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2 @@
1
+ import type { ComponentNature } from '@hatiolab/things-scene';
2
+ export declare function fieldNature(): ComponentNature;
@@ -0,0 +1,42 @@
1
+ import { onStationAxes, stationBindingNames } from '../station-board-binding';
2
+ /**
3
+ * What the board editor shows for `station-field` — **its own file so it can be tested without the scene.**
4
+ *
5
+ * The scene class carries a decorator, and a test run that only strips types cannot load it. The part that
6
+ * matters here — when the field list is read — has no reason to need the canvas.
7
+ */
8
+ const NATURE = {
9
+ mutable: false,
10
+ resizable: true,
11
+ rotatable: false,
12
+ properties: [
13
+ {
14
+ type: 'select',
15
+ label: 'field',
16
+ name: 'field',
17
+ /* Filled in by `fieldNature` — read when asked, not when this file loads. */
18
+ property: { options: [] }
19
+ },
20
+ { type: 'string', label: 'label', name: 'label', property: 'label' },
21
+ { type: 'number', label: 'text size', name: 'valueSize', property: 'valueSize' }
22
+ ]
23
+ };
24
+ /*
25
+ * The field list is read **when the editor asks**, and kept until the axes change.
26
+ *
27
+ * Read once when this file loads, it froze before a product registered its axes — the scene module is
28
+ * imported before bootstrap declares the product, and a factory's thirty-two names would be missing
29
+ * from the picker for good, with no error (ruling on moving the axes, 2026-09-24).
30
+ */
31
+ let natureNow;
32
+ onStationAxes(() => {
33
+ natureNow = undefined;
34
+ });
35
+ export function fieldNature() {
36
+ natureNow ??= {
37
+ ...NATURE,
38
+ properties: NATURE.properties.map(one => (one.name === 'field' ? { ...one, property: { options: stationBindingNames() } } : one))
39
+ };
40
+ return natureNow;
41
+ }
42
+ //# sourceMappingURL=station-field-nature.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"station-field-nature.js","sourceRoot":"","sources":["../../../client/station/scene/station-field-nature.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAE7E;;;;;GAKG;AACH,MAAM,MAAM,GAAoB;IAC9B,OAAO,EAAE,KAAK;IACd,SAAS,EAAE,IAAI;IACf,SAAS,EAAE,KAAK;IAChB,UAAU,EAAE;QACV;YACE,IAAI,EAAE,QAAQ;YACd,KAAK,EAAE,OAAO;YACd,IAAI,EAAE,OAAO;YACb,6EAA6E;YAC7E,QAAQ,EAAE,EAAE,OAAO,EAAE,EAAc,EAAE;SACtC;QACD,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE;QACpE,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,WAAW,EAAE;KACjF;CACF,CAAA;AAED;;;;;;GAMG;AACH,IAAI,SAAsC,CAAA;AAE1C,aAAa,CAAC,GAAG,EAAE;IACjB,SAAS,GAAG,SAAS,CAAA;AACvB,CAAC,CAAC,CAAA;AAEF,MAAM,UAAU,WAAW;IACzB,SAAS,KAAK;QACZ,GAAG,MAAM;QACT,UAAU,EAAE,MAAM,CAAC,UAAW,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,EAAE,GAAG,GAAG,EAAE,QAAQ,EAAE,EAAE,OAAO,EAAE,mBAAmB,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;KACnI,CAAA;IAED,OAAO,SAAS,CAAA;AAClB,CAAC","sourcesContent":["import type { ComponentNature } from '@hatiolab/things-scene'\n\nimport { onStationAxes, stationBindingNames } from '../station-board-binding'\n\n/**\n * What the board editor shows for `station-field` — **its own file so it can be tested without the scene.**\n *\n * The scene class carries a decorator, and a test run that only strips types cannot load it. The part that\n * matters here — when the field list is read — has no reason to need the canvas.\n */\nconst NATURE: ComponentNature = {\n mutable: false,\n resizable: true,\n rotatable: false,\n properties: [\n {\n type: 'select',\n label: 'field',\n name: 'field',\n /* Filled in by `fieldNature` — read when asked, not when this file loads. */\n property: { options: [] as string[] }\n },\n { type: 'string', label: 'label', name: 'label', property: 'label' },\n { type: 'number', label: 'text size', name: 'valueSize', property: 'valueSize' }\n ]\n}\n\n/*\n * The field list is read **when the editor asks**, and kept until the axes change.\n *\n * Read once when this file loads, it froze before a product registered its axes — the scene module is\n * imported before bootstrap declares the product, and a factory's thirty-two names would be missing\n * from the picker for good, with no error (ruling on moving the axes, 2026-09-24).\n */\nlet natureNow: ComponentNature | undefined\n\nonStationAxes(() => {\n natureNow = undefined\n})\n\nexport function fieldNature(): ComponentNature {\n natureNow ??= {\n ...NATURE,\n properties: NATURE.properties!.map(one => (one.name === 'field' ? { ...one, property: { options: stationBindingNames() } } : one))\n }\n\n return natureNow\n}\n"]}
@@ -1,6 +1,26 @@
1
- import { ComponentNature } from '@hatiolab/things-scene';
2
1
  import { StationScene } from './station-scene';
2
+ /**
3
+ * On a board · one thing from the context, **chosen from a list.**
4
+ *
5
+ * ── Why this exists at all ─────────────────────────────────────────────────
6
+ * Values already reach any plain text component whose id is a context name — set the id to
7
+ * `order.name` and the job order appears. That works, and it is also magic: nothing on the property
8
+ * panel says that particular id means anything, so an author has to be told, and a typo is
9
+ * indistinguishable from a name that was never offered.
10
+ *
11
+ * This component takes the same names and makes them a **dropdown**. The list comes out of
12
+ * `stationBindingNames()`, so it cannot drift from what is actually pushed. An author drops one of
13
+ * these, opens the field list, and reads what a station can tell them.
14
+ *
15
+ * The magic id still works. It is the shorter path for someone who knows the names, and drawing a
16
+ * whole line map out of these boxes would be tedious. This is the path for someone who does not.
17
+ *
18
+ * ── It shows the name while being drawn ────────────────────────────────────
19
+ * There is no runtime in the board modeller, so in edit mode this draws the field name where the
20
+ * value will be. An empty box during authoring is how a board ends up shipped with a field nobody
21
+ * ever bound.
22
+ */
3
23
  export default class StationFieldScene extends StationScene {
4
24
  render(context: CanvasRenderingContext2D): void;
5
- get nature(): ComponentNature;
25
+ get nature(): import("@hatiolab/things-scene").ComponentNature;
6
26
  }
@@ -1,6 +1,6 @@
1
1
  import { __decorate } from "tslib";
2
2
  import { sceneComponent } from '@hatiolab/things-scene';
3
- import { stationBindingNames } from '../station-board-binding';
3
+ import { fieldNature } from './station-field-nature';
4
4
  import { STATION_COLOR, drawDetached, withinBox } from './station-scene-style';
5
5
  import { StationScene } from './station-scene';
6
6
  /**
@@ -24,22 +24,6 @@ import { StationScene } from './station-scene';
24
24
  * value will be. An empty box during authoring is how a board ends up shipped with a field nobody
25
25
  * ever bound.
26
26
  */
27
- const NATURE = {
28
- mutable: false,
29
- resizable: true,
30
- rotatable: false,
31
- properties: [
32
- {
33
- type: 'select',
34
- label: 'field',
35
- name: 'field',
36
- /* Straight out of the projection — a name here that is not pushed cannot happen. */
37
- property: { options: stationBindingNames() }
38
- },
39
- { type: 'string', label: 'label', name: 'label', property: 'label' },
40
- { type: 'number', label: 'text size', name: 'valueSize', property: 'valueSize' }
41
- ]
42
- };
43
27
  let StationFieldScene = class StationFieldScene extends StationScene {
44
28
  render(context) {
45
29
  const state = this.state;
@@ -79,7 +63,7 @@ let StationFieldScene = class StationFieldScene extends StationScene {
79
63
  });
80
64
  }
81
65
  get nature() {
82
- return NATURE;
66
+ return fieldNature();
83
67
  }
84
68
  };
85
69
  StationFieldScene = __decorate([
@@ -1 +1 @@
1
- {"version":3,"file":"station-field-scene.js","sourceRoot":"","sources":["../../../client/station/scene/station-field-scene.ts"],"names":[],"mappings":"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","sourcesContent":["import { ComponentNature, sceneComponent } from '@hatiolab/things-scene'\n\nimport { stationBindingNames } from '../station-board-binding'\nimport { STATION_COLOR, drawDetached, withinBox } from './station-scene-style'\nimport { StationScene } from './station-scene'\n\n/**\n * On a board · one thing from the context, **chosen from a list.**\n *\n * ── Why this exists at all ─────────────────────────────────────────────────\n * Values already reach any plain text component whose id is a context name — set the id to\n * `order.name` and the job order appears. That works, and it is also magic: nothing on the property\n * panel says that particular id means anything, so an author has to be told, and a typo is\n * indistinguishable from a name that was never offered.\n *\n * This component takes the same names and makes them a **dropdown**. The list comes out of\n * `stationBindingNames()`, so it cannot drift from what is actually pushed. An author drops one of\n * these, opens the field list, and reads what a station can tell them.\n *\n * The magic id still works. It is the shorter path for someone who knows the names, and drawing a\n * whole line map out of these boxes would be tedious. This is the path for someone who does not.\n *\n * ── It shows the name while being drawn ────────────────────────────────────\n * There is no runtime in the board modeller, so in edit mode this draws the field name where the\n * value will be. An empty box during authoring is how a board ends up shipped with a field nobody\n * ever bound.\n */\nconst NATURE: ComponentNature = {\n mutable: false,\n resizable: true,\n rotatable: false,\n properties: [\n {\n type: 'select',\n label: 'field',\n name: 'field',\n /* Straight out of the projection — a name here that is not pushed cannot happen. */\n property: { options: stationBindingNames() }\n },\n { type: 'string', label: 'label', name: 'label', property: 'label' },\n { type: 'number', label: 'text size', name: 'valueSize', property: 'valueSize' }\n ]\n}\n\n@sceneComponent('station-field')\nexport default class StationFieldScene extends StationScene {\n render(context: CanvasRenderingContext2D) {\n const state: any = this.state\n const { left, top, width, height } = state\n const field = String(state.field || '')\n\n if (!field) {\n /* Nothing chosen yet. Say which box it is, so the author can find it on a busy board. */\n drawDetached(context, 'pick a field', left, top, width, height)\n return\n }\n\n const bound = this.context\n\n withinBox(context, left, top, width, height, () => {\n /*\n * The caption. Falls back to the field name rather than to nothing — a bare number on a\n * shop-floor screen is the thing operators most often read as the wrong quantity.\n */\n context.textBaseline = 'top'\n context.textAlign = 'left'\n context.fillStyle = STATION_COLOR.quiet\n context.font = 'bold 11px sans-serif'\n context.fillText(String(state.label || field), left + 2, top + 2)\n\n const size = Number(state.valueSize) > 0 ? Number(state.valueSize) : Math.max(14, Math.min(40, height * 0.45))\n\n context.font = `bold ${size}px sans-serif`\n\n if (!bound) {\n /*\n * No context: the modeller, or a screen that has not attached yet. Draw the name of what\n * will land here, greyed — never a zero, which would read as a real count.\n */\n context.fillStyle = STATION_COLOR.line\n context.fillText(field, left + 2, top + 18)\n return\n }\n\n const value = bound.values[field]\n\n context.fillStyle = STATION_COLOR.ink\n context.fillText(value === undefined ? '' : String(value), left + 2, top + 18)\n context.textBaseline = 'alphabetic'\n })\n }\n\n get nature() {\n return NATURE\n }\n}\n"]}
1
+ {"version":3,"file":"station-field-scene.js","sourceRoot":"","sources":["../../../client/station/scene/station-field-scene.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAEvD,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAA;AACpD,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AAC9E,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAE9C;;;;;;;;;;;;;;;;;;;;GAoBG;AAEY,IAAM,iBAAiB,GAAvB,MAAM,iBAAkB,SAAQ,YAAY;IACzD,MAAM,CAAC,OAAiC;QACtC,MAAM,KAAK,GAAQ,IAAI,CAAC,KAAK,CAAA;QAC7B,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,KAAK,CAAA;QAC1C,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC,CAAA;QAEvC,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,yFAAyF;YACzF,YAAY,CAAC,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,CAAC,CAAA;YAC/D,OAAM;QACR,CAAC;QAED,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAA;QAE1B,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE;YAChD;;;eAGG;YACH,OAAO,CAAC,YAAY,GAAG,KAAK,CAAA;YAC5B,OAAO,CAAC,SAAS,GAAG,MAAM,CAAA;YAC1B,OAAO,CAAC,SAAS,GAAG,aAAa,CAAC,KAAK,CAAA;YACvC,OAAO,CAAC,IAAI,GAAG,sBAAsB,CAAA;YACrC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,EAAE,IAAI,GAAG,CAAC,EAAE,GAAG,GAAG,CAAC,CAAC,CAAA;YAEjE,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC,CAAA;YAE9G,OAAO,CAAC,IAAI,GAAG,QAAQ,IAAI,eAAe,CAAA;YAE1C,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX;;;mBAGG;gBACH,OAAO,CAAC,SAAS,GAAG,aAAa,CAAC,IAAI,CAAA;gBACtC,OAAO,CAAC,QAAQ,CAAC,KAAK,EAAE,IAAI,GAAG,CAAC,EAAE,GAAG,GAAG,EAAE,CAAC,CAAA;gBAC3C,OAAM;YACR,CAAC;YAED,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;YAEjC,OAAO,CAAC,SAAS,GAAG,aAAa,CAAC,GAAG,CAAA;YACrC,OAAO,CAAC,QAAQ,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,IAAI,GAAG,CAAC,EAAE,GAAG,GAAG,EAAE,CAAC,CAAA;YAC9E,OAAO,CAAC,YAAY,GAAG,YAAY,CAAA;QACrC,CAAC,CAAC,CAAA;IACJ,CAAC;IAED,IAAI,MAAM;QACR,OAAO,WAAW,EAAE,CAAA;IACtB,CAAC;CACF,CAAA;AAlDoB,iBAAiB;IADrC,cAAc,CAAC,eAAe,CAAC;GACX,iBAAiB,CAkDrC;eAlDoB,iBAAiB","sourcesContent":["import { sceneComponent } from '@hatiolab/things-scene'\n\nimport { fieldNature } from './station-field-nature'\nimport { STATION_COLOR, drawDetached, withinBox } from './station-scene-style'\nimport { StationScene } from './station-scene'\n\n/**\n * On a board · one thing from the context, **chosen from a list.**\n *\n * ── Why this exists at all ─────────────────────────────────────────────────\n * Values already reach any plain text component whose id is a context name — set the id to\n * `order.name` and the job order appears. That works, and it is also magic: nothing on the property\n * panel says that particular id means anything, so an author has to be told, and a typo is\n * indistinguishable from a name that was never offered.\n *\n * This component takes the same names and makes them a **dropdown**. The list comes out of\n * `stationBindingNames()`, so it cannot drift from what is actually pushed. An author drops one of\n * these, opens the field list, and reads what a station can tell them.\n *\n * The magic id still works. It is the shorter path for someone who knows the names, and drawing a\n * whole line map out of these boxes would be tedious. This is the path for someone who does not.\n *\n * ── It shows the name while being drawn ────────────────────────────────────\n * There is no runtime in the board modeller, so in edit mode this draws the field name where the\n * value will be. An empty box during authoring is how a board ends up shipped with a field nobody\n * ever bound.\n */\n@sceneComponent('station-field')\nexport default class StationFieldScene extends StationScene {\n render(context: CanvasRenderingContext2D) {\n const state: any = this.state\n const { left, top, width, height } = state\n const field = String(state.field || '')\n\n if (!field) {\n /* Nothing chosen yet. Say which box it is, so the author can find it on a busy board. */\n drawDetached(context, 'pick a field', left, top, width, height)\n return\n }\n\n const bound = this.context\n\n withinBox(context, left, top, width, height, () => {\n /*\n * The caption. Falls back to the field name rather than to nothing — a bare number on a\n * shop-floor screen is the thing operators most often read as the wrong quantity.\n */\n context.textBaseline = 'top'\n context.textAlign = 'left'\n context.fillStyle = STATION_COLOR.quiet\n context.font = 'bold 11px sans-serif'\n context.fillText(String(state.label || field), left + 2, top + 2)\n\n const size = Number(state.valueSize) > 0 ? Number(state.valueSize) : Math.max(14, Math.min(40, height * 0.45))\n\n context.font = `bold ${size}px sans-serif`\n\n if (!bound) {\n /*\n * No context: the modeller, or a screen that has not attached yet. Draw the name of what\n * will land here, greyed — never a zero, which would read as a real count.\n */\n context.fillStyle = STATION_COLOR.line\n context.fillText(field, left + 2, top + 18)\n return\n }\n\n const value = bound.values[field]\n\n context.fillStyle = STATION_COLOR.ink\n context.fillText(value === undefined ? '' : String(value), left + 2, top + 18)\n context.textBaseline = 'alphabetic'\n })\n }\n\n get nature() {\n return fieldNature()\n }\n}\n"]}
@@ -13,4 +13,14 @@
13
13
  * 뽑던 때에는 원본이 둘이 됐다. 서버가 거절하는 이름을 이쪽이 내놓거나, 서버가 받는 이름을 이쪽이
14
14
  * 안 내놓거나 했고 **양쪽 다 조용했다.**
15
15
  */
16
+ export declare function registerStationAxes(adding: readonly string[]): void;
17
+ /** 제품이 건 축이 바뀌면 알린다. 돌려주는 함수를 부르면 그만 듣는다. */
18
+ export declare function onStationAxes(listener: () => void): () => void;
19
+ /** 이 화면이 읽을 수 있는 축 전부 — 기계의 핵과 제품이 건 것. 늘 이 함수로 읽는다. */
16
20
  export declare function stationBindingNames(): string[];
21
+ /**
22
+ * 시험용 — 등록은 기동 때 한 번 하는 일이다.
23
+ *
24
+ * 듣는 쪽은 지우지 않고 알린다. 모듈을 불러올 때 건 듣기(필드 씬의 고르개 캐시)가 지워지면, 다음 등록이 그 캐시에 안 닿는다.
25
+ */
26
+ export declare function forgetStationAxes(): void;
@@ -13,8 +13,48 @@
13
13
  * 뽑던 때에는 원본이 둘이 됐다. 서버가 거절하는 이름을 이쪽이 내놓거나, 서버가 받는 이름을 이쪽이
14
14
  * 안 내놓거나 했고 **양쪽 다 조용했다.**
15
15
  */
16
- import { STATION_AXES } from '../../shared/station-axes.js';
16
+ import { STATION_AXES, wrongDeclaredAxes } from '../../shared/station-axes.js';
17
+ /*
18
+ * ── 제품의 축 (ADR-0083 판정 ① · 축 옮기기 판정 2026-09-24) ─────────────────────
19
+ * 기계는 핵만 든다. 제품의 낱말(공장의 라인 · 지시서 · 양품 수)은 제품이 **자기 목록 하나**를 서버 선언(`axes`)과 여기에
20
+ * 같이 건다. `registerStationTiles` 와 같은 모양이다 — 읽는 쪽은 늘 함수로 읽고, 바뀌면 알린다. 배열을 export 하면 그 순간의
21
+ * 사본이 되어 늦게 건 제품의 축이 고르개에 안 닿는다.
22
+ *
23
+ * 서버와 **같은 규칙**으로 거절한다(`wrongDeclaredAxes`). 서버만 거절하면 화면은 서버가 받지 않을 이름을 조용히 내놓는다.
24
+ */
25
+ let productAxes = [];
26
+ const listeners = new Set();
27
+ export function registerStationAxes(adding) {
28
+ const wrong = wrongDeclaredAxes(adding);
29
+ if (wrong.length) {
30
+ throw new Error(`station: 화면에 건 축이 틀렸습니다 — ${wrong.map(one => JSON.stringify(one)).join(' · ')}. ` +
31
+ '축은 `무리.이름` 모양이고, 기계가 이미 가진 축은 다시 걸지 않습니다.');
32
+ }
33
+ const twice = adding.filter(one => productAxes.includes(one));
34
+ if (twice.length) {
35
+ console.warn(`[station] 같은 축이 두 번 등록됐습니다 — ${twice.join(' · ')}. 뒤엣것은 버립니다.`);
36
+ }
37
+ productAxes = [...productAxes, ...adding.filter(one => !productAxes.includes(one))];
38
+ listeners.forEach(listener => listener());
39
+ }
40
+ /** 제품이 건 축이 바뀌면 알린다. 돌려주는 함수를 부르면 그만 듣는다. */
41
+ export function onStationAxes(listener) {
42
+ listeners.add(listener);
43
+ return () => {
44
+ listeners.delete(listener);
45
+ };
46
+ }
47
+ /** 이 화면이 읽을 수 있는 축 전부 — 기계의 핵과 제품이 건 것. 늘 이 함수로 읽는다. */
17
48
  export function stationBindingNames() {
18
- return [...STATION_AXES];
49
+ return [...STATION_AXES, ...productAxes];
50
+ }
51
+ /**
52
+ * 시험용 — 등록은 기동 때 한 번 하는 일이다.
53
+ *
54
+ * 듣는 쪽은 지우지 않고 알린다. 모듈을 불러올 때 건 듣기(필드 씬의 고르개 캐시)가 지워지면, 다음 등록이 그 캐시에 안 닿는다.
55
+ */
56
+ export function forgetStationAxes() {
57
+ productAxes = [];
58
+ listeners.forEach(listener => listener());
19
59
  }
20
60
  //# sourceMappingURL=station-board-binding.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"station-board-binding.js","sourceRoot":"","sources":["../../client/station/station-board-binding.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAA;AAE3D,MAAM,UAAU,mBAAmB;IACjC,OAAO,CAAC,GAAG,YAAY,CAAC,CAAA;AAC1B,CAAC","sourcesContent":["/**\n * 저작자에게 내놓는 축 이름 — **보드 편집기와 컴포저가 이것을 읽는다.**\n *\n * ── 왜 기계가 이름만 드나 (ADR-0083 결정 6) ─────────────────────────────────\n * 이 파일은 operato-plant 에서 왔고, 거기서는 이름을 내는 일과 **값을 펴는 일**을 같이 했다\n * (`stationValues` · `stationData` — 작업지시의 수량, 지시서의 절차를 축 이름에 앉히는 것). 값을 펴는\n * 쪽은 제품의 구조를 읽으므로 제품에 남는다(`StationClientProduct.valuesOf`). 여기 오는 것은\n * **이름뿐**이다.\n *\n * ── 이름은 선언에서 나온다. 펴는 함수에서 나오지 않는다 ─────────────────────\n * 글로 적어 두면 저작자가 오타와 「애초에 없는 이름」을 못 가른다. 그래서 코드에서 나오되,\n * `shared/station-axes.js` 라는 **선언**에서 나온다 — 오늘 `stationValues` 가 마침 내놓는 키에서\n * 뽑던 때에는 원본이 둘이 됐다. 서버가 거절하는 이름을 이쪽이 내놓거나, 서버가 받는 이름을 이쪽이\n * 안 내놓거나 했고 **양쪽 다 조용했다.**\n */\n\nimport { STATION_AXES } from '../../shared/station-axes.js'\n\nexport function stationBindingNames(): string[] {\n return [...STATION_AXES]\n}\n"]}
1
+ {"version":3,"file":"station-board-binding.js","sourceRoot":"","sources":["../../client/station/station-board-binding.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAA;AAE9E;;;;;;;GAOG;AACH,IAAI,WAAW,GAAa,EAAE,CAAA;AAC9B,MAAM,SAAS,GAAG,IAAI,GAAG,EAAc,CAAA;AAEvC,MAAM,UAAU,mBAAmB,CAAC,MAAyB;IAC3D,MAAM,KAAK,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAA;IAEvC,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CACb,6BAA6B,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI;YAChF,2CAA2C,CAC9C,CAAA;IACH,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,WAAW,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAA;IAE7D,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;QACjB,OAAO,CAAC,IAAI,CAAC,gCAAgC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,CAAA;IAC/E,CAAC;IAED,WAAW,GAAG,CAAC,GAAG,WAAW,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,WAAW,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;IACnF,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAA;AAC3C,CAAC;AAED,6CAA6C;AAC7C,MAAM,UAAU,aAAa,CAAC,QAAoB;IAChD,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;IAEvB,OAAO,GAAG,EAAE;QACV,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;IAC5B,CAAC,CAAA;AACH,CAAC;AAED,wDAAwD;AACxD,MAAM,UAAU,mBAAmB;IACjC,OAAO,CAAC,GAAG,YAAY,EAAE,GAAG,WAAW,CAAC,CAAA;AAC1C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB;IAC/B,WAAW,GAAG,EAAE,CAAA;IAChB,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAA;AAC3C,CAAC","sourcesContent":["/**\n * 저작자에게 내놓는 축 이름 — **보드 편집기와 컴포저가 이것을 읽는다.**\n *\n * ── 왜 기계가 이름만 드나 (ADR-0083 결정 6) ─────────────────────────────────\n * 이 파일은 operato-plant 에서 왔고, 거기서는 이름을 내는 일과 **값을 펴는 일**을 같이 했다\n * (`stationValues` · `stationData` — 작업지시의 수량, 지시서의 절차를 축 이름에 앉히는 것). 값을 펴는\n * 쪽은 제품의 구조를 읽으므로 제품에 남는다(`StationClientProduct.valuesOf`). 여기 오는 것은\n * **이름뿐**이다.\n *\n * ── 이름은 선언에서 나온다. 펴는 함수에서 나오지 않는다 ─────────────────────\n * 글로 적어 두면 저작자가 오타와 「애초에 없는 이름」을 못 가른다. 그래서 코드에서 나오되,\n * `shared/station-axes.js` 라는 **선언**에서 나온다 — 오늘 `stationValues` 가 마침 내놓는 키에서\n * 뽑던 때에는 원본이 둘이 됐다. 서버가 거절하는 이름을 이쪽이 내놓거나, 서버가 받는 이름을 이쪽이\n * 안 내놓거나 했고 **양쪽 다 조용했다.**\n */\n\nimport { STATION_AXES, wrongDeclaredAxes } from '../../shared/station-axes.js'\n\n/*\n * ── 제품의 축 (ADR-0083 판정 ① · 축 옮기기 판정 2026-09-24) ─────────────────────\n * 기계는 핵만 든다. 제품의 낱말(공장의 라인 · 지시서 · 양품 수)은 제품이 **자기 목록 하나**를 서버 선언(`axes`)과 여기에\n * 같이 건다. `registerStationTiles` 와 같은 모양이다 — 읽는 쪽은 늘 함수로 읽고, 바뀌면 알린다. 배열을 export 하면 그 순간의\n * 사본이 되어 늦게 건 제품의 축이 고르개에 안 닿는다.\n *\n * 서버와 **같은 규칙**으로 거절한다(`wrongDeclaredAxes`). 서버만 거절하면 화면은 서버가 받지 않을 이름을 조용히 내놓는다.\n */\nlet productAxes: string[] = []\nconst listeners = new Set<() => void>()\n\nexport function registerStationAxes(adding: readonly string[]): void {\n const wrong = wrongDeclaredAxes(adding)\n\n if (wrong.length) {\n throw new Error(\n `station: 화면에 건 축이 틀렸습니다 — ${wrong.map(one => JSON.stringify(one)).join(' · ')}. ` +\n '축은 `무리.이름` 모양이고, 기계가 이미 가진 축은 다시 걸지 않습니다.'\n )\n }\n\n const twice = adding.filter(one => productAxes.includes(one))\n\n if (twice.length) {\n console.warn(`[station] 같은 축이 두 번 등록됐습니다 — ${twice.join(' · ')}. 뒤엣것은 버립니다.`)\n }\n\n productAxes = [...productAxes, ...adding.filter(one => !productAxes.includes(one))]\n listeners.forEach(listener => listener())\n}\n\n/** 제품이 건 축이 바뀌면 알린다. 돌려주는 함수를 부르면 그만 듣는다. */\nexport function onStationAxes(listener: () => void): () => void {\n listeners.add(listener)\n\n return () => {\n listeners.delete(listener)\n }\n}\n\n/** 이 화면이 읽을 수 있는 축 전부 — 기계의 핵과 제품이 건 것. 늘 이 함수로 읽는다. */\nexport function stationBindingNames(): string[] {\n return [...STATION_AXES, ...productAxes]\n}\n\n/**\n * 시험용 — 등록은 기동 때 한 번 하는 일이다.\n *\n * 듣는 쪽은 지우지 않고 알린다. 모듈을 불러올 때 건 듣기(필드 씬의 고르개 캐시)가 지워지면, 다음 등록이 그 캐시에 안 닿는다.\n */\nexport function forgetStationAxes(): void {\n productAxes = []\n listeners.forEach(listener => listener())\n}\n"]}