@things-factory/station 10.1.33
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/README.md +124 -0
- package/dist-client/index.d.ts +11 -0
- package/dist-client/index.js +13 -0
- package/dist-client/index.js.map +1 -0
- package/dist-client/station/scene/index.d.ts +11 -0
- package/dist-client/station/scene/index.js +12 -0
- package/dist-client/station/scene/index.js.map +1 -0
- package/dist-client/station/scene/station-context-binding.d.ts +25 -0
- package/dist-client/station/scene/station-context-binding.js +39 -0
- package/dist-client/station/scene/station-context-binding.js.map +1 -0
- package/dist-client/station/scene/station-context-scene.d.ts +28 -0
- package/dist-client/station/scene/station-context-scene.js +138 -0
- package/dist-client/station/scene/station-context-scene.js.map +1 -0
- package/dist-client/station/scene/station-counts-scene.d.ts +6 -0
- package/dist-client/station/scene/station-counts-scene.js +51 -0
- package/dist-client/station/scene/station-counts-scene.js.map +1 -0
- package/dist-client/station/scene/station-field-scene.d.ts +6 -0
- package/dist-client/station/scene/station-field-scene.js +89 -0
- package/dist-client/station/scene/station-field-scene.js.map +1 -0
- package/dist-client/station/scene/station-icons.d.ts +82 -0
- package/dist-client/station/scene/station-icons.js +131 -0
- package/dist-client/station/scene/station-icons.js.map +1 -0
- package/dist-client/station/scene/station-numpad-scene.d.ts +9 -0
- package/dist-client/station/scene/station-numpad-scene.js +144 -0
- package/dist-client/station/scene/station-numpad-scene.js.map +1 -0
- package/dist-client/station/scene/station-scene-style.d.ts +51 -0
- package/dist-client/station/scene/station-scene-style.js +108 -0
- package/dist-client/station/scene/station-scene-style.js.map +1 -0
- package/dist-client/station/scene/station-scene.d.ts +295 -0
- package/dist-client/station/scene/station-scene.js +68 -0
- package/dist-client/station/scene/station-scene.js.map +1 -0
- package/dist-client/station/scene/station-who-scene.d.ts +6 -0
- package/dist-client/station/scene/station-who-scene.js +48 -0
- package/dist-client/station/scene/station-who-scene.js.map +1 -0
- package/dist-client/station/scene/station-work-panel-scene.d.ts +28 -0
- package/dist-client/station/scene/station-work-panel-scene.js +37 -0
- package/dist-client/station/scene/station-work-panel-scene.js.map +1 -0
- package/dist-client/station/station-board-binding.d.ts +16 -0
- package/dist-client/station/station-board-binding.js +20 -0
- package/dist-client/station/station-board-binding.js.map +1 -0
- package/dist-client/station/station-context.d.ts +178 -0
- package/dist-client/station/station-context.js +30 -0
- package/dist-client/station/station-context.js.map +1 -0
- package/dist-client/station/station-palette.d.ts +34 -0
- package/dist-client/station/station-palette.js +119 -0
- package/dist-client/station/station-palette.js.map +1 -0
- package/dist-client/station/station-runtime.d.ts +47 -0
- package/dist-client/station/station-runtime.js +47 -0
- package/dist-client/station/station-runtime.js.map +1 -0
- package/dist-client/station/station-scene-context.d.ts +68 -0
- package/dist-client/station/station-scene-context.js +62 -0
- package/dist-client/station/station-scene-context.js.map +1 -0
- package/dist-client/station/station-screen-flow.d.ts +16 -0
- package/dist-client/station/station-screen-flow.js +58 -0
- package/dist-client/station/station-screen-flow.js.map +1 -0
- package/dist-client/station/station-tile-registry.d.ts +89 -0
- package/dist-client/station/station-tile-registry.js +86 -0
- package/dist-client/station/station-tile-registry.js.map +1 -0
- package/dist-client/station/station-work-panel-view.d.ts +23 -0
- package/dist-client/station/station-work-panel-view.js +125 -0
- package/dist-client/station/station-work-panel-view.js.map +1 -0
- package/dist-client/station/station-work-runtime.d.ts +47 -0
- package/dist-client/station/station-work-runtime.js +161 -0
- package/dist-client/station/station-work-runtime.js.map +1 -0
- package/dist-client/station/station-work-scenarios.d.ts +41 -0
- package/dist-client/station/station-work-scenarios.js +59 -0
- package/dist-client/station/station-work-scenarios.js.map +1 -0
- package/dist-client/station/task-counts.d.ts +32 -0
- package/dist-client/station/task-counts.js +26 -0
- package/dist-client/station/task-counts.js.map +1 -0
- package/dist-client/tsconfig.tsbuildinfo +1 -0
- package/dist-server/activities/activity-station-screen-control-withdrawal.d.ts +62 -0
- package/dist-server/activities/activity-station-screen-control-withdrawal.js +147 -0
- package/dist-server/activities/activity-station-screen-control-withdrawal.js.map +1 -0
- package/dist-server/activities/activity-station-screen-release.d.ts +81 -0
- package/dist-server/activities/activity-station-screen-release.js +147 -0
- package/dist-server/activities/activity-station-screen-release.js.map +1 -0
- package/dist-server/activities/index.d.ts +49 -0
- package/dist-server/activities/index.js +21 -0
- package/dist-server/activities/index.js.map +1 -0
- package/dist-server/index.d.ts +3 -0
- package/dist-server/index.js +8 -0
- package/dist-server/index.js.map +1 -0
- package/dist-server/service/index.d.ts +23 -0
- package/dist-server/service/index.js +41 -0
- package/dist-server/service/index.js.map +1 -0
- package/dist-server/service/station/deletion-changes.d.ts +14 -0
- package/dist-server/service/station/deletion-changes.js +25 -0
- package/dist-server/service/station/deletion-changes.js.map +1 -0
- package/dist-server/service/station/index.d.ts +13 -0
- package/dist-server/service/station/index.js +25 -0
- package/dist-server/service/station/index.js.map +1 -0
- package/dist-server/service/station/reversibility.d.ts +21 -0
- package/dist-server/service/station/reversibility.js +24 -0
- package/dist-server/service/station/reversibility.js.map +1 -0
- package/dist-server/service/station/screen-model-digest.d.ts +60 -0
- package/dist-server/service/station/screen-model-digest.js +97 -0
- package/dist-server/service/station/screen-model-digest.js.map +1 -0
- package/dist-server/service/station/staged-material.d.ts +62 -0
- package/dist-server/service/station/staged-material.js +44 -0
- package/dist-server/service/station/staged-material.js.map +1 -0
- package/dist-server/service/station/station-argument-sources.d.ts +31 -0
- package/dist-server/service/station/station-argument-sources.js +66 -0
- package/dist-server/service/station/station-argument-sources.js.map +1 -0
- package/dist-server/service/station/station-assignment-choice.d.ts +81 -0
- package/dist-server/service/station/station-assignment-choice.js +105 -0
- package/dist-server/service/station/station-assignment-choice.js.map +1 -0
- package/dist-server/service/station/station-assignment-resolver.d.ts +104 -0
- package/dist-server/service/station/station-assignment-resolver.js +426 -0
- package/dist-server/service/station/station-assignment-resolver.js.map +1 -0
- package/dist-server/service/station/station-assignment-service.d.ts +33 -0
- package/dist-server/service/station/station-assignment-service.js +64 -0
- package/dist-server/service/station/station-assignment-service.js.map +1 -0
- package/dist-server/service/station/station-axes.d.ts +9 -0
- package/dist-server/service/station/station-axes.js +16 -0
- package/dist-server/service/station/station-axes.js.map +1 -0
- package/dist-server/service/station/station-flow.d.ts +156 -0
- package/dist-server/service/station/station-flow.js +255 -0
- package/dist-server/service/station/station-flow.js.map +1 -0
- package/dist-server/service/station/station-generate.d.ts +76 -0
- package/dist-server/service/station/station-generate.js +178 -0
- package/dist-server/service/station/station-generate.js.map +1 -0
- package/dist-server/service/station/station-product.d.ts +123 -0
- package/dist-server/service/station/station-product.js +77 -0
- package/dist-server/service/station/station-product.js.map +1 -0
- package/dist-server/service/station/station-runtime-binding.d.ts +6 -0
- package/dist-server/service/station/station-runtime-binding.js +46 -0
- package/dist-server/service/station/station-runtime-binding.js.map +1 -0
- package/dist-server/service/station/station-screen-assignment.d.ts +94 -0
- package/dist-server/service/station/station-screen-assignment.js +154 -0
- package/dist-server/service/station/station-screen-assignment.js.map +1 -0
- package/dist-server/service/station/station-screen-control-withdrawal-resolver.d.ts +37 -0
- package/dist-server/service/station/station-screen-control-withdrawal-resolver.js +130 -0
- package/dist-server/service/station/station-screen-control-withdrawal-resolver.js.map +1 -0
- package/dist-server/service/station/station-screen-control-withdrawal.d.ts +104 -0
- package/dist-server/service/station/station-screen-control-withdrawal.js +128 -0
- package/dist-server/service/station/station-screen-control-withdrawal.js.map +1 -0
- package/dist-server/service/station/station-screen-draft-resolver.d.ts +47 -0
- package/dist-server/service/station/station-screen-draft-resolver.js +153 -0
- package/dist-server/service/station/station-screen-draft-resolver.js.map +1 -0
- package/dist-server/service/station/station-screen-layout.d.ts +119 -0
- package/dist-server/service/station/station-screen-layout.js +202 -0
- package/dist-server/service/station/station-screen-layout.js.map +1 -0
- package/dist-server/service/station/station-screen-list-resolver.d.ts +69 -0
- package/dist-server/service/station/station-screen-list-resolver.js +215 -0
- package/dist-server/service/station/station-screen-list-resolver.js.map +1 -0
- package/dist-server/service/station/station-screen-publish-subscriber.d.ts +22 -0
- package/dist-server/service/station/station-screen-publish-subscriber.js +90 -0
- package/dist-server/service/station/station-screen-publish-subscriber.js.map +1 -0
- package/dist-server/service/station/station-screen-release-gate.d.ts +66 -0
- package/dist-server/service/station/station-screen-release-gate.js +67 -0
- package/dist-server/service/station/station-screen-release-gate.js.map +1 -0
- package/dist-server/service/station/station-screen-release-record.d.ts +62 -0
- package/dist-server/service/station/station-screen-release-record.js +139 -0
- package/dist-server/service/station/station-screen-release-record.js.map +1 -0
- package/dist-server/service/station/station-screen-release-signature.d.ts +23 -0
- package/dist-server/service/station/station-screen-release-signature.js +32 -0
- package/dist-server/service/station/station-screen-release-signature.js.map +1 -0
- package/dist-server/service/station/station-screen-scorecard.d.ts +15 -0
- package/dist-server/service/station/station-screen-scorecard.js +35 -0
- package/dist-server/service/station/station-screen-scorecard.js.map +1 -0
- package/dist-server/service/station/station-screen-spec-resolver.d.ts +36 -0
- package/dist-server/service/station/station-screen-spec-resolver.js +202 -0
- package/dist-server/service/station/station-screen-spec-resolver.js.map +1 -0
- package/dist-server/service/station/station-screen-spec.d.ts +104 -0
- package/dist-server/service/station/station-screen-spec.js +114 -0
- package/dist-server/service/station/station-screen-spec.js.map +1 -0
- package/dist-server/service/station/station-screen-thumbnail.d.ts +11 -0
- package/dist-server/service/station/station-screen-thumbnail.js +36 -0
- package/dist-server/service/station/station-screen-thumbnail.js.map +1 -0
- package/dist-server/service/station/station-service-resolver.d.ts +27 -0
- package/dist-server/service/station/station-service-resolver.js +95 -0
- package/dist-server/service/station/station-service-resolver.js.map +1 -0
- package/dist-server/tsconfig.tsbuildinfo +1 -0
- package/package.json +44 -0
- package/shared/station-axes.d.ts +4 -0
- package/shared/station-axes.js +155 -0
- package/shared/station-screen-flow-check.d.ts +12 -0
- package/shared/station-screen-flow-check.js +74 -0
- package/things-scene.config.js +15 -0
- package/translations/en.json +42 -0
- package/translations/ja.json +42 -0
- package/translations/ko.json +42 -0
- package/translations/ms.json +42 -0
- package/translations/zh.json +42 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a board's components get to see — **the context, as the scene knows it.**
|
|
3
|
+
*
|
|
4
|
+
* ── 씬이 생기는 순간에 건넨다 ──────────────────────────────────────────────
|
|
5
|
+
* 뷰어가 `scene-changed` 를 낼 때 `RootContainer.registerService` 로 건네고 한 번 다시 그리게 한다.
|
|
6
|
+
* 컴포넌트는 읽을 때마다 레지스트리를 보므로(`StationContextBinding`) 늦게 붙어도 잡힌다.
|
|
7
|
+
*
|
|
8
|
+
* It replaces a module-level accessor, and the reason is not tidiness. This app runs MDI by default.
|
|
9
|
+
* Two station boards open on two machines, and a global lookup with no equipment name hands back
|
|
10
|
+
* "whichever opened last" — so a component the author left unnamed draws one machine's numbers and
|
|
11
|
+
* **records against the other**. A registry that belongs to the scene cannot cross those wires,
|
|
12
|
+
* because each page holds its own scene.
|
|
13
|
+
*
|
|
14
|
+
* ── Why the formatted labels come along ────────────────────────────────────
|
|
15
|
+
* `values` is the same map the no-code bindings run on, already turned into words — times included.
|
|
16
|
+
* Times differ per app (`client/when.ts`) and the scene folder is kept clear of that, so a component
|
|
17
|
+
* that formatted its own would end up disagreeing with every other screen. It reads, it does not
|
|
18
|
+
* format.
|
|
19
|
+
*
|
|
20
|
+
* ── Custom contexts arrive by the same door ────────────────────────────────
|
|
21
|
+
* `root.registerService('acme', …)` and `getService('acme')`. Nothing here is station-specific
|
|
22
|
+
* except the name, and nothing in things-scene needs changing to add one.
|
|
23
|
+
*/
|
|
24
|
+
/** The name this context travels under. Other domains take other names. */
|
|
25
|
+
export const STATION_SERVICE = 'station';
|
|
26
|
+
/**
|
|
27
|
+
* The name a board's bindings reach the context component by — `source: '#station-context'`.
|
|
28
|
+
*
|
|
29
|
+
* ── Why it is not just `station` ───────────────────────────────────────────
|
|
30
|
+
* That word is already a **data branch**. The host pushes `scene.data` keyed by component id
|
|
31
|
+
* (`station`, `order`, `today`, …) and the context component adds itself to the same index those
|
|
32
|
+
* lookups use — so naming it `station` made the branch land on the component and replace everything
|
|
33
|
+
* it had published. Measured: a binding for `content.체결 토크` drew the equipment name.
|
|
34
|
+
*
|
|
35
|
+
* It lives here, beside the branch names, so the two can be checked against each other without
|
|
36
|
+
* loading any scene code.
|
|
37
|
+
*/
|
|
38
|
+
export const STATION_SOURCE_NAME = `${STATION_SERVICE}-context`;
|
|
39
|
+
/**
|
|
40
|
+
* Find the context from inside a component.
|
|
41
|
+
*
|
|
42
|
+
* Two places, in the order they become true. The model is where the page puts it before the scene
|
|
43
|
+
* opens, so it is the one that answers on the first frame. The service registry
|
|
44
|
+
* (`RootContainer.registerService`) is the late door — for a host that attaches after the fact, or
|
|
45
|
+
* for a second context put there by someone else.
|
|
46
|
+
*
|
|
47
|
+
* `root.model.model` is where a key added to the board model comes to rest: `create()` wraps the
|
|
48
|
+
* board model inside the root's own model. Ugly, and it is written down once, here, rather than in
|
|
49
|
+
* every component.
|
|
50
|
+
*/
|
|
51
|
+
export function sceneContextOf(component, name = STATION_SERVICE) {
|
|
52
|
+
const root = component?.root;
|
|
53
|
+
/*
|
|
54
|
+
* 레지스트리 하나만 본다.
|
|
55
|
+
*
|
|
56
|
+
* 모델에 얹는 길도 있었는데 걷었다 — 뷰어가 모델을 복제하면서 **함수가 떨어져 나가** 값만 남은
|
|
57
|
+
* 껍데기가 왔다(`found?.subscribe is not a function`). JSON 으로 오갈 수 있는 자리에 살아 있는 것을
|
|
58
|
+
* 태우면 그렇게 된다. 문을 둘 두었다가 둘째가 조용히 반쪽만 전달한 셈이다.
|
|
59
|
+
*/
|
|
60
|
+
return root?.getService?.(name);
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=station-scene-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"station-scene-context.js","sourceRoot":"","sources":["../../client/station/station-scene-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAIH,2EAA2E;AAC3E,MAAM,CAAC,MAAM,eAAe,GAAG,SAAS,CAAA;AAExC;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,GAAG,eAAe,UAAU,CAAA;AAqB/D;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,cAAc,CAA0B,SAAyB,EAAE,IAAI,GAAG,eAAe;IACvG,MAAM,IAAI,GAAG,SAAS,EAAE,IAAI,CAAA;IAE5B;;;;;;OAMG;IACH,OAAO,IAAI,EAAE,UAAU,EAAE,CAAC,IAAI,CAAC,CAAA;AACjC,CAAC","sourcesContent":["/**\n * What a board's components get to see — **the context, as the scene knows it.**\n *\n * ── 씬이 생기는 순간에 건넨다 ──────────────────────────────────────────────\n * 뷰어가 `scene-changed` 를 낼 때 `RootContainer.registerService` 로 건네고 한 번 다시 그리게 한다.\n * 컴포넌트는 읽을 때마다 레지스트리를 보므로(`StationContextBinding`) 늦게 붙어도 잡힌다.\n *\n * It replaces a module-level accessor, and the reason is not tidiness. This app runs MDI by default.\n * Two station boards open on two machines, and a global lookup with no equipment name hands back\n * \"whichever opened last\" — so a component the author left unnamed draws one machine's numbers and\n * **records against the other**. A registry that belongs to the scene cannot cross those wires,\n * because each page holds its own scene.\n *\n * ── Why the formatted labels come along ────────────────────────────────────\n * `values` is the same map the no-code bindings run on, already turned into words — times included.\n * Times differ per app (`client/when.ts`) and the scene folder is kept clear of that, so a component\n * that formatted its own would end up disagreeing with every other screen. It reads, it does not\n * format.\n *\n * ── Custom contexts arrive by the same door ────────────────────────────────\n * `root.registerService('acme', …)` and `getService('acme')`. Nothing here is station-specific\n * except the name, and nothing in things-scene needs changing to add one.\n */\n\nimport type { StationAction, StationSnapshot } from './station-context'\n\n/** The name this context travels under. Other domains take other names. */\nexport const STATION_SERVICE = 'station'\n\n/**\n * The name a board's bindings reach the context component by — `source: '#station-context'`.\n *\n * ── Why it is not just `station` ───────────────────────────────────────────\n * That word is already a **data branch**. The host pushes `scene.data` keyed by component id\n * (`station`, `order`, `today`, …) and the context component adds itself to the same index those\n * lookups use — so naming it `station` made the branch land on the component and replace everything\n * it had published. Measured: a binding for `content.체결 토크` drew the equipment name.\n *\n * It lives here, beside the branch names, so the two can be checked against each other without\n * loading any scene code.\n */\nexport const STATION_SOURCE_NAME = `${STATION_SERVICE}-context`\n\nexport interface StationSceneContext {\n /** 저작 미리보기는 조회만 허용한다. */\n readonly readOnly?: boolean\n /** Which machine this board is drawing. */\n readonly equipmentName: string\n\n /** The whole picture, for components that need its shape. */\n get snapshot(): StationSnapshot\n\n /** The same picture as finished labels — `order.name`, `today.good`, and the rest. */\n get values(): Record<string, string | number>\n\n /** Leaving a fact. The one door, the same one the tile screen goes through. */\n act(action: StationAction): Promise<void>\n\n /** Something changed. The returned function stops listening. */\n subscribe(listener: () => void): () => void\n}\n\n/**\n * Find the context from inside a component.\n *\n * Two places, in the order they become true. The model is where the page puts it before the scene\n * opens, so it is the one that answers on the first frame. The service registry\n * (`RootContainer.registerService`) is the late door — for a host that attaches after the fact, or\n * for a second context put there by someone else.\n *\n * `root.model.model` is where a key added to the board model comes to rest: `create()` wraps the\n * board model inside the root's own model. Ugly, and it is written down once, here, rather than in\n * every component.\n */\nexport function sceneContextOf<T = StationSceneContext>(component: { root?: any }, name = STATION_SERVICE): T | undefined {\n const root = component?.root\n\n /*\n * 레지스트리 하나만 본다.\n *\n * 모델에 얹는 길도 있었는데 걷었다 — 뷰어가 모델을 복제하면서 **함수가 떨어져 나가** 값만 남은\n * 껍데기가 왔다(`found?.subscribe is not a function`). JSON 으로 오갈 수 있는 자리에 살아 있는 것을\n * 태우면 그렇게 된다. 문을 둘 두었다가 둘째가 조용히 반쪽만 전달한 셈이다.\n */\n return root?.getService?.(name)\n}\n"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { StationWorkRuntime } from './station-work-runtime';
|
|
2
|
+
export interface ScreenTransition {
|
|
3
|
+
action: 'next' | 'back' | 'cancel' | 'complete' | 'next-work';
|
|
4
|
+
target: string;
|
|
5
|
+
guard?: 'none' | 'order' | 'materials' | 'ready';
|
|
6
|
+
reset?: 'none' | 'inputs' | 'work';
|
|
7
|
+
}
|
|
8
|
+
export interface ScreenFlow {
|
|
9
|
+
version: 1;
|
|
10
|
+
start: string;
|
|
11
|
+
screens: Record<string, Record<string, any>>;
|
|
12
|
+
}
|
|
13
|
+
/** Check destination BEFORE changing work state; invalid links must not erase input. */
|
|
14
|
+
export declare function transitionScreen(flow: ScreenFlow, runtime: StationWorkRuntime, transition: ScreenTransition): string;
|
|
15
|
+
/** Four editable screens, sharing only the current work context. */
|
|
16
|
+
export declare function packingScreenFlow(): Record<string, any>;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import i18next from 'i18next';
|
|
2
|
+
import { scenarioScreen, scenarioById } from './station-work-scenarios';
|
|
3
|
+
/** Check destination BEFORE changing work state; invalid links must not erase input. */
|
|
4
|
+
export function transitionScreen(flow, runtime, transition) {
|
|
5
|
+
if (!['next', 'back', 'cancel', 'complete', 'next-work'].includes(transition.action))
|
|
6
|
+
throw new Error(i18next.t('text.station-sim-unsupported-action'));
|
|
7
|
+
if (!['none', 'order', 'materials', 'ready'].includes(transition.guard ?? 'none'))
|
|
8
|
+
throw new Error(i18next.t('text.station-sim-unsupported-guard'));
|
|
9
|
+
if (!['none', 'inputs', 'work'].includes(transition.reset ?? 'none'))
|
|
10
|
+
throw new Error(i18next.t('text.station-sim-unsupported-reset'));
|
|
11
|
+
if (!Object.prototype.hasOwnProperty.call(flow.screens, transition.target))
|
|
12
|
+
throw new Error(i18next.t('text.station-sim-target-not-found', { target: transition.target, interpolation: { escapeValue: false } }));
|
|
13
|
+
const state = runtime.state;
|
|
14
|
+
if (transition.action === 'complete') {
|
|
15
|
+
if (!runtime.commit())
|
|
16
|
+
throw new Error(runtime.state.message);
|
|
17
|
+
}
|
|
18
|
+
else if (transition.action === 'next-work') {
|
|
19
|
+
if (!state.completed)
|
|
20
|
+
throw new Error(i18next.t('text.station-sim-work-not-complete'));
|
|
21
|
+
runtime.resetWork('work', true);
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
if (state.completed)
|
|
25
|
+
throw new Error(i18next.t('text.station-sim-confirmed-cannot-cancel'));
|
|
26
|
+
if (transition.guard === 'order' || transition.guard === 'materials') {
|
|
27
|
+
if (!runtime.scenario.orders.some(order => order.id === state.orderId && order.ready))
|
|
28
|
+
throw new Error(i18next.t('text.station-sim-select-order-first'));
|
|
29
|
+
}
|
|
30
|
+
if (transition.guard === 'materials') {
|
|
31
|
+
for (const [item, count] of Object.entries(runtime.scenario.requirements)) {
|
|
32
|
+
if (state.serials.filter(id => runtime.scenario.stock.some(stock => stock.serial === id && stock.item === item && stock.eligible)).length !== count)
|
|
33
|
+
throw new Error(i18next.t('text.station-sim-fill-requirement', { item, interpolation: { escapeValue: false } }));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (transition.guard === 'ready' && runtime.validate().length)
|
|
37
|
+
throw new Error(runtime.validate()[0]);
|
|
38
|
+
if (transition.action === 'cancel')
|
|
39
|
+
runtime.resetWork(transition.reset ?? 'inputs');
|
|
40
|
+
}
|
|
41
|
+
return transition.target;
|
|
42
|
+
}
|
|
43
|
+
/** Four editable screens, sharing only the current work context. */
|
|
44
|
+
export function packingScreenFlow() {
|
|
45
|
+
const scenario = scenarioById('packing'), base = scenarioScreen(scenario);
|
|
46
|
+
/* Component ids stay fixed across languages; only the label is translated. */
|
|
47
|
+
const part = (id, view, labelKey, left, top, width, height, extra = {}) => ({ type: 'station-work-panel', id, view, label: i18next.t(labelKey), left, top, width, height, ...extra });
|
|
48
|
+
const nav = (id, labelKey, target, action, left, guard = 'none', reset = 'none') => part(id, 'navigation', labelKey, left, 850, 420, 130, { targetScreen: target, flowAction: action, flowGuard: guard, resetScope: reset });
|
|
49
|
+
const page = (components) => ({ width: 1440, height: 1000, threed: false, stationScenario: 'packing', components });
|
|
50
|
+
const screens = {
|
|
51
|
+
order: page([part('order-orders', 'orders', 'label.station-sim-flow-order', 24, 24, 680, 750), part('order-status', 'status', 'label.station-sim-flow-current-work', 724, 24, 690, 750), nav('order-to-scan', 'label.station-sim-flow-to-scan', 'scan', 'next', 24, 'order')]),
|
|
52
|
+
scan: page([part('scan-materials', 'materials', 'label.station-sim-flow-parts', 24, 24, 680, 780), part('scan-scan', 'scan', 'label.station-sim-flow-serial-scan', 724, 24, 690, 200), part('scan-status', 'status', 'label.station-sim-flow-requirement-check', 724, 244, 690, 560), nav('scan-to-order', 'label.station-sim-flow-to-order', 'order', 'back', 24), nav('scan-to-review', 'label.station-sim-flow-to-review', 'review', 'next', 504, 'materials'), nav('scan-cancel-input', 'label.station-sim-flow-cancel-input', 'scan', 'cancel', 984, 'none', 'inputs')]),
|
|
53
|
+
review: page([part('review-inputs', 'inputs', 'label.station-sim-flow-packing-info', 24, 24, 680, 400), part('review-checks', 'checks', 'label.station-sim-context-packing-inspection', 24, 444, 680, 350), part('review-status', 'status', 'label.station-sim-flow-final-check', 724, 24, 690, 560), part('review-commit', 'commit', 'label.station-sim-packing-result', 724, 604, 690, 200, { targetScreen: 'done' }), nav('review-to-scan', 'label.station-sim-flow-to-scan-screen', 'scan', 'back', 24), nav('review-cancel-work', 'label.station-sim-flow-cancel-work', 'order', 'cancel', 504, 'none', 'work')]),
|
|
54
|
+
done: page([part('done-status', 'status', 'label.station-sim-flow-result', 24, 24, 1390, 780), nav('done-next-work', 'label.station-sim-flow-next-work', 'order', 'next-work', 24)])
|
|
55
|
+
};
|
|
56
|
+
return { ...base, ...screens.order, stationPages: { version: 1, start: 'order', screens } };
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=station-screen-flow.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"station-screen-flow.js","sourceRoot":"","sources":["../../client/station/station-screen-flow.ts"],"names":[],"mappings":"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","sourcesContent":["import i18next from 'i18next'\nimport type { StationWorkRuntime } from './station-work-runtime'\nimport { scenarioScreen, scenarioById } from './station-work-scenarios'\nexport interface ScreenTransition { action:'next'|'back'|'cancel'|'complete'|'next-work'; target:string; guard?:'none'|'order'|'materials'|'ready'; reset?:'none'|'inputs'|'work' }\nexport interface ScreenFlow { version:1; start:string; screens:Record<string,Record<string,any>> }\n\n/** Check destination BEFORE changing work state; invalid links must not erase input. */\nexport function transitionScreen(flow:ScreenFlow, runtime:StationWorkRuntime, transition:ScreenTransition):string {\n if(!['next','back','cancel','complete','next-work'].includes(transition.action))throw new Error(i18next.t('text.station-sim-unsupported-action'))\n if(!['none','order','materials','ready'].includes(transition.guard??'none'))throw new Error(i18next.t('text.station-sim-unsupported-guard'))\n if(!['none','inputs','work'].includes(transition.reset??'none'))throw new Error(i18next.t('text.station-sim-unsupported-reset'))\n if(!Object.prototype.hasOwnProperty.call(flow.screens,transition.target))throw new Error(i18next.t('text.station-sim-target-not-found',{target:transition.target,interpolation:{escapeValue:false}}))\n const state=runtime.state\n if(transition.action==='complete'){\n if(!runtime.commit())throw new Error(runtime.state.message)\n }else if(transition.action==='next-work'){\n if(!state.completed)throw new Error(i18next.t('text.station-sim-work-not-complete'))\n runtime.resetWork('work',true)\n }else{\n if(state.completed)throw new Error(i18next.t('text.station-sim-confirmed-cannot-cancel'))\n if(transition.guard==='order' || transition.guard==='materials'){\n if(!runtime.scenario.orders.some(order=>order.id===state.orderId&&order.ready))throw new Error(i18next.t('text.station-sim-select-order-first'))\n }\n if(transition.guard==='materials'){\n for(const [item,count] of Object.entries(runtime.scenario.requirements)){\n if(state.serials.filter(id=>runtime.scenario.stock.some(stock=>stock.serial===id&&stock.item===item&&stock.eligible)).length!==count)throw new Error(i18next.t('text.station-sim-fill-requirement',{item,interpolation:{escapeValue:false}}))\n }\n }\n if(transition.guard==='ready' && runtime.validate().length)throw new Error(runtime.validate()[0])\n if(transition.action==='cancel')runtime.resetWork(transition.reset??'inputs')\n }\n return transition.target\n}\n\n/** Four editable screens, sharing only the current work context. */\nexport function packingScreenFlow():Record<string,any>{\n const scenario=scenarioById('packing')!,base=scenarioScreen(scenario)\n /* Component ids stay fixed across languages; only the label is translated. */\n const part=(id:string,view:string,labelKey:string,left:number,top:number,width:number,height:number,extra:Record<string,any>={})=>({type:'station-work-panel',id,view,label:i18next.t(labelKey),left,top,width,height,...extra})\n const nav=(id:string,labelKey:string,target:string,action:string,left:number,guard='none',reset='none')=>part(id,'navigation',labelKey,left,850,420,130,{targetScreen:target,flowAction:action,flowGuard:guard,resetScope:reset})\n const page=(components:any[])=>({width:1440,height:1000,threed:false,stationScenario:'packing',components})\n const screens={\n order:page([part('order-orders','orders','label.station-sim-flow-order',24,24,680,750),part('order-status','status','label.station-sim-flow-current-work',724,24,690,750),nav('order-to-scan','label.station-sim-flow-to-scan','scan','next',24,'order')]),\n scan:page([part('scan-materials','materials','label.station-sim-flow-parts',24,24,680,780),part('scan-scan','scan','label.station-sim-flow-serial-scan',724,24,690,200),part('scan-status','status','label.station-sim-flow-requirement-check',724,244,690,560),nav('scan-to-order','label.station-sim-flow-to-order','order','back',24),nav('scan-to-review','label.station-sim-flow-to-review','review','next',504,'materials'),nav('scan-cancel-input','label.station-sim-flow-cancel-input','scan','cancel',984,'none','inputs')]),\n review:page([part('review-inputs','inputs','label.station-sim-flow-packing-info',24,24,680,400),part('review-checks','checks','label.station-sim-context-packing-inspection',24,444,680,350),part('review-status','status','label.station-sim-flow-final-check',724,24,690,560),part('review-commit','commit','label.station-sim-packing-result',724,604,690,200,{targetScreen:'done'}),nav('review-to-scan','label.station-sim-flow-to-scan-screen','scan','back',24),nav('review-cancel-work','label.station-sim-flow-cancel-work','order','cancel',504,'none','work')]),\n done:page([part('done-status','status','label.station-sim-flow-result',24,24,1390,780),nav('done-next-work','label.station-sim-flow-next-work','order','next-work',24)])\n }\n return {...base,...screens.order,stationPages:{version:1,start:'order',screens}}\n}\n"]}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 제품이 저작면에 **놓을 수 있게** 내놓는 타일 (ADR-0083 결정 6 · UX 판정 · 아키텍트 경계 고침, 2026-09-20).
|
|
3
|
+
*
|
|
4
|
+
* ═══════════════════════════════════════════════════════════════════════════
|
|
5
|
+
* ## 등록은 두 벌이고, 이것은 둘째다
|
|
6
|
+
*
|
|
7
|
+
* `@sceneComponent` 는 **그릴 수 있게** 할 뿐이다. 이미 저장된 화면의 `station-order` 를 씬이 그려
|
|
8
|
+
* 낼 수 있게 되는 것이고, 저작자가 새로 **놓을** 수 있게 되는 것이 아니다.
|
|
9
|
+
*
|
|
10
|
+
* 둘을 한 벌로 세지 않는다. 수명이 다르기 때문이다 — **팔레트에서 뺀 타일도 이미 저장된 화면에서는
|
|
11
|
+
* 계속 그려져야 한다.** 그리기를 지우면 옛 화면이 깨지고, 놓기만 지우면 새로 안 놓일 뿐이다.
|
|
12
|
+
*
|
|
13
|
+
* ## 기계의 것은 여기로 안 온다 (아키텍트 고침)
|
|
14
|
+
*
|
|
15
|
+
* 이 모듈의 공통 타일은 플랫폼의 그물이 걷는다 — `package.json` 의 `"things-scene"` 과 뿌리의
|
|
16
|
+
* `things-scene.config.js` 를 `@things-factory/env` 가 모아 보드 모델러가 세운다(꾸러미 42 개가 이미
|
|
17
|
+
* 그 길이다). plant 가 이 모듈을 의존하므로 조건이 선다. 옛 팔레트가 손으로 등록한 것은 그 부품이
|
|
18
|
+
* **앱 안에 있어서** 그물에 안 걸렸기 때문이고, 옮기고 나면 그 까닭이 없어진다. 플랫폼에 길이 있는데
|
|
19
|
+
* 옆에 사설 수집기를 세우지 않는다.
|
|
20
|
+
*
|
|
21
|
+
* 여기 오는 것은 **제품의 타일**(작업지시 · 정비 · 자재)과, 그것을 그물 대신 직접 훑는 저작 화면이다.
|
|
22
|
+
*
|
|
23
|
+
* ## 왜 배열이 아니라 함수인가
|
|
24
|
+
*
|
|
25
|
+
* 기계와 제품이 다른 꾸러미에서 온다. 배열을 export 해 두고 소비자가 import 하면 **그 순간의 사본**을
|
|
26
|
+
* 들고 가므로 늦게 걸린 제품의 것이 닿을 길이 없다. 저작자에게는 팔레트 칸이 덜 선 채로 보이고,
|
|
27
|
+
* 오류는 없다.
|
|
28
|
+
*
|
|
29
|
+
* ## 봉인 함수는 두지 않는다 (아키텍트 고침)
|
|
30
|
+
*
|
|
31
|
+
* 처음에는 `sealStationTiles()` 로 「다 들어왔다」를 말하게 지었다. 그러면 **앱이 그것을 잊었을 때가
|
|
32
|
+
* 다시 조용하다** — 고치려던 실패와 같은 모양이다. 타일 목록은 클라이언트 선언의 **한 칸**이고, 빈
|
|
33
|
+
* 자리는 `declareStationClientProduct` 가 기동 때 거절한다. 서버에서 `declareStationProduct` 가 하는
|
|
34
|
+
* 그 일이다(결정 6 「클라이언트도 같은 선이다」).
|
|
35
|
+
*
|
|
36
|
+
* **아직 그 칸이 아니다.** 클라이언트 선언(`station-client-product`)이 이 모듈로 오는 것이 다음
|
|
37
|
+
* 걸음이고, 그때 이 함수가 그 칸으로 접힌다. 아래 시험이 그날 빨강으로 알린다.
|
|
38
|
+
* ═══════════════════════════════════════════════════════════════════════════
|
|
39
|
+
*/
|
|
40
|
+
/** 저작면이 이 타일을 어떻게 내놓고, 놓았을 때 무엇이 서는가. */
|
|
41
|
+
export interface StationTileEntry {
|
|
42
|
+
/** 씬 부품의 이름. `@sceneComponent` 가 쓴 그것과 같아야 한다. */
|
|
43
|
+
type: string;
|
|
44
|
+
/** 팔레트 그림. 없으면 보드가 빈 그림을 그린다. */
|
|
45
|
+
icon?: string;
|
|
46
|
+
/**
|
|
47
|
+
* 보드 저작면이 항목에 붙여 그리는 글 — **플랫폼의 칸이라 이름이 이것이고, 영어다.**
|
|
48
|
+
*
|
|
49
|
+
* 같은 타일이 두 저작면에서 다르게 읽힌다: 보드 저작면은 이 영어를, 현장 화면 모델러는 번역한
|
|
50
|
+
* 이름을 보여 준다(UX 실측 2026-09-20). 둘이 한 곳을 보게 하는 것이 `labelKey` 이고, 이 칸은
|
|
51
|
+
* 플랫폼이 읽는 자리라 그대로 둔다.
|
|
52
|
+
*/
|
|
53
|
+
description?: string;
|
|
54
|
+
/**
|
|
55
|
+
* 사람이 읽는 이름의 **열쇠**. 값이 아니다.
|
|
56
|
+
*
|
|
57
|
+
* 여기서 `i18next.t()` 로 굳히면 언어를 바꿨을 때 옛 언어가 남는다 — 등록은 기동 때 한 번이고
|
|
58
|
+
* 언어는 그 뒤에도 바뀐다. 값으로 바꾸는 것은 읽는 쪽에서 한다.
|
|
59
|
+
*/
|
|
60
|
+
labelKey?: string;
|
|
61
|
+
/** 팔레트 그룹. 비면 `station`. */
|
|
62
|
+
group?: string;
|
|
63
|
+
/** 놓았을 때 서는 것 — 자리 · 크기 · 그 부품의 기본 속성. */
|
|
64
|
+
model: Record<string, any>;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* 제품이 자기 타일을 건다. 기동 때 한 번, 자기 선언과 같은 자리에서 부른다.
|
|
68
|
+
*
|
|
69
|
+
* 그룹이 섰는지 볼 필요가 없다 — 목록으로 받아 두고 읽을 때 편다. 부르는 순서로 실패하지 않는다는
|
|
70
|
+
* 것이 이 자리가 있는 까닭이다. 옛 팔레트는 그룹이 먼저 안 서면 템플릿을 **오류 없이 버렸다.**
|
|
71
|
+
*/
|
|
72
|
+
export declare function registerStationTiles(adding: StationTileEntry[]): void;
|
|
73
|
+
/** 제품이 건 타일 — 소비자는 늘 이 함수로 읽는다. 배열을 import 하면 그 순간의 사본이 된다. */
|
|
74
|
+
export declare function stationTiles(): StationTileEntry[];
|
|
75
|
+
/**
|
|
76
|
+
* 목록이 바뀌면 알린다 — 읽고 있던 화면이 다시 그리도록.
|
|
77
|
+
*
|
|
78
|
+
* 돌려주는 함수를 부르면 그만 듣는다. 화면이 사라진 뒤에도 듣고 있으면 없는 것을 다시 그리려 한다.
|
|
79
|
+
*/
|
|
80
|
+
export declare function onStationTiles(listener: () => void): () => void;
|
|
81
|
+
/**
|
|
82
|
+
* 그 타일의 이름 열쇠 — 없으면 `undefined`.
|
|
83
|
+
*
|
|
84
|
+
* 읽는 쪽이 `undefined` 를 받으면 부품 이름(`station-order`)을 그대로 그린다. 그것이 뜨면 등록부에
|
|
85
|
+
* `labelKey` 가 빠진 것이지 이름이 없는 것이 아니다.
|
|
86
|
+
*/
|
|
87
|
+
export declare function stationTileLabelKey(type: string): string | undefined;
|
|
88
|
+
/** 시험용 — 등록은 기동 때 한 번 하는 일이다. */
|
|
89
|
+
export declare function forgetStationTiles(): void;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 제품이 저작면에 **놓을 수 있게** 내놓는 타일 (ADR-0083 결정 6 · UX 판정 · 아키텍트 경계 고침, 2026-09-20).
|
|
3
|
+
*
|
|
4
|
+
* ═══════════════════════════════════════════════════════════════════════════
|
|
5
|
+
* ## 등록은 두 벌이고, 이것은 둘째다
|
|
6
|
+
*
|
|
7
|
+
* `@sceneComponent` 는 **그릴 수 있게** 할 뿐이다. 이미 저장된 화면의 `station-order` 를 씬이 그려
|
|
8
|
+
* 낼 수 있게 되는 것이고, 저작자가 새로 **놓을** 수 있게 되는 것이 아니다.
|
|
9
|
+
*
|
|
10
|
+
* 둘을 한 벌로 세지 않는다. 수명이 다르기 때문이다 — **팔레트에서 뺀 타일도 이미 저장된 화면에서는
|
|
11
|
+
* 계속 그려져야 한다.** 그리기를 지우면 옛 화면이 깨지고, 놓기만 지우면 새로 안 놓일 뿐이다.
|
|
12
|
+
*
|
|
13
|
+
* ## 기계의 것은 여기로 안 온다 (아키텍트 고침)
|
|
14
|
+
*
|
|
15
|
+
* 이 모듈의 공통 타일은 플랫폼의 그물이 걷는다 — `package.json` 의 `"things-scene"` 과 뿌리의
|
|
16
|
+
* `things-scene.config.js` 를 `@things-factory/env` 가 모아 보드 모델러가 세운다(꾸러미 42 개가 이미
|
|
17
|
+
* 그 길이다). plant 가 이 모듈을 의존하므로 조건이 선다. 옛 팔레트가 손으로 등록한 것은 그 부품이
|
|
18
|
+
* **앱 안에 있어서** 그물에 안 걸렸기 때문이고, 옮기고 나면 그 까닭이 없어진다. 플랫폼에 길이 있는데
|
|
19
|
+
* 옆에 사설 수집기를 세우지 않는다.
|
|
20
|
+
*
|
|
21
|
+
* 여기 오는 것은 **제품의 타일**(작업지시 · 정비 · 자재)과, 그것을 그물 대신 직접 훑는 저작 화면이다.
|
|
22
|
+
*
|
|
23
|
+
* ## 왜 배열이 아니라 함수인가
|
|
24
|
+
*
|
|
25
|
+
* 기계와 제품이 다른 꾸러미에서 온다. 배열을 export 해 두고 소비자가 import 하면 **그 순간의 사본**을
|
|
26
|
+
* 들고 가므로 늦게 걸린 제품의 것이 닿을 길이 없다. 저작자에게는 팔레트 칸이 덜 선 채로 보이고,
|
|
27
|
+
* 오류는 없다.
|
|
28
|
+
*
|
|
29
|
+
* ## 봉인 함수는 두지 않는다 (아키텍트 고침)
|
|
30
|
+
*
|
|
31
|
+
* 처음에는 `sealStationTiles()` 로 「다 들어왔다」를 말하게 지었다. 그러면 **앱이 그것을 잊었을 때가
|
|
32
|
+
* 다시 조용하다** — 고치려던 실패와 같은 모양이다. 타일 목록은 클라이언트 선언의 **한 칸**이고, 빈
|
|
33
|
+
* 자리는 `declareStationClientProduct` 가 기동 때 거절한다. 서버에서 `declareStationProduct` 가 하는
|
|
34
|
+
* 그 일이다(결정 6 「클라이언트도 같은 선이다」).
|
|
35
|
+
*
|
|
36
|
+
* **아직 그 칸이 아니다.** 클라이언트 선언(`station-client-product`)이 이 모듈로 오는 것이 다음
|
|
37
|
+
* 걸음이고, 그때 이 함수가 그 칸으로 접힌다. 아래 시험이 그날 빨강으로 알린다.
|
|
38
|
+
* ═══════════════════════════════════════════════════════════════════════════
|
|
39
|
+
*/
|
|
40
|
+
let entries = [];
|
|
41
|
+
const listeners = new Set();
|
|
42
|
+
/**
|
|
43
|
+
* 제품이 자기 타일을 건다. 기동 때 한 번, 자기 선언과 같은 자리에서 부른다.
|
|
44
|
+
*
|
|
45
|
+
* 그룹이 섰는지 볼 필요가 없다 — 목록으로 받아 두고 읽을 때 편다. 부르는 순서로 실패하지 않는다는
|
|
46
|
+
* 것이 이 자리가 있는 까닭이다. 옛 팔레트는 그룹이 먼저 안 서면 템플릿을 **오류 없이 버렸다.**
|
|
47
|
+
*/
|
|
48
|
+
export function registerStationTiles(adding) {
|
|
49
|
+
const already = new Set(entries.map(one => one.type));
|
|
50
|
+
const twice = adding.filter(one => already.has(one.type));
|
|
51
|
+
if (twice.length) {
|
|
52
|
+
console.warn(`[station] 같은 타일이 두 번 등록됐습니다 — ${twice.map(one => one.type).join(' · ')}. 뒤엣것은 버립니다.`);
|
|
53
|
+
}
|
|
54
|
+
entries = [...entries, ...adding.filter(one => !already.has(one.type))];
|
|
55
|
+
listeners.forEach(listener => listener());
|
|
56
|
+
}
|
|
57
|
+
/** 제품이 건 타일 — 소비자는 늘 이 함수로 읽는다. 배열을 import 하면 그 순간의 사본이 된다. */
|
|
58
|
+
export function stationTiles() {
|
|
59
|
+
return entries;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* 목록이 바뀌면 알린다 — 읽고 있던 화면이 다시 그리도록.
|
|
63
|
+
*
|
|
64
|
+
* 돌려주는 함수를 부르면 그만 듣는다. 화면이 사라진 뒤에도 듣고 있으면 없는 것을 다시 그리려 한다.
|
|
65
|
+
*/
|
|
66
|
+
export function onStationTiles(listener) {
|
|
67
|
+
listeners.add(listener);
|
|
68
|
+
return () => {
|
|
69
|
+
listeners.delete(listener);
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* 그 타일의 이름 열쇠 — 없으면 `undefined`.
|
|
74
|
+
*
|
|
75
|
+
* 읽는 쪽이 `undefined` 를 받으면 부품 이름(`station-order`)을 그대로 그린다. 그것이 뜨면 등록부에
|
|
76
|
+
* `labelKey` 가 빠진 것이지 이름이 없는 것이 아니다.
|
|
77
|
+
*/
|
|
78
|
+
export function stationTileLabelKey(type) {
|
|
79
|
+
return entries.find(one => one.type === type)?.labelKey;
|
|
80
|
+
}
|
|
81
|
+
/** 시험용 — 등록은 기동 때 한 번 하는 일이다. */
|
|
82
|
+
export function forgetStationTiles() {
|
|
83
|
+
entries = [];
|
|
84
|
+
listeners.clear();
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=station-tile-registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"station-tile-registry.js","sourceRoot":"","sources":["../../client/station/station-tile-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AA6BH,IAAI,OAAO,GAAuB,EAAE,CAAA;AACpC,MAAM,SAAS,GAAG,IAAI,GAAG,EAAc,CAAA;AAEvC;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAAC,MAA0B;IAC7D,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAA;IACrD,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAA;IAEzD,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;QACjB,OAAO,CAAC,IAAI,CAAC,iCAAiC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,CAAA;IACrG,CAAC;IAED,OAAO,GAAG,CAAC,GAAG,OAAO,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACvE,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAA;AAC3C,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,YAAY;IAC1B,OAAO,OAAO,CAAA;AAChB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,QAAoB;IACjD,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;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,OAAO,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,KAAK,IAAI,CAAC,EAAE,QAAQ,CAAA;AACzD,CAAC;AAED,iCAAiC;AACjC,MAAM,UAAU,kBAAkB;IAChC,OAAO,GAAG,EAAE,CAAA;IACZ,SAAS,CAAC,KAAK,EAAE,CAAA;AACnB,CAAC","sourcesContent":["/**\n * 제품이 저작면에 **놓을 수 있게** 내놓는 타일 (ADR-0083 결정 6 · UX 판정 · 아키텍트 경계 고침, 2026-09-20).\n *\n * ═══════════════════════════════════════════════════════════════════════════\n * ## 등록은 두 벌이고, 이것은 둘째다\n *\n * `@sceneComponent` 는 **그릴 수 있게** 할 뿐이다. 이미 저장된 화면의 `station-order` 를 씬이 그려\n * 낼 수 있게 되는 것이고, 저작자가 새로 **놓을** 수 있게 되는 것이 아니다.\n *\n * 둘을 한 벌로 세지 않는다. 수명이 다르기 때문이다 — **팔레트에서 뺀 타일도 이미 저장된 화면에서는\n * 계속 그려져야 한다.** 그리기를 지우면 옛 화면이 깨지고, 놓기만 지우면 새로 안 놓일 뿐이다.\n *\n * ## 기계의 것은 여기로 안 온다 (아키텍트 고침)\n *\n * 이 모듈의 공통 타일은 플랫폼의 그물이 걷는다 — `package.json` 의 `\"things-scene\"` 과 뿌리의\n * `things-scene.config.js` 를 `@things-factory/env` 가 모아 보드 모델러가 세운다(꾸러미 42 개가 이미\n * 그 길이다). plant 가 이 모듈을 의존하므로 조건이 선다. 옛 팔레트가 손으로 등록한 것은 그 부품이\n * **앱 안에 있어서** 그물에 안 걸렸기 때문이고, 옮기고 나면 그 까닭이 없어진다. 플랫폼에 길이 있는데\n * 옆에 사설 수집기를 세우지 않는다.\n *\n * 여기 오는 것은 **제품의 타일**(작업지시 · 정비 · 자재)과, 그것을 그물 대신 직접 훑는 저작 화면이다.\n *\n * ## 왜 배열이 아니라 함수인가\n *\n * 기계와 제품이 다른 꾸러미에서 온다. 배열을 export 해 두고 소비자가 import 하면 **그 순간의 사본**을\n * 들고 가므로 늦게 걸린 제품의 것이 닿을 길이 없다. 저작자에게는 팔레트 칸이 덜 선 채로 보이고,\n * 오류는 없다.\n *\n * ## 봉인 함수는 두지 않는다 (아키텍트 고침)\n *\n * 처음에는 `sealStationTiles()` 로 「다 들어왔다」를 말하게 지었다. 그러면 **앱이 그것을 잊었을 때가\n * 다시 조용하다** — 고치려던 실패와 같은 모양이다. 타일 목록은 클라이언트 선언의 **한 칸**이고, 빈\n * 자리는 `declareStationClientProduct` 가 기동 때 거절한다. 서버에서 `declareStationProduct` 가 하는\n * 그 일이다(결정 6 「클라이언트도 같은 선이다」).\n *\n * **아직 그 칸이 아니다.** 클라이언트 선언(`station-client-product`)이 이 모듈로 오는 것이 다음\n * 걸음이고, 그때 이 함수가 그 칸으로 접힌다. 아래 시험이 그날 빨강으로 알린다.\n * ═══════════════════════════════════════════════════════════════════════════\n */\n\n/** 저작면이 이 타일을 어떻게 내놓고, 놓았을 때 무엇이 서는가. */\nexport interface StationTileEntry {\n /** 씬 부품의 이름. `@sceneComponent` 가 쓴 그것과 같아야 한다. */\n type: string\n /** 팔레트 그림. 없으면 보드가 빈 그림을 그린다. */\n icon?: string\n /**\n * 보드 저작면이 항목에 붙여 그리는 글 — **플랫폼의 칸이라 이름이 이것이고, 영어다.**\n *\n * 같은 타일이 두 저작면에서 다르게 읽힌다: 보드 저작면은 이 영어를, 현장 화면 모델러는 번역한\n * 이름을 보여 준다(UX 실측 2026-09-20). 둘이 한 곳을 보게 하는 것이 `labelKey` 이고, 이 칸은\n * 플랫폼이 읽는 자리라 그대로 둔다.\n */\n description?: string\n /**\n * 사람이 읽는 이름의 **열쇠**. 값이 아니다.\n *\n * 여기서 `i18next.t()` 로 굳히면 언어를 바꿨을 때 옛 언어가 남는다 — 등록은 기동 때 한 번이고\n * 언어는 그 뒤에도 바뀐다. 값으로 바꾸는 것은 읽는 쪽에서 한다.\n */\n labelKey?: string\n /** 팔레트 그룹. 비면 `station`. */\n group?: string\n /** 놓았을 때 서는 것 — 자리 · 크기 · 그 부품의 기본 속성. */\n model: Record<string, any>\n}\n\nlet entries: StationTileEntry[] = []\nconst listeners = new Set<() => void>()\n\n/**\n * 제품이 자기 타일을 건다. 기동 때 한 번, 자기 선언과 같은 자리에서 부른다.\n *\n * 그룹이 섰는지 볼 필요가 없다 — 목록으로 받아 두고 읽을 때 편다. 부르는 순서로 실패하지 않는다는\n * 것이 이 자리가 있는 까닭이다. 옛 팔레트는 그룹이 먼저 안 서면 템플릿을 **오류 없이 버렸다.**\n */\nexport function registerStationTiles(adding: StationTileEntry[]): void {\n const already = new Set(entries.map(one => one.type))\n const twice = adding.filter(one => already.has(one.type))\n\n if (twice.length) {\n console.warn(`[station] 같은 타일이 두 번 등록됐습니다 — ${twice.map(one => one.type).join(' · ')}. 뒤엣것은 버립니다.`)\n }\n\n entries = [...entries, ...adding.filter(one => !already.has(one.type))]\n listeners.forEach(listener => listener())\n}\n\n/** 제품이 건 타일 — 소비자는 늘 이 함수로 읽는다. 배열을 import 하면 그 순간의 사본이 된다. */\nexport function stationTiles(): StationTileEntry[] {\n return entries\n}\n\n/**\n * 목록이 바뀌면 알린다 — 읽고 있던 화면이 다시 그리도록.\n *\n * 돌려주는 함수를 부르면 그만 듣는다. 화면이 사라진 뒤에도 듣고 있으면 없는 것을 다시 그리려 한다.\n */\nexport function onStationTiles(listener: () => void): () => void {\n listeners.add(listener)\n\n return () => {\n listeners.delete(listener)\n }\n}\n\n/**\n * 그 타일의 이름 열쇠 — 없으면 `undefined`.\n *\n * 읽는 쪽이 `undefined` 를 받으면 부품 이름(`station-order`)을 그대로 그린다. 그것이 뜨면 등록부에\n * `labelKey` 가 빠진 것이지 이름이 없는 것이 아니다.\n */\nexport function stationTileLabelKey(type: string): string | undefined {\n return entries.find(one => one.type === type)?.labelKey\n}\n\n/** 시험용 — 등록은 기동 때 한 번 하는 일이다. */\nexport function forgetStationTiles(): void {\n entries = []\n listeners.clear()\n}\n"]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { LitElement } from 'lit';
|
|
2
|
+
import type { WorkPanel } from './station-work-scenarios';
|
|
3
|
+
import type { StationWorkRuntime } from './station-work-runtime';
|
|
4
|
+
import type { ScreenTransition } from './station-screen-flow';
|
|
5
|
+
/** Native accessible form controls, positioned by things-scene's HTML overlay primitive. */
|
|
6
|
+
export declare class StationWorkPanelView extends LitElement {
|
|
7
|
+
static styles: import("lit").CSSResult;
|
|
8
|
+
runtime?: StationWorkRuntime;
|
|
9
|
+
view: WorkPanel;
|
|
10
|
+
label: string;
|
|
11
|
+
editing: boolean;
|
|
12
|
+
targetScreen: string;
|
|
13
|
+
flowAction: ScreenTransition['action'];
|
|
14
|
+
flowGuard: ScreenTransition['guard'];
|
|
15
|
+
resetScope: ScreenTransition['reset'];
|
|
16
|
+
private confirmingCancel;
|
|
17
|
+
private navigate;
|
|
18
|
+
private unsubscribe?;
|
|
19
|
+
updated(changed: Map<string, unknown>): void;
|
|
20
|
+
disconnectedCallback(): void;
|
|
21
|
+
connectedCallback(): void;
|
|
22
|
+
render(): import("lit-html").TemplateResult<1>;
|
|
23
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { __decorate, __metadata } from "tslib";
|
|
2
|
+
import { LitElement, css, html, nothing } from 'lit';
|
|
3
|
+
import { ScrollbarStyles } from '@operato/styles';
|
|
4
|
+
import { customElement, property, state } from 'lit/decorators.js';
|
|
5
|
+
import { i18next } from '@operato/i18n';
|
|
6
|
+
/** Native accessible form controls, positioned by things-scene's HTML overlay primitive. */
|
|
7
|
+
let StationWorkPanelView = class StationWorkPanelView extends LitElement {
|
|
8
|
+
constructor() {
|
|
9
|
+
super(...arguments);
|
|
10
|
+
this.view = 'status';
|
|
11
|
+
this.label = '';
|
|
12
|
+
this.editing = false;
|
|
13
|
+
this.targetScreen = '';
|
|
14
|
+
this.flowAction = 'next';
|
|
15
|
+
this.flowGuard = 'none';
|
|
16
|
+
this.resetScope = 'none';
|
|
17
|
+
this.confirmingCancel = false;
|
|
18
|
+
}
|
|
19
|
+
static { this.styles = css `
|
|
20
|
+
${ScrollbarStyles}
|
|
21
|
+
:host{display:block;box-sizing:border-box;background:var(--ui-panel,#ffffff);color:var(--ui-ink,#16202b);border:1px solid var(--ui-border,#cbd5df);border-radius:10px;font:16px system-ui;overflow:auto}
|
|
22
|
+
section{padding:16px;box-sizing:border-box;height:100%;overflow:auto;background:var(--md-sys-color-surface-container-low,#fff);color:var(--md-sys-color-on-surface,#20333f);font:16px system-ui;border:1px solid var(--md-sys-color-outline-variant,#cbd5df);border-radius:10px} h3{font-size:18px;margin:0 0 14px} p{line-height:1.5;margin:8px 0} small{color:var(--md-sys-color-on-surface-variant,#536574)}
|
|
23
|
+
button,input,select{box-sizing:border-box;font:inherit;min-height:48px;border:1px solid #b6c5d1;border-radius:6px;background:#fff;color:inherit;padding:9px}
|
|
24
|
+
button,input,select{background:var(--md-sys-color-surface,#fff);color:var(--md-sys-color-on-surface,#20333f);border-color:var(--md-sys-color-outline,#b6c5d1)}button{cursor:pointer;text-align:left}button:focus-visible,input:focus-visible{outline:3px solid var(--md-sys-color-primary,#126fc0);outline-offset:2px}
|
|
25
|
+
button[aria-pressed=true]{background:var(--md-sys-color-primary-container,#e6f2ff);color:var(--md-sys-color-on-primary-container,#164274)}button:disabled{opacity:.55;cursor:default}
|
|
26
|
+
.list{display:grid;gap:8px}.row{display:flex;gap:10px;align-items:center}label{display:grid;gap:5px;margin:0 0 12px}label.check{display:flex;gap:10px;align-items:center;min-height:48px}
|
|
27
|
+
input[type=checkbox]{width:24px;min-height:24px;flex:none}input{width:100%}.primary{background:#1765ad;color:white;width:100%;text-align:center;font-weight:bold}
|
|
28
|
+
.problem{color:var(--md-sys-color-error,#973826)}.ok{color:var(--md-sys-color-primary,#136442)}.badge{padding:6px 10px;background:var(--md-sys-color-surface-container-high,#edf2f6);border-radius:5px}.receipt{white-space:pre-wrap;overflow-wrap:anywhere;font-size:14px}
|
|
29
|
+
`; }
|
|
30
|
+
navigate(action) {
|
|
31
|
+
if (this.editing)
|
|
32
|
+
return;
|
|
33
|
+
if (action === 'cancel' && !this.confirmingCancel) {
|
|
34
|
+
this.confirmingCancel = true;
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
this.confirmingCancel = false;
|
|
38
|
+
this.dispatchEvent(new CustomEvent('station-screen-transition', { bubbles: true, composed: true, detail: { action, target: this.targetScreen, guard: this.flowGuard, reset: this.resetScope } }));
|
|
39
|
+
}
|
|
40
|
+
updated(changed) {
|
|
41
|
+
if (changed.has('runtime')) {
|
|
42
|
+
this.unsubscribe?.();
|
|
43
|
+
this.unsubscribe = this.runtime?.subscribe(() => this.requestUpdate());
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
disconnectedCallback() { super.disconnectedCallback(); this.unsubscribe?.(); this.unsubscribe = undefined; }
|
|
47
|
+
connectedCallback() { super.connectedCallback(); if (this.runtime && !this.unsubscribe)
|
|
48
|
+
this.unsubscribe = this.runtime.subscribe(() => this.requestUpdate()); }
|
|
49
|
+
render() {
|
|
50
|
+
const runtime = this.runtime, label = this.label || i18next.t('label.station-sim-work-form'), unescaped = { escapeValue: false };
|
|
51
|
+
if (!runtime)
|
|
52
|
+
return html `<section><h3>${label}</h3><p>${i18next.t('text.station-sim-not-connected')}</p><small>${i18next.t('text.station-sim-no-production-commands')}</small></section>`;
|
|
53
|
+
const s = runtime.state, d = runtime.scenario, disabled = this.editing || Boolean(s.completed), problems = runtime.validate();
|
|
54
|
+
let body;
|
|
55
|
+
switch (this.view) {
|
|
56
|
+
case 'orders':
|
|
57
|
+
body = html `<div class="list">${d.orders.map(order => html `<button ?disabled=${disabled} aria-pressed=${s.orderId === order.id} @click=${() => runtime.selectOrder(order.id)}>${order.id}<br><small>${order.product} · ${i18next.t('text.station-sim-order-quantity', { count: order.quantity })} · ${order.ready ? i18next.t('label.station-sim-selectable') : i18next.t('label.station-sim-on-hold')}</small></button>`)}</div>`;
|
|
58
|
+
break;
|
|
59
|
+
case 'materials':
|
|
60
|
+
body = html `<div class="list">${d.stock.length ? d.stock.map(item => html `<button ?disabled=${disabled} aria-pressed=${s.serials.includes(item.serial)} @click=${() => s.serials.includes(item.serial) ? runtime.remove(item.serial) : runtime.scan(item.serial)}>${s.serials.includes(item.serial) ? '☑' : '□'} ${item.serial}<br><small>${item.item} · ${item.eligible ? i18next.t('label.station-sim-available') : item.note}</small></button>`) : html `<p>${i18next.t('text.station-sim-no-serial-input')}</p>`}</div>`;
|
|
61
|
+
break;
|
|
62
|
+
case 'scan':
|
|
63
|
+
body = html `<form @submit=${(event) => { event.preventDefault(); if (disabled)
|
|
64
|
+
return; const input = event.target.querySelector('input'); runtime.scan(input.value); input.value = ''; input.focus(); }}><label>${i18next.t('label.station-sim-serial-barcode')}<input aria-label=${i18next.t('label.station-sim-serial-barcode')} ?disabled=${disabled || !d.stock.length} autocomplete="off" placeholder=${i18next.t('text.station-sim-scan-then-enter')}></label></form>`;
|
|
65
|
+
break;
|
|
66
|
+
case 'inputs':
|
|
67
|
+
body = html `${d.inputs.length ? d.inputs.map(field => html `<label>${field.label}<input aria-label=${field.label} type=${field.kind === 'number' ? 'number' : 'text'} step="any" min=${field.min ?? nothing} max=${field.max ?? nothing} maxlength="120" .value=${s.inputs[field.key] ?? ''} ?disabled=${disabled} @input=${(event) => runtime.input(field.key, event.target.value)}><small>${field.kind === 'number' ? i18next.t('text.station-sim-allowed-range', { min: field.min, max: field.max }) : i18next.t('text.station-sim-required-identifier')}</small></label>`) : html `<p>${i18next.t('text.station-sim-no-extra-input')}</p>`}`;
|
|
68
|
+
break;
|
|
69
|
+
case 'checks':
|
|
70
|
+
body = html `${d.checks.map(check => html `<label class="check"><input type="checkbox" .checked=${s.checks.includes(check)} ?disabled=${disabled} @change=${(event) => runtime.check(check, event.target.checked)}>${check}</label>`)}`;
|
|
71
|
+
break;
|
|
72
|
+
case 'status':
|
|
73
|
+
body = html `<p class="badge">${s.orderId || i18next.t('text.station-sim-no-order-selected')} · ${s.role}</p>${Object.entries(d.requirements).map(([item, needed]) => html `<p>${item}: ${s.serials.filter(id => d.stock.some(stock => stock.serial === id && stock.item === item)).length} / ${needed}</p>`)}<div role="status" aria-live="polite"><p>${s.message}</p></div>${s.completed ? html `<p class="ok">${i18next.t('text.station-sim-simulated-done', { result: s.completed.result, interpolation: unescaped })}</p><pre class="receipt">${JSON.stringify(s.completed, null, 2)}</pre>` : html `<p>${problems.length ? i18next.t('text.station-sim-problem-count', { count: problems.length }) : i18next.t('text.station-sim-ready-to-confirm')}</p>${problems.map(problem => html `<p class="problem">• ${problem}</p>`)}`}`;
|
|
74
|
+
break;
|
|
75
|
+
case 'commit':
|
|
76
|
+
body = html `<p>${i18next.t('text.station-sim-simulation-only')}</p><button class="primary" ?disabled=${disabled} @click=${() => this.targetScreen ? this.navigate('complete') : runtime.commit()}>${i18next.t('button.station-sim-confirm-result', { result: d.result, interpolation: unescaped })}</button><small>${i18next.t('text.station-sim-recheck-before-confirm')}</small>`;
|
|
77
|
+
break;
|
|
78
|
+
case 'navigation':
|
|
79
|
+
body = this.confirmingCancel ? html `<p>${i18next.t('text.station-sim-clear-unconfirmed')}</p><button @click=${() => this.navigate('cancel')}>${i18next.t('button.station-sim-confirm-clear')}</button><button @click=${() => this.confirmingCancel = false}>${i18next.t('button.station-sim-keep-working')}</button>` : html `<button class="primary" ?disabled=${this.editing || (this.flowAction === 'next-work' ? !s.completed : Boolean(s.completed))} @click=${() => this.navigate(this.flowAction)}>${label}</button><small>${i18next.t('text.station-sim-target', { target: this.targetScreen || i18next.t('text.station-sim-not-set'), interpolation: unescaped })}</small>`;
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
return html `<section aria-label=${label}><h3>${label}</h3>${body}</section>`;
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
__decorate([
|
|
86
|
+
property({ attribute: false }),
|
|
87
|
+
__metadata("design:type", Function)
|
|
88
|
+
], StationWorkPanelView.prototype, "runtime", void 0);
|
|
89
|
+
__decorate([
|
|
90
|
+
property(),
|
|
91
|
+
__metadata("design:type", String)
|
|
92
|
+
], StationWorkPanelView.prototype, "view", void 0);
|
|
93
|
+
__decorate([
|
|
94
|
+
property(),
|
|
95
|
+
__metadata("design:type", Object)
|
|
96
|
+
], StationWorkPanelView.prototype, "label", void 0);
|
|
97
|
+
__decorate([
|
|
98
|
+
property({ type: Boolean }),
|
|
99
|
+
__metadata("design:type", Object)
|
|
100
|
+
], StationWorkPanelView.prototype, "editing", void 0);
|
|
101
|
+
__decorate([
|
|
102
|
+
property(),
|
|
103
|
+
__metadata("design:type", Object)
|
|
104
|
+
], StationWorkPanelView.prototype, "targetScreen", void 0);
|
|
105
|
+
__decorate([
|
|
106
|
+
property(),
|
|
107
|
+
__metadata("design:type", Object)
|
|
108
|
+
], StationWorkPanelView.prototype, "flowAction", void 0);
|
|
109
|
+
__decorate([
|
|
110
|
+
property(),
|
|
111
|
+
__metadata("design:type", Object)
|
|
112
|
+
], StationWorkPanelView.prototype, "flowGuard", void 0);
|
|
113
|
+
__decorate([
|
|
114
|
+
property(),
|
|
115
|
+
__metadata("design:type", Object)
|
|
116
|
+
], StationWorkPanelView.prototype, "resetScope", void 0);
|
|
117
|
+
__decorate([
|
|
118
|
+
state(),
|
|
119
|
+
__metadata("design:type", Object)
|
|
120
|
+
], StationWorkPanelView.prototype, "confirmingCancel", void 0);
|
|
121
|
+
StationWorkPanelView = __decorate([
|
|
122
|
+
customElement('station-work-panel-view')
|
|
123
|
+
], StationWorkPanelView);
|
|
124
|
+
export { StationWorkPanelView };
|
|
125
|
+
//# sourceMappingURL=station-work-panel-view.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"station-work-panel-view.js","sourceRoot":"","sources":["../../client/station/station-work-panel-view.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement, css, html, nothing } from 'lit'\nimport { ScrollbarStyles } from '@operato/styles'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { i18next } from '@operato/i18n'\nimport type { WorkPanel } from './station-work-scenarios'\nimport type { StationWorkRuntime } from './station-work-runtime'\nimport type { ScreenTransition } from './station-screen-flow'\n\n/** Native accessible form controls, positioned by things-scene's HTML overlay primitive. */\n@customElement('station-work-panel-view')\nexport class StationWorkPanelView extends LitElement {\n static styles=css`\n ${ScrollbarStyles}\n :host{display:block;box-sizing:border-box;background:var(--ui-panel,#ffffff);color:var(--ui-ink,#16202b);border:1px solid var(--ui-border,#cbd5df);border-radius:10px;font:16px system-ui;overflow:auto}\n section{padding:16px;box-sizing:border-box;height:100%;overflow:auto;background:var(--md-sys-color-surface-container-low,#fff);color:var(--md-sys-color-on-surface,#20333f);font:16px system-ui;border:1px solid var(--md-sys-color-outline-variant,#cbd5df);border-radius:10px} h3{font-size:18px;margin:0 0 14px} p{line-height:1.5;margin:8px 0} small{color:var(--md-sys-color-on-surface-variant,#536574)}\n button,input,select{box-sizing:border-box;font:inherit;min-height:48px;border:1px solid #b6c5d1;border-radius:6px;background:#fff;color:inherit;padding:9px}\n button,input,select{background:var(--md-sys-color-surface,#fff);color:var(--md-sys-color-on-surface,#20333f);border-color:var(--md-sys-color-outline,#b6c5d1)}button{cursor:pointer;text-align:left}button:focus-visible,input:focus-visible{outline:3px solid var(--md-sys-color-primary,#126fc0);outline-offset:2px}\n button[aria-pressed=true]{background:var(--md-sys-color-primary-container,#e6f2ff);color:var(--md-sys-color-on-primary-container,#164274)}button:disabled{opacity:.55;cursor:default}\n .list{display:grid;gap:8px}.row{display:flex;gap:10px;align-items:center}label{display:grid;gap:5px;margin:0 0 12px}label.check{display:flex;gap:10px;align-items:center;min-height:48px}\n input[type=checkbox]{width:24px;min-height:24px;flex:none}input{width:100%}.primary{background:#1765ad;color:white;width:100%;text-align:center;font-weight:bold}\n .problem{color:var(--md-sys-color-error,#973826)}.ok{color:var(--md-sys-color-primary,#136442)}.badge{padding:6px 10px;background:var(--md-sys-color-surface-container-high,#edf2f6);border-radius:5px}.receipt{white-space:pre-wrap;overflow-wrap:anywhere;font-size:14px}\n `\n @property({attribute:false}) runtime?:StationWorkRuntime\n @property() view:WorkPanel='status'\n @property() label=''\n @property({type:Boolean}) editing=false\n @property() targetScreen=''\n @property() flowAction:ScreenTransition['action']='next'\n @property() flowGuard:ScreenTransition['guard']='none'\n @property() resetScope:ScreenTransition['reset']='none'\n @state() private confirmingCancel=false\n private navigate(action:ScreenTransition['action']){\n if(this.editing)return\n if(action==='cancel'&&!this.confirmingCancel){this.confirmingCancel=true;return}\n this.confirmingCancel=false\n this.dispatchEvent(new CustomEvent('station-screen-transition',{bubbles:true,composed:true,detail:{action,target:this.targetScreen,guard:this.flowGuard,reset:this.resetScope}}))\n }\n private unsubscribe?:()=>void\n updated(changed:Map<string,unknown>) {\n if(changed.has('runtime')) {this.unsubscribe?.();this.unsubscribe=this.runtime?.subscribe(()=>this.requestUpdate())}\n }\n disconnectedCallback(){super.disconnectedCallback();this.unsubscribe?.();this.unsubscribe=undefined}\n connectedCallback(){super.connectedCallback();if(this.runtime && !this.unsubscribe)this.unsubscribe=this.runtime.subscribe(()=>this.requestUpdate())}\n render() {\n const runtime=this.runtime,label=this.label||i18next.t('label.station-sim-work-form'),unescaped={escapeValue:false}\n if(!runtime)return html`<section><h3>${label}</h3><p>${i18next.t('text.station-sim-not-connected')}</p><small>${i18next.t('text.station-sim-no-production-commands')}</small></section>`\n const s=runtime.state,d=runtime.scenario,disabled=this.editing || Boolean(s.completed),problems=runtime.validate()\n let body\n switch(this.view){\n case 'orders':body=html`<div class=\"list\">${d.orders.map(order=>html`<button ?disabled=${disabled} aria-pressed=${s.orderId===order.id} @click=${()=>runtime.selectOrder(order.id)}>${order.id}<br><small>${order.product} · ${i18next.t('text.station-sim-order-quantity',{count:order.quantity})} · ${order.ready?i18next.t('label.station-sim-selectable'):i18next.t('label.station-sim-on-hold')}</small></button>`)}</div>`;break\n case 'materials':body=html`<div class=\"list\">${d.stock.length?d.stock.map(item=>html`<button ?disabled=${disabled} aria-pressed=${s.serials.includes(item.serial)} @click=${()=>s.serials.includes(item.serial)?runtime.remove(item.serial):runtime.scan(item.serial)}>${s.serials.includes(item.serial)?'☑':'□'} ${item.serial}<br><small>${item.item} · ${item.eligible?i18next.t('label.station-sim-available'):item.note}</small></button>`):html`<p>${i18next.t('text.station-sim-no-serial-input')}</p>`}</div>`;break\n case 'scan':body=html`<form @submit=${(event:Event)=>{event.preventDefault();if(disabled)return;const input=(event.target as HTMLFormElement).querySelector('input')!;runtime.scan(input.value);input.value='';input.focus()}}><label>${i18next.t('label.station-sim-serial-barcode')}<input aria-label=${i18next.t('label.station-sim-serial-barcode')} ?disabled=${disabled || !d.stock.length} autocomplete=\"off\" placeholder=${i18next.t('text.station-sim-scan-then-enter')}></label></form>`;break\n case 'inputs':body=html`${d.inputs.length?d.inputs.map(field=>html`<label>${field.label}<input aria-label=${field.label} type=${field.kind==='number'?'number':'text'} step=\"any\" min=${field.min??nothing} max=${field.max??nothing} maxlength=\"120\" .value=${s.inputs[field.key]??''} ?disabled=${disabled} @input=${(event:Event)=>runtime.input(field.key,(event.target as HTMLInputElement).value)}><small>${field.kind==='number'?i18next.t('text.station-sim-allowed-range',{min:field.min,max:field.max}):i18next.t('text.station-sim-required-identifier')}</small></label>`):html`<p>${i18next.t('text.station-sim-no-extra-input')}</p>`}`;break\n case 'checks':body=html`${d.checks.map(check=>html`<label class=\"check\"><input type=\"checkbox\" .checked=${s.checks.includes(check)} ?disabled=${disabled} @change=${(event:Event)=>runtime.check(check,(event.target as HTMLInputElement).checked)}>${check}</label>`)}`;break\n case 'status':body=html`<p class=\"badge\">${s.orderId||i18next.t('text.station-sim-no-order-selected')} · ${s.role}</p>${Object.entries(d.requirements).map(([item,needed])=>html`<p>${item}: ${s.serials.filter(id=>d.stock.some(stock=>stock.serial===id&&stock.item===item)).length} / ${needed}</p>`)}<div role=\"status\" aria-live=\"polite\"><p>${s.message}</p></div>${s.completed?html`<p class=\"ok\">${i18next.t('text.station-sim-simulated-done',{result:s.completed.result,interpolation:unescaped})}</p><pre class=\"receipt\">${JSON.stringify(s.completed,null,2)}</pre>`:html`<p>${problems.length?i18next.t('text.station-sim-problem-count',{count:problems.length}):i18next.t('text.station-sim-ready-to-confirm')}</p>${problems.map(problem=>html`<p class=\"problem\">• ${problem}</p>`)}`}`;break\n case 'commit':body=html`<p>${i18next.t('text.station-sim-simulation-only')}</p><button class=\"primary\" ?disabled=${disabled} @click=${()=>this.targetScreen?this.navigate('complete'):runtime.commit()}>${i18next.t('button.station-sim-confirm-result',{result:d.result,interpolation:unescaped})}</button><small>${i18next.t('text.station-sim-recheck-before-confirm')}</small>`;break\n case 'navigation':body=this.confirmingCancel?html`<p>${i18next.t('text.station-sim-clear-unconfirmed')}</p><button @click=${()=>this.navigate('cancel')}>${i18next.t('button.station-sim-confirm-clear')}</button><button @click=${()=>this.confirmingCancel=false}>${i18next.t('button.station-sim-keep-working')}</button>`:html`<button class=\"primary\" ?disabled=${this.editing || (this.flowAction==='next-work'?!s.completed:Boolean(s.completed))} @click=${()=>this.navigate(this.flowAction)}>${label}</button><small>${i18next.t('text.station-sim-target',{target:this.targetScreen||i18next.t('text.station-sim-not-set'),interpolation:unescaped})}</small>`;break\n }\n return html`<section aria-label=${label}><h3>${label}</h3>${body}</section>`\n }\n}\n"]}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { WorkScenario } from './station-work-scenarios';
|
|
2
|
+
export declare const WORK_SERVICE = "station-work-sandbox";
|
|
3
|
+
export interface WorkState {
|
|
4
|
+
orderId: string;
|
|
5
|
+
serials: string[];
|
|
6
|
+
inputs: Record<string, string>;
|
|
7
|
+
checks: string[];
|
|
8
|
+
role: string;
|
|
9
|
+
qualified: boolean;
|
|
10
|
+
equipmentReady: boolean;
|
|
11
|
+
online: boolean;
|
|
12
|
+
calibrated: boolean;
|
|
13
|
+
message: string;
|
|
14
|
+
completed?: {
|
|
15
|
+
id: string;
|
|
16
|
+
result: string;
|
|
17
|
+
orderId: string;
|
|
18
|
+
serials: string[];
|
|
19
|
+
inputs: Record<string, string>;
|
|
20
|
+
checks: string[];
|
|
21
|
+
role: string;
|
|
22
|
+
};
|
|
23
|
+
events: string[];
|
|
24
|
+
}
|
|
25
|
+
/** Isolated, deterministic acceptance harness. It never calls Plant mutation APIs. */
|
|
26
|
+
export declare class StationWorkRuntime {
|
|
27
|
+
private sequence;
|
|
28
|
+
readonly receipts: WorkState['completed'][];
|
|
29
|
+
readonly sandbox = true;
|
|
30
|
+
readonly scenario: WorkScenario;
|
|
31
|
+
private current;
|
|
32
|
+
private listeners;
|
|
33
|
+
constructor(scenario: WorkScenario);
|
|
34
|
+
get state(): WorkState;
|
|
35
|
+
subscribe(listener: () => void): () => void;
|
|
36
|
+
private emit;
|
|
37
|
+
private editable;
|
|
38
|
+
selectOrder(id: string): void;
|
|
39
|
+
scan(raw: string): void;
|
|
40
|
+
remove(serial: string): void;
|
|
41
|
+
input(key: string, value: string): void;
|
|
42
|
+
check(label: string, checked: boolean): void;
|
|
43
|
+
environment(key: 'role' | 'qualified' | 'equipmentReady' | 'online' | 'calibrated', value: string | boolean): void;
|
|
44
|
+
validate(): string[];
|
|
45
|
+
commit(): boolean;
|
|
46
|
+
resetWork(scope: 'none' | 'inputs' | 'work', afterCompletion?: boolean): void;
|
|
47
|
+
}
|