underpost 2.8.878 → 2.8.882
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/.env.development +35 -3
- package/.env.production +40 -3
- package/.env.test +35 -3
- package/.github/workflows/release.cd.yml +3 -3
- package/README.md +20 -2
- package/bin/deploy.js +40 -0
- package/cli.md +3 -1
- package/conf.js +29 -3
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/dd-test-development/deployment.yaml +6 -6
- package/package.json +1 -2
- package/src/api/document/document.controller.js +66 -0
- package/src/api/document/document.model.js +51 -0
- package/src/api/document/document.router.js +24 -0
- package/src/api/document/document.service.js +133 -0
- package/src/cli/deploy.js +1 -1
- package/src/cli/index.js +2 -0
- package/src/cli/repository.js +2 -0
- package/src/cli/run.js +27 -1
- package/src/client/Default.index.js +46 -1
- package/src/client/components/core/Account.js +8 -1
- package/src/client/components/core/AgGrid.js +18 -9
- package/src/client/components/core/Auth.js +258 -89
- package/src/client/components/core/BtnIcon.js +13 -3
- package/src/client/components/core/Content.js +2 -1
- package/src/client/components/core/CssCore.js +40 -27
- package/src/client/components/core/Docs.js +189 -88
- package/src/client/components/core/Input.js +34 -19
- package/src/client/components/core/LoadingAnimation.js +5 -10
- package/src/client/components/core/Modal.js +280 -123
- package/src/client/components/core/ObjectLayerEngine.js +470 -104
- package/src/client/components/core/ObjectLayerEngineModal.js +1 -0
- package/src/client/components/core/Panel.js +9 -2
- package/src/client/components/core/PanelForm.js +234 -76
- package/src/client/components/core/Router.js +15 -15
- package/src/client/components/core/ToolTip.js +83 -19
- package/src/client/components/core/Translate.js +1 -1
- package/src/client/components/core/VanillaJs.js +7 -3
- package/src/client/components/core/windowGetDimensions.js +202 -0
- package/src/client/components/default/MenuDefault.js +105 -41
- package/src/client/components/default/RoutesDefault.js +2 -0
- package/src/client/services/default/default.management.js +1 -0
- package/src/client/services/document/document.service.js +97 -0
- package/src/client/services/file/file.service.js +2 -0
- package/src/client/ssr/Render.js +1 -1
- package/src/client/ssr/head/DefaultScripts.js +2 -0
- package/src/client/ssr/head/Seo.js +1 -0
- package/src/index.js +1 -1
- package/src/mailer/EmailRender.js +1 -1
- package/src/server/auth.js +68 -17
- package/src/server/client-build.js +2 -3
- package/src/server/client-formatted.js +40 -12
- package/src/server/conf.js +5 -1
- package/src/server/crypto.js +195 -76
- package/src/server/object-layer.js +196 -0
- package/src/server/peer.js +47 -5
- package/src/server/process.js +85 -1
- package/src/server/runtime.js +23 -23
- package/src/server/ssr.js +52 -10
- package/src/server/valkey.js +89 -1
- package/test/crypto.test.js +117 -0
|
@@ -1,113 +1,112 @@
|
|
|
1
1
|
import { darkTheme, renderChessPattern } from './Css.js';
|
|
2
|
-
|
|
3
|
-
const templateHTML = html`
|
|
4
|
-
<style>
|
|
5
|
-
:host {
|
|
6
|
-
--border: 1px solid #bbb;
|
|
7
|
-
--gap: 8px;
|
|
8
|
-
display: inline-block;
|
|
9
|
-
font-family: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial;
|
|
10
|
-
}
|
|
11
|
-
.wrap {
|
|
12
|
-
display: flex;
|
|
13
|
-
flex-direction: column;
|
|
14
|
-
gap: var(--gap);
|
|
15
|
-
align-items: flex-start;
|
|
16
|
-
}
|
|
17
|
-
.canvas-frame {
|
|
18
|
-
border: var(--border);
|
|
19
|
-
display: inline-block;
|
|
20
|
-
line-height: 0;
|
|
21
|
-
position: relative;
|
|
22
|
-
background: transparent;
|
|
23
|
-
}
|
|
24
|
-
canvas.canvas-layer {
|
|
25
|
-
display: block;
|
|
26
|
-
image-rendering: pixelated;
|
|
27
|
-
touch-action: none;
|
|
28
|
-
cursor: crosshair;
|
|
29
|
-
}
|
|
30
|
-
canvas.grid-layer {
|
|
31
|
-
position: absolute;
|
|
32
|
-
left: 0;
|
|
33
|
-
top: 0;
|
|
34
|
-
pointer-events: none;
|
|
35
|
-
}
|
|
36
|
-
.toolbar {
|
|
37
|
-
display: flex;
|
|
38
|
-
gap: 8px;
|
|
39
|
-
flex-wrap: wrap;
|
|
40
|
-
align-items: center;
|
|
41
|
-
}
|
|
42
|
-
.toolbar label {
|
|
43
|
-
display: inline-flex;
|
|
44
|
-
gap: 6px;
|
|
45
|
-
align-items: center;
|
|
46
|
-
}
|
|
47
|
-
.group {
|
|
48
|
-
display: inline-flex;
|
|
49
|
-
gap: 6px;
|
|
50
|
-
align-items: center;
|
|
51
|
-
}
|
|
52
|
-
</style>
|
|
53
|
-
|
|
54
|
-
<div class="wrap">
|
|
55
|
-
<div class="toolbar">
|
|
56
|
-
<input type="color" part="color" title="Brush color" value="#000000" />
|
|
57
|
-
<select part="tool">
|
|
58
|
-
<option value="pencil">pencil</option>
|
|
59
|
-
<option value="eraser">eraser</option>
|
|
60
|
-
<option value="fill">fill</option>
|
|
61
|
-
<option value="eyedropper">eyedropper</option>
|
|
62
|
-
</select>
|
|
63
|
-
|
|
64
|
-
<label>brush <input type="number" part="brush-size" min="1" value="1" /></label>
|
|
65
|
-
<label>pixel-size <input type="number" part="pixel-size" min="1" value="16" /></label>
|
|
66
|
-
|
|
67
|
-
<!-- New: cell dimensions (width x height) -->
|
|
68
|
-
<label
|
|
69
|
-
>cells <input type="number" part="cell-width" min="1" value="16" style="width:6ch" /> x
|
|
70
|
-
<input type="number" part="cell-height" min="1" value="16" style="width:6ch"
|
|
71
|
-
/></label>
|
|
72
|
-
|
|
73
|
-
<label class="switch"> <input type="checkbox" part="toggle-grid" /> grid </label>
|
|
74
|
-
|
|
75
|
-
<!-- New: transform tools -->
|
|
76
|
-
<div class="group">
|
|
77
|
-
<button part="flip-h" title="Flip horizontally">Flip H</button>
|
|
78
|
-
<button part="flip-v" title="Flip vertically">Flip V</button>
|
|
79
|
-
<button part="rot-ccw" title="Rotate -90°">⟲</button>
|
|
80
|
-
<button part="rot-cw" title="Rotate +90°">⟳</button>
|
|
81
|
-
</div>
|
|
82
|
-
|
|
83
|
-
<label
|
|
84
|
-
>opacity <input type="range" part="opacity" min="0" max="255" value="255" style="width:10rem" /><input
|
|
85
|
-
type="number"
|
|
86
|
-
part="opacity-num"
|
|
87
|
-
min="0"
|
|
88
|
-
max="255"
|
|
89
|
-
value="255"
|
|
90
|
-
style="width:5ch;margin-left:4px"
|
|
91
|
-
/></label>
|
|
92
|
-
|
|
93
|
-
<button part="clear" title="Clear (make fully transparent)">Clear</button>
|
|
94
|
-
|
|
95
|
-
<button part="export">Export PNG</button>
|
|
96
|
-
<button part="export-json">Export JSON</button>
|
|
97
|
-
<button part="import-json">Import JSON</button>
|
|
98
|
-
</div>
|
|
99
|
-
<div class="canvas-frame" style="${renderChessPattern()}">
|
|
100
|
-
<canvas part="canvas" class="canvas-layer"></canvas>
|
|
101
|
-
<canvas part="grid" class="grid-layer"></canvas>
|
|
102
|
-
</div>
|
|
103
|
-
</div>
|
|
104
|
-
`;
|
|
2
|
+
import { append, htmls } from './VanillaJs.js';
|
|
105
3
|
|
|
106
4
|
class ObjectLayerEngineElement extends HTMLElement {
|
|
107
5
|
constructor() {
|
|
108
6
|
super();
|
|
109
7
|
this.attachShadow({ mode: 'open' });
|
|
110
|
-
this.shadowRoot.innerHTML =
|
|
8
|
+
this.shadowRoot.innerHTML = html`
|
|
9
|
+
<style>
|
|
10
|
+
:host {
|
|
11
|
+
--border: 1px solid #bbb;
|
|
12
|
+
--gap: 8px;
|
|
13
|
+
display: inline-block;
|
|
14
|
+
font-family: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial;
|
|
15
|
+
}
|
|
16
|
+
.wrap {
|
|
17
|
+
display: flex;
|
|
18
|
+
flex-direction: column;
|
|
19
|
+
gap: var(--gap);
|
|
20
|
+
align-items: flex-start;
|
|
21
|
+
}
|
|
22
|
+
.canvas-frame {
|
|
23
|
+
border: var(--border);
|
|
24
|
+
display: inline-block;
|
|
25
|
+
line-height: 0;
|
|
26
|
+
position: relative;
|
|
27
|
+
background: transparent;
|
|
28
|
+
}
|
|
29
|
+
canvas.canvas-layer {
|
|
30
|
+
display: block;
|
|
31
|
+
image-rendering: pixelated;
|
|
32
|
+
touch-action: none;
|
|
33
|
+
cursor: crosshair;
|
|
34
|
+
}
|
|
35
|
+
canvas.grid-layer {
|
|
36
|
+
position: absolute;
|
|
37
|
+
left: 0;
|
|
38
|
+
top: 0;
|
|
39
|
+
pointer-events: none;
|
|
40
|
+
}
|
|
41
|
+
.toolbar {
|
|
42
|
+
display: flex;
|
|
43
|
+
gap: 8px;
|
|
44
|
+
flex-wrap: wrap;
|
|
45
|
+
align-items: center;
|
|
46
|
+
}
|
|
47
|
+
.toolbar label {
|
|
48
|
+
display: inline-flex;
|
|
49
|
+
gap: 6px;
|
|
50
|
+
align-items: center;
|
|
51
|
+
}
|
|
52
|
+
.group {
|
|
53
|
+
display: inline-flex;
|
|
54
|
+
gap: 6px;
|
|
55
|
+
align-items: center;
|
|
56
|
+
}
|
|
57
|
+
</style>
|
|
58
|
+
|
|
59
|
+
<div class="wrap">
|
|
60
|
+
<div class="toolbar">
|
|
61
|
+
<input type="color" part="color" title="Brush color" value="#000000" />
|
|
62
|
+
<select part="tool">
|
|
63
|
+
<option value="pencil">pencil</option>
|
|
64
|
+
<option value="eraser">eraser</option>
|
|
65
|
+
<option value="fill">fill</option>
|
|
66
|
+
<option value="eyedropper">eyedropper</option>
|
|
67
|
+
</select>
|
|
68
|
+
|
|
69
|
+
<label>brush <input type="number" part="brush-size" min="1" value="1" /></label>
|
|
70
|
+
<label>pixel-size <input type="number" part="pixel-size" min="1" value="16" /></label>
|
|
71
|
+
|
|
72
|
+
<!-- New: cell dimensions (width x height) -->
|
|
73
|
+
<label
|
|
74
|
+
>cells <input type="number" part="cell-width" min="1" value="16" style="width:6ch" /> x
|
|
75
|
+
<input type="number" part="cell-height" min="1" value="16" style="width:6ch"
|
|
76
|
+
/></label>
|
|
77
|
+
|
|
78
|
+
<label class="switch"> <input type="checkbox" part="toggle-grid" /> grid </label>
|
|
79
|
+
|
|
80
|
+
<!-- New: transform tools -->
|
|
81
|
+
<div class="group">
|
|
82
|
+
<button part="flip-h" title="Flip horizontally">Flip H</button>
|
|
83
|
+
<button part="flip-v" title="Flip vertically">Flip V</button>
|
|
84
|
+
<button part="rot-ccw" title="Rotate -90°">⟲</button>
|
|
85
|
+
<button part="rot-cw" title="Rotate +90°">⟳</button>
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
<label
|
|
89
|
+
>opacity <input type="range" part="opacity" min="0" max="255" value="255" style="width:10rem" /><input
|
|
90
|
+
type="number"
|
|
91
|
+
part="opacity-num"
|
|
92
|
+
min="0"
|
|
93
|
+
max="255"
|
|
94
|
+
value="255"
|
|
95
|
+
style="width:5ch;margin-left:4px"
|
|
96
|
+
/></label>
|
|
97
|
+
|
|
98
|
+
<button part="clear" title="Clear (make fully transparent)">Clear</button>
|
|
99
|
+
|
|
100
|
+
<button part="export">Export PNG</button>
|
|
101
|
+
<button part="export-json">Export JSON</button>
|
|
102
|
+
<button part="import-json">Import JSON</button>
|
|
103
|
+
</div>
|
|
104
|
+
<div class="canvas-frame" style="${renderChessPattern()}">
|
|
105
|
+
<canvas part="canvas" class="canvas-layer"></canvas>
|
|
106
|
+
<canvas part="grid" class="grid-layer"></canvas>
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
`;
|
|
111
110
|
|
|
112
111
|
// DOM
|
|
113
112
|
this._pixelCanvas = this.shadowRoot.querySelector('canvas[part="canvas"]');
|
|
@@ -861,3 +860,370 @@ customElements.define('object-layer-engine', ObjectLayerEngineElement);
|
|
|
861
860
|
Example usage:
|
|
862
861
|
<object-layer-engine id="ole" width="20" height="12" pixel-size="20"></object-layer-engine>
|
|
863
862
|
*/
|
|
863
|
+
|
|
864
|
+
class ObjectLayerPngLoader extends HTMLElement {
|
|
865
|
+
constructor() {
|
|
866
|
+
super();
|
|
867
|
+
this.attachShadow({ mode: 'open' });
|
|
868
|
+
this.shadowRoot.innerHTML = html`
|
|
869
|
+
<style>
|
|
870
|
+
:host {
|
|
871
|
+
display: block;
|
|
872
|
+
font-family: system-ui, -apple-system, 'Segoe UI', Roboto, Arial;
|
|
873
|
+
}
|
|
874
|
+
.wrap {
|
|
875
|
+
display: flex;
|
|
876
|
+
flex-direction: column;
|
|
877
|
+
gap: 8px;
|
|
878
|
+
}
|
|
879
|
+
.controls {
|
|
880
|
+
display: flex;
|
|
881
|
+
gap: 8px;
|
|
882
|
+
align-items: center;
|
|
883
|
+
flex-wrap: wrap;
|
|
884
|
+
}
|
|
885
|
+
.drop-area {
|
|
886
|
+
border: 2px dashed #999;
|
|
887
|
+
padding: 12px;
|
|
888
|
+
border-radius: 8px;
|
|
889
|
+
text-align: center;
|
|
890
|
+
color: #555;
|
|
891
|
+
user-select: none;
|
|
892
|
+
}
|
|
893
|
+
.drop-area.dragover {
|
|
894
|
+
border-color: #4a90e2;
|
|
895
|
+
color: #1a73e8;
|
|
896
|
+
background: rgba(74, 144, 226, 0.04);
|
|
897
|
+
}
|
|
898
|
+
input[type='file'] {
|
|
899
|
+
display: inline-block;
|
|
900
|
+
}
|
|
901
|
+
.hint {
|
|
902
|
+
font-size: 0.9rem;
|
|
903
|
+
color: #666;
|
|
904
|
+
}
|
|
905
|
+
</style>
|
|
906
|
+
|
|
907
|
+
<div class="wrap">
|
|
908
|
+
<div class="controls">
|
|
909
|
+
<label title="Load PNG file">
|
|
910
|
+
<input type="file" accept="image/png" part="file-input" />
|
|
911
|
+
<span class="btn">Choose PNG</span>
|
|
912
|
+
</label>
|
|
913
|
+
<div class="hint">Only PNG images accepted. Drop PNG onto the box below.</div>
|
|
914
|
+
</div>
|
|
915
|
+
|
|
916
|
+
<div class="drop-area" part="drop-area">Drop PNG here or click "Choose PNG"</div>
|
|
917
|
+
</div>
|
|
918
|
+
`;
|
|
919
|
+
|
|
920
|
+
this._fileInput = this.shadowRoot.querySelector('input[type="file"]');
|
|
921
|
+
this._dropArea = this.shadowRoot.querySelector('.drop-area');
|
|
922
|
+
|
|
923
|
+
this._editor = null; // will hold external editor instance
|
|
924
|
+
this._options = { fitMode: 'contain' };
|
|
925
|
+
|
|
926
|
+
// Bind handlers
|
|
927
|
+
this._onFileChange = this._onFileChange.bind(this);
|
|
928
|
+
this._onDrop = this._onDrop.bind(this);
|
|
929
|
+
this._onDragOver = this._onDragOver.bind(this);
|
|
930
|
+
this._onDragLeave = this._onDragLeave.bind(this);
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
static get observedAttributes() {
|
|
934
|
+
return ['editor-selector', 'fit-mode', 'target-cells-x', 'target-cells-y'];
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
attributeChangedCallback(name, oldVal, newVal) {
|
|
938
|
+
if (name === 'editor-selector' && newVal) {
|
|
939
|
+
const el = document.querySelector(newVal);
|
|
940
|
+
if (el) this.setEditor(el);
|
|
941
|
+
}
|
|
942
|
+
if (name === 'fit-mode') {
|
|
943
|
+
this._options.fitMode = newVal || 'contain';
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
connectedCallback() {
|
|
948
|
+
this._fileInput.addEventListener('change', this._onFileChange);
|
|
949
|
+
this._dropArea.addEventListener('dragover', this._onDragOver);
|
|
950
|
+
this._dropArea.addEventListener('dragleave', this._onDragLeave);
|
|
951
|
+
this._dropArea.addEventListener('drop', this._onDrop);
|
|
952
|
+
this.addEventListener('dragover', this._onDragOver);
|
|
953
|
+
this.addEventListener('dragleave', this._onDragLeave);
|
|
954
|
+
this.addEventListener('drop', this._onDrop);
|
|
955
|
+
|
|
956
|
+
// If editor-selector attribute was present at creation, try to resolve
|
|
957
|
+
const sel = this.getAttribute('editor-selector');
|
|
958
|
+
if (sel) {
|
|
959
|
+
const target = document.querySelector(sel);
|
|
960
|
+
if (target) this.setEditor(target);
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
// read fit-mode
|
|
964
|
+
const fit = this.getAttribute('fit-mode');
|
|
965
|
+
if (fit) this._options.fitMode = fit;
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
disconnectedCallback() {
|
|
969
|
+
this._fileInput.removeEventListener('change', this._onFileChange);
|
|
970
|
+
this._dropArea.removeEventListener('dragover', this._onDragOver);
|
|
971
|
+
this._dropArea.removeEventListener('dragleave', this._onDragLeave);
|
|
972
|
+
this._dropArea.removeEventListener('drop', this._onDrop);
|
|
973
|
+
this.removeEventListener('dragover', this._onDragOver);
|
|
974
|
+
this.removeEventListener('dragleave', this._onDragLeave);
|
|
975
|
+
this.removeEventListener('drop', this._onDrop);
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
// ----------------- Public API -----------------
|
|
979
|
+
setEditor(editor) {
|
|
980
|
+
if (!editor) throw new Error('Editor cannot be null/undefined');
|
|
981
|
+
if (typeof editor.loadMatrix !== 'function') {
|
|
982
|
+
throw new Error('Provided editor does not expose loadMatrix(matrix)');
|
|
983
|
+
}
|
|
984
|
+
this._editor = editor;
|
|
985
|
+
this.dispatchEvent(new CustomEvent('editorconnected', { detail: { editor } }));
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
setOptions(options = {}) {
|
|
989
|
+
if (options.fitMode) this._options.fitMode = options.fitMode;
|
|
990
|
+
if (options.targetCellsX) this.setAttribute('target-cells-x', String(options.targetCellsX));
|
|
991
|
+
if (options.targetCellsY) this.setAttribute('target-cells-y', String(options.targetCellsY));
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
get editor() {
|
|
995
|
+
return this._editor;
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
// ----------------- Events -----------------
|
|
999
|
+
_onFileChange(e) {
|
|
1000
|
+
const file = e.target.files && e.target.files[0] ? e.target.files[0] : null;
|
|
1001
|
+
if (!file) return;
|
|
1002
|
+
this._handleFile(file);
|
|
1003
|
+
this._fileInput.value = '';
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
_onDragOver(e) {
|
|
1007
|
+
e.preventDefault();
|
|
1008
|
+
e.dataTransfer.dropEffect = 'copy';
|
|
1009
|
+
this._dropArea.classList.add('dragover');
|
|
1010
|
+
}
|
|
1011
|
+
_onDragLeave(e) {
|
|
1012
|
+
e.preventDefault();
|
|
1013
|
+
this._dropArea.classList.remove('dragover');
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
_onDrop(e) {
|
|
1017
|
+
e.preventDefault();
|
|
1018
|
+
this._dropArea.classList.remove('dragover');
|
|
1019
|
+
const file = e.dataTransfer.files && e.dataTransfer.files[0] ? e.dataTransfer.files[0] : null;
|
|
1020
|
+
if (!file) return;
|
|
1021
|
+
this._handleFile(file);
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
// ----------------- File handling -----------------
|
|
1025
|
+
async _handleFile(file) {
|
|
1026
|
+
const isPngByType = file.type === 'image/png';
|
|
1027
|
+
const isPngByName = file.name && file.name.toLowerCase().endsWith('.png');
|
|
1028
|
+
if (!isPngByType && !isPngByName) {
|
|
1029
|
+
this._showError('Only PNG files are supported.');
|
|
1030
|
+
return;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
if (!this._editor) {
|
|
1034
|
+
this._showError('No editor connected. Use setEditor(editor) or provide editor-selector attribute.');
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
try {
|
|
1039
|
+
await this._loadPngToEditorAdaptive(file);
|
|
1040
|
+
this._dispatchLoadedEvent(file.name);
|
|
1041
|
+
} catch (err) {
|
|
1042
|
+
console.error('Failed to load PNG', err);
|
|
1043
|
+
this._showError('Failed to load PNG (see console).');
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
_showError(msg) {
|
|
1048
|
+
alert(msg);
|
|
1049
|
+
}
|
|
1050
|
+
_dispatchLoadedEvent(filename) {
|
|
1051
|
+
this.dispatchEvent(new CustomEvent('pngloaded', { detail: { filename } }));
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
// ----------------- Adaptive load -----------------
|
|
1055
|
+
_readEditorConfig() {
|
|
1056
|
+
const ed = this._editor;
|
|
1057
|
+
const cfg = { pixelSize: null, cellsX: null, cellsY: null };
|
|
1058
|
+
|
|
1059
|
+
if (!ed) return cfg;
|
|
1060
|
+
|
|
1061
|
+
// pixel size detection (try multiple forms)
|
|
1062
|
+
cfg.pixelSize = ed.pixelSize || ed.pixel_size || null;
|
|
1063
|
+
if (!cfg.pixelSize) {
|
|
1064
|
+
const attr = ed.getAttribute && (ed.getAttribute('pixel-size') || ed.getAttribute('pixelSize'));
|
|
1065
|
+
if (attr) cfg.pixelSize = parseInt(attr, 10);
|
|
1066
|
+
}
|
|
1067
|
+
if (typeof cfg.pixelSize === 'string') cfg.pixelSize = parseInt(cfg.pixelSize, 10);
|
|
1068
|
+
|
|
1069
|
+
// cells detection (common attribute names: width/height on engine represent cells)
|
|
1070
|
+
const widthAttr = ed.getAttribute && ed.getAttribute('width');
|
|
1071
|
+
const heightAttr = ed.getAttribute && ed.getAttribute('height');
|
|
1072
|
+
if (widthAttr && heightAttr) {
|
|
1073
|
+
cfg.cellsX = parseInt(widthAttr, 10);
|
|
1074
|
+
cfg.cellsY = parseInt(heightAttr, 10);
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
// alternative property names
|
|
1078
|
+
cfg.cellsX = cfg.cellsX || ed.cellsX || ed.cellCountX || (ed.cells && ed.cells.x) || null;
|
|
1079
|
+
cfg.cellsY = cfg.cellsY || ed.cellsY || ed.cellCountY || (ed.cells && ed.cells.y) || null;
|
|
1080
|
+
|
|
1081
|
+
// if editor exposes getCells() prefer that
|
|
1082
|
+
try {
|
|
1083
|
+
if ((!cfg.cellsX || !cfg.cellsY) && typeof ed.getCells === 'function') {
|
|
1084
|
+
const c = ed.getCells();
|
|
1085
|
+
if (c && c.x && c.y) {
|
|
1086
|
+
cfg.cellsX = cfg.cellsX || c.x;
|
|
1087
|
+
cfg.cellsY = cfg.cellsY || c.y;
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
} catch (e) {
|
|
1091
|
+
/* ignore */
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
return cfg;
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
// core adaptive loader: scales image to editor cells (or computes fallback)
|
|
1098
|
+
async _loadPngToEditorAdaptive(blobOrFile) {
|
|
1099
|
+
const imgBitmap = await createImageBitmap(blobOrFile);
|
|
1100
|
+
const srcW = imgBitmap.width;
|
|
1101
|
+
const srcH = imgBitmap.height;
|
|
1102
|
+
|
|
1103
|
+
// read editor config and loader explicit overrides
|
|
1104
|
+
const editorCfg = this._readEditorConfig();
|
|
1105
|
+
const overrideX = this.getAttribute('target-cells-x');
|
|
1106
|
+
const overrideY = this.getAttribute('target-cells-y');
|
|
1107
|
+
|
|
1108
|
+
let targetCellsX = overrideX ? parseInt(overrideX, 10) : editorCfg.cellsX || null;
|
|
1109
|
+
let targetCellsY = overrideY ? parseInt(overrideY, 10) : editorCfg.cellsY || null;
|
|
1110
|
+
|
|
1111
|
+
// if cells unknown but pixelSize known, compute approximate cells from image dimensions
|
|
1112
|
+
if ((!targetCellsX || !targetCellsY) && editorCfg.pixelSize) {
|
|
1113
|
+
const px = parseInt(editorCfg.pixelSize, 10);
|
|
1114
|
+
if (px > 0) {
|
|
1115
|
+
if (!targetCellsX) targetCellsX = Math.max(1, Math.round(srcW / px));
|
|
1116
|
+
if (!targetCellsY) targetCellsY = Math.max(1, Math.round(srcH / px));
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
// if still missing, fallback to native image pixels
|
|
1121
|
+
if (!targetCellsX) targetCellsX = srcW;
|
|
1122
|
+
if (!targetCellsY) targetCellsY = srcH;
|
|
1123
|
+
|
|
1124
|
+
// Decide fit mode
|
|
1125
|
+
const fitMode = this._options.fitMode || this.getAttribute('fit-mode') || 'contain';
|
|
1126
|
+
|
|
1127
|
+
// Create a small canvas sized to the target cells (we will render the image into this canvas
|
|
1128
|
+
// with smoothing disabled to preserve blocky/pixel look). Then read each pixel as a cell.
|
|
1129
|
+
const small = document.createElement('canvas');
|
|
1130
|
+
small.width = targetCellsX;
|
|
1131
|
+
small.height = targetCellsY;
|
|
1132
|
+
const sctx = small.getContext('2d');
|
|
1133
|
+
|
|
1134
|
+
// nearest-neighbour / crisp scaling
|
|
1135
|
+
sctx.imageSmoothingEnabled = false;
|
|
1136
|
+
sctx.clearRect(0, 0, small.width, small.height);
|
|
1137
|
+
|
|
1138
|
+
if (fitMode === 'stretch') {
|
|
1139
|
+
// non-uniform scale to fill exactly
|
|
1140
|
+
sctx.drawImage(imgBitmap, 0, 0, srcW, srcH, 0, 0, small.width, small.height);
|
|
1141
|
+
} else {
|
|
1142
|
+
// compute uniform scale to contain or cover
|
|
1143
|
+
let scaleX = small.width / srcW;
|
|
1144
|
+
let scaleY = small.height / srcH;
|
|
1145
|
+
let scale = fitMode === 'cover' ? Math.max(scaleX, scaleY) : Math.min(scaleX, scaleY);
|
|
1146
|
+
// compute destination size in small-canvas pixels
|
|
1147
|
+
const destW = Math.max(1, Math.round(srcW * scale));
|
|
1148
|
+
const destH = Math.max(1, Math.round(srcH * scale));
|
|
1149
|
+
const dx = Math.floor((small.width - destW) / 2);
|
|
1150
|
+
const dy = Math.floor((small.height - destH) / 2);
|
|
1151
|
+
sctx.drawImage(imgBitmap, 0, 0, srcW, srcH, dx, dy, destW, destH);
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
// read pixel data from the small canvas
|
|
1155
|
+
const imageData = sctx.getImageData(0, 0, small.width, small.height).data;
|
|
1156
|
+
|
|
1157
|
+
// build matrix[y][x] = [r,g,b,a]
|
|
1158
|
+
const matrix = new Array(small.height);
|
|
1159
|
+
let p = 0;
|
|
1160
|
+
for (let y = 0; y < small.height; y++) {
|
|
1161
|
+
const row = new Array(small.width);
|
|
1162
|
+
for (let x = 0; x < small.width; x++) {
|
|
1163
|
+
const r = imageData[p++];
|
|
1164
|
+
const g = imageData[p++];
|
|
1165
|
+
const b = imageData[p++];
|
|
1166
|
+
const a = imageData[p++];
|
|
1167
|
+
row[x] = [r, g, b, a];
|
|
1168
|
+
}
|
|
1169
|
+
matrix[y] = row;
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
// attempt to optionally align editor settings (best-effort)
|
|
1173
|
+
try {
|
|
1174
|
+
// if editor has setCells(x,y) or setCellCount, call it
|
|
1175
|
+
if (typeof this._editor.setCells === 'function') {
|
|
1176
|
+
this._editor.setCells(small.width, small.height);
|
|
1177
|
+
} else if (typeof this._editor.setCellCount === 'function') {
|
|
1178
|
+
this._editor.setCellCount(small.width, small.height);
|
|
1179
|
+
} else {
|
|
1180
|
+
// try common attribute setter
|
|
1181
|
+
if (this._editor.setAttribute) {
|
|
1182
|
+
this._editor.setAttribute('width', String(small.width));
|
|
1183
|
+
this._editor.setAttribute('height', String(small.height));
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
// if editor has setPixelSize and editorCfg.pixelSize exists, keep it
|
|
1188
|
+
if (editorCfg.pixelSize && typeof this._editor.setPixelSize === 'function') {
|
|
1189
|
+
this._editor.setPixelSize(parseInt(editorCfg.pixelSize, 10));
|
|
1190
|
+
}
|
|
1191
|
+
} catch (e) {
|
|
1192
|
+
// non-critical; continue
|
|
1193
|
+
console.warn('Failed to align editor config:', e);
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
// finally, hand matrix to editor
|
|
1197
|
+
if (!this._editor || typeof this._editor.loadMatrix !== 'function') {
|
|
1198
|
+
throw new Error('Editor disconnected or does not expose loadMatrix');
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
this._editor.loadMatrix(matrix);
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
// Public helpers
|
|
1205
|
+
async loadPngBlob(blob) {
|
|
1206
|
+
return this._handleFile(blob);
|
|
1207
|
+
}
|
|
1208
|
+
async loadPngUrl(url) {
|
|
1209
|
+
const resp = await fetch(url);
|
|
1210
|
+
const blob = await resp.blob();
|
|
1211
|
+
return this._handleFile(blob);
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
customElements.define('object-layer-png-loader', ObjectLayerPngLoader);
|
|
1216
|
+
|
|
1217
|
+
/* Example wiring (NOT code repeated in canvas):
|
|
1218
|
+
|
|
1219
|
+
// HTML
|
|
1220
|
+
<object-layer-engine id="editor"></object-layer-engine>
|
|
1221
|
+
<object-layer-png-loader id="loader" editor-selector="#editor"></object-layer-png-loader>
|
|
1222
|
+
|
|
1223
|
+
// JS (programmatic)
|
|
1224
|
+
const editor = document.getElementById('editor');
|
|
1225
|
+
const loader = document.getElementById('loader');
|
|
1226
|
+
// Alternatively: loader.setEditor(editor);
|
|
1227
|
+
loader.addEventListener('pngloaded', (e) => console.log('Loaded', e.detail.filename));
|
|
1228
|
+
|
|
1229
|
+
*/
|
|
@@ -272,6 +272,7 @@ const ObjectLayerEngineModal = {
|
|
|
272
272
|
|
|
273
273
|
<object-layer-engine id="ole" width="${cells}" height="${cells}" pixel-size="${pixelSize}">
|
|
274
274
|
</object-layer-engine>
|
|
275
|
+
<object-layer-png-loader id="loader" editor-selector="#ole"></object-layer-png-loader>
|
|
275
276
|
</div>
|
|
276
277
|
|
|
277
278
|
<div class="in section-mp section-mp-border">
|
|
@@ -114,6 +114,7 @@ const Panel = {
|
|
|
114
114
|
options.originData().find((d) => d._id === obj._id || d.id === obj.id),
|
|
115
115
|
options.filesData().find((d) => d._id === obj._id || d.id === obj.id),
|
|
116
116
|
);
|
|
117
|
+
if (options.on.initEdit) await options.on.initEdit({ data: obj });
|
|
117
118
|
});
|
|
118
119
|
s(`.a-${payload._id}`).onclick = async (e) => {
|
|
119
120
|
e.preventDefault();
|
|
@@ -130,6 +131,7 @@ const Panel = {
|
|
|
130
131
|
${await BtnIcon.Render({
|
|
131
132
|
class: `in flr main-btn-menu action-bar-box ${idPanel}-btn-tool ${idPanel}-btn-edit-${id}`,
|
|
132
133
|
label: html`<div class="abs center"><i class="fas fa-edit"></i></div>`,
|
|
134
|
+
useVisibilityHover: true,
|
|
133
135
|
tooltipHtml: await Badge.Render({
|
|
134
136
|
id: `tooltip-${idPanel}-${id}`,
|
|
135
137
|
text: `${Translate.Render(`edit`)}`,
|
|
@@ -140,6 +142,7 @@ const Panel = {
|
|
|
140
142
|
${await BtnIcon.Render({
|
|
141
143
|
class: `in flr main-btn-menu action-bar-box ${idPanel}-btn-tool ${idPanel}-btn-delete-${id}`,
|
|
142
144
|
label: html`<div class="abs center"><i class="fas fa-trash"></i></div>`,
|
|
145
|
+
useVisibilityHover: true,
|
|
143
146
|
tooltipHtml: await Badge.Render({
|
|
144
147
|
id: `tooltip-${idPanel}-${id}`,
|
|
145
148
|
text: `${Translate.Render(`delete`)}`,
|
|
@@ -469,7 +472,7 @@ const Panel = {
|
|
|
469
472
|
}
|
|
470
473
|
s(`.${idPanel}-form-body`).classList.add('hide');
|
|
471
474
|
};
|
|
472
|
-
s(`.btn-${idPanel}-add`).onclick = (e) => {
|
|
475
|
+
s(`.btn-${idPanel}-add`).onclick = async (e) => {
|
|
473
476
|
e.preventDefault();
|
|
474
477
|
// s(`.btn-${idPanel}-clean`).click();
|
|
475
478
|
Panel.Tokens[idPanel].editId = undefined;
|
|
@@ -478,16 +481,20 @@ const Panel = {
|
|
|
478
481
|
s(`.${scrollClassContainer}`).scrollTop = 0;
|
|
479
482
|
|
|
480
483
|
openPanelForm();
|
|
484
|
+
if (options.on.initAdd) await options.on.initAdd();
|
|
481
485
|
};
|
|
482
486
|
if (s(`.${scrollClassContainer}`)) s(`.${scrollClassContainer}`).style.overflow = 'auto';
|
|
483
487
|
});
|
|
484
488
|
|
|
485
489
|
if (data.length > 0) for (const obj of data) render += await renderPanel(obj);
|
|
486
|
-
else
|
|
490
|
+
else {
|
|
487
491
|
render += html`<div class="in" style="min-height: 200px">
|
|
488
492
|
<div class="abs center"><i class="fas fa-exclamation-circle"></i> ${Translate.Render(`no-result-found`)}</div>
|
|
489
493
|
</div>`;
|
|
490
494
|
|
|
495
|
+
if (options.on.noResultFound) setTimeout(options.on.noResultFound);
|
|
496
|
+
}
|
|
497
|
+
|
|
491
498
|
this.Tokens[idPanel] = { idPanel, scrollClassContainer, formData, data, titleKey, subTitleKey, renderPanel };
|
|
492
499
|
|
|
493
500
|
let customButtonsRender = '';
|