@aryacode/dsh-engineering 0.0.0-stage → 0.4.0
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/CHANGELOG.md +128 -0
- package/LICENSE +21 -0
- package/README.md +230 -2
- package/cordis.patch.yml +123 -0
- package/lib/client.js +3448 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +9 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +94 -3
- package/vendor/ftview/README.md +55 -0
- package/vendor/ftview/SKILL.md +74 -0
- package/vendor/ftview/lib/checks.d.ts +27 -0
- package/vendor/ftview/lib/checks.js +39 -0
- package/vendor/ftview/lib/core.d.ts +208 -0
- package/vendor/ftview/lib/core.js +160 -0
- package/vendor/ftview/lib/edit/drafts.d.ts +49 -0
- package/vendor/ftview/lib/edit/drafts.js +84 -0
- package/vendor/ftview/lib/edit/operations.d.ts +62 -0
- package/vendor/ftview/lib/edit/operations.js +126 -0
- package/vendor/ftview/lib/edit/prepare.d.ts +38 -0
- package/vendor/ftview/lib/edit/prepare.js +133 -0
- package/vendor/ftview/lib/edit/tools.d.ts +25 -0
- package/vendor/ftview/lib/edit/tools.js +198 -0
- package/vendor/ftview/lib/edit.d.ts +30 -0
- package/vendor/ftview/lib/edit.js +38 -0
- package/vendor/ftview/lib/index.d.ts +31 -0
- package/vendor/ftview/lib/index.js +36 -0
- package/vendor/ftview/lib/model.d.ts +80 -0
- package/vendor/ftview/lib/model.js +267 -0
- package/vendor/ftview/lib/session.d.ts +48 -0
- package/vendor/ftview/lib/session.js +98 -0
- package/vendor/ftview/lib/skill.d.ts +29 -0
- package/vendor/ftview/lib/skill.js +43 -0
- package/vendor/ftview/lib/studio/discover.d.ts +36 -0
- package/vendor/ftview/lib/studio/discover.js +87 -0
- package/vendor/ftview/lib/studio/preflight.d.ts +51 -0
- package/vendor/ftview/lib/studio/preflight.js +64 -0
- package/vendor/ftview/lib/studio/tools.d.ts +15 -0
- package/vendor/ftview/lib/studio/tools.js +83 -0
- package/vendor/ftview/lib/studio.d.ts +30 -0
- package/vendor/ftview/lib/studio.js +37 -0
- package/vendor/ftview/lib/tools.d.ts +14 -0
- package/vendor/ftview/lib/tools.js +202 -0
- package/vendor/l5x/README.md +62 -0
- package/vendor/l5x/SKILL.md +81 -0
- package/vendor/l5x/lib/approvals.d.ts +50 -0
- package/vendor/l5x/lib/approvals.js +93 -0
- package/vendor/l5x/lib/client.js +412 -0
- package/vendor/l5x/lib/client.js.map +1 -0
- package/vendor/l5x/lib/core/errors.d.ts +38 -0
- package/vendor/l5x/lib/core/errors.js +48 -0
- package/vendor/l5x/lib/core/params.d.ts +49 -0
- package/vendor/l5x/lib/core/params.js +39 -0
- package/vendor/l5x/lib/core/result.d.ts +46 -0
- package/vendor/l5x/lib/core/result.js +21 -0
- package/vendor/l5x/lib/core/tool.d.ts +102 -0
- package/vendor/l5x/lib/core/tool.js +19 -0
- package/vendor/l5x/lib/drafts.d.ts +66 -0
- package/vendor/l5x/lib/drafts.js +98 -0
- package/vendor/l5x/lib/editing.d.ts +30 -0
- package/vendor/l5x/lib/editing.js +42 -0
- package/vendor/l5x/lib/engine/acd-edit.d.ts +13 -0
- package/vendor/l5x/lib/engine/acd-edit.js +115 -0
- package/vendor/l5x/lib/engine/analysis.d.ts +22 -0
- package/vendor/l5x/lib/engine/analysis.js +258 -0
- package/vendor/l5x/lib/engine/capabilities.d.ts +9 -0
- package/vendor/l5x/lib/engine/capabilities.js +40 -0
- package/vendor/l5x/lib/engine/change-set.d.ts +122 -0
- package/vendor/l5x/lib/engine/change-set.js +446 -0
- package/vendor/l5x/lib/engine/configuration.d.ts +21 -0
- package/vendor/l5x/lib/engine/configuration.js +42 -0
- package/vendor/l5x/lib/engine/details.d.ts +47 -0
- package/vendor/l5x/lib/engine/details.js +173 -0
- package/vendor/l5x/lib/engine/direct-polling.d.ts +91 -0
- package/vendor/l5x/lib/engine/direct-polling.js +193 -0
- package/vendor/l5x/lib/engine/download-review.d.ts +15 -0
- package/vendor/l5x/lib/engine/download-review.js +63 -0
- package/vendor/l5x/lib/engine/logic-syntax.d.ts +60 -0
- package/vendor/l5x/lib/engine/logic-syntax.js +237 -0
- package/vendor/l5x/lib/engine/observations.d.ts +41 -0
- package/vendor/l5x/lib/engine/observations.js +67 -0
- package/vendor/l5x/lib/engine/project-analysis.d.ts +131 -0
- package/vendor/l5x/lib/engine/project-analysis.js +314 -0
- package/vendor/l5x/lib/engine/review-evidence.d.ts +8 -0
- package/vendor/l5x/lib/engine/review-evidence.js +49 -0
- package/vendor/l5x/lib/engine/review.d.ts +18 -0
- package/vendor/l5x/lib/engine/review.js +123 -0
- package/vendor/l5x/lib/engine/studio-sdk.d.ts +101 -0
- package/vendor/l5x/lib/engine/studio-sdk.js +571 -0
- package/vendor/l5x/lib/engine/tag-resolution.d.ts +20 -0
- package/vendor/l5x/lib/engine/tag-resolution.js +169 -0
- package/vendor/l5x/lib/engine/usage.d.ts +16 -0
- package/vendor/l5x/lib/engine/usage.js +92 -0
- package/vendor/l5x/lib/engine/xml-regions.d.ts +23 -0
- package/vendor/l5x/lib/engine/xml-regions.js +77 -0
- package/vendor/l5x/lib/fs.d.ts +17 -0
- package/vendor/l5x/lib/fs.js +36 -0
- package/vendor/l5x/lib/index.d.ts +36 -0
- package/vendor/l5x/lib/index.js +42 -0
- package/vendor/l5x/lib/live.d.ts +48 -0
- package/vendor/l5x/lib/live.js +48 -0
- package/vendor/l5x/lib/model/aois.d.ts +39 -0
- package/vendor/l5x/lib/model/aois.js +112 -0
- package/vendor/l5x/lib/model/errors.d.ts +15 -0
- package/vendor/l5x/lib/model/errors.js +18 -0
- package/vendor/l5x/lib/model/index.d.ts +20 -0
- package/vendor/l5x/lib/model/index.js +19 -0
- package/vendor/l5x/lib/model/module.d.ts +37 -0
- package/vendor/l5x/lib/model/module.js +54 -0
- package/vendor/l5x/lib/model/modules.d.ts +24 -0
- package/vendor/l5x/lib/model/modules.js +55 -0
- package/vendor/l5x/lib/model/parse.d.ts +32 -0
- package/vendor/l5x/lib/model/parse.js +116 -0
- package/vendor/l5x/lib/model/project.d.ts +15 -0
- package/vendor/l5x/lib/model/project.js +31 -0
- package/vendor/l5x/lib/model/routines.d.ts +71 -0
- package/vendor/l5x/lib/model/routines.js +183 -0
- package/vendor/l5x/lib/model/tags.d.ts +51 -0
- package/vendor/l5x/lib/model/tags.js +119 -0
- package/vendor/l5x/lib/model/types.d.ts +304 -0
- package/vendor/l5x/lib/model/types.js +12 -0
- package/vendor/l5x/lib/model/udts.d.ts +34 -0
- package/vendor/l5x/lib/model/udts.js +81 -0
- package/vendor/l5x/lib/model/xml.d.ts +139 -0
- package/vendor/l5x/lib/model/xml.js +291 -0
- package/vendor/l5x/lib/model/xref.d.ts +28 -0
- package/vendor/l5x/lib/model/xref.js +154 -0
- package/vendor/l5x/lib/session.d.ts +119 -0
- package/vendor/l5x/lib/session.js +236 -0
- package/vendor/l5x/lib/skill.d.ts +30 -0
- package/vendor/l5x/lib/skill.js +44 -0
- package/vendor/l5x/lib/studio-state.d.ts +77 -0
- package/vendor/l5x/lib/studio-state.js +79 -0
- package/vendor/l5x/lib/studio.d.ts +29 -0
- package/vendor/l5x/lib/studio.js +40 -0
- package/vendor/l5x/lib/tools/checks.d.ts +19 -0
- package/vendor/l5x/lib/tools/checks.js +140 -0
- package/vendor/l5x/lib/tools/common.d.ts +41 -0
- package/vendor/l5x/lib/tools/common.js +84 -0
- package/vendor/l5x/lib/tools/controller.d.ts +21 -0
- package/vendor/l5x/lib/tools/controller.js +170 -0
- package/vendor/l5x/lib/tools/drafts.d.ts +23 -0
- package/vendor/l5x/lib/tools/drafts.js +270 -0
- package/vendor/l5x/lib/tools/live.d.ts +21 -0
- package/vendor/l5x/lib/tools/live.js +175 -0
- package/vendor/l5x/lib/tools/read.d.ts +19 -0
- package/vendor/l5x/lib/tools/read.js +245 -0
- package/vendor/l5x/lib/tools/references.d.ts +20 -0
- package/vendor/l5x/lib/tools/references.js +269 -0
- package/vendor/l5x/lib/tools/studio.d.ts +21 -0
- package/vendor/l5x/lib/tools/studio.js +193 -0
- package/vendor/l5x/poll-worker.mjs +77 -0
- package/vendor/l5x/studio-bridge/NuGet.Config +1 -0
- package/vendor/l5x/studio-bridge/Program.cs +304 -0
- package/vendor/l5x/studio-bridge/StudioBridge.csproj +11 -0
- package/vendor/mwi/README.md +60 -0
- package/vendor/mwi/SKILL.md +87 -0
- package/vendor/mwi/bridge/README.md +8 -0
- package/vendor/mwi/bridge/mw_bridge.ps1 +2763 -0
- package/vendor/mwi/bridge/sdk-interop-importer.cs +30 -0
- package/vendor/mwi/bridge/sdk-variable-reader.cs +22 -0
- package/vendor/mwi/bridge/start_bridge.cmd +21 -0
- package/vendor/mwi/bridge/watch_dialogs.cmd +13 -0
- package/vendor/mwi/bridge/watch_dialogs.ps1 +153 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__init__.py +14 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/__init__.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/block_interfaces.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/cfb.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/datatypes.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/declarations.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/errors.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/graphical_listing.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/grid.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/knowledge.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/mwt_bind.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/navigation.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/program_checks.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/project.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/staging.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/structure.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/task_bindings.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/tasks.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/tree.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/validation.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/variable_edit.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/variables.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/workflow.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/block_interfaces.py +275 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/build.py +339 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/capture.py +448 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/cfb.py +765 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/com.py +268 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/datatypes.py +204 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/declarations.py +399 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/descriptions.py +30 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/eip.py +292 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/errors.py +36 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/graphical_listing.py +208 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/grid.py +227 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/ide.py +317 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/iec.py +317 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/installed_help.py +151 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/knowledge.py +218 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/libraries.py +250 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/manuals.py +520 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/mwt_bind.py +133 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/navigation.py +44 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/pou_writer.py +1547 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/program_checks.py +360 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/project.py +704 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/reference_data/catalog.json +672 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/reference_data/patterns.json +86 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/restore.py +208 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/server.py +1913 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/snapshot.py +138 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/st_comments.py +25 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/staging.py +179 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/stlint.py +676 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/structure.py +98 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/task_bindings.py +68 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/tasks.py +162 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/transaction.py +157 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/tree.py +563 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/tree_writer.py +790 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/uia.py +465 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/validation.py +41 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/variable_edit.py +183 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/variables.py +472 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/workflow.py +130 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/writer.py +666 -0
- package/vendor/mwi/code/mw_code.py +1258 -0
- package/vendor/mwi/legacy/edit-session.js +50 -0
- package/vendor/mwi/legacy/fb-insertion.js +74 -0
- package/vendor/mwi/legacy/native-code.js +83 -0
- package/vendor/mwi/legacy/native-groups.js +75 -0
- package/vendor/mwi/legacy/native-structure.js +143 -0
- package/vendor/mwi/legacy/native-variables.js +77 -0
- package/vendor/mwi/legacy/pou-conversion.js +47 -0
- package/vendor/mwi/legacy/pou-package.js +66 -0
- package/vendor/mwi/lib/core/bridge.d.ts +71 -0
- package/vendor/mwi/lib/core/bridge.js +53 -0
- package/vendor/mwi/lib/core/code-engine.d.ts +47 -0
- package/vendor/mwi/lib/core/code-engine.js +109 -0
- package/vendor/mwi/lib/core/errors.d.ts +37 -0
- package/vendor/mwi/lib/core/errors.js +47 -0
- package/vendor/mwi/lib/core/params.d.ts +52 -0
- package/vendor/mwi/lib/core/params.js +46 -0
- package/vendor/mwi/lib/core/ps-bridge.d.ts +62 -0
- package/vendor/mwi/lib/core/ps-bridge.js +127 -0
- package/vendor/mwi/lib/core/result.d.ts +45 -0
- package/vendor/mwi/lib/core/result.js +20 -0
- package/vendor/mwi/lib/core/tool.d.ts +80 -0
- package/vendor/mwi/lib/core/tool.js +55 -0
- package/vendor/mwi/lib/edit/compare.d.ts +33 -0
- package/vendor/mwi/lib/edit/compare.js +70 -0
- package/vendor/mwi/lib/edit.d.ts +20 -0
- package/vendor/mwi/lib/edit.js +32 -0
- package/vendor/mwi/lib/index.d.ts +21 -0
- package/vendor/mwi/lib/index.js +33 -0
- package/vendor/mwi/lib/read/service.d.ts +117 -0
- package/vendor/mwi/lib/read/service.js +229 -0
- package/vendor/mwi/lib/read.d.ts +21 -0
- package/vendor/mwi/lib/read.js +33 -0
- package/vendor/mwi/lib/session/project.d.ts +34 -0
- package/vendor/mwi/lib/session/project.js +54 -0
- package/vendor/mwi/lib/session/service.d.ts +167 -0
- package/vendor/mwi/lib/session/service.js +566 -0
- package/vendor/mwi/lib/session/stage.d.ts +84 -0
- package/vendor/mwi/lib/session/stage.js +187 -0
- package/vendor/mwi/lib/session/workspace.d.ts +52 -0
- package/vendor/mwi/lib/session/workspace.js +90 -0
- package/vendor/mwi/lib/skill.d.ts +36 -0
- package/vendor/mwi/lib/skill.js +56 -0
- package/vendor/mwi/lib/tools/edit.d.ts +20 -0
- package/vendor/mwi/lib/tools/edit.js +286 -0
- package/vendor/mwi/lib/tools/read.d.ts +19 -0
- package/vendor/mwi/lib/tools/read.js +161 -0
- package/vendor/mwi/lib/tools/session.d.ts +20 -0
- package/vendor/mwi/lib/tools/session.js +166 -0
- package/vendor/mwi/lib/tools/verify.d.ts +19 -0
- package/vendor/mwi/lib/tools/verify.js +85 -0
- package/vendor/mwi/lib/verify.d.ts +17 -0
- package/vendor/mwi/lib/verify.js +29 -0
package/lib/client.js
ADDED
|
@@ -0,0 +1,3448 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The engineering bundle's one browser half: the ladder viewer and the
|
|
3
|
+
* FactoryTalk View ME display preview inside a single module registration.
|
|
4
|
+
*
|
|
5
|
+
* Generated by scripts/build-client.mjs from the committed face bundles
|
|
6
|
+
* (packages/viewer/lib/client.js, packages/ftview-viewer/lib/client.js).
|
|
7
|
+
* Do not edit: change a face in its own package and rebuild.
|
|
8
|
+
*
|
|
9
|
+
* The package name is the registration id because the shell keys a client
|
|
10
|
+
* module by package name; a mismatch registers under a key the shell never
|
|
11
|
+
* asks for, presenting as a plugin that loaded and drew nothing.
|
|
12
|
+
*/
|
|
13
|
+
window.__ModuleLoader__.load({
|
|
14
|
+
id: "@aryacode/dsh-engineering",
|
|
15
|
+
factory: (require) => {
|
|
16
|
+
// One closure per face: both bodies declare their own React binding,
|
|
17
|
+
// document-preview id, stylesheet text and helpers.
|
|
18
|
+
const ladder = (() => {
|
|
19
|
+
|
|
20
|
+
var module = { exports: {} };
|
|
21
|
+
var exports = module.exports;
|
|
22
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
23
|
+
let react = require("react");
|
|
24
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
25
|
+
let react_dom = require("react-dom");
|
|
26
|
+
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
27
|
+
//#region ../../util/workspace-path/lib/index.js
|
|
28
|
+
/**
|
|
29
|
+
* The `dsh-resource://file/…` address grammar: how a file is named across the
|
|
30
|
+
* Sidebar and the resource model, built and parsed without touching a
|
|
31
|
+
* filesystem.
|
|
32
|
+
* @module
|
|
33
|
+
*/
|
|
34
|
+
/** The scheme and type every file address opens with. */
|
|
35
|
+
const FILE_ADDRESS_PREFIX = "dsh-resource://file/";
|
|
36
|
+
/** Component-encode one id or path segment, keeping `:` literal for drive letters. */
|
|
37
|
+
function encodeSegment(segment) {
|
|
38
|
+
return encodeURIComponent(segment).replace(/%3A/gi, ":");
|
|
39
|
+
}
|
|
40
|
+
/** Encode a `/`-separated path segment by segment. */
|
|
41
|
+
function encodePath(path) {
|
|
42
|
+
return path.split("/").map(encodeSegment).join("/");
|
|
43
|
+
}
|
|
44
|
+
/** Whether a decoded first path segment is a Windows drive (`C:`). */
|
|
45
|
+
function isDriveSegment(segment) {
|
|
46
|
+
return segment !== void 0 && /^[A-Za-z]:$/.test(segment);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Build the address of a file read through one Session.
|
|
50
|
+
* @param sessionId - the Session whose Host workspace resolves the path.
|
|
51
|
+
* @param path - absolute or workspace-relative path; backslashes are normalized to `/`, and leading `./` prefixes are dropped.
|
|
52
|
+
* @returns the `dsh-resource://file/session/<sessionId>/<path>` address.
|
|
53
|
+
*/
|
|
54
|
+
function sessionFileAddress(sessionId, path) {
|
|
55
|
+
const normalized = path.replace(/\\/g, "/").replace(/^(?:\.\/)+/, "");
|
|
56
|
+
return `${FILE_ADDRESS_PREFIX}session/${encodeSegment(sessionId)}/${encodePath(normalized)}`;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Read a file address back into its parts without resolving `.` or `..`.
|
|
60
|
+
* Query and fragment suffixes are ignored; encoded path segments are decoded.
|
|
61
|
+
* @param address - a candidate address.
|
|
62
|
+
* @returns the parts, or `undefined` when the string is not a `dsh-resource://file/` URI in a known scope with a path, or a segment is not validly encoded.
|
|
63
|
+
*/
|
|
64
|
+
function parseFileAddress(address) {
|
|
65
|
+
try {
|
|
66
|
+
if (!address.startsWith(FILE_ADDRESS_PREFIX)) return void 0;
|
|
67
|
+
const end = address.search(/[?#]/);
|
|
68
|
+
const [scope, ...rest] = address.slice(20, end === -1 ? void 0 : end).split("/");
|
|
69
|
+
if (scope === "session") {
|
|
70
|
+
const [id, ...segments] = rest;
|
|
71
|
+
if (id === void 0 || id === "" || segments.length === 0) return void 0;
|
|
72
|
+
return {
|
|
73
|
+
scope,
|
|
74
|
+
sessionId: decodeURIComponent(id),
|
|
75
|
+
path: segments.map(decodeURIComponent).join("/")
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
if (scope === "absolute") {
|
|
79
|
+
const unc = rest[0] === "" && rest.length > 1;
|
|
80
|
+
const segments = (unc ? rest.slice(1) : rest).map(decodeURIComponent);
|
|
81
|
+
if (segments.length === 0 || segments[0] === "") return void 0;
|
|
82
|
+
if (unc) return {
|
|
83
|
+
scope,
|
|
84
|
+
path: `//${segments.join("/")}`
|
|
85
|
+
};
|
|
86
|
+
return {
|
|
87
|
+
scope,
|
|
88
|
+
path: isDriveSegment(segments[0]) ? segments.join("/") : `/${segments.join("/")}`
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
return;
|
|
92
|
+
} catch {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Browser-safe Workspace path and display helpers.
|
|
98
|
+
* @module @deepseek-ai/dsh-util-workspace-path
|
|
99
|
+
*/
|
|
100
|
+
/** Whether a path uses a Windows drive or UNC prefix. */
|
|
101
|
+
function isWindowsStylePath(value) {
|
|
102
|
+
return /^[A-Za-z]:[/\\]/.test(value) || value.startsWith("\\\\");
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Whether a path is absolute in either spelling the Host accepts: POSIX (`/a/b`) or Windows drive or UNC.
|
|
106
|
+
* @param path - the path to classify.
|
|
107
|
+
* @returns `true` for an absolute path; `false` for a Workspace-relative one.
|
|
108
|
+
*/
|
|
109
|
+
function isAbsoluteWorkspacePath(path) {
|
|
110
|
+
return path.startsWith("/") || isWindowsStylePath(path);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* The address for a path as a caller holds it: a relative path, or an absolute
|
|
114
|
+
* path inside the Session's workspace, becomes a `session`-scoped address; an
|
|
115
|
+
* absolute path outside it, or one whose workspace root is unknown, keeps its
|
|
116
|
+
* absolute path in that Session's address.
|
|
117
|
+
* @param sessionId - the Session the path is read in.
|
|
118
|
+
* @param cwd - that Session's workspace root, when known.
|
|
119
|
+
* @param path - absolute or workspace-relative path, in either separator spelling.
|
|
120
|
+
* @returns the `dsh-resource://file/…` address.
|
|
121
|
+
*/
|
|
122
|
+
function fileAddressFor(sessionId, cwd, path) {
|
|
123
|
+
const normalized = path.replace(/\\/g, "/");
|
|
124
|
+
if (!isAbsoluteWorkspacePath(normalized)) return sessionFileAddress(sessionId, normalized);
|
|
125
|
+
const root = cwd === void 0 ? "" : cwd.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
126
|
+
if (root !== "" && normalized === root) return sessionFileAddress(sessionId, "");
|
|
127
|
+
if (root !== "" && normalized.startsWith(`${root}/`)) return sessionFileAddress(sessionId, normalized.slice(root.length + 1));
|
|
128
|
+
return sessionFileAddress(sessionId, normalized);
|
|
129
|
+
}
|
|
130
|
+
//#endregion
|
|
131
|
+
//#region lib/types/client/model.js
|
|
132
|
+
const children = (parent, name) => parent === null ? [] : Array.from(parent.children).filter((child) => child.tagName === name);
|
|
133
|
+
const child = (parent, name) => children(parent, name)[0] ?? null;
|
|
134
|
+
const attr = (element, name) => element?.getAttribute(name) ?? "";
|
|
135
|
+
const content = (element) => element?.textContent.trim() ?? "";
|
|
136
|
+
const description = (element) => content(child(element, "Description")) || attr(element, "Description");
|
|
137
|
+
const dataKey = (scope, symbol) => `${scope.toLowerCase()}::${symbol.toLowerCase()}`;
|
|
138
|
+
/**
|
|
139
|
+
* Resolve only values actually present in the saved export; no entry implies no value evidence.
|
|
140
|
+
* @param project - Saved controller export projection.
|
|
141
|
+
* @param scope - Controller or program scope containing the symbol.
|
|
142
|
+
* @param symbol - Tag name to look up.
|
|
143
|
+
* @returns The saved value, or undefined when the export contains no matching value.
|
|
144
|
+
*/
|
|
145
|
+
function exportedDatum(project, scope, symbol) {
|
|
146
|
+
return project.exportedData.get(dataKey(scope, symbol)) ?? (scope === "controller" ? void 0 : project.exportedData.get(dataKey("controller", symbol)));
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Parse a controller export without retaining XML nodes or customer source text.
|
|
150
|
+
* @param xml - L5X XML text to parse.
|
|
151
|
+
* @returns Parsed read-only controller export.
|
|
152
|
+
*/
|
|
153
|
+
function parseProject(xml) {
|
|
154
|
+
const parsed = new DOMParser().parseFromString(xml, "application/xml");
|
|
155
|
+
const error = parsed.getElementsByTagName("parsererror")[0];
|
|
156
|
+
if (error !== void 0) throw new Error(`Invalid XML: ${content(error)}`);
|
|
157
|
+
const root = parsed.documentElement;
|
|
158
|
+
if (root.tagName !== "RSLogix5000Content") throw new Error("This file is not an L5X export.");
|
|
159
|
+
const controller = child(root, "Controller");
|
|
160
|
+
if (controller === null) throw new Error("The L5X export has no controller.");
|
|
161
|
+
const exportedData = /* @__PURE__ */ new Map();
|
|
162
|
+
const collectValues = (tag, scope) => {
|
|
163
|
+
const decorated = children(tag, "Data").find((data) => attr(data, "Format") === "Decorated");
|
|
164
|
+
if (decorated === void 0) return;
|
|
165
|
+
const limit = 6e4;
|
|
166
|
+
const visit = (node, path) => {
|
|
167
|
+
if (exportedData.size >= limit) return;
|
|
168
|
+
const name = attr(node, "Name");
|
|
169
|
+
const index = attr(node, "Index");
|
|
170
|
+
const next = name !== "" ? `${path}.${name}` : index !== "" ? `${path}${index}` : path;
|
|
171
|
+
const value = node.getAttribute("Value");
|
|
172
|
+
if (value !== null && value.length <= 256) exportedData.set(dataKey(scope, next), {
|
|
173
|
+
value,
|
|
174
|
+
type: attr(node, "DataType") || attr(tag, "DataType")
|
|
175
|
+
});
|
|
176
|
+
for (const member of Array.from(node.children)) visit(member, next);
|
|
177
|
+
};
|
|
178
|
+
const tagName = attr(tag, "Name");
|
|
179
|
+
for (const node of Array.from(decorated.children)) visit(node, tagName);
|
|
180
|
+
};
|
|
181
|
+
const tagsOf = (element, scope) => children(child(element, "Tags"), "Tag").map((tag) => {
|
|
182
|
+
collectValues(tag, scope);
|
|
183
|
+
return {
|
|
184
|
+
name: attr(tag, "Name"),
|
|
185
|
+
type: attr(tag, "DataType"),
|
|
186
|
+
scope,
|
|
187
|
+
description: description(tag),
|
|
188
|
+
alias: attr(tag, "AliasFor")
|
|
189
|
+
};
|
|
190
|
+
});
|
|
191
|
+
const programs = children(child(controller, "Programs"), "Program").map((program) => {
|
|
192
|
+
const name = attr(program, "Name");
|
|
193
|
+
const routines = children(child(program, "Routines"), "Routine").map((routine) => ({
|
|
194
|
+
name: attr(routine, "Name"),
|
|
195
|
+
type: attr(routine, "Type"),
|
|
196
|
+
description: description(routine),
|
|
197
|
+
rungs: children(child(routine, "RLLContent"), "Rung").map((rung) => ({
|
|
198
|
+
number: attr(rung, "Number"),
|
|
199
|
+
comment: content(child(rung, "Comment")) || attr(rung, "Comment"),
|
|
200
|
+
text: content(child(rung, "Text"))
|
|
201
|
+
})),
|
|
202
|
+
code: children(child(routine, "STContent"), "Line").map((line) => line.textContent).join("\n")
|
|
203
|
+
}));
|
|
204
|
+
return {
|
|
205
|
+
name,
|
|
206
|
+
main: attr(program, "MainRoutineName"),
|
|
207
|
+
description: description(program),
|
|
208
|
+
routines,
|
|
209
|
+
tags: tagsOf(program, name)
|
|
210
|
+
};
|
|
211
|
+
});
|
|
212
|
+
const tasks = children(child(controller, "Tasks"), "Task").map((task) => ({
|
|
213
|
+
name: attr(task, "Name"),
|
|
214
|
+
type: attr(task, "Type"),
|
|
215
|
+
rate: attr(task, "Rate"),
|
|
216
|
+
priority: attr(task, "Priority"),
|
|
217
|
+
watchdog: attr(task, "Watchdog"),
|
|
218
|
+
programs: children(child(task, "ScheduledPrograms"), "ScheduledProgram").map((program) => attr(program, "Name"))
|
|
219
|
+
}));
|
|
220
|
+
const modules = children(child(controller, "Modules"), "Module").map((module) => ({
|
|
221
|
+
name: attr(module, "Name"),
|
|
222
|
+
catalog: attr(module, "CatalogNumber"),
|
|
223
|
+
parent: attr(module, "ParentModule"),
|
|
224
|
+
description: description(module),
|
|
225
|
+
slot: attr(child(module, "Ports"), "Address") || attr(child(child(module, "Ports"), "Port"), "Address")
|
|
226
|
+
}));
|
|
227
|
+
return {
|
|
228
|
+
name: attr(controller, "Name"),
|
|
229
|
+
processor: attr(controller, "ProcessorType"),
|
|
230
|
+
exportRevision: attr(root, "SoftwareRevision"),
|
|
231
|
+
revision: [attr(controller, "MajorRev"), attr(controller, "MinorRev")].filter(Boolean).join("."),
|
|
232
|
+
programs,
|
|
233
|
+
tasks,
|
|
234
|
+
tags: tagsOf(controller, "controller"),
|
|
235
|
+
modules,
|
|
236
|
+
exportedData,
|
|
237
|
+
aois: children(child(controller, "AddOnInstructionDefinitions"), "AddOnInstructionDefinition").map((aoi) => attr(aoi, "Name")),
|
|
238
|
+
types: children(child(controller, "DataTypes"), "DataType").map((type) => attr(type, "Name"))
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Find literal evidence in ladder and Structured Text without claiming execution semantics.
|
|
243
|
+
* @param project - Saved controller export projection.
|
|
244
|
+
* @param query - Symbol text to search for in routines.
|
|
245
|
+
* @returns Matching routine locations.
|
|
246
|
+
*/
|
|
247
|
+
function findReferences(project, query) {
|
|
248
|
+
const needle = query.trim().toLocaleLowerCase();
|
|
249
|
+
if (needle === "") return [];
|
|
250
|
+
const hits = [];
|
|
251
|
+
for (const program of project.programs) for (const routine of program.routines) {
|
|
252
|
+
if (routine.type === "RLL") {
|
|
253
|
+
for (const rung of routine.rungs) if (rung.text.toLocaleLowerCase().includes(needle)) hits.push({
|
|
254
|
+
program: program.name,
|
|
255
|
+
routine: routine.name,
|
|
256
|
+
location: `Rung ${rung.number}`,
|
|
257
|
+
text: rung.text
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
if (routine.type === "ST") routine.code.split("\n").forEach((line, index) => {
|
|
261
|
+
if (line.toLocaleLowerCase().includes(needle)) hits.push({
|
|
262
|
+
program: program.name,
|
|
263
|
+
routine: routine.name,
|
|
264
|
+
location: `Line ${index + 1}`,
|
|
265
|
+
text: line
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
return hits;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Decode NeutralText structure for a read-only ladder diagram. Unknown syntax keeps source text visible.
|
|
273
|
+
* @param text - Text to inspect or parse.
|
|
274
|
+
* @returns Parsed ladder segments, or null for unsupported text.
|
|
275
|
+
*/
|
|
276
|
+
function ladderSegments(text) {
|
|
277
|
+
const source = text.trim().replace(/;$/, "");
|
|
278
|
+
let cursor = 0;
|
|
279
|
+
const skip = () => {
|
|
280
|
+
while (/\s/.test(source[cursor] ?? "")) cursor++;
|
|
281
|
+
};
|
|
282
|
+
const marker = () => {
|
|
283
|
+
skip();
|
|
284
|
+
const value = source.slice(cursor, cursor + 3).toUpperCase();
|
|
285
|
+
return value === "BST" || value === "NXB" || value === "BND" ? value : void 0;
|
|
286
|
+
};
|
|
287
|
+
const sequence = (nested) => {
|
|
288
|
+
const segments = [];
|
|
289
|
+
while (cursor < source.length) {
|
|
290
|
+
skip();
|
|
291
|
+
if (cursor >= source.length) break;
|
|
292
|
+
const next = marker();
|
|
293
|
+
if (next === "NXB" || next === "BND") return nested === "bst" ? segments : null;
|
|
294
|
+
if (source[cursor] === "," || source[cursor] === "]") return nested === "bracket" ? segments : null;
|
|
295
|
+
if (next === "BST") {
|
|
296
|
+
cursor += 3;
|
|
297
|
+
const paths = [];
|
|
298
|
+
while (true) {
|
|
299
|
+
const path = sequence("bst");
|
|
300
|
+
if (path === null) return null;
|
|
301
|
+
paths.push(path);
|
|
302
|
+
const end = marker();
|
|
303
|
+
if (end === void 0) return null;
|
|
304
|
+
cursor += 3;
|
|
305
|
+
if (end === "BND") break;
|
|
306
|
+
if (end !== "NXB") return null;
|
|
307
|
+
}
|
|
308
|
+
segments.push({ paths });
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
if (source[cursor] === "[") {
|
|
312
|
+
cursor++;
|
|
313
|
+
const paths = [];
|
|
314
|
+
while (true) {
|
|
315
|
+
const path = sequence("bracket");
|
|
316
|
+
if (path === null) return null;
|
|
317
|
+
paths.push(path);
|
|
318
|
+
skip();
|
|
319
|
+
const separator = source[cursor];
|
|
320
|
+
if (separator !== "," && separator !== "]") return null;
|
|
321
|
+
cursor++;
|
|
322
|
+
if (separator === "]") break;
|
|
323
|
+
}
|
|
324
|
+
segments.push({ paths });
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
const match = /^[A-Za-z_][\w]*\(/.exec(source.slice(cursor));
|
|
328
|
+
if (match === null) return null;
|
|
329
|
+
const mnemonic = match[0].slice(0, -1);
|
|
330
|
+
cursor += match[0].length;
|
|
331
|
+
const start = cursor;
|
|
332
|
+
let depth = 1;
|
|
333
|
+
while (cursor < source.length && depth > 0) {
|
|
334
|
+
if (source[cursor] === "(") depth++;
|
|
335
|
+
if (source[cursor] === ")") depth--;
|
|
336
|
+
cursor++;
|
|
337
|
+
}
|
|
338
|
+
if (depth !== 0) return null;
|
|
339
|
+
segments.push({
|
|
340
|
+
mnemonic,
|
|
341
|
+
operands: source.slice(start, cursor - 1)
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
return nested === null ? segments : null;
|
|
345
|
+
};
|
|
346
|
+
const result = sequence(null);
|
|
347
|
+
return result !== null && result.length > 0 ? result : null;
|
|
348
|
+
}
|
|
349
|
+
//#endregion
|
|
350
|
+
//#region lib/types/client/inspection.js
|
|
351
|
+
/**
|
|
352
|
+
* Source-backed review hints. These are not Studio 5000 build diagnostics.
|
|
353
|
+
* @param project - Saved controller export projection.
|
|
354
|
+
* @returns Static findings without changing the controller export.
|
|
355
|
+
*/
|
|
356
|
+
function inspectProject(project) {
|
|
357
|
+
const findings = [];
|
|
358
|
+
const programs = new Map(project.programs.map((program) => [program.name.toLowerCase(), program]));
|
|
359
|
+
const scheduled = /* @__PURE__ */ new Map();
|
|
360
|
+
for (const task of project.tasks) for (const name of task.programs) {
|
|
361
|
+
const key = name.toLowerCase();
|
|
362
|
+
scheduled.set(key, [...scheduled.get(key) ?? [], task.name]);
|
|
363
|
+
if (!programs.has(key)) findings.push({
|
|
364
|
+
severity: "warning",
|
|
365
|
+
code: "missing-scheduled-program",
|
|
366
|
+
location: `Task ${task.name}`,
|
|
367
|
+
message: `Scheduled program ${name} is absent from this export.`,
|
|
368
|
+
evidence: `${task.name} → ${name}`
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
for (const program of project.programs) {
|
|
372
|
+
const location = `Program ${program.name}`;
|
|
373
|
+
const owners = scheduled.get(program.name.toLowerCase()) ?? [];
|
|
374
|
+
if (owners.length === 0) findings.push({
|
|
375
|
+
severity: "info",
|
|
376
|
+
code: "unscheduled-program",
|
|
377
|
+
location,
|
|
378
|
+
message: "Program is not scheduled by an exported task.",
|
|
379
|
+
evidence: program.name
|
|
380
|
+
});
|
|
381
|
+
if (owners.length > 1) findings.push({
|
|
382
|
+
severity: "warning",
|
|
383
|
+
code: "duplicate-schedule",
|
|
384
|
+
location,
|
|
385
|
+
message: "Program appears in more than one task schedule.",
|
|
386
|
+
evidence: owners.join(", ")
|
|
387
|
+
});
|
|
388
|
+
if (program.main !== "" && !program.routines.some((routine) => routine.name.toLowerCase() === program.main.toLowerCase())) findings.push({
|
|
389
|
+
severity: "warning",
|
|
390
|
+
code: "missing-main-routine",
|
|
391
|
+
location,
|
|
392
|
+
message: `Main routine ${program.main} is absent from this export.`,
|
|
393
|
+
evidence: program.main
|
|
394
|
+
});
|
|
395
|
+
const routineNames = new Set(program.routines.map((routine) => routine.name.toLowerCase()));
|
|
396
|
+
for (const routine of program.routines) {
|
|
397
|
+
const routineLocation = `${program.name}/${routine.name}`;
|
|
398
|
+
if (routine.type !== "RLL" && routine.type !== "ST") findings.push({
|
|
399
|
+
severity: "info",
|
|
400
|
+
code: "unsupported-diagram",
|
|
401
|
+
location: routineLocation,
|
|
402
|
+
message: `${routine.type} is listed but is not diagrammed.`,
|
|
403
|
+
evidence: routine.type
|
|
404
|
+
});
|
|
405
|
+
if (routine.type !== "RLL") continue;
|
|
406
|
+
for (const rung of routine.rungs) for (const match of rung.text.matchAll(/\bJSR\s*\(\s*([^,()\s]+)/giu)) {
|
|
407
|
+
const target = match[1];
|
|
408
|
+
if (target !== void 0 && !routineNames.has(target.toLowerCase())) findings.push({
|
|
409
|
+
severity: "warning",
|
|
410
|
+
code: "unresolved-jsr",
|
|
411
|
+
location: `${routineLocation} rung ${rung.number}`,
|
|
412
|
+
message: `Called routine ${target} is absent from this program export.`,
|
|
413
|
+
evidence: match[0]
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
return findings;
|
|
419
|
+
}
|
|
420
|
+
//#endregion
|
|
421
|
+
//#region \0dsh-css:C:\Users\KNPhu\.arya\github\KonnectSuite\AryaAI\packages\client\ui-logix-project-viewer\src\client\L5xBody.module.css.mjs
|
|
422
|
+
const css = "._0tcyLG_root{width:100%;min-width:0;height:100%;min-height:0;color:var(--dsw-alias-label-primary);flex-direction:column;font-size:12px;display:flex}._0tcyLG_toolbar{border-bottom:.5px solid var(--dsw-alias-border-l2);white-space:nowrap;align-items:center;gap:10px;padding:8px 12px;display:flex;overflow:hidden}._0tcyLG_toolbar strong{text-overflow:ellipsis;overflow:hidden}._0tcyLG_toolbar span{color:var(--dsw-alias-label-secondary)}._0tcyLG_toolbar button{border:.5px solid var(--dsw-alias-border-l2);color:inherit;cursor:pointer;background:0 0;border-radius:4px;padding:4px 8px}._0tcyLG_offline{margin-left:auto}._0tcyLG_notice{border-bottom:.5px solid var(--dsw-alias-border-l2);padding:8px 12px}._0tcyLG_layout{flex:1;grid-template-columns:minmax(190px,23%) minmax(300px,1fr) minmax(200px,22%);min-height:0;display:grid}._0tcyLG_tree,._0tcyLG_inspector{border-right:.5px solid var(--dsw-alias-border-l2);min-width:0;padding:8px;overflow:auto}._0tcyLG_inspector{border-right:0;border-left:.5px solid var(--dsw-alias-border-l2)}._0tcyLG_treeHeading,._0tcyLG_inspectorHeading{padding:6px 5px;font-weight:600}._0tcyLG_tree details{margin:3px 0}._0tcyLG_tree summary{cursor:pointer;white-space:nowrap;text-overflow:ellipsis;padding:4px;overflow:hidden}._0tcyLG_nested{margin-left:12px!important}._0tcyLG_leaf,._0tcyLG_leafButton,._0tcyLG_selected{box-sizing:border-box;text-align:left;width:100%;color:inherit;white-space:nowrap;text-overflow:ellipsis;padding:5px 5px 5px 20px;display:block;overflow:hidden}._0tcyLG_leafButton,._0tcyLG_selected{cursor:pointer;background:0 0;border:0}._0tcyLG_selected,._0tcyLG_leafButton:hover,._0tcyLG_reference:hover{background:var(--dsw-alias-interactive-bg-hover)}._0tcyLG_leafButton small,._0tcyLG_selected small{color:var(--dsw-alias-label-secondary);margin-left:5px}._0tcyLG_main{min-width:0;min-height:0;padding:0 12px 24px;overflow:auto}._0tcyLG_routineHeader{background:var(--dsw-alias-bg-document-preview);z-index:1;border-bottom:.5px solid var(--dsw-alias-border-l2);align-items:center;gap:10px;padding:12px 0;display:flex;position:sticky;top:0}._0tcyLG_routineHeader span,._0tcyLG_description{color:var(--dsw-alias-label-secondary)}._0tcyLG_description{margin:12px 4px}._0tcyLG_rung{border-bottom:.5px solid var(--dsw-alias-border-l2);grid-template-columns:32px minmax(0,1fr);min-height:110px;display:grid}._0tcyLG_rungBody{border-left:2px solid var(--dsw-alias-label-primary);border-right:2px solid var(--dsw-alias-label-primary);min-width:0;padding:12px}._0tcyLG_comment{color:var(--dsw-alias-label-secondary);white-space:pre-wrap;margin-bottom:10px}._0tcyLG_rail{align-items:center;gap:0;padding:8px 0;display:flex;overflow:auto}._0tcyLG_path{align-items:center;min-width:max-content;display:flex}._0tcyLG_branch{border-left:1px solid var(--dsw-alias-label-primary);border-right:1px solid var(--dsw-alias-label-primary);flex-direction:column;display:flex}._0tcyLG_branchPath{border-bottom:.5px solid var(--dsw-alias-border-l2);align-items:center;min-height:54px;display:flex}._0tcyLG_branchPath:last-child{border-bottom:0}._0tcyLG_instruction{cursor:context-menu;flex-direction:column;align-items:center;min-width:100px;padding:0 7px;display:flex;position:relative}._0tcyLG_instruction:before{content:\"\";background:var(--dsw-alias-label-primary);height:1px;position:absolute;top:15px;left:0;right:0}._0tcyLG_glyph{z-index:1;background:var(--dsw-alias-bg-document-preview);height:25px;font-family:monospace;font-size:17px}._0tcyLG_instruction strong{font-size:10px}._0tcyLG_instruction code{white-space:nowrap;font-size:11px}._0tcyLG_source{color:var(--dsw-alias-label-secondary);white-space:pre-wrap;overflow-wrap:anywhere;margin-top:12px;font-size:10px;display:block}._0tcyLG_neutral,._0tcyLG_structured{white-space:pre-wrap;overflow-wrap:anywhere;font-family:monospace}._0tcyLG_structured{padding:15px}._0tcyLG_status{color:var(--dsw-alias-label-secondary);padding:20px}._0tcyLG_status button{margin-left:8px}._0tcyLG_searchLabel{flex-direction:column;gap:5px;font-weight:600;display:flex}._0tcyLG_searchLabel input{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l2);width:100%;color:inherit;background:0 0;border-radius:4px;padding:6px}._0tcyLG_reference{text-align:left;width:100%;color:inherit;border:0;border-bottom:.5px solid var(--dsw-alias-border-l2);cursor:pointer;background:0 0;flex-direction:column;gap:3px;padding:8px 5px;display:flex}._0tcyLG_reference span,._0tcyLG_reference code,._0tcyLG_empty{color:var(--dsw-alias-label-secondary)}._0tcyLG_reference code{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}._0tcyLG_menu{z-index:1000;flex-direction:column;min-width:150px;padding:5px;display:flex;position:fixed}._0tcyLG_menuItem{color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;padding:7px 10px}._0tcyLG_menuItem:hover{background:var(--dsw-alias-interactive-bg-hover)}._0tcyLG_root{background:var(--dsw-alias-bg-document-preview);container-type:inline-size}._0tcyLG_toolbar{box-sizing:border-box;border-bottom:.5px solid var(--dsw-alias-border-l2);min-height:43px}._0tcyLG_productMark{letter-spacing:.1em;border-right:.5px solid var(--dsw-alias-border-l2);padding-right:10px;font-size:10px;font-weight:800;color:var(--dsw-alias-label-primary)!important}._0tcyLG_productMark b{color:#3983c9}._0tcyLG_offline{text-transform:uppercase;background:#eaf4fc;border:1px solid #83a9c8;border-radius:3px;padding:3px 7px;font-size:10px;font-weight:700;color:#225e87!important}._0tcyLG_sourceBar{border-bottom:.5px solid var(--dsw-alias-border-l2);background:color-mix(in srgb,var(--dsw-alias-bg-document-preview) 85%,#a4bed4);min-height:27px;color:var(--dsw-alias-label-secondary);white-space:nowrap;flex:none;align-items:center;gap:16px;padding:0 12px;font-size:10px;display:flex;overflow:hidden}._0tcyLG_sourceBar span:last-child{margin-left:auto}._0tcyLG_layout{grid-template-columns:minmax(190px,22%) minmax(320px,1fr) minmax(190px,19%)}._0tcyLG_tree{background:color-mix(in srgb,var(--dsw-alias-bg-document-preview) 94%,#b6cbe0);flex-direction:column;padding:0;display:flex;overflow:hidden}._0tcyLG_treeHeading{text-transform:uppercase;letter-spacing:.07em;flex-direction:column;gap:2px;padding:11px 12px 7px;font-size:10px;display:flex}._0tcyLG_treeHeading span{text-transform:none;letter-spacing:0;color:var(--dsw-alias-label-secondary);text-overflow:ellipsis;font-size:11px;font-weight:400;overflow:hidden}._0tcyLG_organizerSearch{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-document-preview);width:calc(100% - 20px);color:inherit;border-radius:4px;margin:0 10px 8px;padding:6px 8px}._0tcyLG_treeScroll{flex:1;min-height:0;padding:0 6px 16px;overflow:auto}._0tcyLG_tree summary{font-weight:600}._0tcyLG_tree summary:hover{background:var(--dsw-alias-interactive-bg-hover)}._0tcyLG_controllerRoot>summary{margin:2px 0 6px;font-weight:700}._0tcyLG_controllerRoot>._0tcyLG_nested{margin-left:11px!important}._0tcyLG_unexportedLeaf{color:var(--dsw-alias-label-secondary);opacity:.65;white-space:nowrap;text-overflow:ellipsis;cursor:help;padding:5px 5px 5px 24px;font-size:11px;overflow:hidden}._0tcyLG_unexportedLeaf:before{content:\"◇\";margin-right:6px}._0tcyLG_tree summary small{float:right;color:var(--dsw-alias-label-secondary);padding-left:5px;font-size:10px;font-weight:400}._0tcyLG_leaf,._0tcyLG_leafButton,._0tcyLG_selected{font-size:11px}._0tcyLG_selected{border-left:2px solid #3b88d1;font-weight:700}._0tcyLG_routineIcon{color:#3a75a4;min-width:20px;font-family:monospace;display:inline-block}._0tcyLG_editor{flex-direction:column;min-width:0;min-height:0;display:flex}._0tcyLG_tabStrip{border-bottom:.5px solid var(--dsw-alias-border-l2);background:color-mix(in srgb,var(--dsw-alias-bg-document-preview) 88%,#bac9d8);flex:none;height:33px;display:flex;overflow:auto hidden}._0tcyLG_tab,._0tcyLG_activeTab{border-right:.5px solid var(--dsw-alias-border-l2);flex:none;align-items:center;max-width:220px;display:flex}._0tcyLG_activeTab{background:var(--dsw-alias-bg-document-preview);border-top:2px solid #3b88d1}._0tcyLG_tab button,._0tcyLG_activeTab button{color:inherit;cursor:pointer;white-space:nowrap;text-overflow:ellipsis;background:0 0;border:0;padding:7px 9px;font-size:11px;overflow:hidden}._0tcyLG_tab ._0tcyLG_closeTab,._0tcyLG_activeTab ._0tcyLG_closeTab{color:var(--dsw-alias-label-secondary);padding:6px;font-size:16px}._0tcyLG_editorTools{border-bottom:.5px solid var(--dsw-alias-border-l2);flex:none;align-items:center;gap:9px;min-height:34px;padding:2px 10px;display:flex}._0tcyLG_editorTools strong{white-space:nowrap;text-overflow:ellipsis;overflow:hidden}._0tcyLG_editorTools span{color:var(--dsw-alias-label-secondary)}._0tcyLG_toolSpacer{flex:1}._0tcyLG_editorTools button{border:.5px solid var(--dsw-alias-border-l2);color:inherit;cursor:pointer;background:0 0;border-radius:4px;padding:3px 7px;font-size:11px}._0tcyLG_main{flex:1;padding:0 13px 24px}._0tcyLG_rung{grid-template-columns:34px minmax(0,1fr);min-height:100px}._0tcyLG_rungNumber{text-align:right;padding:14px 4px 0 0}._0tcyLG_rungBody{padding:12px 15px}._0tcyLG_comment{text-align:center;font-size:11px}._0tcyLG_rail{min-height:70px}._0tcyLG_branch{border-left-width:2px;border-right-width:2px}._0tcyLG_branchPath{min-height:68px}._0tcyLG_instruction{text-align:center;min-width:132px}._0tcyLG_instruction:before{height:2px;top:31px}._0tcyLG_instructionDescription{color:var(--dsw-alias-label-secondary);text-overflow:ellipsis;max-width:160px;min-height:24px;font-size:10px;line-height:1.15;overflow:hidden}._0tcyLG_glyph{min-width:54px;height:28px;font-size:20px;line-height:28px}._0tcyLG_functionBlock ._0tcyLG_glyph{border:1px solid var(--dsw-alias-label-primary);min-width:70px;font-size:12px;font-weight:700}._0tcyLG_instruction code{text-overflow:ellipsis;max-width:170px;overflow:hidden}._0tcyLG_path{width:max-content;min-width:100%}._0tcyLG_outputInstruction{margin-left:auto}._0tcyLG_blockCard{z-index:1;border:1px solid var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-document-preview);text-align:left;min-width:128px;max-width:220px;box-shadow:0 1px 2px #0002}._0tcyLG_blockCard>strong{background:color-mix(in srgb,var(--dsw-alias-bg-document-preview) 75%,#8399ad);border-bottom:.5px solid var(--dsw-alias-border-l2);padding:3px 6px;font-size:11px;display:block}._0tcyLG_blockCard>div{border-bottom:.5px solid var(--dsw-alias-border-l2);grid-template-columns:45px minmax(65px,1fr);gap:4px;padding:2px 5px;display:grid}._0tcyLG_blockCard>div:last-child{border:0}._0tcyLG_blockCard code{font-size:10px}._0tcyLG_glyph{background:var(--dsw-alias-bg-document-preview);justify-content:center;align-items:center;min-width:66px;display:flex}._0tcyLG_wireLead{background:var(--dsw-alias-label-primary);width:16px;height:2px;display:block}._0tcyLG_contact{border-left:2px solid var(--dsw-alias-label-primary);border-right:2px solid var(--dsw-alias-label-primary);width:16px;height:25px;display:block;position:relative}._0tcyLG_contact._0tcyLG_negated:after{content:\"\";background:var(--dsw-alias-label-primary);width:25px;height:2px;position:absolute;top:11px;left:-4px;transform:rotate(-50deg)}._0tcyLG_coil{border-left:2px solid var(--dsw-alias-label-primary);border-right:2px solid var(--dsw-alias-label-primary);corner-shape:round;border-radius:50%;justify-content:center;align-items:center;width:23px;height:26px;font:700 10px monospace;display:flex}._0tcyLG_source{border:.5px solid var(--dsw-alias-border-l2);border-radius:3px;padding:6px}._0tcyLG_loadMore{border:.5px solid var(--dsw-alias-border-l2);color:inherit;cursor:pointer;background:0 0;border-radius:4px;margin:14px auto;padding:7px 14px;display:block}._0tcyLG_tagTableView{min-width:0}._0tcyLG_tagTableToolbar{z-index:2;background:var(--dsw-alias-bg-document-preview);border-bottom:.5px solid var(--dsw-alias-border-l2);align-items:center;gap:14px;padding:8px 4px;display:flex;position:sticky;top:0}._0tcyLG_tagTableToolbar input{width:min(220px,30vw);color:inherit;border:.5px solid var(--dsw-alias-border-l2);background:0 0;border-radius:3px;margin-left:5px;padding:4px 6px}._0tcyLG_tagTableToolbar span{color:var(--dsw-alias-label-secondary)}._0tcyLG_tagTable{border-collapse:collapse;table-layout:fixed;width:100%;min-width:720px;font-size:11px}._0tcyLG_tagTable th,._0tcyLG_tagTable td{text-align:left;border:.5px solid var(--dsw-alias-border-l2);white-space:nowrap;text-overflow:ellipsis;padding:6px 8px;overflow:hidden}._0tcyLG_tagTable thead{z-index:1;background:color-mix(in srgb,var(--dsw-alias-bg-document-preview) 70%,#a4bed4);position:sticky;top:42px}._0tcyLG_tagTable tbody tr:hover{background:var(--dsw-alias-interactive-bg-hover)}._0tcyLG_tagTable th:first-child{width:22%}._0tcyLG_tagTable td:nth-child(2),._0tcyLG_tagTable td:nth-child(3){width:12%}._0tcyLG_tagTable td:nth-child(4){width:14%}._0tcyLG_tagTable td:nth-child(5){width:25%}._0tcyLG_tagTable ._0tcyLG_noValue{color:var(--dsw-alias-label-secondary);font-style:italic}._0tcyLG_tagActions{gap:5px;display:flex}._0tcyLG_tagActions button{border:.5px solid var(--dsw-alias-border-l2);color:inherit;cursor:pointer;background:0 0;border-radius:3px;padding:2px 4px;font-size:10px}._0tcyLG_inspector{background:color-mix(in srgb,var(--dsw-alias-bg-document-preview) 94%,#b6cbe0);flex-direction:column;gap:4px;min-height:0;padding:7px 9px;display:flex;overflow:auto}._0tcyLG_inspectorHeading{text-transform:uppercase;letter-spacing:.05em;border-bottom:.5px solid var(--dsw-alias-border-l2);padding:7px 4px 5px;font-size:10px}._0tcyLG_propertyList{overflow-wrap:anywhere;flex-direction:column;gap:8px;padding:8px 4px 14px;display:flex}._0tcyLG_propertyList span,._0tcyLG_propertyList p{color:var(--dsw-alias-label-secondary);margin:0}._0tcyLG_actionButton{text-align:left;border:.5px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-document-preview);width:100%;color:inherit;cursor:pointer;border-radius:4px;padding:7px 8px}._0tcyLG_actionButton:hover,._0tcyLG_loadMore:hover{background:var(--dsw-alias-interactive-bg-hover)}._0tcyLG_inspectorHelp{color:var(--dsw-alias-label-secondary);margin:7px 4px;font-size:11px;line-height:1.35}._0tcyLG_bottomPanel{border-top:2px solid #314c67;flex-direction:column;flex:none;height:190px;min-height:100px;display:flex}._0tcyLG_bottomTabs{background:color-mix(in srgb,var(--dsw-alias-bg-document-preview) 55%,#314c67);border-bottom:.5px solid var(--dsw-alias-border-l2);flex:none;display:flex;overflow-x:auto}._0tcyLG_bottomTabs button{color:inherit;border:0;border-right:.5px solid var(--dsw-alias-border-l2);cursor:pointer;white-space:nowrap;background:0 0;padding:8px 13px;font-size:11px}._0tcyLG_bottomTabs ._0tcyLG_bottomActive{background:var(--dsw-alias-bg-document-preview);font-weight:700;box-shadow:inset 0 2px #3b88d1}._0tcyLG_bottomContent{min-height:0;padding:6px 13px 12px;overflow:auto}._0tcyLG_panelNote,._0tcyLG_empty{color:var(--dsw-alias-label-secondary);margin:4px 0 8px;font-size:11px}._0tcyLG_finding{border-bottom:.5px solid var(--dsw-alias-border-l2);align-items:baseline;gap:10px;padding:5px 1px;font-size:11px;display:flex}._0tcyLG_finding strong{flex:none;min-width:160px}._0tcyLG_finding code{text-overflow:ellipsis;white-space:nowrap;max-width:30%;color:var(--dsw-alias-label-secondary);margin-left:auto;overflow:hidden}._0tcyLG_warning,._0tcyLG_info{text-transform:uppercase;font-size:9px;font-weight:800}._0tcyLG_warning{color:#bb7426}._0tcyLG_info{color:#3983c9}._0tcyLG_searchLabel{flex-direction:row;align-items:center}._0tcyLG_searchLabel input{width:min(410px,100%)}._0tcyLG_reference{flex-direction:row;align-items:baseline;gap:12px}._0tcyLG_reference strong{min-width:160px}._0tcyLG_reference span{min-width:65px}._0tcyLG_watchRow{border-bottom:.5px solid var(--dsw-alias-border-l2);align-items:center;gap:12px;padding:5px 2px;display:flex}._0tcyLG_watchRow code{min-width:160px}._0tcyLG_watchRow span{color:var(--dsw-alias-label-secondary)}._0tcyLG_watchRow button{color:inherit;cursor:pointer;background:0 0;border:0;margin-left:auto}._0tcyLG_watchTable{border-collapse:collapse;width:100%;min-width:670px;font-size:11px}._0tcyLG_watchTable th,._0tcyLG_watchTable td{text-align:left;border:.5px solid var(--dsw-alias-border-l2);padding:5px 7px}._0tcyLG_watchTable thead{background:color-mix(in srgb,var(--dsw-alias-bg-document-preview) 70%,#a4bed4)}._0tcyLG_watchTable ._0tcyLG_noValue{color:var(--dsw-alias-label-secondary);font-style:italic}._0tcyLG_main{color:#1d2730;background:#fff}._0tcyLG_rung{border-bottom:1px solid #d0d5da;min-height:88px}._0tcyLG_rungNumber{color:#89939c;padding-top:13px}._0tcyLG_rungBody{border-left:1px solid #89939c;border-right:1px solid #89939c;padding:8px 14px}._0tcyLG_comment{color:#697681;margin:0 0 6px;font-size:10px}._0tcyLG_rail{min-height:47px;padding:5px 0;position:relative;overflow:auto hidden}._0tcyLG_rail:before{content:\"\";background:#89939c;height:1px;position:absolute;top:37px;left:0;right:0}._0tcyLG_instruction{min-width:115px;padding:0 6px}._0tcyLG_instruction:before{background:#89939c;height:1px;top:32px}._0tcyLG_instructionDescription{color:#7e8994;min-height:25px;max-height:25px;font-size:9px}._0tcyLG_glyph{background:#fff;height:25px}._0tcyLG_wireLead{background:#3d4852;height:1px}._0tcyLG_contact{border-color:#3d4852;height:22px}._0tcyLG_contact._0tcyLG_negated:after{background:#3d4852}._0tcyLG_coil{border-color:#3d4852;height:23px}._0tcyLG_functionBlock{min-width:150px}._0tcyLG_functionBlock ._0tcyLG_instructionDescription{min-height:25px}._0tcyLG_blockCard{min-width:145px;max-width:190px;box-shadow:none;background:#fff;border:1px solid #a4abb1}._0tcyLG_blockCard>strong{background:#e1e2e3;border-bottom:1px solid #a4abb1;padding:2px 5px;font-size:10px}._0tcyLG_blockCard>div{grid-template-columns:68px minmax(50px,1fr);gap:3px;padding:1px 4px}._0tcyLG_blockCard small{color:#77828b;font-size:9px}._0tcyLG_blockCard code{color:#1d2730;font-size:10px}._0tcyLG_instruction code{font-size:10px}@container (width<=900px){._0tcyLG_layout{grid-template-columns:180px minmax(0,1fr)}._0tcyLG_inspector,._0tcyLG_toolbar span:not(._0tcyLG_productMark):not(._0tcyLG_offline),._0tcyLG_sourceBar span:last-child{display:none}}@container (width<=650px){._0tcyLG_layout{grid-template-columns:170px minmax(0,1fr)}._0tcyLG_toolbar{gap:5px;padding:5px 7px}._0tcyLG_toolbar button{padding:3px 5px;font-size:10px}._0tcyLG_sourceBar{gap:8px;padding:0 7px}._0tcyLG_sourceBar span:nth-child(2){display:none}._0tcyLG_treeHeading{padding:8px 6px 5px}._0tcyLG_organizerSearch{width:calc(100% - 12px);margin:0 6px 6px}._0tcyLG_treeScroll{padding:0 2px 10px}._0tcyLG_main{padding:0 7px 18px}._0tcyLG_rungBody{padding:7px}._0tcyLG_editorTools{gap:4px;padding:2px 5px}._0tcyLG_editorTools button{padding:3px 5px}._0tcyLG_bottomPanel{height:155px}._0tcyLG_bottomTabs button{padding:6px 7px}._0tcyLG_bottomContent{padding:4px 7px}}@container (width<=500px){._0tcyLG_productMark,._0tcyLG_toolbar ._0tcyLG_offline,._0tcyLG_sourceBar{display:none}._0tcyLG_layout{grid-template-columns:145px minmax(0,1fr)}._0tcyLG_editorTools button:last-child{display:none}}@media (width<=1000px){._0tcyLG_layout{grid-template-columns:minmax(170px,25%) minmax(0,1fr)}._0tcyLG_inspector,._0tcyLG_sourceBar span:last-child{display:none}}@media (height<=600px){._0tcyLG_bottomPanel{height:125px}._0tcyLG_toolbar{min-height:34px}._0tcyLG_sourceBar{display:none}}._0tcyLG_tree ._0tcyLG_leafButton small{float:right}._0tcyLG_instruction{box-sizing:border-box;color:inherit;font:inherit;cursor:pointer;background:0 0;border:0;border-radius:2px}._0tcyLG_instruction:hover{z-index:4;background:var(--dsw-alias-interactive-bg-hover)}._0tcyLG_instruction:focus-visible,._0tcyLG_focusedInstruction{outline:var(--dsw-focus-ring-width,2px) solid var(--dsw-alias-state-business-primary);outline-offset:1px;z-index:5}._0tcyLG_focusedInstruction{background:var(--dsw-alias-interactive-bg-hover)}._0tcyLG_focusedRung ._0tcyLG_rungNumber{color:var(--dsw-alias-state-business-primary);font-weight:700}._0tcyLG_rungNumber{cursor:pointer;font:inherit;background:0 0;border:0}._0tcyLG_rungNumber:hover,._0tcyLG_rungNumber:focus-visible{background:var(--dsw-alias-interactive-bg-hover);outline:var(--dsw-focus-ring-width,2px) solid var(--dsw-alias-state-business-primary);outline-offset:-2px}._0tcyLG_rail{overflow:visible}._0tcyLG_instructionTooltip{z-index:20;border:.5px solid var(--dsw-alias-border-l2);border-radius:var(--dsw-radius-sm);width:max-content;min-width:190px;max-width:300px;box-shadow:0 5px 18px color-mix(in srgb,var(--dsw-alias-label-primary) 20%,transparent);background:var(--dsw-alias-bg-document-preview);color:var(--dsw-alias-label-primary);text-align:left;white-space:normal;pointer-events:none;padding:8px 10px;font-size:11px;line-height:1.35;display:none;position:absolute;top:calc(100% + 5px);left:50%;transform:translate(-50%)}._0tcyLG_instructionTooltip strong,._0tcyLG_instructionTooltip span{overflow-wrap:anywhere;display:block}._0tcyLG_instructionTooltip span{color:var(--dsw-alias-label-secondary);margin-top:3px}._0tcyLG_instruction:hover ._0tcyLG_instructionTooltip{display:block}._0tcyLG_focusBar{border-bottom:.5px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-interactive-bg-hover);flex-wrap:wrap;align-items:center;gap:5px 10px;min-height:35px;padding:5px 10px;font-size:11px;display:flex}._0tcyLG_focusBar code{text-overflow:ellipsis;white-space:nowrap;max-width:32%;overflow:hidden}._0tcyLG_focusBar button{border:.5px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-document-preview);color:inherit;cursor:pointer;border-radius:3px;padding:3px 6px;font-size:10px}._0tcyLG_focusBar button:disabled{opacity:.5;cursor:default}._0tcyLG_focusBadge{text-transform:uppercase;letter-spacing:.04em;color:var(--dsw-alias-state-business-primary);font-size:9px;font-weight:800}._0tcyLG_focusValue{margin-left:auto;font-weight:600}._0tcyLG_inspectorCaution{font-size:10px;line-height:1.4}._0tcyLG_operandDetails{flex-direction:column;gap:7px;display:flex}._0tcyLG_operandDetails>div{border:.5px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-document-preview);grid-template-columns:minmax(0,1fr) auto;gap:3px 7px;padding:6px;display:grid}._0tcyLG_operandDetails code{overflow-wrap:anywhere}._0tcyLG_operandDetails strong{text-align:right}._0tcyLG_operandDetails span,._0tcyLG_operandDetails p{color:var(--dsw-alias-label-secondary);grid-column:1/-1;margin:0;font-size:10px}._0tcyLG_menuHeading{border-bottom:.5px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);padding:5px 9px;font-size:10px;font-weight:700}._0tcyLG_blockCard>span{border-bottom:.5px solid var(--dsw-alias-border-l2);grid-template-columns:65px minmax(52px,1fr) auto;align-items:baseline;gap:3px;padding:2px 5px;display:grid}._0tcyLG_blockCard>span:last-child{border-bottom:0}._0tcyLG_blockCard>span small{color:var(--dsw-alias-label-secondary);font-size:9px}._0tcyLG_blockCard>span code{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}._0tcyLG_blockCard>span b{font-size:10px;font-weight:700}._0tcyLG_exportedChip{border:.5px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-document-preview);max-width:180px;color:var(--dsw-alias-label-secondary);text-overflow:ellipsis;white-space:nowrap;border-radius:2px;margin-top:2px;padding:1px 4px;font-size:9px;overflow:hidden}._0tcyLG_nestedPath{width:max-content;min-width:max-content}._0tcyLG_outputInstruction ._0tcyLG_instructionTooltip{left:auto;right:0;transform:none}";
|
|
423
|
+
const tagId = "@deepseek-ai/dsh-client-ui-logix-project-viewer/L5xBody.module.css";
|
|
424
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
425
|
+
const tag = document.createElement("style");
|
|
426
|
+
tag.dataset.plugin = "@deepseek-ai/dsh-client-ui-logix-project-viewer";
|
|
427
|
+
tag.dataset.pluginCss = tagId;
|
|
428
|
+
tag.textContent = css;
|
|
429
|
+
document.head.appendChild(tag);
|
|
430
|
+
}
|
|
431
|
+
var L5xBody_module_css_default = {
|
|
432
|
+
"actionButton": "_0tcyLG_actionButton",
|
|
433
|
+
"activeTab": "_0tcyLG_activeTab",
|
|
434
|
+
"blockCard": "_0tcyLG_blockCard",
|
|
435
|
+
"bottomActive": "_0tcyLG_bottomActive",
|
|
436
|
+
"bottomContent": "_0tcyLG_bottomContent",
|
|
437
|
+
"bottomPanel": "_0tcyLG_bottomPanel",
|
|
438
|
+
"bottomTabs": "_0tcyLG_bottomTabs",
|
|
439
|
+
"branch": "_0tcyLG_branch",
|
|
440
|
+
"branchPath": "_0tcyLG_branchPath",
|
|
441
|
+
"closeTab": "_0tcyLG_closeTab",
|
|
442
|
+
"coil": "_0tcyLG_coil",
|
|
443
|
+
"comment": "_0tcyLG_comment",
|
|
444
|
+
"contact": "_0tcyLG_contact",
|
|
445
|
+
"controllerRoot": "_0tcyLG_controllerRoot",
|
|
446
|
+
"description": "_0tcyLG_description",
|
|
447
|
+
"editor": "_0tcyLG_editor",
|
|
448
|
+
"editorTools": "_0tcyLG_editorTools",
|
|
449
|
+
"empty": "_0tcyLG_empty",
|
|
450
|
+
"exportedChip": "_0tcyLG_exportedChip",
|
|
451
|
+
"finding": "_0tcyLG_finding",
|
|
452
|
+
"focusBadge": "_0tcyLG_focusBadge",
|
|
453
|
+
"focusBar": "_0tcyLG_focusBar",
|
|
454
|
+
"focusValue": "_0tcyLG_focusValue",
|
|
455
|
+
"focusedInstruction": "_0tcyLG_focusedInstruction",
|
|
456
|
+
"focusedRung": "_0tcyLG_focusedRung",
|
|
457
|
+
"functionBlock": "_0tcyLG_functionBlock",
|
|
458
|
+
"glyph": "_0tcyLG_glyph",
|
|
459
|
+
"info": "_0tcyLG_info",
|
|
460
|
+
"inspector": "_0tcyLG_inspector",
|
|
461
|
+
"inspectorCaution": "_0tcyLG_inspectorCaution",
|
|
462
|
+
"inspectorHeading": "_0tcyLG_inspectorHeading",
|
|
463
|
+
"inspectorHelp": "_0tcyLG_inspectorHelp",
|
|
464
|
+
"instruction": "_0tcyLG_instruction",
|
|
465
|
+
"instructionDescription": "_0tcyLG_instructionDescription",
|
|
466
|
+
"instructionTooltip": "_0tcyLG_instructionTooltip",
|
|
467
|
+
"layout": "_0tcyLG_layout",
|
|
468
|
+
"leaf": "_0tcyLG_leaf",
|
|
469
|
+
"leafButton": "_0tcyLG_leafButton",
|
|
470
|
+
"loadMore": "_0tcyLG_loadMore",
|
|
471
|
+
"main": "_0tcyLG_main",
|
|
472
|
+
"menu": "_0tcyLG_menu",
|
|
473
|
+
"menuHeading": "_0tcyLG_menuHeading",
|
|
474
|
+
"menuItem": "_0tcyLG_menuItem",
|
|
475
|
+
"negated": "_0tcyLG_negated",
|
|
476
|
+
"nested": "_0tcyLG_nested",
|
|
477
|
+
"nestedPath": "_0tcyLG_nestedPath",
|
|
478
|
+
"neutral": "_0tcyLG_neutral",
|
|
479
|
+
"noValue": "_0tcyLG_noValue",
|
|
480
|
+
"notice": "_0tcyLG_notice",
|
|
481
|
+
"offline": "_0tcyLG_offline",
|
|
482
|
+
"operandDetails": "_0tcyLG_operandDetails",
|
|
483
|
+
"organizerSearch": "_0tcyLG_organizerSearch",
|
|
484
|
+
"outputInstruction": "_0tcyLG_outputInstruction",
|
|
485
|
+
"panelNote": "_0tcyLG_panelNote",
|
|
486
|
+
"path": "_0tcyLG_path",
|
|
487
|
+
"productMark": "_0tcyLG_productMark",
|
|
488
|
+
"propertyList": "_0tcyLG_propertyList",
|
|
489
|
+
"rail": "_0tcyLG_rail",
|
|
490
|
+
"reference": "_0tcyLG_reference",
|
|
491
|
+
"root": "_0tcyLG_root",
|
|
492
|
+
"routineHeader": "_0tcyLG_routineHeader",
|
|
493
|
+
"routineIcon": "_0tcyLG_routineIcon",
|
|
494
|
+
"rung": "_0tcyLG_rung",
|
|
495
|
+
"rungBody": "_0tcyLG_rungBody",
|
|
496
|
+
"rungNumber": "_0tcyLG_rungNumber",
|
|
497
|
+
"searchLabel": "_0tcyLG_searchLabel",
|
|
498
|
+
"selected": "_0tcyLG_selected",
|
|
499
|
+
"source": "_0tcyLG_source",
|
|
500
|
+
"sourceBar": "_0tcyLG_sourceBar",
|
|
501
|
+
"status": "_0tcyLG_status",
|
|
502
|
+
"structured": "_0tcyLG_structured",
|
|
503
|
+
"tab": "_0tcyLG_tab",
|
|
504
|
+
"tabStrip": "_0tcyLG_tabStrip",
|
|
505
|
+
"tagActions": "_0tcyLG_tagActions",
|
|
506
|
+
"tagTable": "_0tcyLG_tagTable",
|
|
507
|
+
"tagTableToolbar": "_0tcyLG_tagTableToolbar",
|
|
508
|
+
"tagTableView": "_0tcyLG_tagTableView",
|
|
509
|
+
"toolSpacer": "_0tcyLG_toolSpacer",
|
|
510
|
+
"toolbar": "_0tcyLG_toolbar",
|
|
511
|
+
"tree": "_0tcyLG_tree",
|
|
512
|
+
"treeHeading": "_0tcyLG_treeHeading",
|
|
513
|
+
"treeScroll": "_0tcyLG_treeScroll",
|
|
514
|
+
"unexportedLeaf": "_0tcyLG_unexportedLeaf",
|
|
515
|
+
"warning": "_0tcyLG_warning",
|
|
516
|
+
"watchRow": "_0tcyLG_watchRow",
|
|
517
|
+
"watchTable": "_0tcyLG_watchTable",
|
|
518
|
+
"wireLead": "_0tcyLG_wireLead"
|
|
519
|
+
};
|
|
520
|
+
//#endregion
|
|
521
|
+
//#region lib/types/client/L5xBody.js
|
|
522
|
+
/** Offline Logix project browser: navigation, rung evidence, and context actions. */
|
|
523
|
+
function operandParts(operands) {
|
|
524
|
+
const parts = [];
|
|
525
|
+
let start = 0;
|
|
526
|
+
let depth = 0;
|
|
527
|
+
for (let index = 0; index < operands.length; index++) {
|
|
528
|
+
if ("([{".includes(operands[index] ?? "")) depth++;
|
|
529
|
+
if (")]}".includes(operands[index] ?? "")) depth--;
|
|
530
|
+
if (operands[index] === "," && depth === 0) {
|
|
531
|
+
parts.push(operands.slice(start, index).trim());
|
|
532
|
+
start = index + 1;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
parts.push(operands.slice(start).trim());
|
|
536
|
+
return parts;
|
|
537
|
+
}
|
|
538
|
+
function displayOperands(mnemonic, operands) {
|
|
539
|
+
const parts = operandParts(operands);
|
|
540
|
+
return mnemonic.toUpperCase() === "JSR" && parts[1] === "0" ? [parts[0] ?? ""] : parts;
|
|
541
|
+
}
|
|
542
|
+
const operandKeys = {
|
|
543
|
+
MOV: ["operand.source", "operand.destination"],
|
|
544
|
+
MOVE: ["operand.source", "operand.destination"],
|
|
545
|
+
COP: [
|
|
546
|
+
"operand.source",
|
|
547
|
+
"operand.destination",
|
|
548
|
+
"operand.length"
|
|
549
|
+
],
|
|
550
|
+
CPS: [
|
|
551
|
+
"operand.source",
|
|
552
|
+
"operand.destination",
|
|
553
|
+
"operand.length"
|
|
554
|
+
],
|
|
555
|
+
TON: ["operand.timer"],
|
|
556
|
+
TOF: ["operand.timer"],
|
|
557
|
+
RTO: ["operand.timer"],
|
|
558
|
+
RES: ["operand.timer"],
|
|
559
|
+
ADD: [
|
|
560
|
+
"operand.a",
|
|
561
|
+
"operand.b",
|
|
562
|
+
"operand.destination"
|
|
563
|
+
],
|
|
564
|
+
SUB: [
|
|
565
|
+
"operand.a",
|
|
566
|
+
"operand.b",
|
|
567
|
+
"operand.destination"
|
|
568
|
+
],
|
|
569
|
+
MUL: [
|
|
570
|
+
"operand.a",
|
|
571
|
+
"operand.b",
|
|
572
|
+
"operand.destination"
|
|
573
|
+
],
|
|
574
|
+
DIV: [
|
|
575
|
+
"operand.a",
|
|
576
|
+
"operand.b",
|
|
577
|
+
"operand.destination"
|
|
578
|
+
],
|
|
579
|
+
EQU: ["operand.sourceA", "operand.sourceB"],
|
|
580
|
+
EQ: ["operand.sourceA", "operand.sourceB"],
|
|
581
|
+
NEQ: ["operand.sourceA", "operand.sourceB"],
|
|
582
|
+
NE: ["operand.sourceA", "operand.sourceB"],
|
|
583
|
+
GRT: ["operand.sourceA", "operand.sourceB"],
|
|
584
|
+
GT: ["operand.sourceA", "operand.sourceB"],
|
|
585
|
+
LES: ["operand.sourceA", "operand.sourceB"],
|
|
586
|
+
LT: ["operand.sourceA", "operand.sourceB"],
|
|
587
|
+
GEQ: ["operand.sourceA", "operand.sourceB"],
|
|
588
|
+
GE: ["operand.sourceA", "operand.sourceB"],
|
|
589
|
+
LEQ: ["operand.sourceA", "operand.sourceB"],
|
|
590
|
+
LE: ["operand.sourceA", "operand.sourceB"],
|
|
591
|
+
JSR: ["operand.routine", "operand.parameter"]
|
|
592
|
+
};
|
|
593
|
+
const terminalInstructions = new Set([
|
|
594
|
+
"OTE",
|
|
595
|
+
"OTL",
|
|
596
|
+
"OTU",
|
|
597
|
+
"JSR",
|
|
598
|
+
"MOV",
|
|
599
|
+
"MOVE",
|
|
600
|
+
"COP",
|
|
601
|
+
"CPS",
|
|
602
|
+
"CLR",
|
|
603
|
+
"FLL",
|
|
604
|
+
"RES",
|
|
605
|
+
"MSG",
|
|
606
|
+
"GSV",
|
|
607
|
+
"SSV",
|
|
608
|
+
"TON",
|
|
609
|
+
"TOF",
|
|
610
|
+
"RTO",
|
|
611
|
+
"ADD",
|
|
612
|
+
"SUB",
|
|
613
|
+
"MUL",
|
|
614
|
+
"DIV"
|
|
615
|
+
]);
|
|
616
|
+
function operandDetail(project, tags, expression) {
|
|
617
|
+
const text = expression.trim();
|
|
618
|
+
const root = /^[A-Za-z_][\w:]*/u.exec(text)?.[0]?.toLowerCase();
|
|
619
|
+
const tag = root === void 0 ? void 0 : tags.program.get(root) ?? tags.controller.get(root);
|
|
620
|
+
const datum = tag === void 0 ? void 0 : exportedDatum(project, tag.scope, text) ?? (tag.alias !== "" ? exportedDatum(project, tag.scope, tag.alias + text.slice(tag.name.length)) : void 0);
|
|
621
|
+
return {
|
|
622
|
+
text,
|
|
623
|
+
...tag === void 0 ? {} : { tag },
|
|
624
|
+
...datum === void 0 ? {} : { datum }
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
function instructionSymbols(mnemonic, operands) {
|
|
628
|
+
if (mnemonic.toUpperCase() === "JSR") return [];
|
|
629
|
+
return operandParts(operands).filter((part) => /^[A-Za-z_][\w:]*/u.test(part));
|
|
630
|
+
}
|
|
631
|
+
function L5xBody(props) {
|
|
632
|
+
const { resourceAddress, content, t, read, ask, scrollportRef } = props;
|
|
633
|
+
const request = content.kind === "renderer" ? content : void 0;
|
|
634
|
+
const [loaded, setLoaded] = (0, react.useState)();
|
|
635
|
+
const [failure, setFailure] = (0, react.useState)();
|
|
636
|
+
const [choice, setChoice] = (0, react.useState)();
|
|
637
|
+
const [query, setQuery] = (0, react.useState)("");
|
|
638
|
+
const [showReferences, setShowReferences] = (0, react.useState)(false);
|
|
639
|
+
const [context, setContext] = (0, react.useState)();
|
|
640
|
+
const [chatUnavailable, setChatUnavailable] = (0, react.useState)(false);
|
|
641
|
+
const [organizerQuery, setOrganizerQuery] = (0, react.useState)("");
|
|
642
|
+
const [tabs, setTabs] = (0, react.useState)([]);
|
|
643
|
+
const [inspector, setInspector] = (0, react.useState)();
|
|
644
|
+
const [bottomPane, setBottomPane] = (0, react.useState)("search");
|
|
645
|
+
const [watch, setWatch] = (0, react.useState)([]);
|
|
646
|
+
const [rungLimit, setRungLimit] = (0, react.useState)(80);
|
|
647
|
+
const [showSource, setShowSource] = (0, react.useState)(false);
|
|
648
|
+
const [tagView, setTagView] = (0, react.useState)();
|
|
649
|
+
const [tagFilter, setTagFilter] = (0, react.useState)("");
|
|
650
|
+
const [targetRung, setTargetRung] = (0, react.useState)();
|
|
651
|
+
const [focused, setFocused] = (0, react.useState)();
|
|
652
|
+
const [focusedRung, setFocusedRung] = (0, react.useState)();
|
|
653
|
+
const isAcd = resourceAddress.toLocaleLowerCase().endsWith(".acd");
|
|
654
|
+
(0, react.useEffect)(() => {
|
|
655
|
+
if (request === void 0 || isAcd) return;
|
|
656
|
+
const controller = new AbortController();
|
|
657
|
+
setLoaded(void 0);
|
|
658
|
+
setFailure(void 0);
|
|
659
|
+
setChoice(void 0);
|
|
660
|
+
setTabs([]);
|
|
661
|
+
setInspector(void 0);
|
|
662
|
+
setTagView(void 0);
|
|
663
|
+
setFocused(void 0);
|
|
664
|
+
setFocusedRung(void 0);
|
|
665
|
+
read(resourceAddress, controller.signal).then(({ xml, version }) => {
|
|
666
|
+
if (controller.signal.aborted) return;
|
|
667
|
+
const project = parseProject(xml);
|
|
668
|
+
setLoaded({
|
|
669
|
+
revision: request.revision,
|
|
670
|
+
project,
|
|
671
|
+
version
|
|
672
|
+
});
|
|
673
|
+
request.loaded(version);
|
|
674
|
+
}).catch((error) => {
|
|
675
|
+
if (controller.signal.aborted) return;
|
|
676
|
+
setFailure(error instanceof Error ? error.message : t("invalid"));
|
|
677
|
+
request.failed();
|
|
678
|
+
});
|
|
679
|
+
return () => {
|
|
680
|
+
controller.abort();
|
|
681
|
+
};
|
|
682
|
+
}, [
|
|
683
|
+
request?.revision,
|
|
684
|
+
resourceAddress,
|
|
685
|
+
read,
|
|
686
|
+
isAcd
|
|
687
|
+
]);
|
|
688
|
+
(0, react.useEffect)(() => {
|
|
689
|
+
if (context === void 0) return;
|
|
690
|
+
const close = () => {
|
|
691
|
+
setContext(void 0);
|
|
692
|
+
};
|
|
693
|
+
document.addEventListener("pointerdown", close);
|
|
694
|
+
document.addEventListener("keydown", close);
|
|
695
|
+
return () => {
|
|
696
|
+
document.removeEventListener("pointerdown", close);
|
|
697
|
+
document.removeEventListener("keydown", close);
|
|
698
|
+
};
|
|
699
|
+
}, [context]);
|
|
700
|
+
const project = loaded !== void 0 && loaded.revision === request?.revision ? loaded.project : void 0;
|
|
701
|
+
const firstProgram = project?.programs[0];
|
|
702
|
+
const firstRoutine = firstProgram?.routines.find((routine) => routine.name === firstProgram.main) ?? firstProgram?.routines[0];
|
|
703
|
+
const current = choice === null ? void 0 : choice ?? (firstProgram !== void 0 && firstRoutine !== void 0 ? {
|
|
704
|
+
program: firstProgram.name,
|
|
705
|
+
routine: firstRoutine.name
|
|
706
|
+
} : void 0);
|
|
707
|
+
const selected = project?.programs.find((program) => program.name === current?.program)?.routines.find((routine) => routine.name === current?.routine);
|
|
708
|
+
const selectedProgram = project?.programs.find((program) => program.name === current?.program);
|
|
709
|
+
const tagIndex = (0, react.useMemo)(() => ({
|
|
710
|
+
controller: new Map(project?.tags.map((tag) => [tag.name.toLowerCase(), tag]) ?? []),
|
|
711
|
+
program: new Map(selectedProgram?.tags.map((tag) => [tag.name.toLowerCase(), tag]) ?? [])
|
|
712
|
+
}), [project, selectedProgram]);
|
|
713
|
+
(0, react.useEffect)(() => {
|
|
714
|
+
if (targetRung === void 0) return;
|
|
715
|
+
const frame = requestAnimationFrame(() => {
|
|
716
|
+
Array.from(document.querySelectorAll("[data-l5x-project-view] [data-rung]")).find((element) => element.dataset.rung === targetRung)?.scrollIntoView({ block: "center" });
|
|
717
|
+
setTargetRung(void 0);
|
|
718
|
+
});
|
|
719
|
+
return () => {
|
|
720
|
+
cancelAnimationFrame(frame);
|
|
721
|
+
};
|
|
722
|
+
}, [
|
|
723
|
+
targetRung,
|
|
724
|
+
current?.program,
|
|
725
|
+
current?.routine,
|
|
726
|
+
rungLimit
|
|
727
|
+
]);
|
|
728
|
+
const detailFor = (value) => project === void 0 ? void 0 : operandDetail(project, tagIndex, value);
|
|
729
|
+
const describeOperand = (value) => detailFor(value)?.tag?.description ?? "";
|
|
730
|
+
const focusedOperands = focused === void 0 ? [] : displayOperands(focused.mnemonic, focused.operands).map((operand) => detailFor(operand)).filter((item) => item !== void 0);
|
|
731
|
+
const references = (0, react.useMemo)(() => project === void 0 ? [] : findReferences(project, query), [project, query]);
|
|
732
|
+
const findings = (0, react.useMemo)(() => project === void 0 ? [] : inspectProject(project), [project]);
|
|
733
|
+
const scheduled = (0, react.useMemo)(() => new Set(project?.tasks.flatMap((task) => task.programs.map((name) => name.toLowerCase())) ?? []), [project]);
|
|
734
|
+
const visibleTabs = tabs.length > 0 ? tabs : current === void 0 ? [] : [current];
|
|
735
|
+
const select = (program, routine) => {
|
|
736
|
+
const next = {
|
|
737
|
+
program: program.name,
|
|
738
|
+
routine: routine.name
|
|
739
|
+
};
|
|
740
|
+
setChoice(next);
|
|
741
|
+
setTagView(void 0);
|
|
742
|
+
setTabs((previous) => {
|
|
743
|
+
const opened = previous.length === 0 && current !== void 0 ? [current] : previous;
|
|
744
|
+
return opened.some((tab) => tab.program === next.program && tab.routine === next.routine) ? opened : [...opened, next];
|
|
745
|
+
});
|
|
746
|
+
setInspector(void 0);
|
|
747
|
+
setFocused(void 0);
|
|
748
|
+
setFocusedRung(void 0);
|
|
749
|
+
setRungLimit(80);
|
|
750
|
+
setContext(void 0);
|
|
751
|
+
};
|
|
752
|
+
const find = (symbol) => {
|
|
753
|
+
setQuery(symbol);
|
|
754
|
+
setShowReferences(true);
|
|
755
|
+
setBottomPane("search");
|
|
756
|
+
setContext(void 0);
|
|
757
|
+
};
|
|
758
|
+
const selectReference = (programName, routineName, location) => {
|
|
759
|
+
const program = project?.programs.find((item) => item.name === programName);
|
|
760
|
+
const routine = program?.routines.find((item) => item.name === routineName);
|
|
761
|
+
if (program !== void 0 && routine !== void 0) {
|
|
762
|
+
select(program, routine);
|
|
763
|
+
const rungNumber = /^Rung (.+)$/u.exec(location)?.[1];
|
|
764
|
+
if (rungNumber !== void 0) {
|
|
765
|
+
const index = routine.rungs.findIndex((rung) => rung.number === rungNumber);
|
|
766
|
+
if (index >= 80) setRungLimit(Math.ceil((index + 1) / 80) * 80);
|
|
767
|
+
setTargetRung(rungNumber);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
};
|
|
771
|
+
const closeTab = (tab) => {
|
|
772
|
+
const remaining = visibleTabs.filter((item) => item.program !== tab.program || item.routine !== tab.routine);
|
|
773
|
+
setTabs(remaining);
|
|
774
|
+
if (current?.program === tab.program && current.routine === tab.routine) {
|
|
775
|
+
setChoice(remaining.at(-1) ?? null);
|
|
776
|
+
setFocused(void 0);
|
|
777
|
+
setFocusedRung(void 0);
|
|
778
|
+
}
|
|
779
|
+
};
|
|
780
|
+
const addWatch = (symbol) => {
|
|
781
|
+
const name = symbol.trim();
|
|
782
|
+
if (name !== "") setWatch((previous) => previous.includes(name) ? previous : [...previous, name]);
|
|
783
|
+
setBottomPane("watch");
|
|
784
|
+
setContext(void 0);
|
|
785
|
+
};
|
|
786
|
+
const openTags = (scope, symbol = "") => {
|
|
787
|
+
setTagView(scope);
|
|
788
|
+
setTagFilter(symbol);
|
|
789
|
+
setInspector(void 0);
|
|
790
|
+
setFocused(void 0);
|
|
791
|
+
setFocusedRung(void 0);
|
|
792
|
+
};
|
|
793
|
+
const focusInstruction = (instruction) => {
|
|
794
|
+
setFocused(instruction);
|
|
795
|
+
setFocusedRung(instruction.rung);
|
|
796
|
+
setInspector(void 0);
|
|
797
|
+
};
|
|
798
|
+
const focusRung = (number) => {
|
|
799
|
+
setFocused(void 0);
|
|
800
|
+
setFocusedRung(number);
|
|
801
|
+
setInspector(void 0);
|
|
802
|
+
};
|
|
803
|
+
const contextFor = (event, text, symbol, instruction) => {
|
|
804
|
+
event.preventDefault();
|
|
805
|
+
if (instruction !== void 0) setFocused(instruction);
|
|
806
|
+
setContext({
|
|
807
|
+
x: event.clientX,
|
|
808
|
+
y: event.clientY,
|
|
809
|
+
text,
|
|
810
|
+
...symbol === void 0 ? {} : { symbol },
|
|
811
|
+
...instruction === void 0 ? {} : { instruction }
|
|
812
|
+
});
|
|
813
|
+
};
|
|
814
|
+
if (request === void 0) return null;
|
|
815
|
+
if (isAcd) return (0, react_jsx_runtime.jsxs)("div", {
|
|
816
|
+
className: L5xBody_module_css_default.status,
|
|
817
|
+
role: "status",
|
|
818
|
+
children: [
|
|
819
|
+
t("acd"),
|
|
820
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
821
|
+
type: "button",
|
|
822
|
+
onClick: () => {
|
|
823
|
+
setChatUnavailable(!ask(resourceAddress, "Check whether the Logix Designer SDK is installed. If available, prepare a new offline L5X export from this ACD for review. Do not overwrite the ACD, connect online, download, or modify controller state."));
|
|
824
|
+
},
|
|
825
|
+
children: t("exportAcd")
|
|
826
|
+
}),
|
|
827
|
+
chatUnavailable && (0, react_jsx_runtime.jsx)("p", { children: t("chatUnavailable") })
|
|
828
|
+
]
|
|
829
|
+
});
|
|
830
|
+
if (failure !== void 0) return (0, react_jsx_runtime.jsxs)("div", {
|
|
831
|
+
className: L5xBody_module_css_default.status,
|
|
832
|
+
role: "alert",
|
|
833
|
+
children: [
|
|
834
|
+
failure,
|
|
835
|
+
" ",
|
|
836
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
837
|
+
type: "button",
|
|
838
|
+
onClick: request.reload,
|
|
839
|
+
children: t("retry")
|
|
840
|
+
})
|
|
841
|
+
]
|
|
842
|
+
});
|
|
843
|
+
if (project === void 0) return (0, react_jsx_runtime.jsx)("div", {
|
|
844
|
+
className: L5xBody_module_css_default.status,
|
|
845
|
+
role: "status",
|
|
846
|
+
children: t("loading")
|
|
847
|
+
});
|
|
848
|
+
const filter = organizerQuery.trim().toLowerCase();
|
|
849
|
+
const matches = (name) => filter === "" || name.toLowerCase().includes(filter);
|
|
850
|
+
const programMatches = (program) => matches(program.name) || program.routines.some((routine) => matches(routine.name)) || program.tags.some((tag) => matches(tag.name));
|
|
851
|
+
const orderedRoutines = (program) => [...program.routines].sort((left, right) => left.name === program.main ? -1 : right.name === program.main ? 1 : left.name.localeCompare(right.name));
|
|
852
|
+
const treeProgram = (program) => (0, react_jsx_runtime.jsxs)("details", {
|
|
853
|
+
className: L5xBody_module_css_default.nested,
|
|
854
|
+
open: filter !== "" || program.name === current?.program,
|
|
855
|
+
children: [
|
|
856
|
+
(0, react_jsx_runtime.jsxs)("summary", {
|
|
857
|
+
title: program.description,
|
|
858
|
+
children: [program.name, (0, react_jsx_runtime.jsx)("small", { children: program.routines.length })]
|
|
859
|
+
}),
|
|
860
|
+
(filter === "" || program.tags.some((tag) => matches(tag.name))) && (0, react_jsx_runtime.jsxs)("button", {
|
|
861
|
+
type: "button",
|
|
862
|
+
className: L5xBody_module_css_default.leafButton,
|
|
863
|
+
onClick: () => {
|
|
864
|
+
setChoice({
|
|
865
|
+
program: program.name,
|
|
866
|
+
routine: program.main || program.routines[0]?.name || ""
|
|
867
|
+
});
|
|
868
|
+
openTags("program", filter);
|
|
869
|
+
},
|
|
870
|
+
children: [
|
|
871
|
+
t("programTags"),
|
|
872
|
+
" ",
|
|
873
|
+
(0, react_jsx_runtime.jsx)("small", { children: program.tags.length })
|
|
874
|
+
]
|
|
875
|
+
}),
|
|
876
|
+
orderedRoutines(program).filter((routine) => matches(program.name) || matches(routine.name)).map((routine) => (0, react_jsx_runtime.jsxs)("button", {
|
|
877
|
+
type: "button",
|
|
878
|
+
className: current?.program === program.name && current.routine === routine.name ? L5xBody_module_css_default.selected : L5xBody_module_css_default.leafButton,
|
|
879
|
+
onClick: () => {
|
|
880
|
+
select(program, routine);
|
|
881
|
+
},
|
|
882
|
+
onContextMenu: (event) => {
|
|
883
|
+
contextFor(event, `${program.name}/${routine.name}`);
|
|
884
|
+
},
|
|
885
|
+
children: [
|
|
886
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
887
|
+
className: L5xBody_module_css_default.routineIcon,
|
|
888
|
+
children: routine.type === "RLL" ? "≡" : routine.type === "ST" ? "{ }" : "◇"
|
|
889
|
+
}),
|
|
890
|
+
routine.name,
|
|
891
|
+
" ",
|
|
892
|
+
(0, react_jsx_runtime.jsx)("small", { children: routine.type })
|
|
893
|
+
]
|
|
894
|
+
}, routine.name)),
|
|
895
|
+
program.routines.length === 0 && (0, react_jsx_runtime.jsx)("span", {
|
|
896
|
+
className: L5xBody_module_css_default.leaf,
|
|
897
|
+
children: t("noRoutines")
|
|
898
|
+
})
|
|
899
|
+
]
|
|
900
|
+
}, program.name);
|
|
901
|
+
const unscheduled = project.programs.filter((program) => !scheduled.has(program.name.toLowerCase()));
|
|
902
|
+
const moduleNames = new Set(project.modules.map((module) => module.name.toLowerCase()));
|
|
903
|
+
const treeModule = (module, ancestors = []) => {
|
|
904
|
+
const shown = project.modules.filter((childModule) => childModule.parent.toLowerCase() === module.name.toLowerCase() && !ancestors.includes(childModule.name.toLowerCase()) && childModule.name !== module.name).filter((childModule) => matches(childModule.name) || matches(childModule.catalog) || filter === "");
|
|
905
|
+
if (shown.length === 0) return (0, react_jsx_runtime.jsxs)("button", {
|
|
906
|
+
type: "button",
|
|
907
|
+
className: L5xBody_module_css_default.leafButton,
|
|
908
|
+
onClick: () => {
|
|
909
|
+
setInspector({
|
|
910
|
+
kind: "module",
|
|
911
|
+
module
|
|
912
|
+
});
|
|
913
|
+
},
|
|
914
|
+
title: module.catalog,
|
|
915
|
+
children: [
|
|
916
|
+
module.name,
|
|
917
|
+
" ",
|
|
918
|
+
(0, react_jsx_runtime.jsx)("small", { children: module.catalog })
|
|
919
|
+
]
|
|
920
|
+
}, module.name);
|
|
921
|
+
return (0, react_jsx_runtime.jsxs)("details", {
|
|
922
|
+
className: L5xBody_module_css_default.nested,
|
|
923
|
+
open: filter !== "" ? true : void 0,
|
|
924
|
+
children: [
|
|
925
|
+
(0, react_jsx_runtime.jsxs)("summary", {
|
|
926
|
+
title: module.catalog,
|
|
927
|
+
children: [module.name, (0, react_jsx_runtime.jsx)("small", { children: module.catalog })]
|
|
928
|
+
}),
|
|
929
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
930
|
+
type: "button",
|
|
931
|
+
className: L5xBody_module_css_default.leafButton,
|
|
932
|
+
onClick: () => {
|
|
933
|
+
setInspector({
|
|
934
|
+
kind: "module",
|
|
935
|
+
module
|
|
936
|
+
});
|
|
937
|
+
},
|
|
938
|
+
children: t("inspect")
|
|
939
|
+
}),
|
|
940
|
+
shown.map((childModule) => treeModule(childModule, [...ancestors, module.name.toLowerCase()]))
|
|
941
|
+
]
|
|
942
|
+
}, module.name);
|
|
943
|
+
};
|
|
944
|
+
const rootModules = project.modules.filter((module) => module.parent === "" || !moduleNames.has(module.parent.toLowerCase()) || module.parent === module.name);
|
|
945
|
+
const selectedRung = selected?.rungs.find((rung) => rung.number === focusedRung);
|
|
946
|
+
return (0, react_jsx_runtime.jsxs)("div", {
|
|
947
|
+
className: L5xBody_module_css_default.root,
|
|
948
|
+
"data-l5x-project-view": true,
|
|
949
|
+
children: [
|
|
950
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
951
|
+
className: L5xBody_module_css_default.toolbar,
|
|
952
|
+
children: [
|
|
953
|
+
(0, react_jsx_runtime.jsxs)("span", {
|
|
954
|
+
className: L5xBody_module_css_default.productMark,
|
|
955
|
+
children: [
|
|
956
|
+
t("productMark"),
|
|
957
|
+
" ",
|
|
958
|
+
(0, react_jsx_runtime.jsx)("b", { children: t("workspaceMark") })
|
|
959
|
+
]
|
|
960
|
+
}),
|
|
961
|
+
(0, react_jsx_runtime.jsx)("strong", { children: project.name }),
|
|
962
|
+
(0, react_jsx_runtime.jsxs)("span", { children: [
|
|
963
|
+
project.processor,
|
|
964
|
+
" · ",
|
|
965
|
+
t("revision", { revision: project.revision })
|
|
966
|
+
] }),
|
|
967
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
968
|
+
className: L5xBody_module_css_default.offline,
|
|
969
|
+
children: t("offline")
|
|
970
|
+
}),
|
|
971
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
972
|
+
type: "button",
|
|
973
|
+
onClick: () => {
|
|
974
|
+
setChatUnavailable(!ask(resourceAddress, `Review the offline L5X export for ${project.name}. Start with the current routine ${current?.program ?? ""}/${current?.routine ?? ""}. Use the L5X engineering plugin tools and cite the export identity and source locations.`));
|
|
975
|
+
},
|
|
976
|
+
children: t("ask")
|
|
977
|
+
}),
|
|
978
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
979
|
+
type: "button",
|
|
980
|
+
onClick: request.reload,
|
|
981
|
+
children: t("reloadFile")
|
|
982
|
+
})
|
|
983
|
+
]
|
|
984
|
+
}),
|
|
985
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
986
|
+
className: L5xBody_module_css_default.sourceBar,
|
|
987
|
+
children: [
|
|
988
|
+
(0, react_jsx_runtime.jsx)("span", { children: t("offlineSource") }),
|
|
989
|
+
(0, react_jsx_runtime.jsxs)("span", {
|
|
990
|
+
title: loaded?.version,
|
|
991
|
+
children: [
|
|
992
|
+
t("sourceVersion"),
|
|
993
|
+
": ",
|
|
994
|
+
loaded?.version.slice(0, 12) ?? "—",
|
|
995
|
+
"…"
|
|
996
|
+
]
|
|
997
|
+
}),
|
|
998
|
+
(0, react_jsx_runtime.jsxs)("span", { children: [
|
|
999
|
+
t("exportRevision"),
|
|
1000
|
+
": ",
|
|
1001
|
+
project.exportRevision || "—"
|
|
1002
|
+
] }),
|
|
1003
|
+
(0, react_jsx_runtime.jsx)("span", { children: t("manualImport") })
|
|
1004
|
+
]
|
|
1005
|
+
}),
|
|
1006
|
+
chatUnavailable && (0, react_jsx_runtime.jsx)("div", {
|
|
1007
|
+
className: L5xBody_module_css_default.notice,
|
|
1008
|
+
role: "status",
|
|
1009
|
+
children: t("chatUnavailable")
|
|
1010
|
+
}),
|
|
1011
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
1012
|
+
className: L5xBody_module_css_default.layout,
|
|
1013
|
+
children: [
|
|
1014
|
+
(0, react_jsx_runtime.jsxs)("aside", {
|
|
1015
|
+
className: L5xBody_module_css_default.tree,
|
|
1016
|
+
"aria-label": t("controller"),
|
|
1017
|
+
children: [
|
|
1018
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
1019
|
+
className: L5xBody_module_css_default.treeHeading,
|
|
1020
|
+
children: [t("organizer"), (0, react_jsx_runtime.jsx)("span", { children: project.name })]
|
|
1021
|
+
}),
|
|
1022
|
+
(0, react_jsx_runtime.jsx)("input", {
|
|
1023
|
+
className: L5xBody_module_css_default.organizerSearch,
|
|
1024
|
+
value: organizerQuery,
|
|
1025
|
+
onChange: (event) => {
|
|
1026
|
+
setOrganizerQuery(event.target.value);
|
|
1027
|
+
},
|
|
1028
|
+
placeholder: t("organizerSearch"),
|
|
1029
|
+
"aria-label": t("organizerSearch")
|
|
1030
|
+
}),
|
|
1031
|
+
(0, react_jsx_runtime.jsx)("div", {
|
|
1032
|
+
className: L5xBody_module_css_default.treeScroll,
|
|
1033
|
+
children: (0, react_jsx_runtime.jsxs)("details", {
|
|
1034
|
+
open: true,
|
|
1035
|
+
className: L5xBody_module_css_default.controllerRoot,
|
|
1036
|
+
children: [
|
|
1037
|
+
(0, react_jsx_runtime.jsxs)("summary", { children: [
|
|
1038
|
+
t("controller"),
|
|
1039
|
+
" ",
|
|
1040
|
+
project.name
|
|
1041
|
+
] }),
|
|
1042
|
+
(filter === "" || project.tags.some((tag) => matches(tag.name))) && (0, react_jsx_runtime.jsxs)("button", {
|
|
1043
|
+
type: "button",
|
|
1044
|
+
className: L5xBody_module_css_default.leafButton,
|
|
1045
|
+
onClick: () => {
|
|
1046
|
+
openTags("controller", filter);
|
|
1047
|
+
},
|
|
1048
|
+
children: [
|
|
1049
|
+
t("tags"),
|
|
1050
|
+
" ",
|
|
1051
|
+
(0, react_jsx_runtime.jsx)("small", { children: project.tags.length })
|
|
1052
|
+
]
|
|
1053
|
+
}),
|
|
1054
|
+
filter === "" && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)("div", {
|
|
1055
|
+
className: L5xBody_module_css_default.unexportedLeaf,
|
|
1056
|
+
title: t("notExported"),
|
|
1057
|
+
children: t("controllerFault")
|
|
1058
|
+
}), (0, react_jsx_runtime.jsx)("div", {
|
|
1059
|
+
className: L5xBody_module_css_default.unexportedLeaf,
|
|
1060
|
+
title: t("notExported"),
|
|
1061
|
+
children: t("powerUp")
|
|
1062
|
+
})] }),
|
|
1063
|
+
(0, react_jsx_runtime.jsxs)("details", {
|
|
1064
|
+
open: true,
|
|
1065
|
+
className: L5xBody_module_css_default.nested,
|
|
1066
|
+
children: [
|
|
1067
|
+
(0, react_jsx_runtime.jsx)("summary", { children: t("tasks") }),
|
|
1068
|
+
project.tasks.filter((task) => matches(task.name) || task.programs.some((name) => {
|
|
1069
|
+
const program = project.programs.find((item) => item.name === name);
|
|
1070
|
+
return program !== void 0 && programMatches(program);
|
|
1071
|
+
})).map((task) => (0, react_jsx_runtime.jsxs)("details", {
|
|
1072
|
+
className: L5xBody_module_css_default.nested,
|
|
1073
|
+
open: filter !== "" || task.programs.includes(current?.program ?? ""),
|
|
1074
|
+
children: [(0, react_jsx_runtime.jsxs)("summary", {
|
|
1075
|
+
title: `${task.type} · ${task.rate}`,
|
|
1076
|
+
children: [task.name, (0, react_jsx_runtime.jsx)("small", { children: task.rate ? `${task.rate} ${t("milliseconds")}` : task.type })]
|
|
1077
|
+
}), task.programs.map((name) => project.programs.find((program) => program.name === name)).filter((program) => program !== void 0 && programMatches(program)).map(treeProgram)]
|
|
1078
|
+
}, task.name)),
|
|
1079
|
+
(filter === "" || unscheduled.some(programMatches)) && (0, react_jsx_runtime.jsxs)("details", {
|
|
1080
|
+
className: L5xBody_module_css_default.nested,
|
|
1081
|
+
open: filter !== "" ? true : void 0,
|
|
1082
|
+
children: [
|
|
1083
|
+
(0, react_jsx_runtime.jsx)("summary", { children: t("unscheduled") }),
|
|
1084
|
+
unscheduled.filter(programMatches).map(treeProgram),
|
|
1085
|
+
unscheduled.length === 0 && (0, react_jsx_runtime.jsx)("span", {
|
|
1086
|
+
className: L5xBody_module_css_default.leaf,
|
|
1087
|
+
children: t("none")
|
|
1088
|
+
})
|
|
1089
|
+
]
|
|
1090
|
+
})
|
|
1091
|
+
]
|
|
1092
|
+
}),
|
|
1093
|
+
filter === "" && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
1094
|
+
(0, react_jsx_runtime.jsx)("div", {
|
|
1095
|
+
className: L5xBody_module_css_default.unexportedLeaf,
|
|
1096
|
+
title: t("notExported"),
|
|
1097
|
+
children: t("motionGroups")
|
|
1098
|
+
}),
|
|
1099
|
+
(0, react_jsx_runtime.jsx)("div", {
|
|
1100
|
+
className: L5xBody_module_css_default.unexportedLeaf,
|
|
1101
|
+
title: t("notExported"),
|
|
1102
|
+
children: t("alarmManager")
|
|
1103
|
+
}),
|
|
1104
|
+
(0, react_jsx_runtime.jsx)("div", {
|
|
1105
|
+
className: L5xBody_module_css_default.unexportedLeaf,
|
|
1106
|
+
title: t("notExported"),
|
|
1107
|
+
children: t("assets")
|
|
1108
|
+
}),
|
|
1109
|
+
(0, react_jsx_runtime.jsx)("div", {
|
|
1110
|
+
className: L5xBody_module_css_default.unexportedLeaf,
|
|
1111
|
+
title: t("notExported"),
|
|
1112
|
+
children: t("logicalModel")
|
|
1113
|
+
})
|
|
1114
|
+
] }),
|
|
1115
|
+
(0, react_jsx_runtime.jsxs)("details", {
|
|
1116
|
+
className: L5xBody_module_css_default.nested,
|
|
1117
|
+
open: filter !== "" ? true : void 0,
|
|
1118
|
+
children: [(0, react_jsx_runtime.jsxs)("summary", { children: [
|
|
1119
|
+
t("modules"),
|
|
1120
|
+
" (",
|
|
1121
|
+
project.modules.length,
|
|
1122
|
+
")"
|
|
1123
|
+
] }), rootModules.filter((module) => filter === "" || matches(module.name) || matches(module.catalog) || project.modules.some((childModule) => childModule.parent === module.name && (matches(childModule.name) || matches(childModule.catalog)))).map((module) => treeModule(module))]
|
|
1124
|
+
}),
|
|
1125
|
+
(0, react_jsx_runtime.jsxs)("details", {
|
|
1126
|
+
className: L5xBody_module_css_default.nested,
|
|
1127
|
+
open: filter !== "" ? true : void 0,
|
|
1128
|
+
children: [(0, react_jsx_runtime.jsxs)("summary", { children: [
|
|
1129
|
+
t("aois"),
|
|
1130
|
+
" (",
|
|
1131
|
+
project.aois.length,
|
|
1132
|
+
")"
|
|
1133
|
+
] }), project.aois.filter(matches).map((name) => (0, react_jsx_runtime.jsx)("div", {
|
|
1134
|
+
className: L5xBody_module_css_default.leaf,
|
|
1135
|
+
children: name
|
|
1136
|
+
}, name))]
|
|
1137
|
+
}),
|
|
1138
|
+
(0, react_jsx_runtime.jsxs)("details", {
|
|
1139
|
+
className: L5xBody_module_css_default.nested,
|
|
1140
|
+
open: filter !== "" ? true : void 0,
|
|
1141
|
+
children: [(0, react_jsx_runtime.jsxs)("summary", { children: [
|
|
1142
|
+
t("types"),
|
|
1143
|
+
" (",
|
|
1144
|
+
project.types.length,
|
|
1145
|
+
")"
|
|
1146
|
+
] }), project.types.filter(matches).map((name) => (0, react_jsx_runtime.jsx)("div", {
|
|
1147
|
+
className: L5xBody_module_css_default.leaf,
|
|
1148
|
+
children: name
|
|
1149
|
+
}, name))]
|
|
1150
|
+
})
|
|
1151
|
+
]
|
|
1152
|
+
})
|
|
1153
|
+
}, filter)
|
|
1154
|
+
]
|
|
1155
|
+
}),
|
|
1156
|
+
(0, react_jsx_runtime.jsxs)("section", {
|
|
1157
|
+
className: L5xBody_module_css_default.editor,
|
|
1158
|
+
children: [
|
|
1159
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
1160
|
+
className: L5xBody_module_css_default.tabStrip,
|
|
1161
|
+
children: [tagView !== void 0 && (0, react_jsx_runtime.jsxs)("div", {
|
|
1162
|
+
className: L5xBody_module_css_default.activeTab,
|
|
1163
|
+
children: [(0, react_jsx_runtime.jsx)("button", {
|
|
1164
|
+
type: "button",
|
|
1165
|
+
children: tagView === "controller" ? t("tags") : `${current?.program} / ${t("programTags")}`
|
|
1166
|
+
}), (0, react_jsx_runtime.jsx)("button", {
|
|
1167
|
+
type: "button",
|
|
1168
|
+
className: L5xBody_module_css_default.closeTab,
|
|
1169
|
+
onClick: () => {
|
|
1170
|
+
setTagView(void 0);
|
|
1171
|
+
},
|
|
1172
|
+
"aria-label": t("closeTab"),
|
|
1173
|
+
children: "×"
|
|
1174
|
+
})]
|
|
1175
|
+
}), visibleTabs.map((tab) => (0, react_jsx_runtime.jsxs)("div", {
|
|
1176
|
+
className: tagView === void 0 && tab.program === current?.program && tab.routine === current.routine ? L5xBody_module_css_default.activeTab : L5xBody_module_css_default.tab,
|
|
1177
|
+
children: [(0, react_jsx_runtime.jsxs)("button", {
|
|
1178
|
+
type: "button",
|
|
1179
|
+
onClick: () => {
|
|
1180
|
+
setTagView(void 0);
|
|
1181
|
+
setChoice(tab);
|
|
1182
|
+
setInspector(void 0);
|
|
1183
|
+
setFocused(void 0);
|
|
1184
|
+
setFocusedRung(void 0);
|
|
1185
|
+
setRungLimit(80);
|
|
1186
|
+
},
|
|
1187
|
+
children: [
|
|
1188
|
+
tab.program,
|
|
1189
|
+
" / ",
|
|
1190
|
+
tab.routine
|
|
1191
|
+
]
|
|
1192
|
+
}), (0, react_jsx_runtime.jsx)("button", {
|
|
1193
|
+
type: "button",
|
|
1194
|
+
className: L5xBody_module_css_default.closeTab,
|
|
1195
|
+
onClick: () => {
|
|
1196
|
+
closeTab(tab);
|
|
1197
|
+
},
|
|
1198
|
+
"aria-label": `${t("closeTab")} ${tab.routine}`,
|
|
1199
|
+
children: "×"
|
|
1200
|
+
})]
|
|
1201
|
+
}, `${tab.program}/${tab.routine}`))]
|
|
1202
|
+
}),
|
|
1203
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
1204
|
+
className: L5xBody_module_css_default.editorTools,
|
|
1205
|
+
children: [
|
|
1206
|
+
(0, react_jsx_runtime.jsx)("strong", { children: tagView === void 0 ? `${current?.program} / ${selected?.name ?? t("noSelection")}` : tagView === "controller" ? t("tags") : `${current?.program} / ${t("programTags")}` }),
|
|
1207
|
+
(0, react_jsx_runtime.jsx)("span", { children: tagView === void 0 ? selected?.type ?? "—" : t("offlineValues") }),
|
|
1208
|
+
(0, react_jsx_runtime.jsx)("span", { className: L5xBody_module_css_default.toolSpacer }),
|
|
1209
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
1210
|
+
type: "button",
|
|
1211
|
+
onClick: () => {
|
|
1212
|
+
setShowSource((value) => !value);
|
|
1213
|
+
},
|
|
1214
|
+
"aria-pressed": showSource,
|
|
1215
|
+
children: t("source")
|
|
1216
|
+
}),
|
|
1217
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
1218
|
+
type: "button",
|
|
1219
|
+
onClick: () => {
|
|
1220
|
+
setChatUnavailable(!ask(resourceAddress, `Explain and review the offline routine ${current?.program}/${current?.routine} in the current L5X export. Use the L5X engineering plugin tools for references and checks. Do not change a controller.`));
|
|
1221
|
+
},
|
|
1222
|
+
children: t("askRoutine")
|
|
1223
|
+
})
|
|
1224
|
+
]
|
|
1225
|
+
}),
|
|
1226
|
+
tagView === void 0 && focused !== void 0 && (0, react_jsx_runtime.jsxs)("div", {
|
|
1227
|
+
className: L5xBody_module_css_default.focusBar,
|
|
1228
|
+
role: "status",
|
|
1229
|
+
children: [
|
|
1230
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
1231
|
+
className: L5xBody_module_css_default.focusBadge,
|
|
1232
|
+
children: t("focusedInstruction")
|
|
1233
|
+
}),
|
|
1234
|
+
(0, react_jsx_runtime.jsx)("strong", { children: focused.mnemonic }),
|
|
1235
|
+
(0, react_jsx_runtime.jsx)("code", { children: focused.operands }),
|
|
1236
|
+
(0, react_jsx_runtime.jsxs)("span", { children: [
|
|
1237
|
+
t("rung"),
|
|
1238
|
+
" ",
|
|
1239
|
+
focused.rung
|
|
1240
|
+
] }),
|
|
1241
|
+
(0, react_jsx_runtime.jsxs)("span", {
|
|
1242
|
+
className: L5xBody_module_css_default.focusValue,
|
|
1243
|
+
children: [
|
|
1244
|
+
focused.symbol !== void 0 && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)("code", { children: focused.symbol }), " · "] }),
|
|
1245
|
+
t("exportedValue"),
|
|
1246
|
+
": ",
|
|
1247
|
+
focused.symbol === void 0 ? t("noExportedValue") : detailFor(focused.symbol)?.datum?.value ?? t("noExportedValue")
|
|
1248
|
+
]
|
|
1249
|
+
}),
|
|
1250
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
1251
|
+
type: "button",
|
|
1252
|
+
disabled: focused.symbol === void 0,
|
|
1253
|
+
onClick: () => {
|
|
1254
|
+
if (focused.symbol !== void 0) find(focused.symbol);
|
|
1255
|
+
},
|
|
1256
|
+
children: t("references")
|
|
1257
|
+
}),
|
|
1258
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
1259
|
+
type: "button",
|
|
1260
|
+
disabled: focused.symbol === void 0,
|
|
1261
|
+
onClick: () => {
|
|
1262
|
+
if (focused.symbol !== void 0) addWatch(focused.symbol);
|
|
1263
|
+
},
|
|
1264
|
+
children: t("addWatch")
|
|
1265
|
+
}),
|
|
1266
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
1267
|
+
type: "button",
|
|
1268
|
+
onClick: () => {
|
|
1269
|
+
setFocused(void 0);
|
|
1270
|
+
setFocusedRung(void 0);
|
|
1271
|
+
},
|
|
1272
|
+
"aria-label": t("clearFocus"),
|
|
1273
|
+
children: "×"
|
|
1274
|
+
})
|
|
1275
|
+
]
|
|
1276
|
+
}),
|
|
1277
|
+
tagView === void 0 && focused === void 0 && focusedRung !== void 0 && (0, react_jsx_runtime.jsxs)("div", {
|
|
1278
|
+
className: L5xBody_module_css_default.focusBar,
|
|
1279
|
+
role: "status",
|
|
1280
|
+
children: [
|
|
1281
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
1282
|
+
className: L5xBody_module_css_default.focusBadge,
|
|
1283
|
+
children: t("focusedRung")
|
|
1284
|
+
}),
|
|
1285
|
+
(0, react_jsx_runtime.jsxs)("strong", { children: [
|
|
1286
|
+
t("rung"),
|
|
1287
|
+
" ",
|
|
1288
|
+
focusedRung
|
|
1289
|
+
] }),
|
|
1290
|
+
(0, react_jsx_runtime.jsx)("code", { children: selectedRung?.comment || selectedRung?.text }),
|
|
1291
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
1292
|
+
type: "button",
|
|
1293
|
+
onClick: () => {
|
|
1294
|
+
navigator.clipboard.writeText(selectedRung?.text ?? "");
|
|
1295
|
+
},
|
|
1296
|
+
children: t("copyRung")
|
|
1297
|
+
}),
|
|
1298
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
1299
|
+
type: "button",
|
|
1300
|
+
onClick: () => {
|
|
1301
|
+
setFocusedRung(void 0);
|
|
1302
|
+
},
|
|
1303
|
+
"aria-label": t("clearFocus"),
|
|
1304
|
+
children: "×"
|
|
1305
|
+
})
|
|
1306
|
+
]
|
|
1307
|
+
}),
|
|
1308
|
+
(0, react_jsx_runtime.jsx)("main", {
|
|
1309
|
+
className: L5xBody_module_css_default.main,
|
|
1310
|
+
ref: scrollportRef,
|
|
1311
|
+
children: tagView !== void 0 ? (0, react_jsx_runtime.jsx)(TagTable, {
|
|
1312
|
+
tags: tagView === "controller" ? project.tags : selectedProgram?.tags ?? [],
|
|
1313
|
+
filter: tagFilter,
|
|
1314
|
+
setFilter: setTagFilter,
|
|
1315
|
+
onContext: contextFor,
|
|
1316
|
+
onWatch: addWatch,
|
|
1317
|
+
onFind: find,
|
|
1318
|
+
getDatum: (tag) => exportedDatum(project, tag.scope, tag.name),
|
|
1319
|
+
t
|
|
1320
|
+
}) : selected === void 0 ? (0, react_jsx_runtime.jsx)("div", {
|
|
1321
|
+
className: L5xBody_module_css_default.status,
|
|
1322
|
+
children: t("noSelection")
|
|
1323
|
+
}) : (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
1324
|
+
selected.description !== "" && (0, react_jsx_runtime.jsx)("p", {
|
|
1325
|
+
className: L5xBody_module_css_default.description,
|
|
1326
|
+
children: selected.description
|
|
1327
|
+
}),
|
|
1328
|
+
selected.type === "RLL" && (selected.rungs.length === 0 ? (0, react_jsx_runtime.jsx)("div", {
|
|
1329
|
+
className: L5xBody_module_css_default.status,
|
|
1330
|
+
children: t("noRungs")
|
|
1331
|
+
}) : (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [selected.rungs.slice(0, rungLimit).map((rung) => (0, react_jsx_runtime.jsx)(RungView, {
|
|
1332
|
+
rung,
|
|
1333
|
+
showSource,
|
|
1334
|
+
describeOperand,
|
|
1335
|
+
detailFor,
|
|
1336
|
+
focused,
|
|
1337
|
+
focusedRung,
|
|
1338
|
+
onFocusRung: focusRung,
|
|
1339
|
+
onFocusInstruction: focusInstruction,
|
|
1340
|
+
onContext: contextFor,
|
|
1341
|
+
t
|
|
1342
|
+
}, rung.number)), selected.rungs.length > rungLimit && (0, react_jsx_runtime.jsxs)("button", {
|
|
1343
|
+
type: "button",
|
|
1344
|
+
className: L5xBody_module_css_default.loadMore,
|
|
1345
|
+
onClick: () => {
|
|
1346
|
+
setRungLimit((limit) => limit + 80);
|
|
1347
|
+
},
|
|
1348
|
+
children: [
|
|
1349
|
+
t("loadMore"),
|
|
1350
|
+
" (",
|
|
1351
|
+
selected.rungs.length - rungLimit,
|
|
1352
|
+
")"
|
|
1353
|
+
]
|
|
1354
|
+
})] })),
|
|
1355
|
+
selected.type === "ST" && (0, react_jsx_runtime.jsx)("pre", {
|
|
1356
|
+
className: L5xBody_module_css_default.structured,
|
|
1357
|
+
onContextMenu: (event) => {
|
|
1358
|
+
contextFor(event, selected.code);
|
|
1359
|
+
},
|
|
1360
|
+
children: selected.code
|
|
1361
|
+
}),
|
|
1362
|
+
selected.type !== "RLL" && selected.type !== "ST" && (0, react_jsx_runtime.jsxs)("div", {
|
|
1363
|
+
className: L5xBody_module_css_default.status,
|
|
1364
|
+
children: [
|
|
1365
|
+
t("unsupported"),
|
|
1366
|
+
" (",
|
|
1367
|
+
selected.type,
|
|
1368
|
+
")"
|
|
1369
|
+
]
|
|
1370
|
+
})
|
|
1371
|
+
] })
|
|
1372
|
+
})
|
|
1373
|
+
]
|
|
1374
|
+
}),
|
|
1375
|
+
(0, react_jsx_runtime.jsxs)("aside", {
|
|
1376
|
+
className: L5xBody_module_css_default.inspector,
|
|
1377
|
+
"aria-label": t("inspect"),
|
|
1378
|
+
children: [
|
|
1379
|
+
(0, react_jsx_runtime.jsx)("div", {
|
|
1380
|
+
className: L5xBody_module_css_default.inspectorHeading,
|
|
1381
|
+
children: t("inspect")
|
|
1382
|
+
}),
|
|
1383
|
+
focused !== void 0 && tagView === void 0 ? (0, react_jsx_runtime.jsxs)("div", {
|
|
1384
|
+
className: L5xBody_module_css_default.propertyList,
|
|
1385
|
+
children: [
|
|
1386
|
+
(0, react_jsx_runtime.jsxs)("strong", { children: [
|
|
1387
|
+
focused.mnemonic,
|
|
1388
|
+
"(",
|
|
1389
|
+
focused.operands,
|
|
1390
|
+
")"
|
|
1391
|
+
] }),
|
|
1392
|
+
(0, react_jsx_runtime.jsxs)("span", { children: [
|
|
1393
|
+
current?.program,
|
|
1394
|
+
" / ",
|
|
1395
|
+
current?.routine,
|
|
1396
|
+
" · ",
|
|
1397
|
+
t("rung"),
|
|
1398
|
+
" ",
|
|
1399
|
+
focused.rung
|
|
1400
|
+
] }),
|
|
1401
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
1402
|
+
className: L5xBody_module_css_default.inspectorCaution,
|
|
1403
|
+
children: t("exportedValueScope")
|
|
1404
|
+
}),
|
|
1405
|
+
(0, react_jsx_runtime.jsx)("div", {
|
|
1406
|
+
className: L5xBody_module_css_default.operandDetails,
|
|
1407
|
+
children: focusedOperands.map((operand, index) => (0, react_jsx_runtime.jsxs)("div", { children: [
|
|
1408
|
+
(0, react_jsx_runtime.jsx)("code", { children: operand.text }),
|
|
1409
|
+
(0, react_jsx_runtime.jsx)("strong", { children: operand.datum?.value ?? (/^[-+]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][-+]?\d+)?$/u.test(operand.text) ? operand.text : t("noExportedValue")) }),
|
|
1410
|
+
(0, react_jsx_runtime.jsxs)("span", { children: [
|
|
1411
|
+
operand.datum?.type ?? operand.tag?.type ?? t("literal"),
|
|
1412
|
+
" · ",
|
|
1413
|
+
operand.tag?.scope ?? "—"
|
|
1414
|
+
] }),
|
|
1415
|
+
operand.tag?.description && (0, react_jsx_runtime.jsx)("p", { children: operand.tag.description })
|
|
1416
|
+
] }, index))
|
|
1417
|
+
}),
|
|
1418
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
1419
|
+
type: "button",
|
|
1420
|
+
className: L5xBody_module_css_default.actionButton,
|
|
1421
|
+
onClick: () => {
|
|
1422
|
+
setChatUnavailable(!ask(resourceAddress, `Explain ${focused.mnemonic}(${focused.operands}) at ${current?.program}/${current?.routine} rung ${focused.rung} in this offline L5X. Identify operand scope, cross references, and evidence. Do not assume exported values are live.`));
|
|
1423
|
+
},
|
|
1424
|
+
children: t("askInstruction")
|
|
1425
|
+
}),
|
|
1426
|
+
focused.symbol !== void 0 && (0, react_jsx_runtime.jsx)("button", {
|
|
1427
|
+
type: "button",
|
|
1428
|
+
className: L5xBody_module_css_default.actionButton,
|
|
1429
|
+
onClick: () => {
|
|
1430
|
+
setChatUnavailable(!ask(resourceAddress, `Read the current value of ${focused.symbol} only if the installed read-only live diagnostic tool supports its type and controller path. Report the observed timestamp and limitations. Do not write data, force, download, or change controller mode.`));
|
|
1431
|
+
},
|
|
1432
|
+
children: t("askLiveRead")
|
|
1433
|
+
})
|
|
1434
|
+
]
|
|
1435
|
+
}) : focusedRung !== void 0 && tagView === void 0 ? (0, react_jsx_runtime.jsxs)("div", {
|
|
1436
|
+
className: L5xBody_module_css_default.propertyList,
|
|
1437
|
+
children: [
|
|
1438
|
+
(0, react_jsx_runtime.jsxs)("strong", { children: [
|
|
1439
|
+
t("rung"),
|
|
1440
|
+
" ",
|
|
1441
|
+
focusedRung
|
|
1442
|
+
] }),
|
|
1443
|
+
(0, react_jsx_runtime.jsxs)("span", { children: [
|
|
1444
|
+
current?.program,
|
|
1445
|
+
" / ",
|
|
1446
|
+
current?.routine
|
|
1447
|
+
] }),
|
|
1448
|
+
(0, react_jsx_runtime.jsx)("p", { children: selectedRung?.comment || t("noDescription") }),
|
|
1449
|
+
(0, react_jsx_runtime.jsx)("code", { children: selectedRung?.text })
|
|
1450
|
+
]
|
|
1451
|
+
}) : inspector?.kind === "tag" ? (0, react_jsx_runtime.jsxs)("div", {
|
|
1452
|
+
className: L5xBody_module_css_default.propertyList,
|
|
1453
|
+
children: [
|
|
1454
|
+
(0, react_jsx_runtime.jsx)("strong", { children: inspector.tag.name }),
|
|
1455
|
+
(0, react_jsx_runtime.jsxs)("span", { children: [
|
|
1456
|
+
t("type"),
|
|
1457
|
+
": ",
|
|
1458
|
+
inspector.tag.type
|
|
1459
|
+
] }),
|
|
1460
|
+
(0, react_jsx_runtime.jsxs)("span", { children: [
|
|
1461
|
+
t("scope"),
|
|
1462
|
+
": ",
|
|
1463
|
+
inspector.tag.scope
|
|
1464
|
+
] }),
|
|
1465
|
+
(0, react_jsx_runtime.jsxs)("span", { children: [
|
|
1466
|
+
t("exportedValue"),
|
|
1467
|
+
": ",
|
|
1468
|
+
exportedDatum(project, inspector.tag.scope, inspector.tag.name)?.value ?? t("noExportedValue")
|
|
1469
|
+
] }),
|
|
1470
|
+
inspector.tag.alias !== "" && (0, react_jsx_runtime.jsxs)("span", { children: [
|
|
1471
|
+
t("alias"),
|
|
1472
|
+
": ",
|
|
1473
|
+
inspector.tag.alias
|
|
1474
|
+
] }),
|
|
1475
|
+
(0, react_jsx_runtime.jsx)("p", { children: inspector.tag.description || t("noDescription") })
|
|
1476
|
+
]
|
|
1477
|
+
}) : inspector?.kind === "module" ? (0, react_jsx_runtime.jsxs)("div", {
|
|
1478
|
+
className: L5xBody_module_css_default.propertyList,
|
|
1479
|
+
children: [
|
|
1480
|
+
(0, react_jsx_runtime.jsx)("strong", { children: inspector.module.name }),
|
|
1481
|
+
(0, react_jsx_runtime.jsxs)("span", { children: [
|
|
1482
|
+
t("type"),
|
|
1483
|
+
": ",
|
|
1484
|
+
inspector.module.catalog
|
|
1485
|
+
] }),
|
|
1486
|
+
(0, react_jsx_runtime.jsxs)("span", { children: [
|
|
1487
|
+
t("parent"),
|
|
1488
|
+
": ",
|
|
1489
|
+
inspector.module.parent || "—"
|
|
1490
|
+
] }),
|
|
1491
|
+
(0, react_jsx_runtime.jsxs)("span", { children: [
|
|
1492
|
+
t("slot"),
|
|
1493
|
+
": ",
|
|
1494
|
+
inspector.module.slot || "—"
|
|
1495
|
+
] }),
|
|
1496
|
+
(0, react_jsx_runtime.jsx)("p", { children: inspector.module.description || t("noDescription") })
|
|
1497
|
+
]
|
|
1498
|
+
}) : (0, react_jsx_runtime.jsxs)("div", {
|
|
1499
|
+
className: L5xBody_module_css_default.propertyList,
|
|
1500
|
+
children: [
|
|
1501
|
+
(0, react_jsx_runtime.jsx)("strong", { children: selected?.name ?? project.name }),
|
|
1502
|
+
(0, react_jsx_runtime.jsxs)("span", { children: [
|
|
1503
|
+
t("type"),
|
|
1504
|
+
": ",
|
|
1505
|
+
selected?.type ?? project.processor
|
|
1506
|
+
] }),
|
|
1507
|
+
(0, react_jsx_runtime.jsxs)("span", { children: [
|
|
1508
|
+
t("programs"),
|
|
1509
|
+
": ",
|
|
1510
|
+
selectedProgram?.name ?? "—"
|
|
1511
|
+
] }),
|
|
1512
|
+
selected?.type === "RLL" && (0, react_jsx_runtime.jsxs)("span", { children: [
|
|
1513
|
+
t("rungCount"),
|
|
1514
|
+
": ",
|
|
1515
|
+
selected.rungs.length
|
|
1516
|
+
] }),
|
|
1517
|
+
selectedProgram !== void 0 && (0, react_jsx_runtime.jsxs)("span", { children: [
|
|
1518
|
+
t("mainRoutine"),
|
|
1519
|
+
": ",
|
|
1520
|
+
selectedProgram.main || "—"
|
|
1521
|
+
] }),
|
|
1522
|
+
(0, react_jsx_runtime.jsx)("p", { children: selected?.description || selectedProgram?.description || t("noDescription") })
|
|
1523
|
+
]
|
|
1524
|
+
}),
|
|
1525
|
+
(0, react_jsx_runtime.jsx)("div", {
|
|
1526
|
+
className: L5xBody_module_css_default.inspectorHeading,
|
|
1527
|
+
children: t("aryaActions")
|
|
1528
|
+
}),
|
|
1529
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
1530
|
+
type: "button",
|
|
1531
|
+
className: L5xBody_module_css_default.actionButton,
|
|
1532
|
+
onClick: () => {
|
|
1533
|
+
setChatUnavailable(!ask(resourceAddress, `Review ${current?.program}/${current?.routine} in the offline L5X using the engineering plugin. Explain the logic and cite source rungs.`));
|
|
1534
|
+
},
|
|
1535
|
+
children: t("explainRoutine")
|
|
1536
|
+
}),
|
|
1537
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
1538
|
+
type: "button",
|
|
1539
|
+
className: L5xBody_module_css_default.actionButton,
|
|
1540
|
+
onClick: () => {
|
|
1541
|
+
setChatUnavailable(!ask(resourceAddress, `Propose an offline edit to ${current?.program}/${current?.routine} in this L5X. Show the before/after and validation evidence. Do not connect online or download.`));
|
|
1542
|
+
},
|
|
1543
|
+
children: t("askChange")
|
|
1544
|
+
}),
|
|
1545
|
+
(0, react_jsx_runtime.jsx)("p", {
|
|
1546
|
+
className: L5xBody_module_css_default.inspectorHelp,
|
|
1547
|
+
children: t("agentEdits")
|
|
1548
|
+
})
|
|
1549
|
+
]
|
|
1550
|
+
})
|
|
1551
|
+
]
|
|
1552
|
+
}),
|
|
1553
|
+
(0, react_jsx_runtime.jsxs)("section", {
|
|
1554
|
+
className: L5xBody_module_css_default.bottomPanel,
|
|
1555
|
+
children: [(0, react_jsx_runtime.jsxs)("div", {
|
|
1556
|
+
className: L5xBody_module_css_default.bottomTabs,
|
|
1557
|
+
children: [
|
|
1558
|
+
(0, react_jsx_runtime.jsxs)("button", {
|
|
1559
|
+
type: "button",
|
|
1560
|
+
className: bottomPane === "search" ? L5xBody_module_css_default.bottomActive : "",
|
|
1561
|
+
onClick: () => {
|
|
1562
|
+
setBottomPane("search");
|
|
1563
|
+
},
|
|
1564
|
+
children: [
|
|
1565
|
+
t("references"),
|
|
1566
|
+
" ",
|
|
1567
|
+
query !== "" && `(${references.length})`
|
|
1568
|
+
]
|
|
1569
|
+
}),
|
|
1570
|
+
(0, react_jsx_runtime.jsxs)("button", {
|
|
1571
|
+
type: "button",
|
|
1572
|
+
className: bottomPane === "watch" ? L5xBody_module_css_default.bottomActive : "",
|
|
1573
|
+
onClick: () => {
|
|
1574
|
+
setBottomPane("watch");
|
|
1575
|
+
},
|
|
1576
|
+
children: [
|
|
1577
|
+
t("offlineWatch"),
|
|
1578
|
+
" (",
|
|
1579
|
+
watch.length,
|
|
1580
|
+
")"
|
|
1581
|
+
]
|
|
1582
|
+
}),
|
|
1583
|
+
(0, react_jsx_runtime.jsxs)("button", {
|
|
1584
|
+
type: "button",
|
|
1585
|
+
className: bottomPane === "diagnostics" ? L5xBody_module_css_default.bottomActive : "",
|
|
1586
|
+
onClick: () => {
|
|
1587
|
+
setBottomPane("diagnostics");
|
|
1588
|
+
},
|
|
1589
|
+
children: [
|
|
1590
|
+
t("reviewHints"),
|
|
1591
|
+
" (",
|
|
1592
|
+
findings.length,
|
|
1593
|
+
")"
|
|
1594
|
+
]
|
|
1595
|
+
})
|
|
1596
|
+
]
|
|
1597
|
+
}), (0, react_jsx_runtime.jsxs)("div", {
|
|
1598
|
+
className: L5xBody_module_css_default.bottomContent,
|
|
1599
|
+
children: [
|
|
1600
|
+
bottomPane === "diagnostics" && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
1601
|
+
(0, react_jsx_runtime.jsx)("p", {
|
|
1602
|
+
className: L5xBody_module_css_default.panelNote,
|
|
1603
|
+
children: t("reviewHintScope")
|
|
1604
|
+
}),
|
|
1605
|
+
findings.length === 0 ? (0, react_jsx_runtime.jsx)("p", {
|
|
1606
|
+
className: L5xBody_module_css_default.empty,
|
|
1607
|
+
children: t("noFindings")
|
|
1608
|
+
}) : findings.slice(0, 100).map((finding, index) => (0, react_jsx_runtime.jsxs)("div", {
|
|
1609
|
+
className: L5xBody_module_css_default.finding,
|
|
1610
|
+
children: [
|
|
1611
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
1612
|
+
className: finding.severity === "warning" ? L5xBody_module_css_default.warning : L5xBody_module_css_default.info,
|
|
1613
|
+
children: finding.severity
|
|
1614
|
+
}),
|
|
1615
|
+
(0, react_jsx_runtime.jsx)("strong", { children: finding.location }),
|
|
1616
|
+
(0, react_jsx_runtime.jsx)("span", { children: finding.message }),
|
|
1617
|
+
(0, react_jsx_runtime.jsx)("code", { children: finding.evidence })
|
|
1618
|
+
]
|
|
1619
|
+
}, `${finding.code}/${finding.location}/${index}`)),
|
|
1620
|
+
findings.length > 100 && (0, react_jsx_runtime.jsxs)("p", {
|
|
1621
|
+
className: L5xBody_module_css_default.empty,
|
|
1622
|
+
children: [
|
|
1623
|
+
t("moreFindings"),
|
|
1624
|
+
" (",
|
|
1625
|
+
findings.length - 100,
|
|
1626
|
+
")"
|
|
1627
|
+
]
|
|
1628
|
+
})
|
|
1629
|
+
] }),
|
|
1630
|
+
bottomPane === "search" && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
1631
|
+
(0, react_jsx_runtime.jsxs)("label", {
|
|
1632
|
+
className: L5xBody_module_css_default.searchLabel,
|
|
1633
|
+
children: [t("find"), (0, react_jsx_runtime.jsx)("input", {
|
|
1634
|
+
value: query,
|
|
1635
|
+
onChange: (event) => {
|
|
1636
|
+
setQuery(event.target.value);
|
|
1637
|
+
setShowReferences(true);
|
|
1638
|
+
},
|
|
1639
|
+
placeholder: t("findPlaceholder")
|
|
1640
|
+
})]
|
|
1641
|
+
}),
|
|
1642
|
+
showReferences && query !== "" && (0, react_jsx_runtime.jsx)("p", {
|
|
1643
|
+
className: L5xBody_module_css_default.panelNote,
|
|
1644
|
+
children: t("referenceScope")
|
|
1645
|
+
}),
|
|
1646
|
+
showReferences && query !== "" && (references.length === 0 ? (0, react_jsx_runtime.jsx)("p", {
|
|
1647
|
+
className: L5xBody_module_css_default.empty,
|
|
1648
|
+
children: t("noResults")
|
|
1649
|
+
}) : references.slice(0, 200).map((reference, index) => (0, react_jsx_runtime.jsxs)("button", {
|
|
1650
|
+
type: "button",
|
|
1651
|
+
className: L5xBody_module_css_default.reference,
|
|
1652
|
+
onClick: () => {
|
|
1653
|
+
selectReference(reference.program, reference.routine, reference.location);
|
|
1654
|
+
},
|
|
1655
|
+
onContextMenu: (event) => {
|
|
1656
|
+
contextFor(event, reference.text, query);
|
|
1657
|
+
},
|
|
1658
|
+
children: [
|
|
1659
|
+
(0, react_jsx_runtime.jsxs)("strong", { children: [
|
|
1660
|
+
reference.program,
|
|
1661
|
+
" / ",
|
|
1662
|
+
reference.routine
|
|
1663
|
+
] }),
|
|
1664
|
+
(0, react_jsx_runtime.jsx)("span", { children: reference.location }),
|
|
1665
|
+
(0, react_jsx_runtime.jsx)("code", { children: reference.text })
|
|
1666
|
+
]
|
|
1667
|
+
}, `${reference.program}/${reference.routine}/${reference.location}/${index}`))),
|
|
1668
|
+
references.length > 200 && (0, react_jsx_runtime.jsxs)("p", {
|
|
1669
|
+
className: L5xBody_module_css_default.empty,
|
|
1670
|
+
children: [
|
|
1671
|
+
t("moreResults"),
|
|
1672
|
+
" (",
|
|
1673
|
+
references.length - 200,
|
|
1674
|
+
")"
|
|
1675
|
+
]
|
|
1676
|
+
})
|
|
1677
|
+
] }),
|
|
1678
|
+
bottomPane === "watch" && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)("p", {
|
|
1679
|
+
className: L5xBody_module_css_default.panelNote,
|
|
1680
|
+
children: t("watchScope")
|
|
1681
|
+
}), watch.length === 0 ? (0, react_jsx_runtime.jsx)("p", {
|
|
1682
|
+
className: L5xBody_module_css_default.empty,
|
|
1683
|
+
children: t("watchEmpty")
|
|
1684
|
+
}) : (0, react_jsx_runtime.jsxs)("table", {
|
|
1685
|
+
className: L5xBody_module_css_default.watchTable,
|
|
1686
|
+
children: [(0, react_jsx_runtime.jsx)("thead", { children: (0, react_jsx_runtime.jsxs)("tr", { children: [
|
|
1687
|
+
(0, react_jsx_runtime.jsx)("th", { children: t("name") }),
|
|
1688
|
+
(0, react_jsx_runtime.jsx)("th", { children: t("scope") }),
|
|
1689
|
+
(0, react_jsx_runtime.jsx)("th", { children: t("type") }),
|
|
1690
|
+
(0, react_jsx_runtime.jsx)("th", { children: t("exportedValue") }),
|
|
1691
|
+
(0, react_jsx_runtime.jsx)("th", { children: t("context") })
|
|
1692
|
+
] }) }), (0, react_jsx_runtime.jsx)("tbody", { children: watch.map((symbol) => {
|
|
1693
|
+
const detail = detailFor(symbol);
|
|
1694
|
+
return (0, react_jsx_runtime.jsxs)("tr", { children: [
|
|
1695
|
+
(0, react_jsx_runtime.jsx)("th", { children: symbol }),
|
|
1696
|
+
(0, react_jsx_runtime.jsx)("td", { children: detail?.tag?.scope ?? "—" }),
|
|
1697
|
+
(0, react_jsx_runtime.jsx)("td", { children: detail?.datum?.type ?? detail?.tag?.type ?? "—" }),
|
|
1698
|
+
(0, react_jsx_runtime.jsx)("td", {
|
|
1699
|
+
className: L5xBody_module_css_default.noValue,
|
|
1700
|
+
children: detail?.datum?.value ?? t("noExportedValue")
|
|
1701
|
+
}),
|
|
1702
|
+
(0, react_jsx_runtime.jsxs)("td", {
|
|
1703
|
+
className: L5xBody_module_css_default.tagActions,
|
|
1704
|
+
children: [
|
|
1705
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
1706
|
+
type: "button",
|
|
1707
|
+
onClick: () => {
|
|
1708
|
+
find(symbol);
|
|
1709
|
+
},
|
|
1710
|
+
children: t("references")
|
|
1711
|
+
}),
|
|
1712
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
1713
|
+
type: "button",
|
|
1714
|
+
onClick: () => {
|
|
1715
|
+
setChatUnavailable(!ask(resourceAddress, `Read the current value of ${symbol} only if the installed read-only live diagnostic tool supports its type and controller path. Report the observed timestamp and limitations. Do not write data, force, download, or change controller mode.`));
|
|
1716
|
+
},
|
|
1717
|
+
children: t("askLiveRead")
|
|
1718
|
+
}),
|
|
1719
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
1720
|
+
type: "button",
|
|
1721
|
+
onClick: () => {
|
|
1722
|
+
setWatch((previous) => previous.filter((item) => item !== symbol));
|
|
1723
|
+
},
|
|
1724
|
+
"aria-label": `${t("removeWatch")} ${symbol}`,
|
|
1725
|
+
children: "×"
|
|
1726
|
+
})
|
|
1727
|
+
]
|
|
1728
|
+
})
|
|
1729
|
+
] }, symbol);
|
|
1730
|
+
}) })]
|
|
1731
|
+
})] })
|
|
1732
|
+
]
|
|
1733
|
+
})]
|
|
1734
|
+
}),
|
|
1735
|
+
context !== void 0 && (0, react_dom.createPortal)((0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.MenuSurface, {
|
|
1736
|
+
className: L5xBody_module_css_default.menu,
|
|
1737
|
+
role: "menu",
|
|
1738
|
+
style: {
|
|
1739
|
+
left: context.x,
|
|
1740
|
+
top: context.y
|
|
1741
|
+
},
|
|
1742
|
+
onPointerDown: (event) => {
|
|
1743
|
+
event.stopPropagation();
|
|
1744
|
+
},
|
|
1745
|
+
children: [
|
|
1746
|
+
context.instruction !== void 0 && (0, react_jsx_runtime.jsxs)("div", {
|
|
1747
|
+
className: L5xBody_module_css_default.menuHeading,
|
|
1748
|
+
children: [
|
|
1749
|
+
context.instruction.mnemonic,
|
|
1750
|
+
" · ",
|
|
1751
|
+
t("rung"),
|
|
1752
|
+
" ",
|
|
1753
|
+
context.instruction.rung
|
|
1754
|
+
]
|
|
1755
|
+
}),
|
|
1756
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
1757
|
+
type: "button",
|
|
1758
|
+
role: "menuitem",
|
|
1759
|
+
className: L5xBody_module_css_default.menuItem,
|
|
1760
|
+
onClick: () => {
|
|
1761
|
+
setChatUnavailable(!ask(resourceAddress, context.instruction === void 0 ? `Context: ${context.text}` : `Explain ${context.text} at ${current?.program}/${current?.routine} rung ${context.instruction.rung} using the offline L5X engineering tools. Identify symbols, scope, and evidence. Do not assume exported values are live.`));
|
|
1762
|
+
setContext(void 0);
|
|
1763
|
+
},
|
|
1764
|
+
children: context.instruction === void 0 ? t("ask") : t("askInstruction")
|
|
1765
|
+
}),
|
|
1766
|
+
context.instruction?.mnemonic.toUpperCase() === "JSR" && selectedProgram?.routines.some((routine) => routine.name === displayOperands("JSR", context.instruction?.operands ?? "")[0]) && (0, react_jsx_runtime.jsx)("button", {
|
|
1767
|
+
type: "button",
|
|
1768
|
+
role: "menuitem",
|
|
1769
|
+
className: L5xBody_module_css_default.menuItem,
|
|
1770
|
+
onClick: () => {
|
|
1771
|
+
const called = selectedProgram.routines.find((routine) => routine.name === displayOperands("JSR", context.instruction?.operands ?? "")[0]);
|
|
1772
|
+
if (called !== void 0) select(selectedProgram, called);
|
|
1773
|
+
setContext(void 0);
|
|
1774
|
+
},
|
|
1775
|
+
children: t("openCalledRoutine")
|
|
1776
|
+
}),
|
|
1777
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
1778
|
+
type: "button",
|
|
1779
|
+
role: "menuitem",
|
|
1780
|
+
className: L5xBody_module_css_default.menuItem,
|
|
1781
|
+
onClick: () => {
|
|
1782
|
+
find(context.symbol ?? context.text);
|
|
1783
|
+
},
|
|
1784
|
+
children: t("references")
|
|
1785
|
+
}),
|
|
1786
|
+
context.symbol !== void 0 && (0, react_jsx_runtime.jsx)("button", {
|
|
1787
|
+
type: "button",
|
|
1788
|
+
role: "menuitem",
|
|
1789
|
+
className: L5xBody_module_css_default.menuItem,
|
|
1790
|
+
onClick: () => {
|
|
1791
|
+
addWatch(context.symbol ?? "");
|
|
1792
|
+
},
|
|
1793
|
+
children: t("addWatch")
|
|
1794
|
+
}),
|
|
1795
|
+
context.symbol !== void 0 && (0, react_jsx_runtime.jsx)("button", {
|
|
1796
|
+
type: "button",
|
|
1797
|
+
role: "menuitem",
|
|
1798
|
+
className: L5xBody_module_css_default.menuItem,
|
|
1799
|
+
onClick: () => {
|
|
1800
|
+
setChatUnavailable(!ask(resourceAddress, `Read the current value of ${context.symbol} only if the installed read-only live diagnostic tool supports its type and controller path. Report timestamp and limitations; do not write, force, download, or change controller mode.`));
|
|
1801
|
+
setContext(void 0);
|
|
1802
|
+
},
|
|
1803
|
+
children: t("askLiveRead")
|
|
1804
|
+
}),
|
|
1805
|
+
context.symbol !== void 0 && (0, react_jsx_runtime.jsx)("button", {
|
|
1806
|
+
type: "button",
|
|
1807
|
+
role: "menuitem",
|
|
1808
|
+
className: L5xBody_module_css_default.menuItem,
|
|
1809
|
+
onClick: () => {
|
|
1810
|
+
navigator.clipboard.writeText(context.symbol ?? "");
|
|
1811
|
+
setContext(void 0);
|
|
1812
|
+
},
|
|
1813
|
+
children: t("copyOperand")
|
|
1814
|
+
}),
|
|
1815
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
1816
|
+
type: "button",
|
|
1817
|
+
role: "menuitem",
|
|
1818
|
+
className: L5xBody_module_css_default.menuItem,
|
|
1819
|
+
onClick: () => {
|
|
1820
|
+
navigator.clipboard.writeText(context.text);
|
|
1821
|
+
setContext(void 0);
|
|
1822
|
+
},
|
|
1823
|
+
children: context.instruction === void 0 ? t("copy") : t("copyInstruction")
|
|
1824
|
+
}),
|
|
1825
|
+
context.instruction !== void 0 && (0, react_jsx_runtime.jsx)("button", {
|
|
1826
|
+
type: "button",
|
|
1827
|
+
role: "menuitem",
|
|
1828
|
+
className: L5xBody_module_css_default.menuItem,
|
|
1829
|
+
onClick: () => {
|
|
1830
|
+
navigator.clipboard.writeText(selected?.rungs.find((rung) => rung.number === context.instruction?.rung)?.text ?? context.text);
|
|
1831
|
+
setContext(void 0);
|
|
1832
|
+
},
|
|
1833
|
+
children: t("copyRung")
|
|
1834
|
+
}),
|
|
1835
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
1836
|
+
type: "button",
|
|
1837
|
+
role: "menuitem",
|
|
1838
|
+
className: L5xBody_module_css_default.menuItem,
|
|
1839
|
+
onClick: () => {
|
|
1840
|
+
setChatUnavailable(!ask(resourceAddress, `Propose an offline change for ${context.text}${context.instruction === void 0 ? "" : ` at ${current?.program}/${current?.routine} rung ${context.instruction.rung}`} in this L5X export. Show the exact before/after and validation evidence. Do not connect online or download.`));
|
|
1841
|
+
setContext(void 0);
|
|
1842
|
+
},
|
|
1843
|
+
children: t("askChange")
|
|
1844
|
+
})
|
|
1845
|
+
]
|
|
1846
|
+
}), document.body)
|
|
1847
|
+
]
|
|
1848
|
+
});
|
|
1849
|
+
}
|
|
1850
|
+
function TagTable({ tags, filter, setFilter, onContext, onWatch, onFind, getDatum, t }) {
|
|
1851
|
+
const needle = filter.trim().toLowerCase();
|
|
1852
|
+
const visible = tags.filter((tag) => needle === "" || tag.name.toLowerCase().includes(needle) || tag.type.toLowerCase().includes(needle)).slice(0, 250);
|
|
1853
|
+
return (0, react_jsx_runtime.jsxs)("div", {
|
|
1854
|
+
className: L5xBody_module_css_default.tagTableView,
|
|
1855
|
+
children: [
|
|
1856
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
1857
|
+
className: L5xBody_module_css_default.tagTableToolbar,
|
|
1858
|
+
children: [
|
|
1859
|
+
(0, react_jsx_runtime.jsxs)("label", { children: [
|
|
1860
|
+
t("find"),
|
|
1861
|
+
" ",
|
|
1862
|
+
(0, react_jsx_runtime.jsx)("input", {
|
|
1863
|
+
value: filter,
|
|
1864
|
+
onChange: (event) => {
|
|
1865
|
+
setFilter(event.target.value);
|
|
1866
|
+
},
|
|
1867
|
+
placeholder: t("findTag")
|
|
1868
|
+
})
|
|
1869
|
+
] }),
|
|
1870
|
+
(0, react_jsx_runtime.jsxs)("span", { children: [
|
|
1871
|
+
visible.length,
|
|
1872
|
+
" / ",
|
|
1873
|
+
tags.length
|
|
1874
|
+
] }),
|
|
1875
|
+
(0, react_jsx_runtime.jsx)("span", { children: t("exportedValueScope") })
|
|
1876
|
+
]
|
|
1877
|
+
}),
|
|
1878
|
+
(0, react_jsx_runtime.jsxs)("table", {
|
|
1879
|
+
className: L5xBody_module_css_default.tagTable,
|
|
1880
|
+
children: [(0, react_jsx_runtime.jsx)("thead", { children: (0, react_jsx_runtime.jsxs)("tr", { children: [
|
|
1881
|
+
(0, react_jsx_runtime.jsx)("th", { children: t("name") }),
|
|
1882
|
+
(0, react_jsx_runtime.jsx)("th", { children: t("exportedValue") }),
|
|
1883
|
+
(0, react_jsx_runtime.jsx)("th", { children: t("type") }),
|
|
1884
|
+
(0, react_jsx_runtime.jsx)("th", { children: t("alias") }),
|
|
1885
|
+
(0, react_jsx_runtime.jsx)("th", { children: t("description") }),
|
|
1886
|
+
(0, react_jsx_runtime.jsx)("th", { children: t("context") })
|
|
1887
|
+
] }) }), (0, react_jsx_runtime.jsx)("tbody", { children: visible.map((tag) => (0, react_jsx_runtime.jsxs)("tr", {
|
|
1888
|
+
onContextMenu: (event) => {
|
|
1889
|
+
onContext(event, tag.name, tag.name);
|
|
1890
|
+
},
|
|
1891
|
+
children: [
|
|
1892
|
+
(0, react_jsx_runtime.jsx)("th", {
|
|
1893
|
+
title: tag.scope,
|
|
1894
|
+
children: tag.name
|
|
1895
|
+
}),
|
|
1896
|
+
(0, react_jsx_runtime.jsx)("td", {
|
|
1897
|
+
className: L5xBody_module_css_default.noValue,
|
|
1898
|
+
children: getDatum(tag)?.value ?? t("noExportedValue")
|
|
1899
|
+
}),
|
|
1900
|
+
(0, react_jsx_runtime.jsx)("td", { children: tag.type }),
|
|
1901
|
+
(0, react_jsx_runtime.jsx)("td", { children: tag.alias }),
|
|
1902
|
+
(0, react_jsx_runtime.jsx)("td", {
|
|
1903
|
+
title: tag.description,
|
|
1904
|
+
children: tag.description
|
|
1905
|
+
}),
|
|
1906
|
+
(0, react_jsx_runtime.jsxs)("td", {
|
|
1907
|
+
className: L5xBody_module_css_default.tagActions,
|
|
1908
|
+
children: [(0, react_jsx_runtime.jsx)("button", {
|
|
1909
|
+
type: "button",
|
|
1910
|
+
onClick: () => {
|
|
1911
|
+
onWatch(tag.name);
|
|
1912
|
+
},
|
|
1913
|
+
children: t("addWatch")
|
|
1914
|
+
}), (0, react_jsx_runtime.jsx)("button", {
|
|
1915
|
+
type: "button",
|
|
1916
|
+
onClick: () => {
|
|
1917
|
+
onFind(tag.name);
|
|
1918
|
+
},
|
|
1919
|
+
children: t("references")
|
|
1920
|
+
})]
|
|
1921
|
+
})
|
|
1922
|
+
]
|
|
1923
|
+
}, `${tag.scope}/${tag.name}`)) })]
|
|
1924
|
+
}),
|
|
1925
|
+
tags.length > 250 && (0, react_jsx_runtime.jsx)("p", {
|
|
1926
|
+
className: L5xBody_module_css_default.panelNote,
|
|
1927
|
+
children: t("filterMore")
|
|
1928
|
+
})
|
|
1929
|
+
]
|
|
1930
|
+
});
|
|
1931
|
+
}
|
|
1932
|
+
function RungView({ rung, showSource, describeOperand, detailFor, focused, focusedRung, onFocusRung, onFocusInstruction, onContext, t }) {
|
|
1933
|
+
const segments = ladderSegments(rung.text);
|
|
1934
|
+
return (0, react_jsx_runtime.jsxs)("section", {
|
|
1935
|
+
className: `${L5xBody_module_css_default.rung} ${focusedRung === rung.number ? L5xBody_module_css_default.focusedRung : ""}`,
|
|
1936
|
+
"data-rung": rung.number,
|
|
1937
|
+
onContextMenu: (event) => {
|
|
1938
|
+
onContext(event, rung.text);
|
|
1939
|
+
},
|
|
1940
|
+
children: [(0, react_jsx_runtime.jsx)("button", {
|
|
1941
|
+
type: "button",
|
|
1942
|
+
className: L5xBody_module_css_default.rungNumber,
|
|
1943
|
+
"aria-pressed": focusedRung === rung.number && focused === void 0,
|
|
1944
|
+
"aria-label": `${t("focusRung")} ${rung.number}`,
|
|
1945
|
+
onClick: () => {
|
|
1946
|
+
onFocusRung(rung.number);
|
|
1947
|
+
},
|
|
1948
|
+
onContextMenu: (event) => {
|
|
1949
|
+
event.stopPropagation();
|
|
1950
|
+
onFocusRung(rung.number);
|
|
1951
|
+
onContext(event, rung.text);
|
|
1952
|
+
},
|
|
1953
|
+
children: rung.number
|
|
1954
|
+
}), (0, react_jsx_runtime.jsxs)("div", {
|
|
1955
|
+
className: L5xBody_module_css_default.rungBody,
|
|
1956
|
+
children: [
|
|
1957
|
+
rung.comment !== "" && (0, react_jsx_runtime.jsx)("div", {
|
|
1958
|
+
className: L5xBody_module_css_default.comment,
|
|
1959
|
+
children: rung.comment
|
|
1960
|
+
}),
|
|
1961
|
+
segments === null ? (0, react_jsx_runtime.jsx)("code", {
|
|
1962
|
+
className: L5xBody_module_css_default.neutral,
|
|
1963
|
+
children: rung.text
|
|
1964
|
+
}) : (0, react_jsx_runtime.jsx)("div", {
|
|
1965
|
+
className: L5xBody_module_css_default.rail,
|
|
1966
|
+
children: (0, react_jsx_runtime.jsx)(LadderPath, {
|
|
1967
|
+
segments,
|
|
1968
|
+
path: rung.number,
|
|
1969
|
+
rung: rung.number,
|
|
1970
|
+
depth: 0,
|
|
1971
|
+
describeOperand,
|
|
1972
|
+
detailFor,
|
|
1973
|
+
focused,
|
|
1974
|
+
onFocusInstruction,
|
|
1975
|
+
onContext,
|
|
1976
|
+
t
|
|
1977
|
+
})
|
|
1978
|
+
}),
|
|
1979
|
+
showSource && (0, react_jsx_runtime.jsx)("code", {
|
|
1980
|
+
className: L5xBody_module_css_default.source,
|
|
1981
|
+
children: rung.text
|
|
1982
|
+
})
|
|
1983
|
+
]
|
|
1984
|
+
})]
|
|
1985
|
+
});
|
|
1986
|
+
}
|
|
1987
|
+
function LadderPath({ segments, path, rung, depth, describeOperand, detailFor, focused, onFocusInstruction, onContext, t }) {
|
|
1988
|
+
return (0, react_jsx_runtime.jsx)("div", {
|
|
1989
|
+
className: `${L5xBody_module_css_default.path} ${depth > 0 ? L5xBody_module_css_default.nestedPath : ""}`,
|
|
1990
|
+
children: segments.map((segment, index) => "paths" in segment ? (0, react_jsx_runtime.jsx)("div", {
|
|
1991
|
+
className: L5xBody_module_css_default.branch,
|
|
1992
|
+
children: segment.paths.map((branchSegments, pathIndex) => (0, react_jsx_runtime.jsx)("div", {
|
|
1993
|
+
className: L5xBody_module_css_default.branchPath,
|
|
1994
|
+
children: (0, react_jsx_runtime.jsx)(LadderPath, {
|
|
1995
|
+
segments: branchSegments,
|
|
1996
|
+
path: `${path}/${index}/${pathIndex}`,
|
|
1997
|
+
rung,
|
|
1998
|
+
depth: depth + 1,
|
|
1999
|
+
describeOperand,
|
|
2000
|
+
detailFor,
|
|
2001
|
+
focused,
|
|
2002
|
+
onFocusInstruction,
|
|
2003
|
+
onContext,
|
|
2004
|
+
t
|
|
2005
|
+
})
|
|
2006
|
+
}, pathIndex))
|
|
2007
|
+
}, index) : (() => {
|
|
2008
|
+
const id = `${path}/${index}`;
|
|
2009
|
+
const symbol = instructionSymbols(segment.mnemonic, segment.operands)[0];
|
|
2010
|
+
const instruction = {
|
|
2011
|
+
id,
|
|
2012
|
+
rung,
|
|
2013
|
+
mnemonic: segment.mnemonic,
|
|
2014
|
+
operands: segment.operands,
|
|
2015
|
+
...symbol === void 0 ? {} : { symbol }
|
|
2016
|
+
};
|
|
2017
|
+
const detail = symbol === void 0 ? void 0 : detailFor(symbol);
|
|
2018
|
+
const selected = focused?.id === id;
|
|
2019
|
+
return (0, react_jsx_runtime.jsxs)("button", {
|
|
2020
|
+
type: "button",
|
|
2021
|
+
"data-l5x-instruction": true,
|
|
2022
|
+
"data-instruction-id": id,
|
|
2023
|
+
"aria-pressed": selected,
|
|
2024
|
+
"aria-label": `${segment.mnemonic} ${segment.operands}`,
|
|
2025
|
+
className: `${L5xBody_module_css_default.instruction} ${[
|
|
2026
|
+
"XIC",
|
|
2027
|
+
"XIO",
|
|
2028
|
+
"OTE",
|
|
2029
|
+
"OTL",
|
|
2030
|
+
"OTU"
|
|
2031
|
+
].includes(segment.mnemonic.toUpperCase()) ? "" : L5xBody_module_css_default.functionBlock} ${index === segments.length - 1 && terminalInstructions.has(segment.mnemonic.toUpperCase()) ? L5xBody_module_css_default.outputInstruction : ""} ${selected ? L5xBody_module_css_default.focusedInstruction : ""}`,
|
|
2032
|
+
onFocus: () => {
|
|
2033
|
+
onFocusInstruction(instruction);
|
|
2034
|
+
},
|
|
2035
|
+
onClick: () => {
|
|
2036
|
+
onFocusInstruction(instruction);
|
|
2037
|
+
},
|
|
2038
|
+
onKeyDown: (event) => {
|
|
2039
|
+
if (![
|
|
2040
|
+
"ArrowLeft",
|
|
2041
|
+
"ArrowRight",
|
|
2042
|
+
"ArrowUp",
|
|
2043
|
+
"ArrowDown",
|
|
2044
|
+
"Home",
|
|
2045
|
+
"End"
|
|
2046
|
+
].includes(event.key)) return;
|
|
2047
|
+
const buttons = Array.from(event.currentTarget.closest("[data-l5x-project-view]")?.querySelectorAll("button[data-l5x-instruction]") ?? []);
|
|
2048
|
+
const position = buttons.indexOf(event.currentTarget);
|
|
2049
|
+
const next = event.key === "Home" ? 0 : event.key === "End" ? buttons.length - 1 : Math.max(0, Math.min(buttons.length - 1, position + (event.key === "ArrowLeft" || event.key === "ArrowUp" ? -1 : 1)));
|
|
2050
|
+
event.preventDefault();
|
|
2051
|
+
buttons[next]?.focus();
|
|
2052
|
+
},
|
|
2053
|
+
onContextMenu: (event) => {
|
|
2054
|
+
event.stopPropagation();
|
|
2055
|
+
event.currentTarget.focus();
|
|
2056
|
+
onContext(event, `${segment.mnemonic}(${segment.operands})`, symbol, instruction);
|
|
2057
|
+
},
|
|
2058
|
+
children: [
|
|
2059
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
2060
|
+
className: L5xBody_module_css_default.instructionDescription,
|
|
2061
|
+
title: describeOperand(segment.operands),
|
|
2062
|
+
children: describeOperand(segment.operands)
|
|
2063
|
+
}),
|
|
2064
|
+
[
|
|
2065
|
+
"XIC",
|
|
2066
|
+
"XIO",
|
|
2067
|
+
"OTE",
|
|
2068
|
+
"OTL",
|
|
2069
|
+
"OTU"
|
|
2070
|
+
].includes(segment.mnemonic.toUpperCase()) ? (0, react_jsx_runtime.jsxs)("span", {
|
|
2071
|
+
className: L5xBody_module_css_default.glyph,
|
|
2072
|
+
"aria-hidden": "true",
|
|
2073
|
+
children: [
|
|
2074
|
+
(0, react_jsx_runtime.jsx)("i", { className: L5xBody_module_css_default.wireLead }),
|
|
2075
|
+
segment.mnemonic.toUpperCase().startsWith("X") ? (0, react_jsx_runtime.jsx)("i", { className: `${L5xBody_module_css_default.contact} ${segment.mnemonic.toUpperCase() === "XIO" ? L5xBody_module_css_default.negated : ""}` }) : (0, react_jsx_runtime.jsx)("i", {
|
|
2076
|
+
className: L5xBody_module_css_default.coil,
|
|
2077
|
+
children: segment.mnemonic.toUpperCase() === "OTL" ? t("latchGlyph") : segment.mnemonic.toUpperCase() === "OTU" ? t("unlatchGlyph") : ""
|
|
2078
|
+
}),
|
|
2079
|
+
(0, react_jsx_runtime.jsx)("i", { className: L5xBody_module_css_default.wireLead })
|
|
2080
|
+
]
|
|
2081
|
+
}) : (0, react_jsx_runtime.jsxs)("span", {
|
|
2082
|
+
className: L5xBody_module_css_default.blockCard,
|
|
2083
|
+
children: [(0, react_jsx_runtime.jsx)("strong", { children: segment.mnemonic }), displayOperands(segment.mnemonic, segment.operands).map((operand, operandIndex) => {
|
|
2084
|
+
const operandKey = operandKeys[segment.mnemonic.toUpperCase()]?.[operandIndex];
|
|
2085
|
+
const datum = detailFor(operand)?.datum;
|
|
2086
|
+
const value = datum?.value ?? (/^[-+]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][-+]?\d+)?$/u.test(operand) ? operand : "");
|
|
2087
|
+
const operandSymbol = /^[A-Za-z_][\w:]*/u.test(operand) ? operand : void 0;
|
|
2088
|
+
const operandInstruction = {
|
|
2089
|
+
id,
|
|
2090
|
+
rung,
|
|
2091
|
+
mnemonic: segment.mnemonic,
|
|
2092
|
+
operands: segment.operands,
|
|
2093
|
+
...operandSymbol === void 0 ? {} : { symbol: operandSymbol }
|
|
2094
|
+
};
|
|
2095
|
+
return (0, react_jsx_runtime.jsxs)("span", { children: [
|
|
2096
|
+
(0, react_jsx_runtime.jsx)("small", { children: operandKey === void 0 ? `${operandIndex + 1}` : t(operandKey) }),
|
|
2097
|
+
(0, react_jsx_runtime.jsx)("code", {
|
|
2098
|
+
title: datum === void 0 ? operand : `${operand} · ${t("exportedValue")}: ${datum.value}\n${t("exportedValueScope")}`,
|
|
2099
|
+
onClick: (event) => {
|
|
2100
|
+
event.stopPropagation();
|
|
2101
|
+
event.currentTarget.closest("button")?.focus();
|
|
2102
|
+
onFocusInstruction(operandInstruction);
|
|
2103
|
+
},
|
|
2104
|
+
onContextMenu: (event) => {
|
|
2105
|
+
event.stopPropagation();
|
|
2106
|
+
event.currentTarget.closest("button")?.focus();
|
|
2107
|
+
onContext(event, `${segment.mnemonic}(${segment.operands})`, operandSymbol, operandInstruction);
|
|
2108
|
+
},
|
|
2109
|
+
children: operand
|
|
2110
|
+
}),
|
|
2111
|
+
value !== "" && (0, react_jsx_runtime.jsx)("b", {
|
|
2112
|
+
title: datum === void 0 ? operand : t("exportedValueScope"),
|
|
2113
|
+
children: value
|
|
2114
|
+
})
|
|
2115
|
+
] }, operandIndex);
|
|
2116
|
+
})]
|
|
2117
|
+
}),
|
|
2118
|
+
[
|
|
2119
|
+
"XIC",
|
|
2120
|
+
"XIO",
|
|
2121
|
+
"OTE",
|
|
2122
|
+
"OTL",
|
|
2123
|
+
"OTU"
|
|
2124
|
+
].includes(segment.mnemonic.toUpperCase()) && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
2125
|
+
(0, react_jsx_runtime.jsx)("strong", { children: segment.mnemonic }),
|
|
2126
|
+
(0, react_jsx_runtime.jsx)("code", {
|
|
2127
|
+
title: segment.operands,
|
|
2128
|
+
children: segment.operands
|
|
2129
|
+
}),
|
|
2130
|
+
detail?.datum !== void 0 && (0, react_jsx_runtime.jsxs)("span", {
|
|
2131
|
+
className: L5xBody_module_css_default.exportedChip,
|
|
2132
|
+
title: t("exportedValueScope"),
|
|
2133
|
+
children: [
|
|
2134
|
+
t("exportedValue"),
|
|
2135
|
+
": ",
|
|
2136
|
+
detail.datum.value
|
|
2137
|
+
]
|
|
2138
|
+
})
|
|
2139
|
+
] }),
|
|
2140
|
+
(0, react_jsx_runtime.jsxs)("span", {
|
|
2141
|
+
className: L5xBody_module_css_default.instructionTooltip,
|
|
2142
|
+
role: "tooltip",
|
|
2143
|
+
children: [
|
|
2144
|
+
(0, react_jsx_runtime.jsxs)("strong", { children: [
|
|
2145
|
+
segment.mnemonic,
|
|
2146
|
+
"(",
|
|
2147
|
+
segment.operands,
|
|
2148
|
+
")"
|
|
2149
|
+
] }),
|
|
2150
|
+
(0, react_jsx_runtime.jsxs)("span", { children: [
|
|
2151
|
+
t("rung"),
|
|
2152
|
+
" ",
|
|
2153
|
+
rung
|
|
2154
|
+
] }),
|
|
2155
|
+
detail?.tag !== void 0 && (0, react_jsx_runtime.jsxs)("span", { children: [
|
|
2156
|
+
t("scope"),
|
|
2157
|
+
": ",
|
|
2158
|
+
detail.tag.scope,
|
|
2159
|
+
" · ",
|
|
2160
|
+
t("type"),
|
|
2161
|
+
": ",
|
|
2162
|
+
detail.datum?.type ?? detail.tag.type
|
|
2163
|
+
] }),
|
|
2164
|
+
(0, react_jsx_runtime.jsxs)("span", { children: [
|
|
2165
|
+
t("exportedValue"),
|
|
2166
|
+
": ",
|
|
2167
|
+
detail?.datum?.value ?? t("noExportedValue")
|
|
2168
|
+
] }),
|
|
2169
|
+
detail?.tag?.description && (0, react_jsx_runtime.jsx)("span", { children: detail.tag.description })
|
|
2170
|
+
]
|
|
2171
|
+
})
|
|
2172
|
+
]
|
|
2173
|
+
}, id);
|
|
2174
|
+
})())
|
|
2175
|
+
});
|
|
2176
|
+
}
|
|
2177
|
+
//#endregion
|
|
2178
|
+
//#region lib/types/client/locales.js
|
|
2179
|
+
/** English product strings. */
|
|
2180
|
+
const en = {
|
|
2181
|
+
"operand.source": "Source",
|
|
2182
|
+
"operand.destination": "Dest",
|
|
2183
|
+
"operand.length": "Length",
|
|
2184
|
+
"operand.timer": "Timer",
|
|
2185
|
+
"operand.a": "A",
|
|
2186
|
+
"operand.b": "B",
|
|
2187
|
+
"operand.sourceA": "Source A",
|
|
2188
|
+
"operand.sourceB": "Source B",
|
|
2189
|
+
"operand.routine": "Routine Name",
|
|
2190
|
+
"operand.parameter": "Param",
|
|
2191
|
+
"productMark": "LOGIX",
|
|
2192
|
+
"workspaceMark": "WORKSPACE",
|
|
2193
|
+
"milliseconds": "ms",
|
|
2194
|
+
"latchGlyph": "L",
|
|
2195
|
+
"unlatchGlyph": "U",
|
|
2196
|
+
title: "Logix project",
|
|
2197
|
+
loading: "Reading Logix project…",
|
|
2198
|
+
invalid: "Could not read this L5X export.",
|
|
2199
|
+
retry: "Retry",
|
|
2200
|
+
acd: "ACD is a binary Studio 5000 project. Export an L5X in Studio 5000 to browse it here.",
|
|
2201
|
+
exportAcd: "Ask Arya to prepare an L5X",
|
|
2202
|
+
chatUnavailable: "Arya chat is unavailable in this session.",
|
|
2203
|
+
controller: "Controller",
|
|
2204
|
+
tasks: "Tasks",
|
|
2205
|
+
programs: "Programs",
|
|
2206
|
+
tags: "Controller Tags",
|
|
2207
|
+
programTags: "Parameters and Local Tags",
|
|
2208
|
+
modules: "I/O Configuration",
|
|
2209
|
+
aois: "Add-On Instructions",
|
|
2210
|
+
types: "Data Types",
|
|
2211
|
+
noRoutines: "No routines in this program.",
|
|
2212
|
+
noRungs: "No rungs in this routine.",
|
|
2213
|
+
unsupported: "This routine language is not diagrammed in this view.",
|
|
2214
|
+
find: "Find All",
|
|
2215
|
+
findPlaceholder: "Find symbol or text",
|
|
2216
|
+
references: "Cross References",
|
|
2217
|
+
noResults: "No references found in ladder or Structured Text.",
|
|
2218
|
+
ask: "Ask Arya",
|
|
2219
|
+
copy: "Copy source",
|
|
2220
|
+
referenceScope: "Text matches in ladder and Structured Text; confirm scope in the engineering tools.",
|
|
2221
|
+
monitor: "Monitor",
|
|
2222
|
+
monitorUnavailable: "Live monitor is not connected to this offline export.",
|
|
2223
|
+
source: "Source",
|
|
2224
|
+
inspect: "Inspect",
|
|
2225
|
+
offline: "Offline export",
|
|
2226
|
+
changed: "The file changed while it was being read. Reload this view.",
|
|
2227
|
+
tooLarge: "This export is too large for the project viewer.",
|
|
2228
|
+
routine: "Routine",
|
|
2229
|
+
rung: "Rung",
|
|
2230
|
+
context: "Actions",
|
|
2231
|
+
noSelection: "Select a routine in the project tree.",
|
|
2232
|
+
revision: "Revision {revision}",
|
|
2233
|
+
reloadFile: "Reload export",
|
|
2234
|
+
offlineSource: "OFFLINE · exported source",
|
|
2235
|
+
sourceVersion: "File version",
|
|
2236
|
+
exportRevision: "Studio export",
|
|
2237
|
+
manualImport: "Review and import in Studio 5000",
|
|
2238
|
+
organizer: "Controller Organizer",
|
|
2239
|
+
organizerSearch: "Find task, routine, tag, or module",
|
|
2240
|
+
unscheduled: "Unscheduled Programs",
|
|
2241
|
+
filterMore: "Search to show additional tags",
|
|
2242
|
+
closeTab: "Close",
|
|
2243
|
+
askRoutine: "Ask Arya about routine",
|
|
2244
|
+
loadMore: "Load more rungs",
|
|
2245
|
+
type: "Type",
|
|
2246
|
+
scope: "Scope",
|
|
2247
|
+
alias: "Alias for",
|
|
2248
|
+
parent: "Parent",
|
|
2249
|
+
slot: "Address",
|
|
2250
|
+
rungCount: "Rungs",
|
|
2251
|
+
mainRoutine: "Main routine",
|
|
2252
|
+
noDescription: "No exported description.",
|
|
2253
|
+
aryaActions: "Work with Arya",
|
|
2254
|
+
explainRoutine: "Explain this routine",
|
|
2255
|
+
askChange: "Ask Arya to change offline file",
|
|
2256
|
+
agentEdits: "Arya edits the L5X in the workspace. Reload the export to inspect the saved result before importing it in Studio 5000.",
|
|
2257
|
+
reviewHints: "Source review",
|
|
2258
|
+
reviewHintScope: "Hints from this export only. Use the engineering plugin and Studio 5000 to validate changes.",
|
|
2259
|
+
noFindings: "No source review hints for the structures shown here.",
|
|
2260
|
+
moreFindings: "Additional hints",
|
|
2261
|
+
moreResults: "Additional matches",
|
|
2262
|
+
offlineWatch: "Offline watch",
|
|
2263
|
+
watchScope: "Values shown here come from the saved export. Current controller values are unavailable in this view.",
|
|
2264
|
+
watchEmpty: "Right-click an operand or tag to add it here.",
|
|
2265
|
+
notLive: "No live value",
|
|
2266
|
+
removeWatch: "Remove",
|
|
2267
|
+
addWatch: "Add to offline watch",
|
|
2268
|
+
openTagTable: "Open tag table",
|
|
2269
|
+
offlineValues: "Offline · saved export",
|
|
2270
|
+
findTag: "Find tag or data type",
|
|
2271
|
+
name: "Name",
|
|
2272
|
+
value: "Value",
|
|
2273
|
+
description: "Description",
|
|
2274
|
+
askLiveRead: "Ask Arya to read",
|
|
2275
|
+
focusedInstruction: "Selected instruction",
|
|
2276
|
+
focusedRung: "Selected rung",
|
|
2277
|
+
focusRung: "Select rung",
|
|
2278
|
+
clearFocus: "Clear selection",
|
|
2279
|
+
exportedValue: "Exported value",
|
|
2280
|
+
noExportedValue: "Not present in export",
|
|
2281
|
+
exportedValueScope: "Saved export value; this is not the current controller state.",
|
|
2282
|
+
literal: "Literal",
|
|
2283
|
+
askInstruction: "Ask Arya about instruction",
|
|
2284
|
+
copyOperand: "Copy operand",
|
|
2285
|
+
copyInstruction: "Copy instruction",
|
|
2286
|
+
copyRung: "Copy rung source",
|
|
2287
|
+
openCalledRoutine: "Open called routine",
|
|
2288
|
+
controllerFault: "Controller Fault Handler",
|
|
2289
|
+
powerUp: "Power-Up Handler",
|
|
2290
|
+
motionGroups: "Motion Groups",
|
|
2291
|
+
alarmManager: "Alarm Manager",
|
|
2292
|
+
assets: "Assets",
|
|
2293
|
+
logicalModel: "Logical Model",
|
|
2294
|
+
notExported: "Not included in this L5X export",
|
|
2295
|
+
none: "None"
|
|
2296
|
+
};
|
|
2297
|
+
/** Chinese product strings. */
|
|
2298
|
+
const zh = {
|
|
2299
|
+
"operand.source": "源",
|
|
2300
|
+
"operand.destination": "目标",
|
|
2301
|
+
"operand.length": "长度",
|
|
2302
|
+
"operand.timer": "定时器",
|
|
2303
|
+
"operand.a": "A",
|
|
2304
|
+
"operand.b": "B",
|
|
2305
|
+
"operand.sourceA": "源 A",
|
|
2306
|
+
"operand.sourceB": "源 B",
|
|
2307
|
+
"operand.routine": "例程名称",
|
|
2308
|
+
"operand.parameter": "参数",
|
|
2309
|
+
"productMark": "LOGIX",
|
|
2310
|
+
"workspaceMark": "工作区",
|
|
2311
|
+
"milliseconds": "ms",
|
|
2312
|
+
"latchGlyph": "L",
|
|
2313
|
+
"unlatchGlyph": "U",
|
|
2314
|
+
title: "Logix 项目",
|
|
2315
|
+
loading: "正在读取 Logix 项目…",
|
|
2316
|
+
invalid: "无法读取此 L5X 导出文件。",
|
|
2317
|
+
retry: "重试",
|
|
2318
|
+
acd: "ACD 是 Studio 5000 二进制项目。请在 Studio 5000 中导出 L5X 后在此浏览。",
|
|
2319
|
+
exportAcd: "请 Arya 准备 L5X",
|
|
2320
|
+
chatUnavailable: "此会话中无法使用 Arya 聊天。",
|
|
2321
|
+
controller: "控制器",
|
|
2322
|
+
tasks: "任务",
|
|
2323
|
+
programs: "程序",
|
|
2324
|
+
tags: "控制器标签",
|
|
2325
|
+
programTags: "参数和本地标签",
|
|
2326
|
+
modules: "I/O 配置",
|
|
2327
|
+
aois: "附加指令",
|
|
2328
|
+
types: "数据类型",
|
|
2329
|
+
noRoutines: "此程序没有例程。",
|
|
2330
|
+
noRungs: "此例程没有梯级。",
|
|
2331
|
+
unsupported: "此例程语言尚未在此视图中绘制。",
|
|
2332
|
+
find: "查找全部",
|
|
2333
|
+
findPlaceholder: "查找符号或文本",
|
|
2334
|
+
references: "交叉引用",
|
|
2335
|
+
noResults: "梯形图和结构化文本中没有找到引用。",
|
|
2336
|
+
ask: "询问 Arya",
|
|
2337
|
+
copy: "复制源代码",
|
|
2338
|
+
referenceScope: "梯形图和结构化文本中的文本匹配;请在工程工具中确认作用域。",
|
|
2339
|
+
monitor: "监视",
|
|
2340
|
+
monitorUnavailable: "此离线导出文件未连接实时监视。",
|
|
2341
|
+
source: "源代码",
|
|
2342
|
+
inspect: "检查",
|
|
2343
|
+
offline: "离线导出",
|
|
2344
|
+
changed: "读取期间文件发生变化。请重新加载此视图。",
|
|
2345
|
+
tooLarge: "此导出文件对于项目查看器过大。",
|
|
2346
|
+
routine: "例程",
|
|
2347
|
+
rung: "梯级",
|
|
2348
|
+
context: "操作",
|
|
2349
|
+
noSelection: "在项目树中选择一个例程。",
|
|
2350
|
+
revision: "版本 {revision}",
|
|
2351
|
+
reloadFile: "重新加载导出",
|
|
2352
|
+
offlineSource: "离线 · 导出源码",
|
|
2353
|
+
sourceVersion: "文件版本",
|
|
2354
|
+
exportRevision: "Studio 导出版本",
|
|
2355
|
+
manualImport: "在 Studio 5000 中审核并导入",
|
|
2356
|
+
organizer: "控制器组织器",
|
|
2357
|
+
organizerSearch: "查找任务、例程、标签或模块",
|
|
2358
|
+
unscheduled: "未调度程序",
|
|
2359
|
+
filterMore: "搜索以显示其他标签",
|
|
2360
|
+
closeTab: "关闭",
|
|
2361
|
+
askRoutine: "询问 Arya 此例程",
|
|
2362
|
+
loadMore: "加载更多梯级",
|
|
2363
|
+
type: "类型",
|
|
2364
|
+
scope: "作用域",
|
|
2365
|
+
alias: "别名指向",
|
|
2366
|
+
parent: "父模块",
|
|
2367
|
+
slot: "地址",
|
|
2368
|
+
rungCount: "梯级数",
|
|
2369
|
+
mainRoutine: "主例程",
|
|
2370
|
+
noDescription: "没有导出的说明。",
|
|
2371
|
+
aryaActions: "与 Arya 协作",
|
|
2372
|
+
explainRoutine: "解释此例程",
|
|
2373
|
+
askChange: "请 Arya 修改离线文件",
|
|
2374
|
+
agentEdits: "Arya 修改工作区中的 L5X。请重新加载导出并检查保存结果,然后在 Studio 5000 中导入。",
|
|
2375
|
+
reviewHints: "源文件检查",
|
|
2376
|
+
reviewHintScope: "仅依据此导出提供提示。请使用工程插件和 Studio 5000 验证修改。",
|
|
2377
|
+
noFindings: "当前展示的结构没有源文件检查提示。",
|
|
2378
|
+
moreFindings: "其他提示",
|
|
2379
|
+
moreResults: "其他匹配",
|
|
2380
|
+
offlineWatch: "离线监视列表",
|
|
2381
|
+
watchScope: "此处显示的数值来自已保存的导出文件;当前控制器数值在此视图中不可用。",
|
|
2382
|
+
watchEmpty: "右键单击操作数或标签以添加。",
|
|
2383
|
+
notLive: "无实时数值",
|
|
2384
|
+
removeWatch: "移除",
|
|
2385
|
+
addWatch: "添加到离线监视列表",
|
|
2386
|
+
openTagTable: "打开标签表",
|
|
2387
|
+
offlineValues: "离线 · 已保存的导出文件",
|
|
2388
|
+
findTag: "查找标签或数据类型",
|
|
2389
|
+
name: "名称",
|
|
2390
|
+
value: "数值",
|
|
2391
|
+
description: "说明",
|
|
2392
|
+
askLiveRead: "请 Arya 读取",
|
|
2393
|
+
focusedInstruction: "选中的指令",
|
|
2394
|
+
focusedRung: "选中的梯级",
|
|
2395
|
+
focusRung: "选择梯级",
|
|
2396
|
+
clearFocus: "清除选择",
|
|
2397
|
+
exportedValue: "导出时的数值",
|
|
2398
|
+
noExportedValue: "导出文件中未包含",
|
|
2399
|
+
exportedValueScope: "此值来自已保存的导出文件,不是控制器当前状态。",
|
|
2400
|
+
literal: "常量",
|
|
2401
|
+
askInstruction: "询问 Arya 此指令",
|
|
2402
|
+
copyOperand: "复制操作数",
|
|
2403
|
+
copyInstruction: "复制指令",
|
|
2404
|
+
copyRung: "复制梯级源代码",
|
|
2405
|
+
openCalledRoutine: "打开调用的例程",
|
|
2406
|
+
controllerFault: "控制器故障处理程序",
|
|
2407
|
+
powerUp: "上电处理程序",
|
|
2408
|
+
motionGroups: "运动组",
|
|
2409
|
+
alarmManager: "报警管理器",
|
|
2410
|
+
assets: "资产",
|
|
2411
|
+
logicalModel: "逻辑模型",
|
|
2412
|
+
notExported: "此 L5X 导出中未包含",
|
|
2413
|
+
none: "无"
|
|
2414
|
+
};
|
|
2415
|
+
//#endregion
|
|
2416
|
+
//#region lib/types/client/index.js
|
|
2417
|
+
const ID = "@deepseek-ai/dsh-client-ui-logix-project-viewer";
|
|
2418
|
+
const CHUNK = 1024 * 1024;
|
|
2419
|
+
const LIMIT = 64 * 1024 * 1024;
|
|
2420
|
+
const inject = [
|
|
2421
|
+
"slots",
|
|
2422
|
+
"locale",
|
|
2423
|
+
"documentPreviews",
|
|
2424
|
+
"remote",
|
|
2425
|
+
"remote.workspaceFiles"
|
|
2426
|
+
];
|
|
2427
|
+
function hostFileOf(address) {
|
|
2428
|
+
const parsed = parseFileAddress(address);
|
|
2429
|
+
if (parsed?.scope !== "session") throw new Error(`ui-logix-project-viewer: not a session file address "${address}"`);
|
|
2430
|
+
return {
|
|
2431
|
+
sessionId: parsed.sessionId,
|
|
2432
|
+
path: parsed.path
|
|
2433
|
+
};
|
|
2434
|
+
}
|
|
2435
|
+
function apply(ctx) {
|
|
2436
|
+
let draft = () => false;
|
|
2437
|
+
ctx.inject(["sessions", "conversation"], (scope) => {
|
|
2438
|
+
draft = (address, context) => {
|
|
2439
|
+
const file = hostFileOf(address);
|
|
2440
|
+
const session = scope.sessions.scope(file.sessionId);
|
|
2441
|
+
if (session === void 0) return false;
|
|
2442
|
+
const input = scope.conversation.input.for(session);
|
|
2443
|
+
const existing = input.state.getSnapshot().draft;
|
|
2444
|
+
const prompt = `Review this offline Studio 5000 project at ${file.path}. ${context}`;
|
|
2445
|
+
input.setDraft(existing === "" ? prompt : `${existing}\n${prompt}`);
|
|
2446
|
+
input.focus();
|
|
2447
|
+
return true;
|
|
2448
|
+
};
|
|
2449
|
+
scope.effect(() => () => {
|
|
2450
|
+
draft = () => false;
|
|
2451
|
+
});
|
|
2452
|
+
});
|
|
2453
|
+
ctx.effect(() => ctx.locale.register("sidebarL5x", {
|
|
2454
|
+
en,
|
|
2455
|
+
zh
|
|
2456
|
+
}));
|
|
2457
|
+
const t = ctx.locale.bind("sidebarL5x");
|
|
2458
|
+
const read = async (address, signal) => {
|
|
2459
|
+
const file = hostFileOf(address);
|
|
2460
|
+
const chunks = [];
|
|
2461
|
+
let offset = 0;
|
|
2462
|
+
let version;
|
|
2463
|
+
let total = 0;
|
|
2464
|
+
while (true) {
|
|
2465
|
+
signal.throwIfAborted();
|
|
2466
|
+
const result = await ctx.remote.workspaceFiles.readBytes(file.sessionId, file.path, { range: {
|
|
2467
|
+
offset,
|
|
2468
|
+
length: CHUNK
|
|
2469
|
+
} }, signal);
|
|
2470
|
+
if (!result.ok) throw new Error(result.error.message);
|
|
2471
|
+
const page = result.value;
|
|
2472
|
+
if (version !== void 0 && version !== page.version) throw new Error(t("changed"));
|
|
2473
|
+
version = page.version;
|
|
2474
|
+
total += page.data.length;
|
|
2475
|
+
if (total > LIMIT || page.bytes !== void 0 && page.bytes > LIMIT) throw new Error(t("tooLarge"));
|
|
2476
|
+
chunks.push(page.data);
|
|
2477
|
+
offset += page.data.length;
|
|
2478
|
+
if (page.eof) break;
|
|
2479
|
+
if (page.data.length === 0) throw new Error(t("invalid"));
|
|
2480
|
+
}
|
|
2481
|
+
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
2482
|
+
let xml = "";
|
|
2483
|
+
for (const chunk of chunks) xml += decoder.decode(chunk, { stream: true });
|
|
2484
|
+
xml += decoder.decode();
|
|
2485
|
+
return {
|
|
2486
|
+
xml,
|
|
2487
|
+
version
|
|
2488
|
+
};
|
|
2489
|
+
};
|
|
2490
|
+
const ask = (address, context) => draft(address, context);
|
|
2491
|
+
function WorkspaceLogixBody({ scope, path }) {
|
|
2492
|
+
const [revision, setRevision] = (0, react.useState)(0);
|
|
2493
|
+
return (0, react.createElement)(L5xBody, {
|
|
2494
|
+
resourceAddress: fileAddressFor(scope.sessionId, scope.cwd, path),
|
|
2495
|
+
content: {
|
|
2496
|
+
kind: "renderer",
|
|
2497
|
+
revision,
|
|
2498
|
+
loaded: () => {},
|
|
2499
|
+
failed: () => {},
|
|
2500
|
+
reload: () => {
|
|
2501
|
+
setRevision((value) => value + 1);
|
|
2502
|
+
}
|
|
2503
|
+
},
|
|
2504
|
+
scrollportRef: () => {},
|
|
2505
|
+
t,
|
|
2506
|
+
read,
|
|
2507
|
+
ask
|
|
2508
|
+
});
|
|
2509
|
+
}
|
|
2510
|
+
ctx.inject(["betterSidebar"], (scope) => {
|
|
2511
|
+
scope.effect(() => scope.betterSidebar.registerFileViewer({
|
|
2512
|
+
id: `${ID}:workspace-file`,
|
|
2513
|
+
title: () => t("title"),
|
|
2514
|
+
exts: ["l5x", "acd"],
|
|
2515
|
+
priority: 20,
|
|
2516
|
+
fetchStrategy: "none",
|
|
2517
|
+
component: WorkspaceLogixBody
|
|
2518
|
+
}), "ui-logix-project-viewer: workspace files");
|
|
2519
|
+
});
|
|
2520
|
+
ctx.effect(() => ctx.documentPreviews.register({
|
|
2521
|
+
id: ID,
|
|
2522
|
+
extensions: ["l5x", "acd"],
|
|
2523
|
+
binaryExtensions: ["acd"],
|
|
2524
|
+
priority: "extension",
|
|
2525
|
+
title: () => t("title"),
|
|
2526
|
+
loading: "renderer",
|
|
2527
|
+
wrap: false
|
|
2528
|
+
}));
|
|
2529
|
+
ctx.effect(() => ctx.slots.inject("sidebar.right.tab.document", () => ctx.slots.register({
|
|
2530
|
+
name: "sidebar.right.tab.document",
|
|
2531
|
+
key: ID,
|
|
2532
|
+
locale: "sidebarL5x",
|
|
2533
|
+
inject: () => ({
|
|
2534
|
+
read,
|
|
2535
|
+
ask
|
|
2536
|
+
})
|
|
2537
|
+
}, L5xBody)));
|
|
2538
|
+
}
|
|
2539
|
+
//#endregion
|
|
2540
|
+
exports.apply = apply;
|
|
2541
|
+
exports.inject = inject;
|
|
2542
|
+
return module.exports;
|
|
2543
|
+
|
|
2544
|
+
})()
|
|
2545
|
+
const ftview = (() => {
|
|
2546
|
+
|
|
2547
|
+
const React = require('react')
|
|
2548
|
+
const h = React.createElement
|
|
2549
|
+
const { useState, useMemo, useRef, useEffect, useCallback } = React
|
|
2550
|
+
|
|
2551
|
+
/** Implementation name, also the keyed document slot key. */
|
|
2552
|
+
const ID = 'dsh-engineering-ftview-viewer/display'
|
|
2553
|
+
/** Copy namespace for the document toolbar's implementation label. */
|
|
2554
|
+
const NS = 'factorytalkView'
|
|
2555
|
+
|
|
2556
|
+
const en = {
|
|
2557
|
+
title: 'FactoryTalk View display',
|
|
2558
|
+
fit: 'Fit',
|
|
2559
|
+
actual: '1:1',
|
|
2560
|
+
tags: 'Tags',
|
|
2561
|
+
objects: 'objects',
|
|
2562
|
+
notDisplay: 'This XML is not a FactoryTalk View ME display export.',
|
|
2563
|
+
notDisplayHint: 'A display export has a <gfx> root with a <displaySettings> child. The source is shown instead.',
|
|
2564
|
+
parseFailed: 'This file is not well-formed XML.',
|
|
2565
|
+
cut: 'Only the start of this file was read, so the XML is incomplete and cannot be drawn.',
|
|
2566
|
+
truncated: 'Only the first 200000 characters are shown.',
|
|
2567
|
+
}
|
|
2568
|
+
const zh = {
|
|
2569
|
+
title: 'FactoryTalk View 画面',
|
|
2570
|
+
fit: '适应',
|
|
2571
|
+
actual: '1:1',
|
|
2572
|
+
tags: '标签',
|
|
2573
|
+
objects: '个对象',
|
|
2574
|
+
notDisplay: '此 XML 不是 FactoryTalk View ME 画面导出文件。',
|
|
2575
|
+
notDisplayHint: '画面导出文件应以 <gfx> 为根元素并包含 <displaySettings> 子元素。此处显示原始源码。',
|
|
2576
|
+
parseFailed: '此文件不是格式正确的 XML。',
|
|
2577
|
+
cut: '仅读取了此文件的开头部分,XML 不完整,无法绘制。',
|
|
2578
|
+
truncated: '仅显示前 200000 个字符。',
|
|
2579
|
+
}
|
|
2580
|
+
|
|
2581
|
+
// ---------------------------------------------------------------------
|
|
2582
|
+
// Palette and metrics
|
|
2583
|
+
// ---------------------------------------------------------------------
|
|
2584
|
+
|
|
2585
|
+
/** FTView named colors whose CSS name differs or that CSS does not define. */
|
|
2586
|
+
const NAMED_COLORS = {
|
|
2587
|
+
silver: '#C0C0C0',
|
|
2588
|
+
gray: '#808080',
|
|
2589
|
+
grey: '#808080',
|
|
2590
|
+
teal: '#008080',
|
|
2591
|
+
aqua: '#00FFFF',
|
|
2592
|
+
lime: '#00FF00',
|
|
2593
|
+
fuchsia: '#FF00FF',
|
|
2594
|
+
maroon: '#800000',
|
|
2595
|
+
navy: '#000080',
|
|
2596
|
+
olive: '#808000',
|
|
2597
|
+
purple: '#800080',
|
|
2598
|
+
}
|
|
2599
|
+
|
|
2600
|
+
/**
|
|
2601
|
+
* Resolve an exported color token.
|
|
2602
|
+
* @param value - the attribute value, `#RRGGBB`, a CSS name, or empty.
|
|
2603
|
+
* @param fallback - color used when the export omits or empties the attribute.
|
|
2604
|
+
* @returns a CSS color.
|
|
2605
|
+
*/
|
|
2606
|
+
function color(value, fallback) {
|
|
2607
|
+
if (value === null || value === undefined) return fallback
|
|
2608
|
+
const token = String(value).trim()
|
|
2609
|
+
if (!token) return fallback
|
|
2610
|
+
return NAMED_COLORS[token.toLowerCase()] ?? token
|
|
2611
|
+
}
|
|
2612
|
+
|
|
2613
|
+
/** Points to device pixels at the 96 dpi a ME display is authored at. */
|
|
2614
|
+
const POINTS_TO_PIXELS = 4 / 3
|
|
2615
|
+
|
|
2616
|
+
/**
|
|
2617
|
+
* Derive the rendered text metrics of a graphic object.
|
|
2618
|
+
*
|
|
2619
|
+
* An export states either `fontSize` (points) or `charHeight`/`charWidth`
|
|
2620
|
+
* (authored device pixels per character cell). `charHeight` is the line
|
|
2621
|
+
* box, so the glyph size is a fraction of it.
|
|
2622
|
+
* @param el - the graphic object element.
|
|
2623
|
+
* @param lines - number of caption lines, used to size text with neither metric.
|
|
2624
|
+
* @returns CSS `fontSize` and `lineHeight` in pixels.
|
|
2625
|
+
*/
|
|
2626
|
+
function textMetrics(el, lines) {
|
|
2627
|
+
const size = Number(el.getAttribute('fontSize'))
|
|
2628
|
+
if (Number.isFinite(size) && size > 0) {
|
|
2629
|
+
const pixels = size * POINTS_TO_PIXELS
|
|
2630
|
+
return { fontSize: `${pixels.toFixed(2)}px`, lineHeight: `${(pixels * 1.34).toFixed(2)}px` }
|
|
2631
|
+
}
|
|
2632
|
+
const cell = Number(el.getAttribute('charHeight'))
|
|
2633
|
+
if (Number.isFinite(cell) && cell > 0) {
|
|
2634
|
+
return { fontSize: `${(cell * 0.625).toFixed(2)}px`, lineHeight: `${cell}px` }
|
|
2635
|
+
}
|
|
2636
|
+
const height = Number(el.getAttribute('height'))
|
|
2637
|
+
const rows = Math.max(1, lines)
|
|
2638
|
+
const box = Number.isFinite(height) && height > 0 ? height / rows : 16
|
|
2639
|
+
return { fontSize: `${(box * 0.72).toFixed(2)}px`, lineHeight: `${box.toFixed(2)}px` }
|
|
2640
|
+
}
|
|
2641
|
+
|
|
2642
|
+
/**
|
|
2643
|
+
* Map an exported alignment token to flexbox placement.
|
|
2644
|
+
* @param value - `middleCenter`, `topLeft`, and so on.
|
|
2645
|
+
* @returns flex and text alignment.
|
|
2646
|
+
*/
|
|
2647
|
+
function alignment(value) {
|
|
2648
|
+
const token = value || 'middleCenter'
|
|
2649
|
+
const vertical = token.startsWith('top') ? 'flex-start'
|
|
2650
|
+
: token.startsWith('bottom') ? 'flex-end'
|
|
2651
|
+
: 'center'
|
|
2652
|
+
const horizontal = token.endsWith('Left') ? 'flex-start'
|
|
2653
|
+
: token.endsWith('Right') ? 'flex-end'
|
|
2654
|
+
: 'center'
|
|
2655
|
+
const text = horizontal === 'flex-start' ? 'left' : horizontal === 'flex-end' ? 'right' : 'center'
|
|
2656
|
+
return { justifyContent: horizontal, alignItems: vertical, textAlign: text }
|
|
2657
|
+
}
|
|
2658
|
+
|
|
2659
|
+
/**
|
|
2660
|
+
* Build the CSS `border` an object's exported border settings describe.
|
|
2661
|
+
* @param el - the graphic object element.
|
|
2662
|
+
* @returns a CSS border value, or `null` when the object has no border.
|
|
2663
|
+
*/
|
|
2664
|
+
function border(el) {
|
|
2665
|
+
const style = el.getAttribute('borderStyle')
|
|
2666
|
+
if (!style || style === 'none') return null
|
|
2667
|
+
const width = Number(el.getAttribute('borderWidth')) || 1
|
|
2668
|
+
const stroke = el.getAttribute('borderUsesBackColor') === 'true'
|
|
2669
|
+
? el.getAttribute('backColor')
|
|
2670
|
+
: el.getAttribute('borderColor')
|
|
2671
|
+
const css = style === 'inset' ? 'inset' : style === 'raised' || style === 'raisedInset' ? 'outset' : 'solid'
|
|
2672
|
+
return `${width}px ${css} ${color(stroke, '#808080')}`
|
|
2673
|
+
}
|
|
2674
|
+
|
|
2675
|
+
/**
|
|
2676
|
+
* Read the fill a graphic object's `backStyle` asks for.
|
|
2677
|
+
* @param el - the graphic object element.
|
|
2678
|
+
* @returns a CSS background, or `undefined` for a transparent object.
|
|
2679
|
+
*/
|
|
2680
|
+
function fill(el) {
|
|
2681
|
+
return el.getAttribute('backStyle') === 'transparent'
|
|
2682
|
+
? undefined
|
|
2683
|
+
: color(el.getAttribute('backColor'), undefined)
|
|
2684
|
+
}
|
|
2685
|
+
|
|
2686
|
+
// ---------------------------------------------------------------------
|
|
2687
|
+
// Export reading
|
|
2688
|
+
// ---------------------------------------------------------------------
|
|
2689
|
+
|
|
2690
|
+
/** Direct child elements of an element, ignoring text and comments. */
|
|
2691
|
+
function children(el, tag) {
|
|
2692
|
+
const found = []
|
|
2693
|
+
for (const child of el.children) {
|
|
2694
|
+
if (tag === undefined || child.localName === tag) found.push(child)
|
|
2695
|
+
}
|
|
2696
|
+
return found
|
|
2697
|
+
}
|
|
2698
|
+
|
|
2699
|
+
/** First direct child element with the given name. */
|
|
2700
|
+
function child(el, tag) {
|
|
2701
|
+
return children(el, tag)[0] ?? null
|
|
2702
|
+
}
|
|
2703
|
+
|
|
2704
|
+
/**
|
|
2705
|
+
* Read an object's `connections`.
|
|
2706
|
+
* @param el - the graphic object element.
|
|
2707
|
+
* @returns the connection name to exported expression, braces removed.
|
|
2708
|
+
*/
|
|
2709
|
+
function connections(el) {
|
|
2710
|
+
const block = child(el, 'connections')
|
|
2711
|
+
if (!block) return {}
|
|
2712
|
+
const found = {}
|
|
2713
|
+
for (const entry of children(block, 'connection')) {
|
|
2714
|
+
const name = entry.getAttribute('name') || 'Value'
|
|
2715
|
+
const expression = (entry.getAttribute('expression') || '').trim()
|
|
2716
|
+
if (expression) found[name] = expression.replace(/^\{|\}$/g, '')
|
|
2717
|
+
}
|
|
2718
|
+
return found
|
|
2719
|
+
}
|
|
2720
|
+
|
|
2721
|
+
/**
|
|
2722
|
+
* Select the state an object is drawing.
|
|
2723
|
+
*
|
|
2724
|
+
* `currentStateId` records the state the export was saved with, which is
|
|
2725
|
+
* what a static preview must show. A `stateId` is a name for the alarm
|
|
2726
|
+
* states and a number otherwise; multistate objects also carry `value`.
|
|
2727
|
+
* @param el - the stateful graphic object element.
|
|
2728
|
+
* @returns the selected `<state>`, or `null` when the object has none.
|
|
2729
|
+
*/
|
|
2730
|
+
function stateOf(el) {
|
|
2731
|
+
const block = child(el, 'states')
|
|
2732
|
+
if (!block) return null
|
|
2733
|
+
const states = children(block, 'state')
|
|
2734
|
+
if (states.length === 0) return null
|
|
2735
|
+
const wanted = el.getAttribute('currentStateId')
|
|
2736
|
+
if (wanted !== null && wanted !== '') {
|
|
2737
|
+
const exact = states.find(state => state.getAttribute('stateId') === wanted)
|
|
2738
|
+
if (exact) return exact
|
|
2739
|
+
const byValue = states.find(state => state.getAttribute('value') === wanted)
|
|
2740
|
+
if (byValue) return byValue
|
|
2741
|
+
}
|
|
2742
|
+
return states.find(state => state.getAttribute('stateId') === '1')
|
|
2743
|
+
?? states.find(state => state.getAttribute('stateId') === '0')
|
|
2744
|
+
?? states[0]
|
|
2745
|
+
}
|
|
2746
|
+
|
|
2747
|
+
/** Expand the newlines a caption stores as XML character references. */
|
|
2748
|
+
function captionText(node) {
|
|
2749
|
+
const raw = node?.getAttribute('caption')
|
|
2750
|
+
if (raw === null || raw === undefined) return ''
|
|
2751
|
+
return raw.replace(/\r\n?/g, '\n').replace(/\n+$/, '')
|
|
2752
|
+
}
|
|
2753
|
+
|
|
2754
|
+
/**
|
|
2755
|
+
* Placeholder a `numericDisplay` reserves for its runtime value.
|
|
2756
|
+
* @param el - the numeric display element.
|
|
2757
|
+
* @returns the digit template, padded as `fillLeftWith` asks.
|
|
2758
|
+
*/
|
|
2759
|
+
function numericTemplate(el) {
|
|
2760
|
+
const digits = Number(el.getAttribute('numberOfDigits')) || 0
|
|
2761
|
+
const decimals = Number(el.getAttribute('decimalPlaces')) || 0
|
|
2762
|
+
if (digits <= 0) return decimals > 0 ? `0.${'0'.repeat(decimals)}` : '0'
|
|
2763
|
+
let text = decimals > 0 ? `0.${'0'.repeat(decimals)}` : '0'
|
|
2764
|
+
if (text.length > digits) return text.slice(0, digits)
|
|
2765
|
+
const pad = digits - text.length
|
|
2766
|
+
if (pad <= 0) return text
|
|
2767
|
+
return (el.getAttribute('fillLeftWith') === 'zeros' ? '0' : ' ').repeat(pad) + text
|
|
2768
|
+
}
|
|
2769
|
+
|
|
2770
|
+
/**
|
|
2771
|
+
* Label of the encoding a byte-order mark names.
|
|
2772
|
+
* @param bytes - raw file bytes.
|
|
2773
|
+
* @returns a TextDecoder label, or undefined when no mark is present.
|
|
2774
|
+
*/
|
|
2775
|
+
function markedEncoding(bytes) {
|
|
2776
|
+
if (bytes.length >= 3 && bytes[0] === 0xEF && bytes[1] === 0xBB && bytes[2] === 0xBF) return 'utf-8'
|
|
2777
|
+
if (bytes.length >= 2 && bytes[0] === 0xFF && bytes[1] === 0xFE) return 'utf-16le'
|
|
2778
|
+
if (bytes.length >= 2 && bytes[0] === 0xFE && bytes[1] === 0xFF) return 'utf-16be'
|
|
2779
|
+
return undefined
|
|
2780
|
+
}
|
|
2781
|
+
|
|
2782
|
+
/**
|
|
2783
|
+
* Label of the encoding an XML declaration names.
|
|
2784
|
+
* @param bytes - raw file bytes.
|
|
2785
|
+
* @returns a TextDecoder label, or undefined when the declaration is absent or unreadable.
|
|
2786
|
+
*/
|
|
2787
|
+
function declaredEncoding(bytes) {
|
|
2788
|
+
const head = new TextDecoder('utf-8').decode(bytes.subarray(0, 200))
|
|
2789
|
+
const declared = /<\?xml[^>]*encoding\s*=\s*["']([\w.-]+)["']/.exec(head)
|
|
2790
|
+
return declared ? declared[1] : undefined
|
|
2791
|
+
}
|
|
2792
|
+
|
|
2793
|
+
/**
|
|
2794
|
+
* Decode an export's bytes, honoring a byte-order mark before the declared encoding.
|
|
2795
|
+
*
|
|
2796
|
+
* FactoryTalk View Studio writes most displays as UTF-8, but emits some generated
|
|
2797
|
+
* exports, such as the batch-import manifests, as UTF-16 with a byte-order mark.
|
|
2798
|
+
* @param bytes - raw file bytes.
|
|
2799
|
+
* @returns the decoded export source.
|
|
2800
|
+
*/
|
|
2801
|
+
function decodeSource(bytes) {
|
|
2802
|
+
const label = markedEncoding(bytes) || declaredEncoding(bytes) || 'utf-8'
|
|
2803
|
+
try {
|
|
2804
|
+
return new TextDecoder(label).decode(bytes)
|
|
2805
|
+
} catch {
|
|
2806
|
+
return new TextDecoder('utf-8').decode(bytes)
|
|
2807
|
+
}
|
|
2808
|
+
}
|
|
2809
|
+
|
|
2810
|
+
/**
|
|
2811
|
+
* Read one ME display export.
|
|
2812
|
+
* @param text - decoded export source.
|
|
2813
|
+
* @returns the display settings, its graphic objects, or why it is not a display.
|
|
2814
|
+
*/
|
|
2815
|
+
function readDisplay(text) {
|
|
2816
|
+
let doc
|
|
2817
|
+
try {
|
|
2818
|
+
doc = new DOMParser().parseFromString(text, 'text/xml')
|
|
2819
|
+
} catch {
|
|
2820
|
+
return { kind: 'invalid' }
|
|
2821
|
+
}
|
|
2822
|
+
const root = doc.documentElement
|
|
2823
|
+
if (!root || root.localName === 'parsererror' || doc.getElementsByTagName('parsererror').length > 0) {
|
|
2824
|
+
return { kind: 'invalid' }
|
|
2825
|
+
}
|
|
2826
|
+
if (root.localName !== 'gfx') return { kind: 'other', root: root.localName }
|
|
2827
|
+
const settings = child(root, 'displaySettings')
|
|
2828
|
+
if (!settings) return { kind: 'other', root: root.localName }
|
|
2829
|
+
const width = Number(settings.getAttribute('width')) || 0
|
|
2830
|
+
const height = Number(settings.getAttribute('height')) || 0
|
|
2831
|
+
if (width <= 0 || height <= 0) return { kind: 'other', root: root.localName }
|
|
2832
|
+
return {
|
|
2833
|
+
kind: 'display',
|
|
2834
|
+
settings: {
|
|
2835
|
+
width,
|
|
2836
|
+
height,
|
|
2837
|
+
backColor: color(settings.getAttribute('backColor'), '#E0E0E0'),
|
|
2838
|
+
displayType: settings.getAttribute('displayType') || 'replace',
|
|
2839
|
+
gradient: settings.getAttribute('useGradientStyle') === 'true',
|
|
2840
|
+
},
|
|
2841
|
+
objects: children(root).filter(node => node !== settings),
|
|
2842
|
+
}
|
|
2843
|
+
}
|
|
2844
|
+
|
|
2845
|
+
// ---------------------------------------------------------------------
|
|
2846
|
+
// Rendering
|
|
2847
|
+
// ---------------------------------------------------------------------
|
|
2848
|
+
|
|
2849
|
+
/**
|
|
2850
|
+
* Absolute placement every graphic object carries.
|
|
2851
|
+
* @param el - the graphic object element.
|
|
2852
|
+
* @returns CSS for its exported box and visibility.
|
|
2853
|
+
*/
|
|
2854
|
+
function box(el) {
|
|
2855
|
+
return {
|
|
2856
|
+
left: `${Number(el.getAttribute('left')) || 0}px`,
|
|
2857
|
+
top: `${Number(el.getAttribute('top')) || 0}px`,
|
|
2858
|
+
width: `${Number(el.getAttribute('width')) || 0}px`,
|
|
2859
|
+
height: `${Number(el.getAttribute('height')) || 0}px`,
|
|
2860
|
+
}
|
|
2861
|
+
}
|
|
2862
|
+
|
|
2863
|
+
/**
|
|
2864
|
+
* Draw one caption.
|
|
2865
|
+
* @param source - the `<caption>` or `<text>` element carrying the text.
|
|
2866
|
+
* @param el - the owning graphic object, for its box and text metrics.
|
|
2867
|
+
* @returns a positioned text node, or `null` when the caption is empty.
|
|
2868
|
+
*/
|
|
2869
|
+
function drawCaption(source, el) {
|
|
2870
|
+
const text = captionText(source)
|
|
2871
|
+
if (!text) return null
|
|
2872
|
+
const lines = text.split('\n').length
|
|
2873
|
+
const metrics = textMetrics(el, lines)
|
|
2874
|
+
return h('div', {
|
|
2875
|
+
key: 'caption',
|
|
2876
|
+
className: 'ftv-obj ftv-txt',
|
|
2877
|
+
style: {
|
|
2878
|
+
...box(el),
|
|
2879
|
+
...alignment(source.getAttribute('alignment')),
|
|
2880
|
+
...metrics,
|
|
2881
|
+
fontFamily: source.getAttribute('fontFamily') || 'Arial Unicode MS, sans-serif',
|
|
2882
|
+
fontWeight: source.getAttribute('bold') === 'true' ? 700 : 400,
|
|
2883
|
+
fontStyle: source.getAttribute('italic') === 'true' ? 'italic' : 'normal',
|
|
2884
|
+
textDecoration: [
|
|
2885
|
+
source.getAttribute('underline') === 'true' ? 'underline' : '',
|
|
2886
|
+
source.getAttribute('strikethrough') === 'true' ? 'line-through' : '',
|
|
2887
|
+
].filter(Boolean).join(' ') || 'none',
|
|
2888
|
+
color: color(source.getAttribute('color'), color(el.getAttribute('foreColor'), '#000000')),
|
|
2889
|
+
background: fill(source),
|
|
2890
|
+
whiteSpace: source.getAttribute('wordWrap') === 'false' ? 'pre' : 'pre-wrap',
|
|
2891
|
+
pointerEvents: 'none',
|
|
2892
|
+
},
|
|
2893
|
+
}, text)
|
|
2894
|
+
}
|
|
2895
|
+
|
|
2896
|
+
/**
|
|
2897
|
+
* Draw an object that has no exported artwork, naming what the export asks for.
|
|
2898
|
+
* @param el - the graphic object element.
|
|
2899
|
+
* @param name - the exported graphic name.
|
|
2900
|
+
* @returns a placeholder box.
|
|
2901
|
+
*/
|
|
2902
|
+
function placeholder(el, name) {
|
|
2903
|
+
return h('div', {
|
|
2904
|
+
key: 'placeholder',
|
|
2905
|
+
className: 'ftv-obj ftv-placeholder',
|
|
2906
|
+
style: box(el),
|
|
2907
|
+
title: name,
|
|
2908
|
+
}, h('span', null, name))
|
|
2909
|
+
}
|
|
2910
|
+
|
|
2911
|
+
/**
|
|
2912
|
+
* Draw a `line` object as its own SVG so document order still decides depth.
|
|
2913
|
+
* @param el - the line element.
|
|
2914
|
+
* @returns a positioned SVG, or a placeholder when the export omits coordinates.
|
|
2915
|
+
*/
|
|
2916
|
+
function drawLine(el) {
|
|
2917
|
+
const numbers = (el.getAttribute('line') || '').trim().split(/\s+/).map(Number)
|
|
2918
|
+
if (numbers.length < 4 || numbers.some(value => !Number.isFinite(value))) {
|
|
2919
|
+
return placeholder(el, el.getAttribute('name') || 'line')
|
|
2920
|
+
}
|
|
2921
|
+
const [x1, y1, x2, y2] = numbers
|
|
2922
|
+
const left = Number(el.getAttribute('left')) || 0
|
|
2923
|
+
const top = Number(el.getAttribute('top')) || 0
|
|
2924
|
+
const width = Number(el.getAttribute('width')) || 1
|
|
2925
|
+
const height = Number(el.getAttribute('height')) || 1
|
|
2926
|
+
return h('svg', {
|
|
2927
|
+
key: 'line',
|
|
2928
|
+
className: 'ftv-obj',
|
|
2929
|
+
style: box(el),
|
|
2930
|
+
viewBox: `${left} ${top} ${width} ${height}`,
|
|
2931
|
+
preserveAspectRatio: 'none',
|
|
2932
|
+
}, h('line', {
|
|
2933
|
+
x1, y1, x2, y2,
|
|
2934
|
+
stroke: color(el.getAttribute('foreColor'), '#000000'),
|
|
2935
|
+
strokeWidth: Number(el.getAttribute('lineWidth')) || 1,
|
|
2936
|
+
strokeDasharray: el.getAttribute('lineStyle') === 'dash' ? '4 3' : undefined,
|
|
2937
|
+
}))
|
|
2938
|
+
}
|
|
2939
|
+
|
|
2940
|
+
/** Draw an `arc` object as its outlined or filled ellipse. */
|
|
2941
|
+
function drawArc(el) {
|
|
2942
|
+
const left = Number(el.getAttribute('left')) || 0
|
|
2943
|
+
const top = Number(el.getAttribute('top')) || 0
|
|
2944
|
+
const width = Number(el.getAttribute('width')) || 0
|
|
2945
|
+
const height = Number(el.getAttribute('height')) || 0
|
|
2946
|
+
if (width <= 0 || height <= 0) return placeholder(el, el.getAttribute('name') || 'arc')
|
|
2947
|
+
const solid = el.getAttribute('backStyle') !== 'transparent'
|
|
2948
|
+
return h('svg', {
|
|
2949
|
+
key: 'arc',
|
|
2950
|
+
className: 'ftv-obj',
|
|
2951
|
+
style: box(el),
|
|
2952
|
+
viewBox: `${left} ${top} ${width} ${height}`,
|
|
2953
|
+
}, h('ellipse', {
|
|
2954
|
+
cx: left + width / 2,
|
|
2955
|
+
cy: top + height / 2,
|
|
2956
|
+
rx: Math.max(0, width / 2 - 1),
|
|
2957
|
+
ry: Math.max(0, height / 2 - 1),
|
|
2958
|
+
fill: solid ? color(el.getAttribute('backColor'), 'none') : 'none',
|
|
2959
|
+
stroke: color(el.getAttribute('foreColor'), '#555555'),
|
|
2960
|
+
strokeWidth: Number(el.getAttribute('lineWidth')) || 1,
|
|
2961
|
+
}))
|
|
2962
|
+
}
|
|
2963
|
+
|
|
2964
|
+
/** Draw a panel: a filled, bordered rectangle behind later objects. */
|
|
2965
|
+
function drawPanel(el) {
|
|
2966
|
+
return h('div', {
|
|
2967
|
+
key: 'panel',
|
|
2968
|
+
className: 'ftv-obj',
|
|
2969
|
+
style: {
|
|
2970
|
+
...box(el),
|
|
2971
|
+
background: fill(el) ?? 'transparent',
|
|
2972
|
+
border: border(el) ?? undefined,
|
|
2973
|
+
},
|
|
2974
|
+
})
|
|
2975
|
+
}
|
|
2976
|
+
|
|
2977
|
+
/**
|
|
2978
|
+
* Draw a `text` object.
|
|
2979
|
+
* @param el - the text element.
|
|
2980
|
+
* @returns its caption in the exported box.
|
|
2981
|
+
*/
|
|
2982
|
+
function drawText(el) {
|
|
2983
|
+
const text = captionText(el)
|
|
2984
|
+
if (!text) return null
|
|
2985
|
+
const lines = text.split('\n').length
|
|
2986
|
+
return h('div', {
|
|
2987
|
+
key: 'text',
|
|
2988
|
+
className: 'ftv-obj ftv-txt',
|
|
2989
|
+
style: {
|
|
2990
|
+
...box(el),
|
|
2991
|
+
...alignment(el.getAttribute('alignment')),
|
|
2992
|
+
...textMetrics(el, lines),
|
|
2993
|
+
fontFamily: el.getAttribute('fontFamily') || 'Arial Unicode MS, sans-serif',
|
|
2994
|
+
fontWeight: el.getAttribute('bold') === 'true' ? 700 : 400,
|
|
2995
|
+
fontStyle: el.getAttribute('italic') === 'true' ? 'italic' : 'normal',
|
|
2996
|
+
textDecoration: [
|
|
2997
|
+
el.getAttribute('underline') === 'true' ? 'underline' : '',
|
|
2998
|
+
el.getAttribute('strikethrough') === 'true' ? 'line-through' : '',
|
|
2999
|
+
].filter(Boolean).join(' ') || 'none',
|
|
3000
|
+
color: color(el.getAttribute('foreColor'), '#000000'),
|
|
3001
|
+
background: fill(el),
|
|
3002
|
+
whiteSpace: el.getAttribute('wordWrap') === 'false' ? 'pre' : 'pre-wrap',
|
|
3003
|
+
},
|
|
3004
|
+
}, text)
|
|
3005
|
+
}
|
|
3006
|
+
|
|
3007
|
+
/** Draw an `image` object as the name of the graphic the project stores elsewhere. */
|
|
3008
|
+
function drawImage(el) {
|
|
3009
|
+
const name = el.getAttribute('imageName') || el.getAttribute('name') || ''
|
|
3010
|
+
if (!name) return null
|
|
3011
|
+
return placeholder(el, name)
|
|
3012
|
+
}
|
|
3013
|
+
|
|
3014
|
+
/**
|
|
3015
|
+
* Draw a button, indicator, or other stateful object.
|
|
3016
|
+
* @param el - the graphic object element.
|
|
3017
|
+
* @returns the selected state's artwork with its caption.
|
|
3018
|
+
*/
|
|
3019
|
+
function drawStateful(el) {
|
|
3020
|
+
const state = stateOf(el)
|
|
3021
|
+
if (!state) {
|
|
3022
|
+
return h('div', {
|
|
3023
|
+
key: 'stateful',
|
|
3024
|
+
className: 'ftv-obj',
|
|
3025
|
+
style: {
|
|
3026
|
+
...box(el),
|
|
3027
|
+
background: fill(el) ?? 'transparent',
|
|
3028
|
+
border: border(el) ?? undefined,
|
|
3029
|
+
borderRadius: el.getAttribute('shape') === 'circle' ? '50%' : undefined,
|
|
3030
|
+
},
|
|
3031
|
+
})
|
|
3032
|
+
}
|
|
3033
|
+
const overlay = child(state, 'caption')
|
|
3034
|
+
const image = child(state, 'imageSettings')
|
|
3035
|
+
const imageName = image?.getAttribute('imageName') || ''
|
|
3036
|
+
return h('div', {
|
|
3037
|
+
key: 'stateful',
|
|
3038
|
+
className: 'ftv-obj',
|
|
3039
|
+
style: {
|
|
3040
|
+
...box(el),
|
|
3041
|
+
background: color(state.getAttribute('backColor'), undefined),
|
|
3042
|
+
border: border({ getAttribute: name => state.getAttribute(name) ?? el.getAttribute(name) }) ?? undefined,
|
|
3043
|
+
borderRadius: el.getAttribute('shape') === 'circle' ? '50%' : undefined,
|
|
3044
|
+
},
|
|
3045
|
+
},
|
|
3046
|
+
imageName ? placeholder(el, imageName) : null,
|
|
3047
|
+
overlay ? drawCaption(overlay, el) : null)
|
|
3048
|
+
}
|
|
3049
|
+
|
|
3050
|
+
/** Draw a `numericDisplay`: its exported digit template, not a live value. */
|
|
3051
|
+
function drawNumericDisplay(el) {
|
|
3052
|
+
return h('div', {
|
|
3053
|
+
key: 'numeric',
|
|
3054
|
+
className: 'ftv-obj ftv-txt',
|
|
3055
|
+
style: {
|
|
3056
|
+
...box(el),
|
|
3057
|
+
...alignment(el.getAttribute('alignment')),
|
|
3058
|
+
...textMetrics(el, 1),
|
|
3059
|
+
fontFamily: el.getAttribute('fontFamily') || 'Arial Unicode MS, sans-serif',
|
|
3060
|
+
fontWeight: el.getAttribute('bold') === 'true' ? 700 : 400,
|
|
3061
|
+
color: color(el.getAttribute('foreColor'), '#000000'),
|
|
3062
|
+
background: fill(el),
|
|
3063
|
+
border: border(el) ?? undefined,
|
|
3064
|
+
whiteSpace: 'pre',
|
|
3065
|
+
},
|
|
3066
|
+
}, numericTemplate(el))
|
|
3067
|
+
}
|
|
3068
|
+
|
|
3069
|
+
/** Draw a `numericInputEnable`: its caption over its entry field. */
|
|
3070
|
+
function drawNumericInput(el) {
|
|
3071
|
+
const overlay = child(el, 'caption')
|
|
3072
|
+
return h('div', {
|
|
3073
|
+
key: 'numericInput',
|
|
3074
|
+
className: 'ftv-obj',
|
|
3075
|
+
style: {
|
|
3076
|
+
...box(el),
|
|
3077
|
+
background: fill(el) ?? 'transparent',
|
|
3078
|
+
border: border(el) ?? undefined,
|
|
3079
|
+
},
|
|
3080
|
+
}, overlay ? drawCaption(overlay, el) : null)
|
|
3081
|
+
}
|
|
3082
|
+
|
|
3083
|
+
/**
|
|
3084
|
+
* Draw an alarm, diagnostic, or information list.
|
|
3085
|
+
* @param el - the list element.
|
|
3086
|
+
* @returns its exported header over empty rows the runtime fills.
|
|
3087
|
+
*/
|
|
3088
|
+
function drawList(el) {
|
|
3089
|
+
const title = el.getAttribute('titleActiveAlarmsMode')
|
|
3090
|
+
|| el.getAttribute('caption')
|
|
3091
|
+
|| el.getAttribute('name')
|
|
3092
|
+
|| ''
|
|
3093
|
+
return h('div', {
|
|
3094
|
+
key: 'list',
|
|
3095
|
+
className: 'ftv-obj ftv-list',
|
|
3096
|
+
style: {
|
|
3097
|
+
...box(el),
|
|
3098
|
+
background: color(el.getAttribute('backColor'), '#FFFFFF'),
|
|
3099
|
+
border: border(el) ?? `1px solid ${color(el.getAttribute('borderColor'), '#555555')}`,
|
|
3100
|
+
},
|
|
3101
|
+
},
|
|
3102
|
+
title ? h('div', {
|
|
3103
|
+
className: 'ftv-list-title',
|
|
3104
|
+
style: {
|
|
3105
|
+
background: color(el.getAttribute('headerBackColor'), color(el.getAttribute('backColor'), '#0B3B6B')),
|
|
3106
|
+
color: color(el.getAttribute('headerForeColor'), '#FFFFFF'),
|
|
3107
|
+
},
|
|
3108
|
+
}, title) : null)
|
|
3109
|
+
}
|
|
3110
|
+
|
|
3111
|
+
/**
|
|
3112
|
+
* Draw a bar graph as its framed, empty plot area.
|
|
3113
|
+
* @param el - the bar graph element.
|
|
3114
|
+
* @returns the graph frame.
|
|
3115
|
+
*/
|
|
3116
|
+
function drawBarGraph(el) {
|
|
3117
|
+
return h('div', {
|
|
3118
|
+
key: 'barGraph',
|
|
3119
|
+
className: 'ftv-obj',
|
|
3120
|
+
style: {
|
|
3121
|
+
...box(el),
|
|
3122
|
+
background: fill(el) ?? 'transparent',
|
|
3123
|
+
border: border(el) ?? `1px solid ${color(el.getAttribute('foreColor'), '#555555')}`,
|
|
3124
|
+
},
|
|
3125
|
+
})
|
|
3126
|
+
}
|
|
3127
|
+
|
|
3128
|
+
/**
|
|
3129
|
+
* Draw one graphic object.
|
|
3130
|
+
* @param el - the graphic object element.
|
|
3131
|
+
* @param index - position among its siblings, for the React key.
|
|
3132
|
+
* @returns the drawn object, or `null` for an object the export hides.
|
|
3133
|
+
*/
|
|
3134
|
+
function drawObject(el, index) {
|
|
3135
|
+
if (el.getAttribute('visible') === 'false') return null
|
|
3136
|
+
switch (el.localName) {
|
|
3137
|
+
case 'panel': {
|
|
3138
|
+
const panel = drawPanel(el)
|
|
3139
|
+
return h(React.Fragment, { key: `panel-${index}` }, panel)
|
|
3140
|
+
}
|
|
3141
|
+
case 'text':
|
|
3142
|
+
return withKey(drawText(el), `text-${index}`)
|
|
3143
|
+
case 'line':
|
|
3144
|
+
return withKey(drawLine(el), `line-${index}`)
|
|
3145
|
+
case 'arc':
|
|
3146
|
+
return withKey(drawArc(el), `arc-${index}`)
|
|
3147
|
+
case 'image':
|
|
3148
|
+
return withKey(drawImage(el), `image-${index}`)
|
|
3149
|
+
case 'barGraph':
|
|
3150
|
+
return withKey(drawBarGraph(el), `bar-${index}`)
|
|
3151
|
+
case 'numericDisplay':
|
|
3152
|
+
return withKey(drawNumericDisplay(el), `numeric-${index}`)
|
|
3153
|
+
case 'numericInputEnable':
|
|
3154
|
+
return withKey(drawNumericInput(el), `numericInput-${index}`)
|
|
3155
|
+
case 'alarmList':
|
|
3156
|
+
case 'alarmStatusList':
|
|
3157
|
+
case 'diagnosticList':
|
|
3158
|
+
case 'informationMessageDisplay':
|
|
3159
|
+
return withKey(drawList(el), `list-${index}`)
|
|
3160
|
+
case 'gotoButton':
|
|
3161
|
+
case 'closeButton':
|
|
3162
|
+
case 'momentaryButton':
|
|
3163
|
+
case 'maintainedButton':
|
|
3164
|
+
case 'multistateButton':
|
|
3165
|
+
case 'multistateIndicator':
|
|
3166
|
+
case 'acknowledgeAlarmButton':
|
|
3167
|
+
case 'silenceAlarmsButton':
|
|
3168
|
+
case 'clearAlarmBannerButton':
|
|
3169
|
+
case 'diagnosticsClearButton':
|
|
3170
|
+
case 'diagnosticsClearAllButton':
|
|
3171
|
+
case 'informationAckButton':
|
|
3172
|
+
case 'moveUpKey':
|
|
3173
|
+
case 'moveDownKey':
|
|
3174
|
+
case 'moveLeftKey':
|
|
3175
|
+
case 'moveRightKey':
|
|
3176
|
+
return withKey(drawStateful(el), `stateful-${index}`)
|
|
3177
|
+
case 'group':
|
|
3178
|
+
return h(React.Fragment, { key: `group-${index}` }, children(el).map(drawObject))
|
|
3179
|
+
default:
|
|
3180
|
+
return null
|
|
3181
|
+
}
|
|
3182
|
+
}
|
|
3183
|
+
|
|
3184
|
+
/** React needs a key on every sibling; give a drawn node its own. */
|
|
3185
|
+
function withKey(node, key) {
|
|
3186
|
+
if (!node) return null
|
|
3187
|
+
return h(React.Fragment, { key }, node)
|
|
3188
|
+
}
|
|
3189
|
+
|
|
3190
|
+
/**
|
|
3191
|
+
* Collect the tag bindings a display declares, in document order.
|
|
3192
|
+
* @param objects - the display's graphic objects.
|
|
3193
|
+
* @returns one entry per connected object.
|
|
3194
|
+
*/
|
|
3195
|
+
function readBindings(objects) {
|
|
3196
|
+
const found = []
|
|
3197
|
+
for (const object of objects) {
|
|
3198
|
+
for (const el of [object, ...object.querySelectorAll('*')]) {
|
|
3199
|
+
const bound = connections(el)
|
|
3200
|
+
const names = Object.keys(bound)
|
|
3201
|
+
if (names.length === 0) continue
|
|
3202
|
+
found.push({
|
|
3203
|
+
name: el.getAttribute('name') || el.localName,
|
|
3204
|
+
type: el.localName,
|
|
3205
|
+
left: Number(el.getAttribute('left')) || 0,
|
|
3206
|
+
top: Number(el.getAttribute('top')) || 0,
|
|
3207
|
+
tags: names.map(key => `${key}: ${bound[key]}`),
|
|
3208
|
+
})
|
|
3209
|
+
}
|
|
3210
|
+
}
|
|
3211
|
+
return found
|
|
3212
|
+
}
|
|
3213
|
+
|
|
3214
|
+
// ---------------------------------------------------------------------
|
|
3215
|
+
// Component
|
|
3216
|
+
// ---------------------------------------------------------------------
|
|
3217
|
+
|
|
3218
|
+
/**
|
|
3219
|
+
* Render one FactoryTalk View ME display export from its decoded source.
|
|
3220
|
+
* @param props - the decoded source, the file address, copy, and whether the
|
|
3221
|
+
* surrounding pane already names the file.
|
|
3222
|
+
* @returns the display, its raw source, or why neither is available.
|
|
3223
|
+
*/
|
|
3224
|
+
function Display(props) {
|
|
3225
|
+
const { source, address, t, embedded, cut } = props
|
|
3226
|
+
const say = useCallback(key => (typeof t === 'function' ? t(key) : en[key]), [t])
|
|
3227
|
+
const [mode, setMode] = useState('fit')
|
|
3228
|
+
const [showTags, setShowTags] = useState(false)
|
|
3229
|
+
const [scale, setScale] = useState(1)
|
|
3230
|
+
const scrollRef = useRef(null)
|
|
3231
|
+
|
|
3232
|
+
const model = useMemo(() => (source === null ? null : readDisplay(source)), [source])
|
|
3233
|
+
const bindings = useMemo(
|
|
3234
|
+
() => (model?.kind === 'display' ? readBindings(model.objects) : []),
|
|
3235
|
+
[model],
|
|
3236
|
+
)
|
|
3237
|
+
|
|
3238
|
+
const displayWidth = model?.kind === 'display' ? model.settings.width : 0
|
|
3239
|
+
|
|
3240
|
+
useEffect(() => {
|
|
3241
|
+
if (model?.kind !== 'display') return undefined
|
|
3242
|
+
const host = scrollRef.current
|
|
3243
|
+
if (!host) return undefined
|
|
3244
|
+
const measure = () => {
|
|
3245
|
+
const available = host.clientWidth - 16
|
|
3246
|
+
setScale(mode === 'actual' ? 1 : Math.min(1, Math.max(0.05, available / displayWidth)))
|
|
3247
|
+
}
|
|
3248
|
+
measure()
|
|
3249
|
+
if (typeof ResizeObserver === 'undefined') return undefined
|
|
3250
|
+
const observer = new ResizeObserver(measure)
|
|
3251
|
+
observer.observe(host)
|
|
3252
|
+
return () => observer.disconnect()
|
|
3253
|
+
}, [mode, displayWidth, model])
|
|
3254
|
+
|
|
3255
|
+
if (source === null) return null
|
|
3256
|
+
|
|
3257
|
+
if (model.kind === 'invalid') {
|
|
3258
|
+
return h('div', { className: 'ftv-fallback' },
|
|
3259
|
+
h('p', { className: 'ftv-note' }, say(cut ? 'cut' : 'parseFailed')),
|
|
3260
|
+
h('pre', { className: 'ftv-pre' }, source.slice(0, 200000)))
|
|
3261
|
+
}
|
|
3262
|
+
|
|
3263
|
+
if (model.kind === 'other') {
|
|
3264
|
+
return h('div', { className: 'ftv-fallback' },
|
|
3265
|
+
h('p', { className: 'ftv-note' },
|
|
3266
|
+
`${say('notDisplay')} ${say('notDisplayHint')}`),
|
|
3267
|
+
h('pre', { className: 'ftv-pre' }, source.slice(0, 200000)),
|
|
3268
|
+
source.length > 200000 ? h('p', { className: 'ftv-note' }, say('truncated')) : null)
|
|
3269
|
+
}
|
|
3270
|
+
|
|
3271
|
+
const fileName = (address || '').split(/[\\/]/).pop() || ''
|
|
3272
|
+
const { width, height, backColor } = model.settings
|
|
3273
|
+
const count = model.objects.filter(node => node.getAttribute('visible') !== 'false').length
|
|
3274
|
+
|
|
3275
|
+
return h('div', { className: 'ftv-root' },
|
|
3276
|
+
h('div', { className: 'ftv-bar' },
|
|
3277
|
+
embedded ? null : h('span', { className: 'ftv-name', title: fileName }, fileName),
|
|
3278
|
+
h('span', { className: 'ftv-dim' }, `${width}×${height}`),
|
|
3279
|
+
h('span', { className: 'ftv-dim' }, `${count} ${say('objects')}`),
|
|
3280
|
+
h('span', { className: 'ftv-spacer' }),
|
|
3281
|
+
h('button', {
|
|
3282
|
+
type: 'button',
|
|
3283
|
+
className: 'ftv-btn',
|
|
3284
|
+
'data-on': mode === 'fit',
|
|
3285
|
+
onClick: () => setMode('fit'),
|
|
3286
|
+
}, say('fit')),
|
|
3287
|
+
h('button', {
|
|
3288
|
+
type: 'button',
|
|
3289
|
+
className: 'ftv-btn',
|
|
3290
|
+
'data-on': mode === 'actual',
|
|
3291
|
+
onClick: () => setMode('actual'),
|
|
3292
|
+
}, say('actual')),
|
|
3293
|
+
h('button', {
|
|
3294
|
+
type: 'button',
|
|
3295
|
+
className: 'ftv-btn',
|
|
3296
|
+
'data-on': showTags,
|
|
3297
|
+
onClick: () => setShowTags(value => !value),
|
|
3298
|
+
}, say('tags'))),
|
|
3299
|
+
h('div', { className: 'ftv-scroll', ref: scrollRef },
|
|
3300
|
+
h('div', {
|
|
3301
|
+
className: 'ftv-frame',
|
|
3302
|
+
style: { width: `${(width * scale).toFixed(2)}px`, height: `${(height * scale).toFixed(2)}px` },
|
|
3303
|
+
},
|
|
3304
|
+
h('div', {
|
|
3305
|
+
className: 'ftv-stage',
|
|
3306
|
+
style: {
|
|
3307
|
+
width: `${width}px`,
|
|
3308
|
+
height: `${height}px`,
|
|
3309
|
+
background: backColor,
|
|
3310
|
+
transform: `scale(${scale})`,
|
|
3311
|
+
},
|
|
3312
|
+
},
|
|
3313
|
+
model.objects.map(drawObject),
|
|
3314
|
+
showTags ? bindings.map((binding, index) => h('div', {
|
|
3315
|
+
key: `tag-${index}`,
|
|
3316
|
+
className: 'ftv-tag',
|
|
3317
|
+
style: { left: `${binding.left}px`, top: `${binding.top}px` },
|
|
3318
|
+
title: `${binding.type} ${binding.name}`,
|
|
3319
|
+
}, binding.tags.join(' '))) : null))))
|
|
3320
|
+
}
|
|
3321
|
+
|
|
3322
|
+
/**
|
|
3323
|
+
* Document-body entry: decode the bytes the document preview read.
|
|
3324
|
+
* @param props - document body props: the loaded bytes, the file address, copy.
|
|
3325
|
+
* @returns the rendered display once its bytes have arrived.
|
|
3326
|
+
*/
|
|
3327
|
+
function FtViewBody(props) {
|
|
3328
|
+
const { content, resourceAddress, t } = props
|
|
3329
|
+
const source = useMemo(() => {
|
|
3330
|
+
if (!content || content.kind !== 'bytes') return null
|
|
3331
|
+
return decodeSource(content.data)
|
|
3332
|
+
}, [content])
|
|
3333
|
+
if (source === null) return null
|
|
3334
|
+
return h(Display, { source, address: resourceAddress, t })
|
|
3335
|
+
}
|
|
3336
|
+
|
|
3337
|
+
/**
|
|
3338
|
+
* The better-sidebar file viewer's component, fed by that plugin's
|
|
3339
|
+
* `fsRead` strategy, which hands over the source as text.
|
|
3340
|
+
* @param t - copy lookup bound to this plugin's namespace.
|
|
3341
|
+
* @returns a viewer component for an exported display.
|
|
3342
|
+
*/
|
|
3343
|
+
function fileViewer(t) {
|
|
3344
|
+
return function FtViewFile(props) {
|
|
3345
|
+
if (typeof props.content !== 'string') return null
|
|
3346
|
+
return h(Display, {
|
|
3347
|
+
source: props.content,
|
|
3348
|
+
address: props.path,
|
|
3349
|
+
t,
|
|
3350
|
+
embedded: true,
|
|
3351
|
+
cut: props.truncated === true,
|
|
3352
|
+
})
|
|
3353
|
+
}
|
|
3354
|
+
}
|
|
3355
|
+
|
|
3356
|
+
// ---------------------------------------------------------------------
|
|
3357
|
+
// Plugin
|
|
3358
|
+
// ---------------------------------------------------------------------
|
|
3359
|
+
|
|
3360
|
+
/** Stylesheet for the display surface, injected once per client module load. */
|
|
3361
|
+
const CSS = `
|
|
3362
|
+
.ftv-root{display:flex;flex-direction:column;height:100%;min-height:0;font:12px/1.4 system-ui,-apple-system,"Segoe UI",sans-serif;color:inherit}
|
|
3363
|
+
.ftv-bar{display:flex;align-items:center;gap:6px;flex:none;padding:4px 8px;font-size:11px;border-bottom:1px solid color-mix(in srgb,currentColor 16%,transparent)}
|
|
3364
|
+
.ftv-name{font-weight:600;max-width:16em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
3365
|
+
.ftv-dim{opacity:.6;font-variant-numeric:tabular-nums;white-space:nowrap}
|
|
3366
|
+
.ftv-spacer{flex:1}
|
|
3367
|
+
.ftv-btn{font:inherit;font-size:11px;padding:1px 6px;border-radius:4px;cursor:pointer;color:inherit;background:transparent;border:1px solid color-mix(in srgb,currentColor 24%,transparent)}
|
|
3368
|
+
.ftv-btn[data-on="true"]{background:color-mix(in srgb,currentColor 18%,transparent);border-color:color-mix(in srgb,currentColor 40%,transparent)}
|
|
3369
|
+
.ftv-scroll{flex:1;min-height:0;overflow:auto;padding:8px;background:color-mix(in srgb,currentColor 4%,transparent)}
|
|
3370
|
+
.ftv-frame{position:relative}
|
|
3371
|
+
.ftv-stage{position:absolute;top:0;left:0;transform-origin:top left;overflow:hidden;box-shadow:0 0 0 1px color-mix(in srgb,currentColor 22%,transparent),0 2px 10px rgba(0,0,0,.28)}
|
|
3372
|
+
.ftv-obj{position:absolute;box-sizing:border-box;overflow:hidden}
|
|
3373
|
+
.ftv-txt{display:flex;overflow:hidden;word-break:break-word}
|
|
3374
|
+
.ftv-placeholder{display:flex;align-items:center;justify-content:center;border:1px dashed color-mix(in srgb,#888 70%,transparent);background:repeating-linear-gradient(45deg,rgba(128,128,128,.10) 0 6px,rgba(128,128,128,.18) 6px 12px);color:#555;font-size:10px;text-align:center;padding:2px}
|
|
3375
|
+
.ftv-list{display:flex;flex-direction:column}
|
|
3376
|
+
.ftv-list-title{flex:none;padding:2px 6px;font-weight:600;font-size:11px}
|
|
3377
|
+
.ftv-tag{position:absolute;z-index:20;padding:0 3px;border-radius:2px;background:rgba(0,0,0,.82);color:#7DFF9B;font:9px/1.5 ui-monospace,SFMono-Regular,Consolas,monospace;white-space:nowrap;pointer-events:none}
|
|
3378
|
+
.ftv-fallback{height:100%;overflow:auto;padding:12px 14px}
|
|
3379
|
+
.ftv-note{margin:0 0 10px;padding:6px 9px;border-radius:4px;font-size:11px;border:1px solid color-mix(in srgb,currentColor 24%,transparent)}
|
|
3380
|
+
.ftv-pre{margin:0;font:11px/1.5 ui-monospace,SFMono-Regular,Consolas,monospace;white-space:pre;tab-size:2}
|
|
3381
|
+
`
|
|
3382
|
+
|
|
3383
|
+
/**
|
|
3384
|
+
* Register the display viewer for `.xml`.
|
|
3385
|
+
*
|
|
3386
|
+
* Two consumers can claim a `.xml` file in the right sidebar.
|
|
3387
|
+
* `documentPreviews` is the Host's own document tab. `betterSidebar` is the
|
|
3388
|
+
* file-viewer registry behind that plugin's editor tab, which is where an
|
|
3389
|
+
* export opened from the file tree lands, so it is the one a reader
|
|
3390
|
+
* normally sees. Both are registered. `betterSidebar` is optional: a
|
|
3391
|
+
* profile without that plugin keeps the Host preview and skips this one.
|
|
3392
|
+
* @param ctx - client context.
|
|
3393
|
+
*/
|
|
3394
|
+
function apply(ctx) {
|
|
3395
|
+
ctx.effect(() => ctx.locale.register(NS, { en, zh }), 'factorytalk-view-use: dictionaries')
|
|
3396
|
+
const t = ctx.locale.bind(NS)
|
|
3397
|
+
ctx.effect(() => ctx.documentPreviews.register({
|
|
3398
|
+
id: ID,
|
|
3399
|
+
extensions: ['xml'],
|
|
3400
|
+
priority: 'extension',
|
|
3401
|
+
title: () => t('title'),
|
|
3402
|
+
loading: 'bytes-complete',
|
|
3403
|
+
}), 'factorytalk-view-use: document viewer')
|
|
3404
|
+
ctx.inject(['betterSidebar'], scope => scope.effect(() => scope.betterSidebar.registerFileViewer({
|
|
3405
|
+
id: ID,
|
|
3406
|
+
title: () => t('title'),
|
|
3407
|
+
exts: ['xml'],
|
|
3408
|
+
priority: 0,
|
|
3409
|
+
fetchStrategy: 'fsRead',
|
|
3410
|
+
component: fileViewer(t),
|
|
3411
|
+
}), 'factorytalk-view-use: sidebar file viewer'))
|
|
3412
|
+
ctx.effect(() => ctx.slots.inject('sidebar.right.tab.document', () => ctx.slots.register(
|
|
3413
|
+
{ name: 'sidebar.right.tab.document', key: ID, locale: NS }, FtViewBody,
|
|
3414
|
+
)), 'factorytalk-view-use: document body')
|
|
3415
|
+
ctx.effect(() => {
|
|
3416
|
+
const style = document.createElement('style')
|
|
3417
|
+
style.textContent = CSS
|
|
3418
|
+
document.head.append(style)
|
|
3419
|
+
return () => style.remove()
|
|
3420
|
+
}, 'factorytalk-view-use: styles')
|
|
3421
|
+
}
|
|
3422
|
+
|
|
3423
|
+
return {
|
|
3424
|
+
inject: ['slots', 'locale', 'documentPreviews'],
|
|
3425
|
+
apply,
|
|
3426
|
+
}
|
|
3427
|
+
|
|
3428
|
+
})()
|
|
3429
|
+
|
|
3430
|
+
if (
|
|
3431
|
+
typeof ladder.apply !== 'function' ||
|
|
3432
|
+
typeof ftview.apply !== 'function'
|
|
3433
|
+
) {
|
|
3434
|
+
throw new Error("@aryacode/dsh-engineering" + ': a merged client face exports no apply()')
|
|
3435
|
+
}
|
|
3436
|
+
|
|
3437
|
+
// The declared services are the union of both faces': one Cordis plugin
|
|
3438
|
+
// cannot inject less than its parts need. CJS shape (exports.apply /
|
|
3439
|
+
// exports.inject) is the loader's contract for a registered factory.
|
|
3440
|
+
return {
|
|
3441
|
+
inject: [...new Set([...ladder.inject, ...ftview.inject])],
|
|
3442
|
+
apply: (ctx) => {
|
|
3443
|
+
ladder.apply(ctx)
|
|
3444
|
+
ftview.apply(ctx)
|
|
3445
|
+
},
|
|
3446
|
+
}
|
|
3447
|
+
},
|
|
3448
|
+
})
|