modm-pinout 1.0.0__tar.gz
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.
- modm_pinout-1.0.0/.github/workflows/pypi.yaml +40 -0
- modm_pinout-1.0.0/.gitignore +4 -0
- modm_pinout-1.0.0/LICENSE +27 -0
- modm_pinout-1.0.0/PKG-INFO +88 -0
- modm_pinout-1.0.0/README.md +63 -0
- modm_pinout-1.0.0/pyproject.toml +54 -0
- modm_pinout-1.0.0/setup.cfg +4 -0
- modm_pinout-1.0.0/src/modm_pinout/__init__.py +5 -0
- modm_pinout-1.0.0/src/modm_pinout/__main__.py +11 -0
- modm_pinout-1.0.0/src/modm_pinout/assets/scripts/pinout-app.js +22 -0
- modm_pinout-1.0.0/src/modm_pinout/assets/scripts/pinout-core.js +328 -0
- modm_pinout-1.0.0/src/modm_pinout/assets/scripts/pinout-io.js +238 -0
- modm_pinout-1.0.0/src/modm_pinout/assets/scripts/pinout-loader.js +66 -0
- modm_pinout-1.0.0/src/modm_pinout/assets/scripts/pinout-package-renderers.js +118 -0
- modm_pinout-1.0.0/src/modm_pinout/assets/scripts/pinout-package.js +1459 -0
- modm_pinout-1.0.0/src/modm_pinout/assets/scripts/pinout-persistence.js +560 -0
- modm_pinout-1.0.0/src/modm_pinout/assets/scripts/pinout-regex.js +690 -0
- modm_pinout-1.0.0/src/modm_pinout/assets/scripts/pinout-table.js +458 -0
- modm_pinout-1.0.0/src/modm_pinout/assets/styles/pinout.css +819 -0
- modm_pinout-1.0.0/src/modm_pinout/catalog.py +52 -0
- modm_pinout-1.0.0/src/modm_pinout/device_loader.py +306 -0
- modm_pinout-1.0.0/src/modm_pinout/resources.py +30 -0
- modm_pinout-1.0.0/src/modm_pinout/templates/pin_index.html.j2 +113 -0
- modm_pinout-1.0.0/src/modm_pinout/templates/pin_matrix.html.j2 +61 -0
- modm_pinout-1.0.0/src/modm_pinout.egg-info/PKG-INFO +88 -0
- modm_pinout-1.0.0/src/modm_pinout.egg-info/SOURCES.txt +28 -0
- modm_pinout-1.0.0/src/modm_pinout.egg-info/dependency_links.txt +1 -0
- modm_pinout-1.0.0/src/modm_pinout.egg-info/entry_points.txt +2 -0
- modm_pinout-1.0.0/src/modm_pinout.egg-info/requires.txt +7 -0
- modm_pinout-1.0.0/src/modm_pinout.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
pypi-publish:
|
|
10
|
+
name: Upload release to PyPI
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
environment:
|
|
13
|
+
name: pypi
|
|
14
|
+
url: https://pypi.org/p/modm-pinout
|
|
15
|
+
permissions:
|
|
16
|
+
contents: read
|
|
17
|
+
id-token: write
|
|
18
|
+
steps:
|
|
19
|
+
- name: Clone repository
|
|
20
|
+
uses: actions/checkout@v4
|
|
21
|
+
with:
|
|
22
|
+
fetch-depth: 0
|
|
23
|
+
|
|
24
|
+
- name: Set up Python 3.12
|
|
25
|
+
uses: actions/setup-python@v5
|
|
26
|
+
with:
|
|
27
|
+
python-version: "3.12"
|
|
28
|
+
|
|
29
|
+
- name: Install release tools
|
|
30
|
+
run: |
|
|
31
|
+
python -m pip install --upgrade pip
|
|
32
|
+
python -m pip install build twine setuptools-scm[toml]
|
|
33
|
+
|
|
34
|
+
- name: Package library
|
|
35
|
+
run: |
|
|
36
|
+
python -m build
|
|
37
|
+
twine check dist/*
|
|
38
|
+
|
|
39
|
+
- name: Upload to PyPI
|
|
40
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Copyright (c) 2026, Niklas Hauser
|
|
2
|
+
All rights reserved.
|
|
3
|
+
|
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
|
5
|
+
modification, are permitted provided that the following conditions are met:
|
|
6
|
+
|
|
7
|
+
* Redistributions of source code must retain the above copyright notice, this
|
|
8
|
+
list of conditions and the following disclaimer.
|
|
9
|
+
|
|
10
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
|
11
|
+
this list of conditions and the following disclaimer in the documentation
|
|
12
|
+
and/or other materials provided with the distribution.
|
|
13
|
+
|
|
14
|
+
* Neither the name of [project] nor the names of its
|
|
15
|
+
contributors may be used to endorse or promote products derived from
|
|
16
|
+
this software without specific prior written permission.
|
|
17
|
+
|
|
18
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
19
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
20
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
21
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
22
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
23
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
24
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
25
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
26
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
27
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: modm-pinout
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Static pin mapping site generator for modm device definitions
|
|
5
|
+
Keywords: embedded,modm,pinout,generator,static-site
|
|
6
|
+
Classifier: Development Status :: 3 - Alpha
|
|
7
|
+
Classifier: Environment :: Console
|
|
8
|
+
Classifier: Intended Audience :: Developers
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Topic :: Software Development :: Code Generators
|
|
14
|
+
Classifier: Topic :: Software Development :: Embedded Systems
|
|
15
|
+
Requires-Python: >=3.11
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: jinja2>=3.1
|
|
19
|
+
Requires-Dist: modm-devices>=0.13.0
|
|
20
|
+
Provides-Extra: dev
|
|
21
|
+
Requires-Dist: build>=1.2; extra == "dev"
|
|
22
|
+
Requires-Dist: setuptools-scm[toml]>=8; extra == "dev"
|
|
23
|
+
Requires-Dist: twine>=5.0; extra == "dev"
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
# modm-pinout
|
|
27
|
+
|
|
28
|
+
`modm-pinout` builds a static pin-mapping site from device definitions provided by `modm-devices`.
|
|
29
|
+
|
|
30
|
+
The generated output contains:
|
|
31
|
+
|
|
32
|
+
- one HTML page per device
|
|
33
|
+
- shared CSS and JavaScript assets
|
|
34
|
+
- shared JSON datasets grouped by source XML
|
|
35
|
+
- an index page for device lookup
|
|
36
|
+
- a manifest describing generated pages and datasets
|
|
37
|
+
|
|
38
|
+
The package installs a CLI named `modm_pinout`.
|
|
39
|
+
|
|
40
|
+
> Please note that this code was vibe-coded, since we're not good at web development.
|
|
41
|
+
> `modm-devices` is still written by humans though and it shows lol.
|
|
42
|
+
|
|
43
|
+
## Installation
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
python -m pip install modm-pinout
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
For local development:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
python -m venv .venv
|
|
53
|
+
source .venv/bin/activate
|
|
54
|
+
python -m pip install -e .
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Usage
|
|
58
|
+
|
|
59
|
+
Generate one device page:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
modm_pinout stm32c011f4p6 -o build
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Generate all supported devices:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
modm_pinout --all -o build
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Output Layout
|
|
72
|
+
|
|
73
|
+
The generated site uses this structure:
|
|
74
|
+
|
|
75
|
+
```text
|
|
76
|
+
build/
|
|
77
|
+
index.html
|
|
78
|
+
manifest.json
|
|
79
|
+
assets/
|
|
80
|
+
data/
|
|
81
|
+
devices/
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Notes
|
|
85
|
+
|
|
86
|
+
- Device metadata and XML inputs are loaded from the installed `modm-devices` package.
|
|
87
|
+
- Templates and frontend assets are bundled as package data inside `modm_pinout`.
|
|
88
|
+
- The package and CLI naming are generic so additional device families can be integrated later.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# modm-pinout
|
|
2
|
+
|
|
3
|
+
`modm-pinout` builds a static pin-mapping site from device definitions provided by `modm-devices`.
|
|
4
|
+
|
|
5
|
+
The generated output contains:
|
|
6
|
+
|
|
7
|
+
- one HTML page per device
|
|
8
|
+
- shared CSS and JavaScript assets
|
|
9
|
+
- shared JSON datasets grouped by source XML
|
|
10
|
+
- an index page for device lookup
|
|
11
|
+
- a manifest describing generated pages and datasets
|
|
12
|
+
|
|
13
|
+
The package installs a CLI named `modm_pinout`.
|
|
14
|
+
|
|
15
|
+
> Please note that this code was vibe-coded, since we're not good at web development.
|
|
16
|
+
> `modm-devices` is still written by humans though and it shows lol.
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
python -m pip install modm-pinout
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
For local development:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
python -m venv .venv
|
|
28
|
+
source .venv/bin/activate
|
|
29
|
+
python -m pip install -e .
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Usage
|
|
33
|
+
|
|
34
|
+
Generate one device page:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
modm_pinout stm32c011f4p6 -o build
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Generate all supported devices:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
modm_pinout --all -o build
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Output Layout
|
|
47
|
+
|
|
48
|
+
The generated site uses this structure:
|
|
49
|
+
|
|
50
|
+
```text
|
|
51
|
+
build/
|
|
52
|
+
index.html
|
|
53
|
+
manifest.json
|
|
54
|
+
assets/
|
|
55
|
+
data/
|
|
56
|
+
devices/
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Notes
|
|
60
|
+
|
|
61
|
+
- Device metadata and XML inputs are loaded from the installed `modm-devices` package.
|
|
62
|
+
- Templates and frontend assets are bundled as package data inside `modm_pinout`.
|
|
63
|
+
- The package and CLI naming are generic so additional device families can be integrated later.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=69", "setuptools-scm[toml]>=8", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "modm-pinout"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Static pin mapping site generator for modm device definitions"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
keywords = ["embedded", "modm", "pinout", "generator", "static-site"]
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Development Status :: 3 - Alpha",
|
|
14
|
+
"Environment :: Console",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
18
|
+
"Programming Language :: Python :: 3.11",
|
|
19
|
+
"Programming Language :: Python :: 3.12",
|
|
20
|
+
"Topic :: Software Development :: Code Generators",
|
|
21
|
+
"Topic :: Software Development :: Embedded Systems",
|
|
22
|
+
]
|
|
23
|
+
dependencies = [
|
|
24
|
+
"jinja2>=3.1",
|
|
25
|
+
"modm-devices>=0.13.0",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[project.optional-dependencies]
|
|
29
|
+
dev = [
|
|
30
|
+
"build>=1.2",
|
|
31
|
+
"setuptools-scm[toml]>=8",
|
|
32
|
+
"twine>=5.0",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[project.scripts]
|
|
36
|
+
modm_pinout = "modm_pinout:main"
|
|
37
|
+
|
|
38
|
+
[tool.setuptools]
|
|
39
|
+
package-dir = {"" = "src"}
|
|
40
|
+
|
|
41
|
+
[tool.setuptools.packages.find]
|
|
42
|
+
where = ["src"]
|
|
43
|
+
|
|
44
|
+
[tool.setuptools.package-data]
|
|
45
|
+
modm_pinout = [
|
|
46
|
+
"assets/scripts/*.js",
|
|
47
|
+
"assets/styles/*.css",
|
|
48
|
+
"templates/*.j2",
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
[tool.setuptools_scm]
|
|
52
|
+
version_scheme = "no-guess-dev"
|
|
53
|
+
local_scheme = "no-local-version"
|
|
54
|
+
tag_regex = "^(?:v)?(?P<version>.+)$"
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
window.initializeModmPinoutApp = function initializeModmPinoutApp(config) {
|
|
4
|
+
const api = window.ModmPinout || {};
|
|
5
|
+
if (typeof api.createContext !== "function") {
|
|
6
|
+
throw new Error("Pin matrix core module did not load.");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const ctx = api.createContext(config);
|
|
10
|
+
api.attachCore(ctx);
|
|
11
|
+
api.attachPersistence(ctx);
|
|
12
|
+
api.attachRegex(ctx);
|
|
13
|
+
api.attachImportExport(ctx);
|
|
14
|
+
api.attachTable(ctx);
|
|
15
|
+
api.attachPackage(ctx);
|
|
16
|
+
|
|
17
|
+
ctx.bindPageEventHandlers();
|
|
18
|
+
ctx.renderPackageDiagram();
|
|
19
|
+
ctx.initializeWithRecovery().catch((error) => {
|
|
20
|
+
ctx.setStatus(`Failed to initialize table: ${String(error)}`, true);
|
|
21
|
+
});
|
|
22
|
+
};
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
(function registerModmPinoutCore(global) {
|
|
4
|
+
const api = global.ModmPinout = global.ModmPinout || {};
|
|
5
|
+
|
|
6
|
+
api.createContext = function createContext(config) {
|
|
7
|
+
const deviceData = config && config.deviceData ? config.deviceData : null;
|
|
8
|
+
const cookieKey = config && typeof config.cookieKey === "string" ? config.cookieKey : "";
|
|
9
|
+
|
|
10
|
+
if (!deviceData || typeof deviceData !== "object") {
|
|
11
|
+
throw new Error("Missing device data.");
|
|
12
|
+
}
|
|
13
|
+
if (!cookieKey) {
|
|
14
|
+
throw new Error("Missing cookie key.");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
DEVICE_DATA: deviceData,
|
|
19
|
+
COOKIE_KEY: cookieKey,
|
|
20
|
+
SAVE_DAYS: 365,
|
|
21
|
+
COOKIE_MAX_VALUE_BYTES: 3800,
|
|
22
|
+
SHARE_URL_PARAM: "share",
|
|
23
|
+
SHARE_URL_FORMAT: "modm-pinout-share",
|
|
24
|
+
SHARE_URL_VERSION: 1,
|
|
25
|
+
ADD_REGEX_FIELD: "__add_regex__",
|
|
26
|
+
BASE_REGEX_FILTER_FIELDS: ["position", "short_name", "selected_function", "internal_name"],
|
|
27
|
+
table: null,
|
|
28
|
+
saveTimer: null,
|
|
29
|
+
htmlDecodeNode: null,
|
|
30
|
+
textMeasureCtx: null,
|
|
31
|
+
state: {
|
|
32
|
+
selectedByRowId: {},
|
|
33
|
+
namesByRowId: {},
|
|
34
|
+
baseFilterPatterns: {},
|
|
35
|
+
reviewByRowId: {},
|
|
36
|
+
unmappedRows: [],
|
|
37
|
+
regexColumns: [],
|
|
38
|
+
nextRegexId: 1,
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
api.attachCore = function attachCore(ctx) {
|
|
44
|
+
const state = ctx.state;
|
|
45
|
+
|
|
46
|
+
ctx.escapeHtml = function escapeHtml(value) {
|
|
47
|
+
return String(value)
|
|
48
|
+
.replaceAll("&", "&")
|
|
49
|
+
.replaceAll("<", "<")
|
|
50
|
+
.replaceAll(">", ">")
|
|
51
|
+
.replaceAll('"', """)
|
|
52
|
+
.replaceAll("'", "'");
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
ctx.setStatus = function setStatus(message, isError = false) {
|
|
56
|
+
const node = document.getElementById("save-status");
|
|
57
|
+
node.textContent = message;
|
|
58
|
+
node.classList.toggle("error", isError);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
ctx.debounce = function debounce(fn, waitMs) {
|
|
62
|
+
return function debounced(...args) {
|
|
63
|
+
if (ctx.saveTimer !== null) {
|
|
64
|
+
window.clearTimeout(ctx.saveTimer);
|
|
65
|
+
}
|
|
66
|
+
ctx.saveTimer = window.setTimeout(() => fn(...args), waitMs);
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
ctx.normalizeRegexColumn = function normalizeRegexColumn(meta) {
|
|
71
|
+
if (!meta || typeof meta !== "object") {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
const id = Number(meta.id);
|
|
75
|
+
if (!Number.isInteger(id) || id <= 0) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
const title = typeof meta.title === "string" && meta.title.trim()
|
|
79
|
+
? meta.title.trim()
|
|
80
|
+
: `Regex ${id}`;
|
|
81
|
+
const pattern = typeof meta.pattern === "string" ? meta.pattern : "";
|
|
82
|
+
return {
|
|
83
|
+
id,
|
|
84
|
+
field: `regex_${id}`,
|
|
85
|
+
title,
|
|
86
|
+
pattern,
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
ctx.normalizeSelectedFunctionList = function normalizeSelectedFunctionList(value, availableFunctions = null) {
|
|
91
|
+
const allowedSet = Array.isArray(availableFunctions) ? new Set(availableFunctions) : null;
|
|
92
|
+
const rawValues = Array.isArray(value)
|
|
93
|
+
? value
|
|
94
|
+
: (typeof value === "string" || typeof value === "number" ? [value] : []);
|
|
95
|
+
|
|
96
|
+
const normalized = [];
|
|
97
|
+
const seen = new Set();
|
|
98
|
+
for (const entry of rawValues) {
|
|
99
|
+
const item = String(entry ?? "").trim();
|
|
100
|
+
if (!item || seen.has(item)) {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (allowedSet && !allowedSet.has(item)) {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
seen.add(item);
|
|
108
|
+
normalized.push(item);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return normalized;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
ctx.renderSelectedFunctionsHtml = function renderSelectedFunctionsHtml(value) {
|
|
115
|
+
const selected = ctx.normalizeSelectedFunctionList(value);
|
|
116
|
+
return selected.map((fn) => ctx.escapeHtml(fn)).join("<br>");
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
ctx.decodeHtmlText = function decodeHtmlText(value) {
|
|
120
|
+
const text = String(value || "");
|
|
121
|
+
if (!text || text.indexOf("&") < 0) {
|
|
122
|
+
return text;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (!ctx.htmlDecodeNode) {
|
|
126
|
+
ctx.htmlDecodeNode = document.createElement("textarea");
|
|
127
|
+
}
|
|
128
|
+
ctx.htmlDecodeNode.innerHTML = text;
|
|
129
|
+
return ctx.htmlDecodeNode.value;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
ctx.valueToSortableText = function valueToSortableText(value) {
|
|
133
|
+
if (Array.isArray(value)) {
|
|
134
|
+
return ctx.normalizeSelectedFunctionList(value).join("\n");
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (value == null) {
|
|
138
|
+
return "";
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const withBreaks = String(value).replace(/<br\s*\/?>/gi, "\n");
|
|
142
|
+
const withoutTags = withBreaks.replace(/<[^>]*>/g, "");
|
|
143
|
+
return ctx.decodeHtmlText(withoutTags).trim();
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
ctx.compareNaturalText = function compareNaturalText(left, right) {
|
|
147
|
+
return String(left || "").localeCompare(String(right || ""), undefined, {
|
|
148
|
+
numeric: true,
|
|
149
|
+
sensitivity: "base",
|
|
150
|
+
});
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
ctx.displayedTextSorter = function displayedTextSorter(a, b) {
|
|
154
|
+
return ctx.compareNaturalText(ctx.valueToSortableText(a), ctx.valueToSortableText(b));
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
ctx.pinSorter = function pinSorter(a, b) {
|
|
158
|
+
const left = String(a || "").trim();
|
|
159
|
+
const right = String(b || "").trim();
|
|
160
|
+
const leftIsInt = /^\d+$/.test(left);
|
|
161
|
+
const rightIsInt = /^\d+$/.test(right);
|
|
162
|
+
|
|
163
|
+
if (leftIsInt && rightIsInt) {
|
|
164
|
+
return Number(left) - Number(right);
|
|
165
|
+
}
|
|
166
|
+
if (leftIsInt) {
|
|
167
|
+
return -1;
|
|
168
|
+
}
|
|
169
|
+
if (rightIsInt) {
|
|
170
|
+
return 1;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return ctx.compareNaturalText(left, right);
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
ctx.compareRowIdValues = function compareRowIdValues(left, right) {
|
|
177
|
+
const leftRaw = String(left ?? "");
|
|
178
|
+
const rightRaw = String(right ?? "");
|
|
179
|
+
const leftNum = Number(leftRaw);
|
|
180
|
+
const rightNum = Number(rightRaw);
|
|
181
|
+
const leftIsNumeric = Number.isFinite(leftNum) && leftRaw.trim() !== "";
|
|
182
|
+
const rightIsNumeric = Number.isFinite(rightNum) && rightRaw.trim() !== "";
|
|
183
|
+
|
|
184
|
+
if (leftIsNumeric && rightIsNumeric) {
|
|
185
|
+
return leftNum - rightNum;
|
|
186
|
+
}
|
|
187
|
+
if (leftIsNumeric) {
|
|
188
|
+
return -1;
|
|
189
|
+
}
|
|
190
|
+
if (rightIsNumeric) {
|
|
191
|
+
return 1;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return ctx.compareNaturalText(leftRaw, rightRaw);
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
ctx.normalizePinNameKey = function normalizePinNameKey(value) {
|
|
198
|
+
return String(value ?? "").trim().toUpperCase();
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
ctx.normalizeStoredUnmappedRow = function normalizeStoredUnmappedRow(node, index) {
|
|
202
|
+
if (!node || typeof node !== "object") {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const shortName = String(node.short_name ?? node.name ?? "").trim();
|
|
207
|
+
if (!shortName) {
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const idCandidate = String(node.row_id ?? "").trim();
|
|
212
|
+
const rowId = idCandidate.startsWith("unmapped_") ? idCandidate : `unmapped_${index + 1}`;
|
|
213
|
+
return {
|
|
214
|
+
row_id: rowId,
|
|
215
|
+
short_name: shortName,
|
|
216
|
+
internal_name: String(node.internal_name ?? node.refName ?? node.ref_name ?? "").trim(),
|
|
217
|
+
};
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
ctx.buildRows = function buildRows() {
|
|
221
|
+
const rows = ctx.DEVICE_DATA.rows.map((row) => {
|
|
222
|
+
const rowId = String(row.row_id);
|
|
223
|
+
const functions = Array.isArray(row.functions) ? row.functions.slice() : [];
|
|
224
|
+
const selected = state.selectedByRowId[rowId];
|
|
225
|
+
const internalName = state.namesByRowId[rowId];
|
|
226
|
+
const selectedFunction = ctx.normalizeSelectedFunctionList(selected, functions);
|
|
227
|
+
const resolvedInternalName = typeof internalName === "string" ? internalName : "";
|
|
228
|
+
|
|
229
|
+
return {
|
|
230
|
+
row_id: row.row_id,
|
|
231
|
+
position: row.position,
|
|
232
|
+
short_name: row.short_name,
|
|
233
|
+
pin_label: row.pin_label,
|
|
234
|
+
functions,
|
|
235
|
+
selected_function: selectedFunction,
|
|
236
|
+
internal_name: resolvedInternalName,
|
|
237
|
+
needs_review: Boolean(state.reviewByRowId[rowId]),
|
|
238
|
+
is_unmapped: false,
|
|
239
|
+
};
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
const existingRowIds = new Set(rows.map((row) => String(row.row_id)));
|
|
243
|
+
const normalizedUnmappedRows = state.unmappedRows
|
|
244
|
+
.map((node, index) => ctx.normalizeStoredUnmappedRow(node, index))
|
|
245
|
+
.filter((node) => node !== null);
|
|
246
|
+
|
|
247
|
+
for (const [index, row] of normalizedUnmappedRows.entries()) {
|
|
248
|
+
let rowId = String(row.row_id || `unmapped_${index + 1}`);
|
|
249
|
+
if (!rowId.startsWith("unmapped_")) {
|
|
250
|
+
rowId = `unmapped_${index + 1}`;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (existingRowIds.has(rowId)) {
|
|
254
|
+
let suffix = 1;
|
|
255
|
+
while (existingRowIds.has(`${rowId}_${suffix}`)) {
|
|
256
|
+
suffix += 1;
|
|
257
|
+
}
|
|
258
|
+
rowId = `${rowId}_${suffix}`;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
existingRowIds.add(rowId);
|
|
262
|
+
const internalName = state.namesByRowId[rowId];
|
|
263
|
+
rows.push({
|
|
264
|
+
row_id: rowId,
|
|
265
|
+
position: "",
|
|
266
|
+
short_name: row.short_name,
|
|
267
|
+
pin_label: row.short_name,
|
|
268
|
+
functions: [],
|
|
269
|
+
selected_function: [],
|
|
270
|
+
internal_name: typeof internalName === "string" ? internalName : row.internal_name,
|
|
271
|
+
needs_review: Boolean(state.reviewByRowId[rowId]),
|
|
272
|
+
is_unmapped: true,
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return rows;
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
ctx.getRowDataById = function getRowDataById(rowId) {
|
|
280
|
+
const rowKey = String(rowId);
|
|
281
|
+
if (ctx.table) {
|
|
282
|
+
const tableRow = ctx.table.getRow(rowId);
|
|
283
|
+
if (tableRow) {
|
|
284
|
+
return tableRow.getData();
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return ctx.buildRows().find((row) => String(row.row_id) === rowKey) || null;
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
ctx.updateSelectedFunctionsForRow = function updateSelectedFunctionsForRow(rowId, value) {
|
|
292
|
+
const rowKey = String(rowId);
|
|
293
|
+
const rowData = ctx.getRowDataById(rowKey);
|
|
294
|
+
if (!rowData) {
|
|
295
|
+
return [];
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const availableFunctions = Array.isArray(rowData.functions) ? rowData.functions : [];
|
|
299
|
+
const normalized = ctx.normalizeSelectedFunctionList(value, availableFunctions);
|
|
300
|
+
|
|
301
|
+
if (normalized.length > 0) {
|
|
302
|
+
state.selectedByRowId[rowKey] = normalized;
|
|
303
|
+
} else {
|
|
304
|
+
delete state.selectedByRowId[rowKey];
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (ctx.table) {
|
|
308
|
+
const tableRow = ctx.table.getRow(rowId);
|
|
309
|
+
if (tableRow) {
|
|
310
|
+
Promise.resolve(tableRow.update({ selected_function: normalized })).then(() => {
|
|
311
|
+
if (typeof tableRow.reformat === "function") {
|
|
312
|
+
tableRow.reformat();
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
ctx.saveStateDebounced();
|
|
319
|
+
return normalized;
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
ctx.applyReviewRowClass = function applyReviewRowClass(row) {
|
|
323
|
+
const data = row.getData();
|
|
324
|
+
const rowElement = row.getElement();
|
|
325
|
+
rowElement.classList.toggle("needs-review-row", Boolean(data && data.needs_review));
|
|
326
|
+
};
|
|
327
|
+
};
|
|
328
|
+
}(window));
|