underpost 2.8.877 → 2.8.881
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 +48 -36
- package/bin/deploy.js +40 -0
- package/cli.md +89 -86
- package/conf.js +28 -3
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/dd-test-development/deployment.yaml +2 -2
- 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 +125 -0
- package/src/api/file/file.controller.js +15 -1
- package/src/api/user/user.router.js +4 -3
- package/src/cli/deploy.js +1 -1
- package/src/cli/index.js +3 -0
- package/src/cli/repository.js +2 -2
- package/src/cli/run.js +29 -1
- package/src/client/Default.index.js +42 -1
- package/src/client/components/core/Account.js +8 -1
- package/src/client/components/core/AgGrid.js +18 -9
- package/src/client/components/core/BtnIcon.js +3 -2
- package/src/client/components/core/Content.js +13 -11
- package/src/client/components/core/CssCore.js +4 -0
- package/src/client/components/core/Docs.js +0 -3
- package/src/client/components/core/Input.js +34 -19
- package/src/client/components/core/Modal.js +29 -7
- package/src/client/components/core/ObjectLayerEngine.js +370 -0
- package/src/client/components/core/ObjectLayerEngineModal.js +1 -0
- package/src/client/components/core/Panel.js +7 -2
- package/src/client/components/core/PanelForm.js +187 -63
- package/src/client/components/core/VanillaJs.js +3 -0
- package/src/client/components/default/MenuDefault.js +94 -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/services/user/user.service.js +1 -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 +4 -3
- package/src/server/client-build.js +2 -3
- package/src/server/client-formatted.js +40 -12
- package/src/server/conf.js +42 -3
- package/src/server/object-layer.js +196 -0
- package/src/server/runtime.js +18 -21
- package/src/server/ssr.js +52 -10
- package/src/server/valkey.js +89 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getId, newInstance } from './CommonJs.js';
|
|
1
|
+
import { getId, newInstance, s4 } from './CommonJs.js';
|
|
2
2
|
import { Draggable } from '@neodrag/vanilla';
|
|
3
3
|
import { append, s, prepend, htmls, sa, getAllChildNodes, isActiveElement } from './VanillaJs.js';
|
|
4
4
|
import { BtnIcon } from './BtnIcon.js';
|
|
@@ -30,8 +30,9 @@ import { DropDown } from './DropDown.js';
|
|
|
30
30
|
import { Keyboard } from './Keyboard.js';
|
|
31
31
|
import { Badge } from './Badge.js';
|
|
32
32
|
import { Worker } from './Worker.js';
|
|
33
|
+
import { Scroll } from './Scroll.js';
|
|
33
34
|
|
|
34
|
-
const logger = loggerFactory(import.meta);
|
|
35
|
+
const logger = loggerFactory(import.meta, { trace: true });
|
|
35
36
|
|
|
36
37
|
const Modal = {
|
|
37
38
|
Data: {},
|
|
@@ -87,10 +88,15 @@ const Modal = {
|
|
|
87
88
|
homeModals: options.homeModals ? options.homeModals : [],
|
|
88
89
|
query: options.query ? `${window.location.search}` : undefined,
|
|
89
90
|
getTop: () => window.innerHeight - (options.heightBottomBar ? options.heightBottomBar : heightDefaultBottomBar),
|
|
90
|
-
getHeight: () =>
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
91
|
+
getHeight: () => {
|
|
92
|
+
return (
|
|
93
|
+
window.innerHeight -
|
|
94
|
+
(s(`.main-body-btn-ui-close`) && !s(`.main-body-btn-ui-close`).classList.contains('hide')
|
|
95
|
+
? (options.heightTopBar ? options.heightTopBar : heightDefaultTopBar) +
|
|
96
|
+
(options.heightBottomBar ? options.heightBottomBar : heightDefaultBottomBar)
|
|
97
|
+
: 0)
|
|
98
|
+
);
|
|
99
|
+
},
|
|
94
100
|
};
|
|
95
101
|
|
|
96
102
|
if (idModal !== 'main-body' && options.mode !== 'view') {
|
|
@@ -367,6 +373,7 @@ const Modal = {
|
|
|
367
373
|
s(`.modal-menu`).style.top = '0px';
|
|
368
374
|
s(`.main-body-btn-container`).style.top = '50px';
|
|
369
375
|
s(`.main-body`).style.top = '0px';
|
|
376
|
+
s(`.main-body`).style.height = `${window.innerHeight}px`;
|
|
370
377
|
for (const event of Object.keys(Modal.Data[idModal].onBarUiClose))
|
|
371
378
|
Modal.Data[idModal].onBarUiClose[event]();
|
|
372
379
|
} else {
|
|
@@ -379,6 +386,7 @@ const Modal = {
|
|
|
379
386
|
s(`.slide-menu-top-bar`).classList.remove('hide');
|
|
380
387
|
s(`.bottom-bar`).classList.remove('hide');
|
|
381
388
|
s(`.main-body`).style.top = `${options.heightTopBar}px`;
|
|
389
|
+
s(`.main-body`).style.height = `${window.innerHeight - options.heightTopBar}px`;
|
|
382
390
|
for (const event of Object.keys(Modal.Data[idModal].onBarUiOpen))
|
|
383
391
|
Modal.Data[idModal].onBarUiOpen[event]();
|
|
384
392
|
}
|
|
@@ -1291,6 +1299,15 @@ const Modal = {
|
|
|
1291
1299
|
}
|
|
1292
1300
|
|
|
1293
1301
|
await NotificationManager.RenderBoard(options);
|
|
1302
|
+
|
|
1303
|
+
const { removeEvent } = Scroll.setEvent('.main-body', async (payload) => {
|
|
1304
|
+
console.warn('scroll', payload);
|
|
1305
|
+
if (payload.scrollTop > 100) {
|
|
1306
|
+
if (!s(`.main-body-btn-ui-close`).classList.contains('hide')) s(`.main-body-btn-ui-close`).click();
|
|
1307
|
+
|
|
1308
|
+
removeEvent();
|
|
1309
|
+
}
|
|
1310
|
+
});
|
|
1294
1311
|
});
|
|
1295
1312
|
})();
|
|
1296
1313
|
break;
|
|
@@ -1385,7 +1402,7 @@ const Modal = {
|
|
|
1385
1402
|
<div
|
|
1386
1403
|
class="fix ${options && options.class ? options.class : ''} modal ${options.disableBoxShadow
|
|
1387
1404
|
? ''
|
|
1388
|
-
: 'box-shadow'} ${idModal}"
|
|
1405
|
+
: 'box-shadow'} ${idModal === 'main-body' ? `${idModal} modal-home` : idModal}"
|
|
1389
1406
|
>
|
|
1390
1407
|
<div class="abs modal-handle-${idModal}"></div>
|
|
1391
1408
|
<div class="in modal-html-${idModal}">
|
|
@@ -2246,6 +2263,11 @@ const Modal = {
|
|
|
2246
2263
|
s(`.bottom-bar`).classList.remove('hide');
|
|
2247
2264
|
s(`.modal-menu`).classList.remove('hide');
|
|
2248
2265
|
},
|
|
2266
|
+
RenderSeoSanitizer: async () => {
|
|
2267
|
+
sa('img').forEach((img) => {
|
|
2268
|
+
if (!img.getAttribute('alt')) img.setAttribute('alt', 'image ' + Worker.title + ' ' + s4());
|
|
2269
|
+
});
|
|
2270
|
+
},
|
|
2249
2271
|
};
|
|
2250
2272
|
|
|
2251
2273
|
const renderMenuLabel = ({ img, text, icon }) => {
|
|
@@ -861,3 +861,373 @@ customElements.define('object-layer-engine', ObjectLayerEngineElement);
|
|
|
861
861
|
Example usage:
|
|
862
862
|
<object-layer-engine id="ole" width="20" height="12" pixel-size="20"></object-layer-engine>
|
|
863
863
|
*/
|
|
864
|
+
|
|
865
|
+
const template = document.createElement('template');
|
|
866
|
+
template.innerHTML = html`
|
|
867
|
+
<style>
|
|
868
|
+
:host {
|
|
869
|
+
display: block;
|
|
870
|
+
font-family: system-ui, -apple-system, 'Segoe UI', Roboto, Arial;
|
|
871
|
+
}
|
|
872
|
+
.wrap {
|
|
873
|
+
display: flex;
|
|
874
|
+
flex-direction: column;
|
|
875
|
+
gap: 8px;
|
|
876
|
+
}
|
|
877
|
+
.controls {
|
|
878
|
+
display: flex;
|
|
879
|
+
gap: 8px;
|
|
880
|
+
align-items: center;
|
|
881
|
+
flex-wrap: wrap;
|
|
882
|
+
}
|
|
883
|
+
.drop-area {
|
|
884
|
+
border: 2px dashed #999;
|
|
885
|
+
padding: 12px;
|
|
886
|
+
border-radius: 8px;
|
|
887
|
+
text-align: center;
|
|
888
|
+
color: #555;
|
|
889
|
+
user-select: none;
|
|
890
|
+
}
|
|
891
|
+
.drop-area.dragover {
|
|
892
|
+
border-color: #4a90e2;
|
|
893
|
+
color: #1a73e8;
|
|
894
|
+
background: rgba(74, 144, 226, 0.04);
|
|
895
|
+
}
|
|
896
|
+
input[type='file'] {
|
|
897
|
+
display: inline-block;
|
|
898
|
+
}
|
|
899
|
+
.hint {
|
|
900
|
+
font-size: 0.9rem;
|
|
901
|
+
color: #666;
|
|
902
|
+
}
|
|
903
|
+
</style>
|
|
904
|
+
|
|
905
|
+
<div class="wrap">
|
|
906
|
+
<div class="controls">
|
|
907
|
+
<label title="Load PNG file">
|
|
908
|
+
<input type="file" accept="image/png" part="file-input" />
|
|
909
|
+
<span class="btn">Choose PNG</span>
|
|
910
|
+
</label>
|
|
911
|
+
<div class="hint">Only PNG images accepted. Drop PNG onto the box below.</div>
|
|
912
|
+
</div>
|
|
913
|
+
|
|
914
|
+
<div class="drop-area" part="drop-area">Drop PNG here or click "Choose PNG"</div>
|
|
915
|
+
</div>
|
|
916
|
+
`;
|
|
917
|
+
|
|
918
|
+
class ObjectLayerPngLoader extends HTMLElement {
|
|
919
|
+
constructor() {
|
|
920
|
+
super();
|
|
921
|
+
this.attachShadow({ mode: 'open' });
|
|
922
|
+
this.shadowRoot.appendChild(template.content.cloneNode(true));
|
|
923
|
+
|
|
924
|
+
this._fileInput = this.shadowRoot.querySelector('input[type="file"]');
|
|
925
|
+
this._dropArea = this.shadowRoot.querySelector('.drop-area');
|
|
926
|
+
|
|
927
|
+
this._editor = null; // will hold external editor instance
|
|
928
|
+
this._options = { fitMode: 'contain' };
|
|
929
|
+
|
|
930
|
+
// Bind handlers
|
|
931
|
+
this._onFileChange = this._onFileChange.bind(this);
|
|
932
|
+
this._onDrop = this._onDrop.bind(this);
|
|
933
|
+
this._onDragOver = this._onDragOver.bind(this);
|
|
934
|
+
this._onDragLeave = this._onDragLeave.bind(this);
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
static get observedAttributes() {
|
|
938
|
+
return ['editor-selector', 'fit-mode', 'target-cells-x', 'target-cells-y'];
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
attributeChangedCallback(name, oldVal, newVal) {
|
|
942
|
+
if (name === 'editor-selector' && newVal) {
|
|
943
|
+
const el = document.querySelector(newVal);
|
|
944
|
+
if (el) this.setEditor(el);
|
|
945
|
+
}
|
|
946
|
+
if (name === 'fit-mode') {
|
|
947
|
+
this._options.fitMode = newVal || 'contain';
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
connectedCallback() {
|
|
952
|
+
this._fileInput.addEventListener('change', this._onFileChange);
|
|
953
|
+
this._dropArea.addEventListener('dragover', this._onDragOver);
|
|
954
|
+
this._dropArea.addEventListener('dragleave', this._onDragLeave);
|
|
955
|
+
this._dropArea.addEventListener('drop', this._onDrop);
|
|
956
|
+
this.addEventListener('dragover', this._onDragOver);
|
|
957
|
+
this.addEventListener('dragleave', this._onDragLeave);
|
|
958
|
+
this.addEventListener('drop', this._onDrop);
|
|
959
|
+
|
|
960
|
+
// If editor-selector attribute was present at creation, try to resolve
|
|
961
|
+
const sel = this.getAttribute('editor-selector');
|
|
962
|
+
if (sel) {
|
|
963
|
+
const target = document.querySelector(sel);
|
|
964
|
+
if (target) this.setEditor(target);
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
// read fit-mode
|
|
968
|
+
const fit = this.getAttribute('fit-mode');
|
|
969
|
+
if (fit) this._options.fitMode = fit;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
disconnectedCallback() {
|
|
973
|
+
this._fileInput.removeEventListener('change', this._onFileChange);
|
|
974
|
+
this._dropArea.removeEventListener('dragover', this._onDragOver);
|
|
975
|
+
this._dropArea.removeEventListener('dragleave', this._onDragLeave);
|
|
976
|
+
this._dropArea.removeEventListener('drop', this._onDrop);
|
|
977
|
+
this.removeEventListener('dragover', this._onDragOver);
|
|
978
|
+
this.removeEventListener('dragleave', this._onDragLeave);
|
|
979
|
+
this.removeEventListener('drop', this._onDrop);
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
// ----------------- Public API -----------------
|
|
983
|
+
setEditor(editor) {
|
|
984
|
+
if (!editor) throw new Error('Editor cannot be null/undefined');
|
|
985
|
+
if (typeof editor.loadMatrix !== 'function') {
|
|
986
|
+
throw new Error('Provided editor does not expose loadMatrix(matrix)');
|
|
987
|
+
}
|
|
988
|
+
this._editor = editor;
|
|
989
|
+
this.dispatchEvent(new CustomEvent('editorconnected', { detail: { editor } }));
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
setOptions(options = {}) {
|
|
993
|
+
if (options.fitMode) this._options.fitMode = options.fitMode;
|
|
994
|
+
if (options.targetCellsX) this.setAttribute('target-cells-x', String(options.targetCellsX));
|
|
995
|
+
if (options.targetCellsY) this.setAttribute('target-cells-y', String(options.targetCellsY));
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
get editor() {
|
|
999
|
+
return this._editor;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// ----------------- Events -----------------
|
|
1003
|
+
_onFileChange(e) {
|
|
1004
|
+
const file = e.target.files && e.target.files[0] ? e.target.files[0] : null;
|
|
1005
|
+
if (!file) return;
|
|
1006
|
+
this._handleFile(file);
|
|
1007
|
+
this._fileInput.value = '';
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
_onDragOver(e) {
|
|
1011
|
+
e.preventDefault();
|
|
1012
|
+
e.dataTransfer.dropEffect = 'copy';
|
|
1013
|
+
this._dropArea.classList.add('dragover');
|
|
1014
|
+
}
|
|
1015
|
+
_onDragLeave(e) {
|
|
1016
|
+
e.preventDefault();
|
|
1017
|
+
this._dropArea.classList.remove('dragover');
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
_onDrop(e) {
|
|
1021
|
+
e.preventDefault();
|
|
1022
|
+
this._dropArea.classList.remove('dragover');
|
|
1023
|
+
const file = e.dataTransfer.files && e.dataTransfer.files[0] ? e.dataTransfer.files[0] : null;
|
|
1024
|
+
if (!file) return;
|
|
1025
|
+
this._handleFile(file);
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
// ----------------- File handling -----------------
|
|
1029
|
+
async _handleFile(file) {
|
|
1030
|
+
const isPngByType = file.type === 'image/png';
|
|
1031
|
+
const isPngByName = file.name && file.name.toLowerCase().endsWith('.png');
|
|
1032
|
+
if (!isPngByType && !isPngByName) {
|
|
1033
|
+
this._showError('Only PNG files are supported.');
|
|
1034
|
+
return;
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
if (!this._editor) {
|
|
1038
|
+
this._showError('No editor connected. Use setEditor(editor) or provide editor-selector attribute.');
|
|
1039
|
+
return;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
try {
|
|
1043
|
+
await this._loadPngToEditorAdaptive(file);
|
|
1044
|
+
this._dispatchLoadedEvent(file.name);
|
|
1045
|
+
} catch (err) {
|
|
1046
|
+
console.error('Failed to load PNG', err);
|
|
1047
|
+
this._showError('Failed to load PNG (see console).');
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
_showError(msg) {
|
|
1052
|
+
alert(msg);
|
|
1053
|
+
}
|
|
1054
|
+
_dispatchLoadedEvent(filename) {
|
|
1055
|
+
this.dispatchEvent(new CustomEvent('pngloaded', { detail: { filename } }));
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
// ----------------- Adaptive load -----------------
|
|
1059
|
+
_readEditorConfig() {
|
|
1060
|
+
const ed = this._editor;
|
|
1061
|
+
const cfg = { pixelSize: null, cellsX: null, cellsY: null };
|
|
1062
|
+
|
|
1063
|
+
if (!ed) return cfg;
|
|
1064
|
+
|
|
1065
|
+
// pixel size detection (try multiple forms)
|
|
1066
|
+
cfg.pixelSize = ed.pixelSize || ed.pixel_size || null;
|
|
1067
|
+
if (!cfg.pixelSize) {
|
|
1068
|
+
const attr = ed.getAttribute && (ed.getAttribute('pixel-size') || ed.getAttribute('pixelSize'));
|
|
1069
|
+
if (attr) cfg.pixelSize = parseInt(attr, 10);
|
|
1070
|
+
}
|
|
1071
|
+
if (typeof cfg.pixelSize === 'string') cfg.pixelSize = parseInt(cfg.pixelSize, 10);
|
|
1072
|
+
|
|
1073
|
+
// cells detection (common attribute names: width/height on engine represent cells)
|
|
1074
|
+
const widthAttr = ed.getAttribute && ed.getAttribute('width');
|
|
1075
|
+
const heightAttr = ed.getAttribute && ed.getAttribute('height');
|
|
1076
|
+
if (widthAttr && heightAttr) {
|
|
1077
|
+
cfg.cellsX = parseInt(widthAttr, 10);
|
|
1078
|
+
cfg.cellsY = parseInt(heightAttr, 10);
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
// alternative property names
|
|
1082
|
+
cfg.cellsX = cfg.cellsX || ed.cellsX || ed.cellCountX || (ed.cells && ed.cells.x) || null;
|
|
1083
|
+
cfg.cellsY = cfg.cellsY || ed.cellsY || ed.cellCountY || (ed.cells && ed.cells.y) || null;
|
|
1084
|
+
|
|
1085
|
+
// if editor exposes getCells() prefer that
|
|
1086
|
+
try {
|
|
1087
|
+
if ((!cfg.cellsX || !cfg.cellsY) && typeof ed.getCells === 'function') {
|
|
1088
|
+
const c = ed.getCells();
|
|
1089
|
+
if (c && c.x && c.y) {
|
|
1090
|
+
cfg.cellsX = cfg.cellsX || c.x;
|
|
1091
|
+
cfg.cellsY = cfg.cellsY || c.y;
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
} catch (e) {
|
|
1095
|
+
/* ignore */
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
return cfg;
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
// core adaptive loader: scales image to editor cells (or computes fallback)
|
|
1102
|
+
async _loadPngToEditorAdaptive(blobOrFile) {
|
|
1103
|
+
const imgBitmap = await createImageBitmap(blobOrFile);
|
|
1104
|
+
const srcW = imgBitmap.width;
|
|
1105
|
+
const srcH = imgBitmap.height;
|
|
1106
|
+
|
|
1107
|
+
// read editor config and loader explicit overrides
|
|
1108
|
+
const editorCfg = this._readEditorConfig();
|
|
1109
|
+
const overrideX = this.getAttribute('target-cells-x');
|
|
1110
|
+
const overrideY = this.getAttribute('target-cells-y');
|
|
1111
|
+
|
|
1112
|
+
let targetCellsX = overrideX ? parseInt(overrideX, 10) : editorCfg.cellsX || null;
|
|
1113
|
+
let targetCellsY = overrideY ? parseInt(overrideY, 10) : editorCfg.cellsY || null;
|
|
1114
|
+
|
|
1115
|
+
// if cells unknown but pixelSize known, compute approximate cells from image dimensions
|
|
1116
|
+
if ((!targetCellsX || !targetCellsY) && editorCfg.pixelSize) {
|
|
1117
|
+
const px = parseInt(editorCfg.pixelSize, 10);
|
|
1118
|
+
if (px > 0) {
|
|
1119
|
+
if (!targetCellsX) targetCellsX = Math.max(1, Math.round(srcW / px));
|
|
1120
|
+
if (!targetCellsY) targetCellsY = Math.max(1, Math.round(srcH / px));
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
// if still missing, fallback to native image pixels
|
|
1125
|
+
if (!targetCellsX) targetCellsX = srcW;
|
|
1126
|
+
if (!targetCellsY) targetCellsY = srcH;
|
|
1127
|
+
|
|
1128
|
+
// Decide fit mode
|
|
1129
|
+
const fitMode = this._options.fitMode || this.getAttribute('fit-mode') || 'contain';
|
|
1130
|
+
|
|
1131
|
+
// Create a small canvas sized to the target cells (we will render the image into this canvas
|
|
1132
|
+
// with smoothing disabled to preserve blocky/pixel look). Then read each pixel as a cell.
|
|
1133
|
+
const small = document.createElement('canvas');
|
|
1134
|
+
small.width = targetCellsX;
|
|
1135
|
+
small.height = targetCellsY;
|
|
1136
|
+
const sctx = small.getContext('2d');
|
|
1137
|
+
|
|
1138
|
+
// nearest-neighbour / crisp scaling
|
|
1139
|
+
sctx.imageSmoothingEnabled = false;
|
|
1140
|
+
sctx.clearRect(0, 0, small.width, small.height);
|
|
1141
|
+
|
|
1142
|
+
if (fitMode === 'stretch') {
|
|
1143
|
+
// non-uniform scale to fill exactly
|
|
1144
|
+
sctx.drawImage(imgBitmap, 0, 0, srcW, srcH, 0, 0, small.width, small.height);
|
|
1145
|
+
} else {
|
|
1146
|
+
// compute uniform scale to contain or cover
|
|
1147
|
+
let scaleX = small.width / srcW;
|
|
1148
|
+
let scaleY = small.height / srcH;
|
|
1149
|
+
let scale = fitMode === 'cover' ? Math.max(scaleX, scaleY) : Math.min(scaleX, scaleY);
|
|
1150
|
+
// compute destination size in small-canvas pixels
|
|
1151
|
+
const destW = Math.max(1, Math.round(srcW * scale));
|
|
1152
|
+
const destH = Math.max(1, Math.round(srcH * scale));
|
|
1153
|
+
const dx = Math.floor((small.width - destW) / 2);
|
|
1154
|
+
const dy = Math.floor((small.height - destH) / 2);
|
|
1155
|
+
sctx.drawImage(imgBitmap, 0, 0, srcW, srcH, dx, dy, destW, destH);
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
// read pixel data from the small canvas
|
|
1159
|
+
const imageData = sctx.getImageData(0, 0, small.width, small.height).data;
|
|
1160
|
+
|
|
1161
|
+
// build matrix[y][x] = [r,g,b,a]
|
|
1162
|
+
const matrix = new Array(small.height);
|
|
1163
|
+
let p = 0;
|
|
1164
|
+
for (let y = 0; y < small.height; y++) {
|
|
1165
|
+
const row = new Array(small.width);
|
|
1166
|
+
for (let x = 0; x < small.width; x++) {
|
|
1167
|
+
const r = imageData[p++];
|
|
1168
|
+
const g = imageData[p++];
|
|
1169
|
+
const b = imageData[p++];
|
|
1170
|
+
const a = imageData[p++];
|
|
1171
|
+
row[x] = [r, g, b, a];
|
|
1172
|
+
}
|
|
1173
|
+
matrix[y] = row;
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
// attempt to optionally align editor settings (best-effort)
|
|
1177
|
+
try {
|
|
1178
|
+
// if editor has setCells(x,y) or setCellCount, call it
|
|
1179
|
+
if (typeof this._editor.setCells === 'function') {
|
|
1180
|
+
this._editor.setCells(small.width, small.height);
|
|
1181
|
+
} else if (typeof this._editor.setCellCount === 'function') {
|
|
1182
|
+
this._editor.setCellCount(small.width, small.height);
|
|
1183
|
+
} else {
|
|
1184
|
+
// try common attribute setter
|
|
1185
|
+
if (this._editor.setAttribute) {
|
|
1186
|
+
this._editor.setAttribute('width', String(small.width));
|
|
1187
|
+
this._editor.setAttribute('height', String(small.height));
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
// if editor has setPixelSize and editorCfg.pixelSize exists, keep it
|
|
1192
|
+
if (editorCfg.pixelSize && typeof this._editor.setPixelSize === 'function') {
|
|
1193
|
+
this._editor.setPixelSize(parseInt(editorCfg.pixelSize, 10));
|
|
1194
|
+
}
|
|
1195
|
+
} catch (e) {
|
|
1196
|
+
// non-critical; continue
|
|
1197
|
+
console.warn('Failed to align editor config:', e);
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
// finally, hand matrix to editor
|
|
1201
|
+
if (!this._editor || typeof this._editor.loadMatrix !== 'function') {
|
|
1202
|
+
throw new Error('Editor disconnected or does not expose loadMatrix');
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
this._editor.loadMatrix(matrix);
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
// Public helpers
|
|
1209
|
+
async loadPngBlob(blob) {
|
|
1210
|
+
return this._handleFile(blob);
|
|
1211
|
+
}
|
|
1212
|
+
async loadPngUrl(url) {
|
|
1213
|
+
const resp = await fetch(url);
|
|
1214
|
+
const blob = await resp.blob();
|
|
1215
|
+
return this._handleFile(blob);
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
customElements.define('object-layer-png-loader', ObjectLayerPngLoader);
|
|
1220
|
+
|
|
1221
|
+
/* Example wiring (NOT code repeated in canvas):
|
|
1222
|
+
|
|
1223
|
+
// HTML
|
|
1224
|
+
<object-layer-engine id="editor"></object-layer-engine>
|
|
1225
|
+
<object-layer-png-loader id="loader" editor-selector="#editor"></object-layer-png-loader>
|
|
1226
|
+
|
|
1227
|
+
// JS (programmatic)
|
|
1228
|
+
const editor = document.getElementById('editor');
|
|
1229
|
+
const loader = document.getElementById('loader');
|
|
1230
|
+
// Alternatively: loader.setEditor(editor);
|
|
1231
|
+
loader.addEventListener('pngloaded', (e) => console.log('Loaded', e.detail.filename));
|
|
1232
|
+
|
|
1233
|
+
*/
|
|
@@ -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();
|
|
@@ -469,7 +470,7 @@ const Panel = {
|
|
|
469
470
|
}
|
|
470
471
|
s(`.${idPanel}-form-body`).classList.add('hide');
|
|
471
472
|
};
|
|
472
|
-
s(`.btn-${idPanel}-add`).onclick = (e) => {
|
|
473
|
+
s(`.btn-${idPanel}-add`).onclick = async (e) => {
|
|
473
474
|
e.preventDefault();
|
|
474
475
|
// s(`.btn-${idPanel}-clean`).click();
|
|
475
476
|
Panel.Tokens[idPanel].editId = undefined;
|
|
@@ -478,16 +479,20 @@ const Panel = {
|
|
|
478
479
|
s(`.${scrollClassContainer}`).scrollTop = 0;
|
|
479
480
|
|
|
480
481
|
openPanelForm();
|
|
482
|
+
if (options.on.initAdd) await options.on.initAdd();
|
|
481
483
|
};
|
|
482
484
|
if (s(`.${scrollClassContainer}`)) s(`.${scrollClassContainer}`).style.overflow = 'auto';
|
|
483
485
|
});
|
|
484
486
|
|
|
485
487
|
if (data.length > 0) for (const obj of data) render += await renderPanel(obj);
|
|
486
|
-
else
|
|
488
|
+
else {
|
|
487
489
|
render += html`<div class="in" style="min-height: 200px">
|
|
488
490
|
<div class="abs center"><i class="fas fa-exclamation-circle"></i> ${Translate.Render(`no-result-found`)}</div>
|
|
489
491
|
</div>`;
|
|
490
492
|
|
|
493
|
+
if (options.on.noResultFound) setTimeout(options.on.noResultFound);
|
|
494
|
+
}
|
|
495
|
+
|
|
491
496
|
this.Tokens[idPanel] = { idPanel, scrollClassContainer, formData, data, titleKey, subTitleKey, renderPanel };
|
|
492
497
|
|
|
493
498
|
let customButtonsRender = '';
|