cyberia 3.2.5 → 3.2.9
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/.github/workflows/engine-cyberia.cd.yml +2 -2
- package/.github/workflows/release.cd.yml +1 -2
- package/CHANGELOG.md +351 -1
- package/CLI-HELP.md +40 -13
- package/Dockerfile +0 -4
- package/README.md +242 -497
- package/bin/build.js +19 -5
- package/bin/cyberia.js +1149 -240
- package/bin/deploy.js +570 -1
- package/bin/file.js +6 -0
- package/bin/index.js +1149 -240
- package/bin/vs.js +1 -1
- package/conf.js +67 -89
- package/deployment.yaml +4 -222
- package/hardhat/package-lock.json +32 -32
- package/hardhat/package.json +3 -3
- package/jsconfig.json +1 -1
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +2 -2
- package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +2 -2
- package/manifests/deployment/dd-cyberia-development/deployment.yaml +4 -222
- package/manifests/deployment/dd-cyberia-development/proxy.yaml +10 -118
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -6
- package/manifests/deployment/dd-test-development/deployment.yaml +136 -66
- package/manifests/deployment/dd-test-development/proxy.yaml +41 -5
- package/package.json +23 -14
- package/proxy.yaml +10 -118
- package/scripts/k3s-node-setup.sh +2 -2
- package/scripts/nat-iptables.sh +103 -18
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.controller.js +18 -18
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.model.js +7 -14
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +76 -21
- package/src/api/core/core.controller.js +10 -10
- package/src/api/core/core.service.js +10 -10
- package/src/api/crypto/crypto.controller.js +8 -8
- package/src/api/crypto/crypto.service.js +8 -8
- package/src/api/cyberia-action/cyberia-action.controller.js +74 -0
- package/src/api/cyberia-action/cyberia-action.model.js +87 -0
- package/src/api/cyberia-action/cyberia-action.router.js +27 -0
- package/src/api/cyberia-action/cyberia-action.service.js +42 -0
- package/src/api/cyberia-dialogue/cyberia-dialogue.controller.js +13 -13
- package/src/api/cyberia-dialogue/cyberia-dialogue.model.js +11 -11
- package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +2 -2
- package/src/api/cyberia-dialogue/cyberia-dialogue.service.js +16 -16
- package/src/api/cyberia-entity/cyberia-entity.controller.js +10 -10
- package/src/api/cyberia-entity/cyberia-entity.service.js +10 -10
- package/src/api/cyberia-instance/cyberia-fallback-world.js +19 -209
- package/src/api/cyberia-instance/cyberia-instance.controller.js +14 -14
- package/src/api/cyberia-instance/cyberia-instance.model.js +3 -0
- package/src/api/cyberia-instance/cyberia-instance.service.js +22 -57
- package/src/api/cyberia-instance/cyberia-portal-connector.js +20 -246
- package/src/api/cyberia-instance/cyberia-world-generator.js +505 -0
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.controller.js +10 -10
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.defaults.js +216 -55
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +4 -1
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.service.js +18 -14
- package/src/api/cyberia-map/cyberia-map.controller.js +10 -10
- package/src/api/cyberia-map/cyberia-map.service.js +10 -10
- package/src/api/cyberia-quest/cyberia-quest.controller.js +74 -0
- package/src/api/cyberia-quest/cyberia-quest.model.js +67 -0
- package/src/api/cyberia-quest/cyberia-quest.router.js +27 -0
- package/src/api/cyberia-quest/cyberia-quest.service.js +42 -0
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.controller.js +74 -0
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.model.js +49 -0
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.router.js +27 -0
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.service.js +42 -0
- package/src/api/default/default.controller.js +10 -10
- package/src/api/default/default.service.js +10 -10
- package/src/api/document/document.controller.js +12 -12
- package/src/api/document/document.model.js +10 -16
- package/src/api/file/file.controller.js +8 -8
- package/src/api/file/file.model.js +10 -10
- package/src/api/file/file.service.js +36 -36
- package/src/api/instance/instance.controller.js +10 -10
- package/src/api/instance/instance.model.js +4 -10
- package/src/api/instance/instance.service.js +10 -10
- package/src/api/ipfs/ipfs.controller.js +12 -12
- package/src/api/ipfs/ipfs.model.js +4 -13
- package/src/api/ipfs/ipfs.service.js +14 -28
- package/src/api/object-layer/object-layer.controller.js +12 -12
- package/src/api/object-layer/object-layer.model.js +4 -17
- package/src/api/object-layer/object-layer.service.js +12 -12
- package/src/api/object-layer-render-frames/object-layer-render-frames.controller.js +10 -10
- package/src/api/object-layer-render-frames/object-layer-render-frames.model.js +6 -16
- package/src/api/object-layer-render-frames/object-layer-render-frames.service.js +18 -14
- package/src/api/test/test.controller.js +8 -8
- package/src/api/test/test.service.js +8 -8
- package/src/api/user/guest.service.js +99 -0
- package/src/api/user/user.controller.js +6 -6
- package/src/api/user/user.model.js +8 -13
- package/src/api/user/user.service.js +3 -20
- package/src/cli/cluster.js +61 -14
- package/src/cli/db.js +47 -2
- package/src/cli/deploy.js +67 -35
- package/src/cli/fs.js +79 -8
- package/src/cli/image.js +43 -1
- package/src/cli/index.js +26 -1
- package/src/cli/release.js +57 -1
- package/src/cli/repository.js +69 -31
- package/src/cli/run.js +415 -36
- package/src/cli/ssh.js +1 -1
- package/src/cli/static.js +43 -115
- package/src/client/Cryptokoyn.index.js +18 -21
- package/src/client/CyberiaPortal.index.js +19 -23
- package/src/client/Default.index.js +21 -33
- package/src/client/Itemledger.index.js +20 -26
- package/src/client/Underpost.index.js +19 -23
- package/src/client/components/core/404.js +4 -4
- package/src/client/components/core/500.js +4 -4
- package/src/client/components/core/Account.js +73 -60
- package/src/client/components/core/AgGrid.js +23 -33
- package/src/client/components/core/Alert.js +12 -13
- package/src/client/components/core/AppStore.js +1 -1
- package/src/client/components/core/Auth.js +35 -37
- package/src/client/components/core/Badge.js +7 -13
- package/src/client/components/core/BtnIcon.js +15 -17
- package/src/client/components/core/CalendarCore.js +42 -63
- package/src/client/components/core/Chat.js +13 -15
- package/src/client/components/core/ClientEvents.js +87 -0
- package/src/client/components/core/ColorPaletteElement.js +309 -0
- package/src/client/components/core/Content.js +17 -14
- package/src/client/components/core/Css.js +15 -71
- package/src/client/components/core/CssCore.js +12 -16
- package/src/client/components/core/D3Chart.js +4 -4
- package/src/client/components/core/Docs.js +64 -91
- package/src/client/components/core/DropDown.js +69 -91
- package/src/client/components/core/EventBus.js +92 -0
- package/src/client/components/core/EventsUI.js +14 -17
- package/src/client/components/core/FileExplorer.js +96 -228
- package/src/client/components/core/FullScreen.js +47 -75
- package/src/client/components/core/Input.js +24 -69
- package/src/client/components/core/Keyboard.js +25 -18
- package/src/client/components/core/KeyboardAvoidance.js +145 -0
- package/src/client/components/core/LoadingAnimation.js +25 -31
- package/src/client/components/core/LogIn.js +41 -41
- package/src/client/components/core/LogOut.js +23 -14
- package/src/client/components/core/Modal.js +462 -178
- package/src/client/components/core/NotificationManager.js +14 -18
- package/src/client/components/core/Panel.js +54 -50
- package/src/client/components/core/PanelForm.js +25 -125
- package/src/client/components/core/Polyhedron.js +110 -214
- package/src/client/components/core/PublicProfile.js +39 -32
- package/src/client/components/core/Recover.js +48 -44
- package/src/client/components/core/Responsive.js +88 -32
- package/src/client/components/core/RichText.js +9 -18
- package/src/client/components/core/Router.js +24 -3
- package/src/client/components/core/SearchBox.js +37 -37
- package/src/client/components/core/SignUp.js +39 -30
- package/src/client/components/core/SocketIo.js +31 -2
- package/src/client/components/core/SocketIoHandler.js +6 -6
- package/src/client/components/core/ToggleSwitch.js +8 -20
- package/src/client/components/core/ToolTip.js +5 -17
- package/src/client/components/core/Translate.js +56 -59
- package/src/client/components/core/Validator.js +26 -16
- package/src/client/components/core/Wallet.js +15 -26
- package/src/client/components/core/Worker.js +163 -27
- package/src/client/components/core/windowGetDimensions.js +7 -7
- package/src/client/components/cryptokoyn/{MenuCryptokoyn.js → AppShellCryptokoyn.js} +57 -57
- package/src/client/components/cryptokoyn/CssCryptokoyn.js +15 -15
- package/src/client/components/cryptokoyn/LogInCryptokoyn.js +6 -4
- package/src/client/components/cryptokoyn/LogOutCryptokoyn.js +6 -4
- package/src/client/components/cryptokoyn/RouterCryptokoyn.js +37 -0
- package/src/client/components/cryptokoyn/SettingsCryptokoyn.js +4 -4
- package/src/client/components/cryptokoyn/SignUpCryptokoyn.js +6 -4
- package/src/client/components/cyberia/InstanceEngineCyberia.js +141 -60
- package/src/client/components/cyberia/MapEngineCyberia.js +691 -214
- package/src/client/components/cyberia/ObjectLayerEngine.js +19 -0
- package/src/client/components/cyberia/ObjectLayerEngineModal.js +1204 -94
- package/src/client/components/cyberia/ObjectLayerEngineViewer.js +196 -298
- package/src/client/components/cyberia-portal/{MenuCyberiaPortal.js → AppShellCyberiaPortal.js} +102 -102
- package/src/client/components/cyberia-portal/CommonCyberiaPortal.js +305 -61
- package/src/client/components/cyberia-portal/CssCyberiaPortal.js +15 -15
- package/src/client/components/cyberia-portal/LogInCyberiaPortal.js +6 -4
- package/src/client/components/cyberia-portal/LogOutCyberiaPortal.js +6 -4
- package/src/client/components/cyberia-portal/MainBodyCyberiaPortal.js +4 -4
- package/src/client/components/cyberia-portal/RouterCyberiaPortal.js +60 -0
- package/src/client/components/cyberia-portal/SettingsCyberiaPortal.js +4 -4
- package/src/client/components/cyberia-portal/SignUpCyberiaPortal.js +6 -4
- package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +4 -4
- package/src/client/components/default/{MenuDefault.js → AppShellDefault.js} +87 -87
- package/src/client/components/default/CssDefault.js +12 -12
- package/src/client/components/default/LogInDefault.js +6 -4
- package/src/client/components/default/LogOutDefault.js +6 -4
- package/src/client/components/default/RouterDefault.js +47 -0
- package/src/client/components/default/SettingsDefault.js +4 -4
- package/src/client/components/default/SignUpDefault.js +6 -4
- package/src/client/components/default/TranslateDefault.js +3 -3
- package/src/client/components/itemledger/{MenuItemledger.js → AppShellItemledger.js} +57 -57
- package/src/client/components/itemledger/CssItemledger.js +15 -15
- package/src/client/components/itemledger/LogInItemledger.js +6 -4
- package/src/client/components/itemledger/LogOutItemledger.js +6 -4
- package/src/client/components/itemledger/RouterItemledger.js +38 -0
- package/src/client/components/itemledger/SettingsItemledger.js +4 -4
- package/src/client/components/itemledger/SignUpItemledger.js +6 -4
- package/src/client/components/itemledger/TranslateItemledger.js +3 -3
- package/src/client/components/underpost/{MenuUnderpost.js → AppShellUnderpost.js} +88 -88
- package/src/client/components/underpost/CssUnderpost.js +14 -14
- package/src/client/components/underpost/CyberpunkBloggerUnderpost.js +4 -4
- package/src/client/components/underpost/DocumentSearchProvider.js +1 -1
- package/src/client/components/underpost/LabGalleryUnderpost.js +12 -15
- package/src/client/components/underpost/LogInUnderpost.js +6 -4
- package/src/client/components/underpost/LogOutUnderpost.js +6 -4
- package/src/client/components/underpost/RouterUnderpost.js +45 -0
- package/src/client/components/underpost/SettingsUnderpost.js +4 -4
- package/src/client/components/underpost/SignUpUnderpost.js +6 -4
- package/src/client/components/underpost/TranslateUnderpost.js +4 -4
- package/src/client/public/cyberia-docs/ACTION-SYSTEM.md +235 -0
- package/src/client/public/cyberia-docs/ARCHITECTURE.md +443 -0
- package/src/client/public/cyberia-docs/CYBERIA-CLI.md +417 -0
- package/src/client/public/cyberia-docs/CYBERIA-CLIENT.md +313 -0
- package/src/client/public/cyberia-docs/CYBERIA-SERVER.md +260 -0
- package/src/client/public/cyberia-docs/ENTITY-PROFILE.md +241 -0
- package/src/client/public/cyberia-docs/HARDHAT-MODULE.md +300 -0
- package/src/client/public/cyberia-docs/OFF-CHAIN-ECONOMY.md +279 -0
- package/src/client/public/cyberia-docs/QUEST-SYSTEM.md +206 -0
- package/src/client/public/cyberia-docs/ROADMAP.md +240 -0
- package/src/client/public/cyberia-docs/WHITE-PAPER.md +732 -0
- package/src/client/services/atlas-sprite-sheet/atlas-sprite-sheet.service.js +14 -20
- package/src/client/services/core/core.service.js +17 -49
- package/src/client/services/crypto/crypto.service.js +8 -13
- package/src/client/services/cyberia-action/cyberia-action.service.js +99 -0
- package/src/client/services/cyberia-dialogue/cyberia-dialogue.service.js +10 -16
- package/src/client/services/cyberia-entity/cyberia-entity.management.js +5 -5
- package/src/client/services/cyberia-entity/cyberia-entity.service.js +10 -16
- package/src/client/services/cyberia-instance/cyberia-instance.management.js +6 -6
- package/src/client/services/cyberia-instance/cyberia-instance.service.js +12 -18
- package/src/client/services/cyberia-instance-conf/cyberia-instance-conf.service.js +10 -16
- package/src/client/services/cyberia-map/cyberia-map.management.js +6 -6
- package/src/client/services/cyberia-map/cyberia-map.service.js +12 -18
- package/src/client/services/cyberia-quest/cyberia-quest.service.js +99 -0
- package/src/client/services/cyberia-quest-progress/cyberia-quest-progress.service.js +99 -0
- package/src/client/services/default/default.management.js +159 -267
- package/src/client/services/default/default.service.js +10 -16
- package/src/client/services/document/document.service.js +14 -19
- package/src/client/services/file/file.service.js +8 -13
- package/src/client/services/instance/instance.management.js +5 -5
- package/src/client/services/instance/instance.service.js +10 -15
- package/src/client/services/ipfs/ipfs.service.js +12 -18
- package/src/client/services/object-layer/object-layer.management.js +12 -12
- package/src/client/services/object-layer/object-layer.service.js +20 -26
- package/src/client/services/object-layer-render-frames/object-layer-render-frames.service.js +10 -16
- package/src/client/services/test/test.service.js +8 -13
- package/src/client/services/user/guest.service.js +86 -0
- package/src/client/services/user/user.management.js +5 -5
- package/src/client/services/user/user.service.js +14 -20
- package/src/client/ssr/body/404.js +3 -3
- package/src/client/ssr/body/500.js +3 -3
- package/src/client/ssr/body/CacheControl.js +5 -2
- package/src/client/ssr/body/DefaultSplashScreen.js +19 -12
- package/src/client/ssr/body/UnderpostDefaultSplashScreen.js +13 -6
- package/src/client/ssr/head/PwaItemledger.js +197 -60
- package/src/client/ssr/mailer/DefaultRecoverEmail.js +19 -20
- package/src/client/ssr/mailer/DefaultVerifyEmail.js +15 -16
- package/src/client/ssr/offline/Maintenance.js +12 -11
- package/src/client/ssr/offline/NoNetworkConnection.js +3 -3
- package/src/client/ssr/pages/Test.js +2 -2
- package/src/client/sw/core.sw.js +212 -0
- package/src/grpc/cyberia/grpc-server.js +179 -67
- package/src/index.js +1 -1
- package/src/runtime/cyberia-client/Dockerfile +80 -0
- package/src/runtime/cyberia-server/Dockerfile +37 -0
- package/src/runtime/express/Dockerfile +4 -4
- package/src/runtime/lampp/Dockerfile +8 -7
- package/src/runtime/wp/Dockerfile +11 -17
- package/src/server/atlas-sprite-sheet-generator.js +4 -2
- package/src/server/client-build-docs.js +45 -46
- package/src/server/client-build.js +334 -60
- package/src/server/client-formatted.js +47 -16
- package/src/server/conf.js +5 -4
- package/src/server/data-query.js +32 -20
- package/src/server/dns.js +22 -0
- package/src/server/ipfs-client.js +232 -91
- package/src/server/object-layer.js +1 -6
- package/src/server/process.js +13 -27
- package/src/server/semantic-layer-generator-floor.js +11 -51
- package/src/server/semantic-layer-generator-resource.js +259 -0
- package/src/server/semantic-layer-generator-skin.js +41 -171
- package/src/server/semantic-layer-generator.js +122 -14
- package/src/server/shape-generator.js +108 -0
- package/src/server/start.js +17 -3
- package/src/server/valkey.js +141 -235
- package/tsconfig.docs.json +15 -0
- package/typedoc.dd-cyberia.json +29 -0
- package/typedoc.json +29 -0
- package/WHITE-PAPER.md +0 -1540
- package/hardhat/README.md +0 -531
- package/hardhat/WHITE-PAPER.md +0 -1540
- package/jsdoc.dd-cyberia.json +0 -68
- package/jsdoc.json +0 -68
- package/src/api/object-layer/README.md +0 -672
- package/src/client/components/core/ColorPalette.js +0 -5267
- package/src/client/components/core/JoyStick.js +0 -80
- package/src/client/components/cryptokoyn/RoutesCryptokoyn.js +0 -39
- package/src/client/components/cyberia-portal/RoutesCyberiaPortal.js +0 -62
- package/src/client/components/cyberia-portal/ServerCyberiaPortal.js +0 -136
- package/src/client/components/default/RoutesDefault.js +0 -49
- package/src/client/components/itemledger/RoutesItemledger.js +0 -40
- package/src/client/components/underpost/RoutesUnderpost.js +0 -47
- package/src/client/sw/default.sw.js +0 -127
- package/src/client/sw/template.sw.js +0 -84
- package/src/grpc/cyberia/OFF_CHAIN_ECONOMY.md +0 -305
- package/src/grpc/cyberia/README.md +0 -326
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CoreService } from '../../services/core/core.service.js';
|
|
2
2
|
import { BtnIcon } from '../core/BtnIcon.js';
|
|
3
|
-
import { borderChar, dynamicCol } from '../core/Css.js';
|
|
3
|
+
import { borderChar, Css, dynamicCol, Themes } from '../core/Css.js';
|
|
4
4
|
import { DropDown } from '../core/DropDown.js';
|
|
5
5
|
import { EventsUI } from '../core/EventsUI.js';
|
|
6
6
|
import { Translate } from '../core/Translate.js';
|
|
@@ -16,22 +16,525 @@ import { Modal } from '../core/Modal.js';
|
|
|
16
16
|
import { LoadingAnimation } from '../core/LoadingAnimation.js';
|
|
17
17
|
import { DefaultManagement } from '../../services/default/default.management.js';
|
|
18
18
|
import * as _ from '../cyberia/ObjectLayerEngine.js';
|
|
19
|
+
import '../core/ColorPaletteElement.js';
|
|
19
20
|
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
21
|
+
const CANVAS_BEHAVIOR_ICON = 'fa-solid fa-shapes';
|
|
22
|
+
|
|
23
|
+
const DISTORTION_TYPES = Object.freeze([
|
|
24
|
+
{
|
|
25
|
+
value: 'position-jitter',
|
|
26
|
+
label: 'Position Jitter',
|
|
27
|
+
group: 'distortion',
|
|
28
|
+
icon: CANVAS_BEHAVIOR_ICON,
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
value: 'rotation-drift',
|
|
32
|
+
label: 'Rotation Drift',
|
|
33
|
+
group: 'distortion',
|
|
34
|
+
icon: CANVAS_BEHAVIOR_ICON,
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
value: 'scale-wobble',
|
|
38
|
+
label: 'Scale Wobble',
|
|
39
|
+
group: 'distortion',
|
|
40
|
+
icon: CANVAS_BEHAVIOR_ICON,
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
value: 'particle-drift',
|
|
44
|
+
label: 'Particle Drift',
|
|
45
|
+
group: 'distortion',
|
|
46
|
+
icon: CANVAS_BEHAVIOR_ICON,
|
|
47
|
+
},
|
|
48
|
+
]);
|
|
49
|
+
|
|
50
|
+
const MOSAIC_TYPES = Object.freeze([
|
|
51
|
+
{
|
|
52
|
+
value: 'mosaic-diamond-checker',
|
|
53
|
+
label: 'Diamond Checker',
|
|
54
|
+
group: 'mosaic',
|
|
55
|
+
icon: CANVAS_BEHAVIOR_ICON,
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
value: 'mosaic-rhombus-lattice',
|
|
59
|
+
label: 'Rhombus Lattice',
|
|
60
|
+
group: 'mosaic',
|
|
61
|
+
icon: CANVAS_BEHAVIOR_ICON,
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
value: 'mosaic-zigzag-rows',
|
|
65
|
+
label: 'Zigzag Rows',
|
|
66
|
+
group: 'mosaic',
|
|
67
|
+
icon: CANVAS_BEHAVIOR_ICON,
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
value: 'mosaic-staggered-tiles',
|
|
71
|
+
label: 'Staggered Tiles',
|
|
72
|
+
group: 'mosaic',
|
|
73
|
+
icon: CANVAS_BEHAVIOR_ICON,
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
value: 'mosaic-brick-offset',
|
|
77
|
+
label: 'Brick Offset',
|
|
78
|
+
group: 'mosaic',
|
|
79
|
+
icon: CANVAS_BEHAVIOR_ICON,
|
|
80
|
+
},
|
|
81
|
+
]);
|
|
82
|
+
|
|
83
|
+
const CANVAS_BEHAVIORS = Object.freeze([...DISTORTION_TYPES, ...MOSAIC_TYPES]);
|
|
84
|
+
|
|
85
|
+
const DEFAULT_DISTORTION_TYPE = DISTORTION_TYPES[0].value;
|
|
86
|
+
const DEFAULT_DISTORTION_STATUS =
|
|
87
|
+
'Applies the selected canvas behavior directly to the current editor frame. factorA controls distortion density or mosaic tile scale.';
|
|
88
|
+
const DEFAULT_DISTORTION_FACTOR_A = 0.12;
|
|
89
|
+
const DEFAULT_STAT_RANDOM_MIN = 0;
|
|
90
|
+
const DEFAULT_STAT_RANDOM_MAX = 10;
|
|
91
|
+
const UNIFORM_OPACITY_TOGGLE_ID = 'ol-uniform-opacity-lock';
|
|
92
|
+
const DIRECTION_PREVIEW_MODAL_ID = 'modal-object-layer-direction-preview';
|
|
93
|
+
const CANVAS_BEHAVIOR_BY_VALUE = Object.freeze(
|
|
94
|
+
Object.fromEntries(CANVAS_BEHAVIORS.map((entry) => [entry.value, entry])),
|
|
95
|
+
);
|
|
96
|
+
const isMosaicBehavior = (value = '') => CANVAS_BEHAVIOR_BY_VALUE[value]?.group === 'mosaic';
|
|
97
|
+
const getCanvasBehaviorDisplay = (value = DEFAULT_DISTORTION_TYPE) => {
|
|
98
|
+
const behavior = CANVAS_BEHAVIOR_BY_VALUE[value] || CANVAS_BEHAVIOR_BY_VALUE[DEFAULT_DISTORTION_TYPE];
|
|
99
|
+
return `<i class="${CANVAS_BEHAVIOR_ICON}"></i> ${behavior.label}`;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const hashString = (str = '') => {
|
|
103
|
+
let hash = 0;
|
|
104
|
+
for (let i = 0; i < str.length; i++) {
|
|
105
|
+
hash = (Math.imul(31, hash) + str.charCodeAt(i)) | 0;
|
|
106
|
+
}
|
|
107
|
+
return hash;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const clampNumber = (value, min, max) => (value < min ? min : value > max ? max : value);
|
|
111
|
+
const lerp = (start, end, factor) => start + (end - start) * factor;
|
|
112
|
+
const smoothstep = (value) => value * value * (3 - 2 * value);
|
|
113
|
+
const normalizedHash = (seed, x, y) => ((hashString(`${seed}:${x}:${y}`) >>> 0) / 4294967295) * 2 - 1;
|
|
114
|
+
|
|
115
|
+
const sampleSmoothNoise = (seed, x, y) => {
|
|
116
|
+
const x0 = Math.floor(x);
|
|
117
|
+
const y0 = Math.floor(y);
|
|
118
|
+
const x1 = x0 + 1;
|
|
119
|
+
const y1 = y0 + 1;
|
|
120
|
+
|
|
121
|
+
const sx = smoothstep(x - x0);
|
|
122
|
+
const sy = smoothstep(y - y0);
|
|
123
|
+
const n00 = normalizedHash(seed, x0, y0);
|
|
124
|
+
const n10 = normalizedHash(seed, x1, y0);
|
|
125
|
+
const n01 = normalizedHash(seed, x0, y1);
|
|
126
|
+
const n11 = normalizedHash(seed, x1, y1);
|
|
127
|
+
|
|
128
|
+
return lerp(lerp(n00, n10, sx), lerp(n01, n11, sx), sy);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const isVisibleCell = (cell) => Array.isArray(cell) && (cell[3] || 0) > 0;
|
|
132
|
+
const cloneMatrix = (matrix = []) => matrix.map((row) => row.map((cell) => cell.slice()));
|
|
133
|
+
const modulo = (value, divisor) => {
|
|
134
|
+
if (!divisor) return 0;
|
|
135
|
+
return ((value % divisor) + divisor) % divisor;
|
|
136
|
+
};
|
|
137
|
+
const normalizeColorCell = (cell = [255, 0, 0, 255]) => [
|
|
138
|
+
clampNumber(Math.round(cell[0] || 0), 0, 255),
|
|
139
|
+
clampNumber(Math.round(cell[1] || 0), 0, 255),
|
|
140
|
+
clampNumber(Math.round(cell[2] || 0), 0, 255),
|
|
141
|
+
clampNumber(Math.round(cell[3] ?? 255), 0, 255),
|
|
142
|
+
];
|
|
143
|
+
const getRenderedInputNode = (id = '') => s(`.${id}`) || s(`#${id}-name`) || s(`#${id}`);
|
|
144
|
+
|
|
145
|
+
const countChangedCells = (baseMatrix = [], nextMatrix = []) => {
|
|
146
|
+
const height = Math.max(baseMatrix.length, nextMatrix.length);
|
|
147
|
+
const width = Math.max(baseMatrix[0]?.length || 0, nextMatrix[0]?.length || 0);
|
|
148
|
+
let changedCells = 0;
|
|
149
|
+
|
|
150
|
+
for (let y = 0; y < height; y++) {
|
|
151
|
+
for (let x = 0; x < width; x++) {
|
|
152
|
+
const baseCell = baseMatrix[y]?.[x] || [0, 0, 0, 0];
|
|
153
|
+
const nextCell = nextMatrix[y]?.[x] || [0, 0, 0, 0];
|
|
154
|
+
if (
|
|
155
|
+
baseCell[0] !== nextCell[0] ||
|
|
156
|
+
baseCell[1] !== nextCell[1] ||
|
|
157
|
+
baseCell[2] !== nextCell[2] ||
|
|
158
|
+
baseCell[3] !== nextCell[3]
|
|
159
|
+
) {
|
|
160
|
+
changedCells++;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return changedCells;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const deriveMatrixLayerSeed = (matrix = []) => {
|
|
169
|
+
const signature = [];
|
|
170
|
+
for (let y = 0; y < matrix.length; y++) {
|
|
171
|
+
for (let x = 0; x < (matrix[y]?.length || 0); x++) {
|
|
172
|
+
const cell = matrix[y][x];
|
|
173
|
+
if (!isVisibleCell(cell)) continue;
|
|
174
|
+
signature.push(`${x}:${y}:${cell.join(',')}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return hashString(`${matrix[0]?.length || 0}x${matrix.length}:${signature.join('|')}`);
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const colorDistance = (left = [0, 0, 0, 0], right = [0, 0, 0, 0]) => {
|
|
181
|
+
const deltaRed = (left[0] - right[0]) / 255;
|
|
182
|
+
const deltaGreen = (left[1] - right[1]) / 255;
|
|
183
|
+
const deltaBlue = (left[2] - right[2]) / 255;
|
|
184
|
+
const deltaAlpha = ((left[3] || 0) - (right[3] || 0)) / 255;
|
|
185
|
+
return (
|
|
186
|
+
Math.sqrt(deltaRed * deltaRed + deltaGreen * deltaGreen + deltaBlue * deltaBlue + deltaAlpha * deltaAlpha * 0.35) /
|
|
187
|
+
1.83
|
|
188
|
+
);
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const getNeighborEntries = (matrix, x, y) => {
|
|
192
|
+
const neighbors = [];
|
|
193
|
+
const sourceCell = matrix[y]?.[x] || [0, 0, 0, 0];
|
|
194
|
+
|
|
195
|
+
for (let offsetY = -1; offsetY <= 1; offsetY++) {
|
|
196
|
+
for (let offsetX = -1; offsetX <= 1; offsetX++) {
|
|
197
|
+
if (offsetX === 0 && offsetY === 0) continue;
|
|
198
|
+
|
|
199
|
+
const neighborX = x + offsetX;
|
|
200
|
+
const neighborY = y + offsetY;
|
|
201
|
+
if (
|
|
202
|
+
neighborY < 0 ||
|
|
203
|
+
neighborX < 0 ||
|
|
204
|
+
neighborY >= matrix.length ||
|
|
205
|
+
neighborX >= (matrix[neighborY]?.length || 0)
|
|
206
|
+
) {
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const neighborCell = matrix[neighborY][neighborX];
|
|
211
|
+
const visible = isVisibleCell(neighborCell);
|
|
212
|
+
neighbors.push({
|
|
213
|
+
x: neighborX,
|
|
214
|
+
y: neighborY,
|
|
215
|
+
dx: offsetX,
|
|
216
|
+
dy: offsetY,
|
|
217
|
+
cell: neighborCell.slice(),
|
|
218
|
+
visible,
|
|
219
|
+
distance: visible ? colorDistance(sourceCell, neighborCell) : 1,
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return neighbors;
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const getDistortionTime = (distortionSeed = 0) => (Math.abs(Number(distortionSeed) || 0) % 4096) / 128 + 1;
|
|
228
|
+
|
|
229
|
+
const getDirectionalVector = ({ x, y, width, height, distortionType, frameSeed, distortionSeed }) => {
|
|
230
|
+
const distortionTime = getDistortionTime(distortionSeed);
|
|
231
|
+
const noiseX = sampleSmoothNoise(frameSeed + 211, x * 0.26 + distortionTime * 0.14, y * 0.26 + 1.9);
|
|
232
|
+
const noiseY = sampleSmoothNoise(frameSeed + 263, x * 0.26 + 7.1, y * 0.26 + distortionTime * 0.14);
|
|
233
|
+
const centerX = (width - 1) / 2;
|
|
234
|
+
const centerY = (height - 1) / 2;
|
|
235
|
+
const deltaX = x - centerX;
|
|
236
|
+
const deltaY = y - centerY;
|
|
237
|
+
const radialX = deltaX === 0 ? 0 : deltaX / Math.max(1, Math.abs(deltaX) + Math.abs(deltaY));
|
|
238
|
+
const radialY = deltaY === 0 ? 0 : deltaY / Math.max(1, Math.abs(deltaX) + Math.abs(deltaY));
|
|
239
|
+
|
|
240
|
+
if (distortionType === 'position-jitter') {
|
|
241
|
+
return { dx: noiseX, dy: noiseY };
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (distortionType === 'rotation-drift') {
|
|
245
|
+
const tangentDirection = sampleSmoothNoise(frameSeed + 307, distortionTime * 0.24, 3.7) >= 0 ? 1 : -1;
|
|
246
|
+
return {
|
|
247
|
+
dx: tangentDirection * -radialY + noiseX * 0.3,
|
|
248
|
+
dy: tangentDirection * radialX + noiseY * 0.3,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (distortionType === 'scale-wobble') {
|
|
253
|
+
const radialDirection = sampleSmoothNoise(frameSeed + 359, distortionTime * 0.18, 4.6) >= 0 ? 1 : -1;
|
|
254
|
+
return {
|
|
255
|
+
dx: radialX * radialDirection + noiseX * 0.2,
|
|
256
|
+
dy: radialY * radialDirection + noiseY * 0.2,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return {
|
|
261
|
+
dx: (noiseX >= 0 ? 0.6 : -0.6) + noiseX * 0.6,
|
|
262
|
+
dy: (noiseY >= 0 ? 0.6 : -0.6) + noiseY * 0.6,
|
|
263
|
+
};
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
const collectVisibleCells = (matrix = [], layerSeed, frameSeed, distortionType, distortionSeed) => {
|
|
267
|
+
const distortionTime = getDistortionTime(distortionSeed);
|
|
268
|
+
const visibleCells = [];
|
|
269
|
+
|
|
270
|
+
for (let y = 0; y < matrix.length; y++) {
|
|
271
|
+
for (let x = 0; x < (matrix[y]?.length || 0); x++) {
|
|
272
|
+
const cell = matrix[y][x];
|
|
273
|
+
if (!isVisibleCell(cell)) continue;
|
|
274
|
+
|
|
275
|
+
const neighbors = getNeighborEntries(matrix, x, y);
|
|
276
|
+
const visibleNeighbors = neighbors.filter((neighbor) => neighbor.visible);
|
|
277
|
+
const edgeScore = neighbors.some((neighbor) => !neighbor.visible) ? 1 : 0;
|
|
278
|
+
const variationScore = visibleNeighbors.length
|
|
279
|
+
? visibleNeighbors.reduce((sum, neighbor) => {
|
|
280
|
+
if (neighbor.distance < 0.01) return sum + 0.04;
|
|
281
|
+
if (neighbor.distance <= 0.35) return sum + (1 - Math.abs(neighbor.distance - 0.14) / 0.21);
|
|
282
|
+
if (neighbor.distance <= 0.55) return sum + 0.18;
|
|
283
|
+
return sum + 0.04;
|
|
284
|
+
}, 0) / visibleNeighbors.length
|
|
285
|
+
: 0;
|
|
286
|
+
|
|
287
|
+
const activity = (sampleSmoothNoise(frameSeed + 401, x * 0.24 + distortionTime * 0.1, y * 0.24 + 2.7) + 1) / 2;
|
|
288
|
+
const stabilityBias = 1 - (hashString(`${layerSeed}:${distortionType}:${x}:${y}`) >>> 0) / 4294967295;
|
|
289
|
+
|
|
290
|
+
visibleCells.push({
|
|
291
|
+
x,
|
|
292
|
+
y,
|
|
293
|
+
cell: cell.slice(),
|
|
294
|
+
neighbors,
|
|
295
|
+
edgeScore,
|
|
296
|
+
variationScore,
|
|
297
|
+
score: variationScore * 0.58 + edgeScore * 0.2 + activity * 0.18 + stabilityBias * 0.04,
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return visibleCells.sort((left, right) => right.score - left.score);
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
const chooseDistortionTarget = ({ entry, matrix, distortionType, frameSeed, distortionSeed, reservedCells }) => {
|
|
306
|
+
const width = matrix[0]?.length || 0;
|
|
307
|
+
const height = matrix.length;
|
|
308
|
+
const vector = getDirectionalVector({
|
|
309
|
+
x: entry.x,
|
|
310
|
+
y: entry.y,
|
|
311
|
+
width,
|
|
312
|
+
height,
|
|
313
|
+
distortionType,
|
|
314
|
+
frameSeed,
|
|
315
|
+
distortionSeed,
|
|
316
|
+
});
|
|
317
|
+
const vectorLength = Math.hypot(vector.dx, vector.dy) || 1;
|
|
318
|
+
|
|
319
|
+
return [...entry.neighbors]
|
|
320
|
+
.map((neighbor) => {
|
|
321
|
+
const alignment =
|
|
322
|
+
(neighbor.dx * vector.dx + neighbor.dy * vector.dy) /
|
|
323
|
+
((Math.hypot(neighbor.dx, neighbor.dy) || 1) * vectorLength);
|
|
324
|
+
let score = alignment * 0.55;
|
|
325
|
+
|
|
326
|
+
if (!neighbor.visible) {
|
|
327
|
+
score += 0.22 + entry.edgeScore * 0.16;
|
|
328
|
+
} else if (neighbor.distance >= 0.01 && neighbor.distance <= 0.38) {
|
|
329
|
+
score += 0.34 + (0.38 - neighbor.distance) * 0.45;
|
|
330
|
+
} else {
|
|
331
|
+
score -= 0.2;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (distortionType === 'particle-drift' && Math.abs(neighbor.dx) === 1 && Math.abs(neighbor.dy) === 1) {
|
|
335
|
+
score += 0.16;
|
|
336
|
+
}
|
|
337
|
+
if (distortionType === 'rotation-drift' && Math.abs(neighbor.dx) + Math.abs(neighbor.dy) === 1) {
|
|
338
|
+
score += 0.06;
|
|
339
|
+
}
|
|
340
|
+
if (distortionType === 'scale-wobble' && !neighbor.visible) {
|
|
341
|
+
score += 0.08;
|
|
342
|
+
}
|
|
343
|
+
if (reservedCells.has(`${neighbor.x}:${neighbor.y}`)) {
|
|
344
|
+
score = -Infinity;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return { ...neighbor, score };
|
|
348
|
+
})
|
|
349
|
+
.sort((left, right) => right.score - left.score)
|
|
350
|
+
.find((candidate) => candidate.score > 0.02);
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
const buildDistortedMatrix = (
|
|
354
|
+
baseMatrix,
|
|
355
|
+
distortionType,
|
|
356
|
+
factorA = DEFAULT_DISTORTION_FACTOR_A,
|
|
357
|
+
distortionSeed = Date.now(),
|
|
358
|
+
) => {
|
|
359
|
+
const layerSeed = deriveMatrixLayerSeed(baseMatrix);
|
|
360
|
+
const normalizedSeed = Number.isFinite(distortionSeed) ? distortionSeed : hashString(String(distortionSeed));
|
|
361
|
+
const frameSeed = hashString(`${layerSeed}:${distortionType}:${normalizedSeed}`);
|
|
362
|
+
const visibleCells = collectVisibleCells(baseMatrix, layerSeed, frameSeed, distortionType, normalizedSeed);
|
|
363
|
+
const result = cloneMatrix(baseMatrix);
|
|
364
|
+
|
|
365
|
+
if (!visibleCells.length) {
|
|
366
|
+
return { matrix: result, changedCells: 0, appliedDistortions: 0, layerSeed, frameSeed };
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const densityFactor = clampNumber(Number.isFinite(factorA) ? factorA : DEFAULT_DISTORTION_FACTOR_A, 0.01, 1);
|
|
370
|
+
const maxBudget = Math.max(1, Math.round(visibleCells.length * 0.35));
|
|
371
|
+
const distortionBudget = clampNumber(Math.round(visibleCells.length * densityFactor), 1, maxBudget);
|
|
372
|
+
const reservedCells = new Set();
|
|
373
|
+
let appliedDistortions = 0;
|
|
374
|
+
|
|
375
|
+
for (const entry of visibleCells) {
|
|
376
|
+
if (appliedDistortions >= distortionBudget) break;
|
|
377
|
+
|
|
378
|
+
const sourceKey = `${entry.x}:${entry.y}`;
|
|
379
|
+
if (reservedCells.has(sourceKey)) continue;
|
|
380
|
+
|
|
381
|
+
const target = chooseDistortionTarget({
|
|
382
|
+
entry,
|
|
383
|
+
matrix: baseMatrix,
|
|
384
|
+
distortionType,
|
|
385
|
+
frameSeed,
|
|
386
|
+
distortionSeed: normalizedSeed,
|
|
387
|
+
reservedCells,
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
if (!target) continue;
|
|
391
|
+
|
|
392
|
+
if (target.visible) {
|
|
393
|
+
const nextCell = result[target.y][target.x].slice();
|
|
394
|
+
result[target.y][target.x] = entry.cell.slice();
|
|
395
|
+
result[entry.y][entry.x] = nextCell;
|
|
396
|
+
} else {
|
|
397
|
+
result[target.y][target.x] = entry.cell.slice();
|
|
398
|
+
result[entry.y][entry.x] = [0, 0, 0, 0];
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
reservedCells.add(sourceKey);
|
|
402
|
+
reservedCells.add(`${target.x}:${target.y}`);
|
|
403
|
+
appliedDistortions++;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
let changedCells = countChangedCells(baseMatrix, result);
|
|
407
|
+
|
|
408
|
+
if (changedCells === 0) {
|
|
409
|
+
for (const entry of visibleCells) {
|
|
410
|
+
const transparentTarget = entry.neighbors.find((neighbor) => !neighbor.visible);
|
|
411
|
+
if (!transparentTarget) continue;
|
|
412
|
+
result[transparentTarget.y][transparentTarget.x] = entry.cell.slice();
|
|
413
|
+
result[entry.y][entry.x] = [0, 0, 0, 0];
|
|
414
|
+
appliedDistortions = 1;
|
|
415
|
+
changedCells = countChangedCells(baseMatrix, result);
|
|
416
|
+
break;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return {
|
|
421
|
+
matrix: result,
|
|
422
|
+
changedCells,
|
|
423
|
+
appliedDistortions,
|
|
424
|
+
densityFactor,
|
|
425
|
+
layerSeed,
|
|
426
|
+
frameSeed,
|
|
427
|
+
};
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
const getMosaicTileSize = (width, height, factorA = DEFAULT_DISTORTION_FACTOR_A) => {
|
|
431
|
+
const minDimension = Math.max(1, Math.min(width, height));
|
|
432
|
+
const normalizedFactor = clampNumber(Number.isFinite(factorA) ? factorA : DEFAULT_DISTORTION_FACTOR_A, 0.01, 1);
|
|
433
|
+
return clampNumber(
|
|
434
|
+
Math.round(1 + normalizedFactor * Math.max(2, Math.min(10, Math.floor(minDimension / 2)))),
|
|
435
|
+
1,
|
|
436
|
+
minDimension,
|
|
437
|
+
);
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
const buildMosaicMatrix = (
|
|
441
|
+
baseMatrix,
|
|
442
|
+
mosaicType,
|
|
443
|
+
factorA = DEFAULT_DISTORTION_FACTOR_A,
|
|
444
|
+
brushCell = [255, 0, 0, 255],
|
|
445
|
+
) => {
|
|
446
|
+
const height = baseMatrix.length;
|
|
447
|
+
const width = baseMatrix[0]?.length || 0;
|
|
448
|
+
const result = cloneMatrix(baseMatrix);
|
|
449
|
+
|
|
450
|
+
if (!height || !width) {
|
|
451
|
+
return { matrix: result, changedCells: 0, paintedCells: 0, tileSize: 1 };
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
const tileSize = getMosaicTileSize(width, height, factorA);
|
|
455
|
+
const gap = Math.max(1, Math.floor(tileSize / 2));
|
|
456
|
+
const thickness = Math.max(1, Math.ceil(tileSize / 3));
|
|
457
|
+
const paintCell = normalizeColorCell(brushCell);
|
|
458
|
+
let paintedCells = 0;
|
|
459
|
+
|
|
460
|
+
const shouldPaint = (x, y) => {
|
|
461
|
+
switch (mosaicType) {
|
|
462
|
+
case 'mosaic-diamond-checker': {
|
|
463
|
+
const span = tileSize * 2 + gap;
|
|
464
|
+
const tileX = Math.floor(x / span);
|
|
465
|
+
const tileY = Math.floor(y / span);
|
|
466
|
+
if ((tileX + tileY) % 2 !== 0) return false;
|
|
467
|
+
const localX = modulo(x, span) - tileSize;
|
|
468
|
+
const localY = modulo(y, span) - tileSize;
|
|
469
|
+
return Math.abs(localX) + Math.abs(localY) <= tileSize - 1;
|
|
470
|
+
}
|
|
471
|
+
case 'mosaic-rhombus-lattice': {
|
|
472
|
+
const period = tileSize * 2 + gap;
|
|
473
|
+
const diagonalA = modulo(x + y, period);
|
|
474
|
+
const diagonalB = modulo(x - y, period);
|
|
475
|
+
return diagonalA < thickness || diagonalB < thickness;
|
|
476
|
+
}
|
|
477
|
+
case 'mosaic-zigzag-rows': {
|
|
478
|
+
const zigzagWidth = Math.max(2, tileSize * 2 + gap);
|
|
479
|
+
const bandHeight = Math.max(1, tileSize);
|
|
480
|
+
const rowIndex = Math.floor(y / bandHeight);
|
|
481
|
+
const localX = modulo(x + (rowIndex % 2) * tileSize, zigzagWidth);
|
|
482
|
+
const ridge = modulo(y, bandHeight);
|
|
483
|
+
return Math.abs(localX - ridge) < thickness || Math.abs(localX - (zigzagWidth - ridge - 1)) < thickness;
|
|
484
|
+
}
|
|
485
|
+
case 'mosaic-staggered-tiles': {
|
|
486
|
+
const step = tileSize + gap;
|
|
487
|
+
const rowIndex = Math.floor(y / step);
|
|
488
|
+
const localX = modulo(x + (rowIndex % 2) * Math.floor(step / 2), step);
|
|
489
|
+
const localY = modulo(y, step);
|
|
490
|
+
return localX < tileSize && localY < tileSize;
|
|
491
|
+
}
|
|
492
|
+
case 'mosaic-brick-offset': {
|
|
493
|
+
const brickWidth = tileSize * 2 + gap;
|
|
494
|
+
const brickHeight = tileSize + gap;
|
|
495
|
+
const rowIndex = Math.floor(y / brickHeight);
|
|
496
|
+
const localX = modulo(x + (rowIndex % 2) * Math.floor(brickWidth / 2), brickWidth);
|
|
497
|
+
const localY = modulo(y, brickHeight);
|
|
498
|
+
return localX < brickWidth - gap && localY < tileSize;
|
|
499
|
+
}
|
|
500
|
+
default:
|
|
501
|
+
return false;
|
|
502
|
+
}
|
|
503
|
+
};
|
|
504
|
+
|
|
505
|
+
for (let y = 0; y < height; y++) {
|
|
506
|
+
for (let x = 0; x < width; x++) {
|
|
507
|
+
if (!shouldPaint(x, y)) continue;
|
|
508
|
+
paintedCells++;
|
|
509
|
+
result[y][x] = paintCell.slice();
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
return {
|
|
514
|
+
matrix: result,
|
|
515
|
+
changedCells: countChangedCells(baseMatrix, result),
|
|
516
|
+
paintedCells,
|
|
517
|
+
tileSize,
|
|
518
|
+
};
|
|
519
|
+
};
|
|
520
|
+
|
|
521
|
+
class ObjectLayerEngineModal {
|
|
522
|
+
static selectItemType = 'skin';
|
|
523
|
+
static itemActivable = false;
|
|
524
|
+
static renderFrameDuration = 100;
|
|
525
|
+
static existingObjectLayerId = null;
|
|
526
|
+
static originalDirectionCodes = [];
|
|
527
|
+
static selectedDistortionType = DEFAULT_DISTORTION_TYPE;
|
|
528
|
+
static distortionFactorA = DEFAULT_DISTORTION_FACTOR_A;
|
|
529
|
+
static uniformOpacityEnabled = false;
|
|
530
|
+
static templates = [
|
|
28
531
|
{
|
|
29
532
|
label: 'empty',
|
|
30
533
|
id: 'empty',
|
|
31
534
|
data: [],
|
|
32
535
|
},
|
|
33
|
-
]
|
|
34
|
-
statDescriptions
|
|
536
|
+
];
|
|
537
|
+
static statDescriptions = {
|
|
35
538
|
effect: {
|
|
36
539
|
title: 'Effect',
|
|
37
540
|
icon: 'fa-solid fa-burst',
|
|
@@ -69,9 +572,9 @@ const ObjectLayerEngineModal = {
|
|
|
69
572
|
description: 'Reduces the cooldown time between actions, allowing for more frequent actions.',
|
|
70
573
|
detail: 'It also increases the chance to trigger life-regeneration events.',
|
|
71
574
|
},
|
|
72
|
-
}
|
|
575
|
+
};
|
|
73
576
|
|
|
74
|
-
RenderTemplate
|
|
577
|
+
static RenderTemplate = (colorTemplate) => {
|
|
75
578
|
const ole = s('object-layer-engine');
|
|
76
579
|
if (!ole) {
|
|
77
580
|
return;
|
|
@@ -84,17 +587,20 @@ const ObjectLayerEngineModal = {
|
|
|
84
587
|
|
|
85
588
|
const matrix = colorTemplate.map((row) => row.map((hex) => [...hexToRgbA(hex), 255]));
|
|
86
589
|
ole.loadMatrix(matrix);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
590
|
+
};
|
|
591
|
+
|
|
592
|
+
static ObjectLayerData = {};
|
|
593
|
+
|
|
594
|
+
static clearData() {
|
|
91
595
|
this.ObjectLayerData = {};
|
|
92
596
|
this.selectItemType = 'skin';
|
|
93
597
|
this.itemActivable = false;
|
|
94
|
-
this.renderIsStateless = false;
|
|
95
598
|
this.renderFrameDuration = 100;
|
|
96
599
|
this.existingObjectLayerId = null;
|
|
97
600
|
this.originalDirectionCodes = [];
|
|
601
|
+
this.selectedDistortionType = DEFAULT_DISTORTION_TYPE;
|
|
602
|
+
this.distortionFactorA = DEFAULT_DISTORTION_FACTOR_A;
|
|
603
|
+
this.uniformOpacityEnabled = false;
|
|
98
604
|
this.templates = [
|
|
99
605
|
{
|
|
100
606
|
label: 'empty',
|
|
@@ -103,13 +609,11 @@ const ObjectLayerEngineModal = {
|
|
|
103
609
|
},
|
|
104
610
|
];
|
|
105
611
|
|
|
106
|
-
// Clear the canvas if it exists
|
|
107
612
|
const ole = s('object-layer-engine');
|
|
108
613
|
if (ole && typeof ole.clear === 'function') {
|
|
109
614
|
ole.clear();
|
|
110
615
|
}
|
|
111
616
|
|
|
112
|
-
// Clear all frame previews from DOM for all direction codes
|
|
113
617
|
const directionCodes = ['08', '18', '02', '12', '04', '14', '06', '16'];
|
|
114
618
|
for (const directionCode of directionCodes) {
|
|
115
619
|
const framesContainer = s(`.frames-${directionCode}`);
|
|
@@ -132,24 +636,42 @@ const ObjectLayerEngineModal = {
|
|
|
132
636
|
const activableCheckbox = s('#ol-toggle-item-activable');
|
|
133
637
|
if (activableCheckbox) activableCheckbox.checked = false;
|
|
134
638
|
|
|
135
|
-
const statelessCheckbox = s('#ol-toggle-render-is-stateless');
|
|
136
|
-
if (statelessCheckbox) statelessCheckbox.checked = false;
|
|
137
|
-
|
|
138
639
|
// Clear stat inputs with correct IDs
|
|
139
640
|
const statTypes = Object.keys(ObjectLayerEngineModal.statDescriptions);
|
|
140
641
|
for (const stat of statTypes) {
|
|
141
|
-
const statInput =
|
|
642
|
+
const statInput = getRenderedInputNode(`ol-input-item-stats-${stat}`);
|
|
142
643
|
if (statInput) statInput.value = '0';
|
|
143
644
|
}
|
|
144
645
|
|
|
646
|
+
const statRandomMinInput = getRenderedInputNode('ol-input-stats-random-min');
|
|
647
|
+
if (statRandomMinInput) statRandomMinInput.value = String(DEFAULT_STAT_RANDOM_MIN);
|
|
648
|
+
|
|
649
|
+
const statRandomMaxInput = getRenderedInputNode('ol-input-stats-random-max');
|
|
650
|
+
if (statRandomMaxInput) statRandomMaxInput.value = String(DEFAULT_STAT_RANDOM_MAX);
|
|
651
|
+
|
|
145
652
|
// Clear DropDown displays
|
|
146
653
|
const templateDropdownCurrent = s(`.dropdown-current-ol-dropdown-template`);
|
|
147
654
|
if (templateDropdownCurrent) templateDropdownCurrent.innerHTML = '';
|
|
148
655
|
|
|
149
656
|
const itemTypeDropdownCurrent = s(`.dropdown-current-ol-dropdown-item-type`);
|
|
150
657
|
if (itemTypeDropdownCurrent) itemTypeDropdownCurrent.innerHTML = 'skin';
|
|
151
|
-
|
|
152
|
-
|
|
658
|
+
|
|
659
|
+
const distortionDropdownCurrent = s(`.dropdown-current-ol-dropdown-distortion-type`);
|
|
660
|
+
if (distortionDropdownCurrent) {
|
|
661
|
+
distortionDropdownCurrent.innerHTML = getCanvasBehaviorDisplay(DEFAULT_DISTORTION_TYPE);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
const distortionFactorInput = s('#ol-input-distortion-factor-a') || s('.ol-input-distortion-factor-a');
|
|
665
|
+
if (distortionFactorInput) distortionFactorInput.value = String(DEFAULT_DISTORTION_FACTOR_A);
|
|
666
|
+
|
|
667
|
+
const distortionStatusNode = s(`.ol-distortion-status`);
|
|
668
|
+
if (distortionStatusNode) {
|
|
669
|
+
distortionStatusNode.style.color = '#888';
|
|
670
|
+
distortionStatusNode.innerHTML = DEFAULT_DISTORTION_STATUS;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
static loadFromDatabase = async (objectLayerId) => {
|
|
153
675
|
try {
|
|
154
676
|
// Load metadata first (lightweight)
|
|
155
677
|
const { status: metaStatus, data: metadata } = await ObjectLayerService.getMetadata({ id: objectLayerId });
|
|
@@ -184,8 +706,9 @@ const ObjectLayerEngineModal = {
|
|
|
184
706
|
});
|
|
185
707
|
return null;
|
|
186
708
|
}
|
|
187
|
-
}
|
|
188
|
-
|
|
709
|
+
};
|
|
710
|
+
|
|
711
|
+
static instance = async (options = { idModal: '', appStore: {} }) => {
|
|
189
712
|
// Clear all cached data at the start of each render to prevent contamination
|
|
190
713
|
ObjectLayerEngineModal.clearData();
|
|
191
714
|
|
|
@@ -202,8 +725,14 @@ const ObjectLayerEngineModal = {
|
|
|
202
725
|
'06': 'Right Idle',
|
|
203
726
|
16: 'Right Walk',
|
|
204
727
|
};
|
|
205
|
-
const itemTypes = ['skin', 'weapon', 'armor', 'artifact', 'floor'];
|
|
728
|
+
const itemTypes = ['skin', 'weapon', 'armor', 'artifact', 'floor', 'resource', 'obstacle', 'foreground', 'portal'];
|
|
206
729
|
const statTypes = ['effect', 'resistance', 'agility', 'range', 'intelligence', 'utility'];
|
|
730
|
+
const distortionDropdownId = 'ol-dropdown-distortion-type';
|
|
731
|
+
const distortionApplyBtnClass = 'ol-btn-apply-distortion';
|
|
732
|
+
const distortionStatusClass = 'ol-distortion-status';
|
|
733
|
+
const statsRandomizeBtnClass = 'ol-btn-randomize-stats';
|
|
734
|
+
const statsRandomMinInputId = 'ol-input-stats-random-min';
|
|
735
|
+
const statsRandomMaxInputId = 'ol-input-stats-random-max';
|
|
207
736
|
|
|
208
737
|
// Check if we have an 'id' query parameter to load existing object layer
|
|
209
738
|
const queryParams = getQueryParams();
|
|
@@ -213,6 +742,130 @@ const ObjectLayerEngineModal = {
|
|
|
213
742
|
let editingFrameId = null;
|
|
214
743
|
let editingDirectionCode = null;
|
|
215
744
|
|
|
745
|
+
const readDistortionFactorA = () => {
|
|
746
|
+
const factorInput = s('.ol-input-distortion-factor-a') || s('#ol-input-distortion-factor-a');
|
|
747
|
+
const parsedFactor = Number.parseFloat(factorInput?.value);
|
|
748
|
+
const normalizedFactor = clampNumber(
|
|
749
|
+
Number.isFinite(parsedFactor)
|
|
750
|
+
? parsedFactor
|
|
751
|
+
: ObjectLayerEngineModal.distortionFactorA || DEFAULT_DISTORTION_FACTOR_A,
|
|
752
|
+
0.01,
|
|
753
|
+
1,
|
|
754
|
+
);
|
|
755
|
+
|
|
756
|
+
ObjectLayerEngineModal.distortionFactorA = normalizedFactor;
|
|
757
|
+
if (factorInput) {
|
|
758
|
+
factorInput.value = String(Math.round(normalizedFactor * 100) / 100);
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
return normalizedFactor;
|
|
762
|
+
};
|
|
763
|
+
|
|
764
|
+
const readRandomStatBounds = () => {
|
|
765
|
+
const minInput = getRenderedInputNode(statsRandomMinInputId);
|
|
766
|
+
const maxInput = getRenderedInputNode(statsRandomMaxInputId);
|
|
767
|
+
let minValue = Number.parseInt(minInput?.value, 10);
|
|
768
|
+
let maxValue = Number.parseInt(maxInput?.value, 10);
|
|
769
|
+
|
|
770
|
+
minValue = clampNumber(Number.isFinite(minValue) ? minValue : DEFAULT_STAT_RANDOM_MIN, 0, 10);
|
|
771
|
+
maxValue = clampNumber(Number.isFinite(maxValue) ? maxValue : DEFAULT_STAT_RANDOM_MAX, 0, 10);
|
|
772
|
+
|
|
773
|
+
if (minValue > maxValue) {
|
|
774
|
+
const nextMin = maxValue;
|
|
775
|
+
maxValue = minValue;
|
|
776
|
+
minValue = nextMin;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
if (minInput) minInput.value = String(minValue);
|
|
780
|
+
if (maxInput) maxInput.value = String(maxValue);
|
|
781
|
+
|
|
782
|
+
return { minValue, maxValue };
|
|
783
|
+
};
|
|
784
|
+
|
|
785
|
+
const randomizeStatInputs = () => {
|
|
786
|
+
const { minValue, maxValue } = readRandomStatBounds();
|
|
787
|
+
|
|
788
|
+
for (const statType of statTypes) {
|
|
789
|
+
const statInput = getRenderedInputNode(`ol-input-item-stats-${statType}`);
|
|
790
|
+
if (!statInput) continue;
|
|
791
|
+
|
|
792
|
+
const randomValue = Math.floor(Math.random() * (maxValue - minValue + 1)) + minValue;
|
|
793
|
+
statInput.value = String(randomValue);
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
NotificationManager.Push({
|
|
797
|
+
html: `Stats randomized between ${minValue} and ${maxValue}.`,
|
|
798
|
+
status: 'success',
|
|
799
|
+
});
|
|
800
|
+
};
|
|
801
|
+
|
|
802
|
+
let uniformOpacitySyncInProgress = false;
|
|
803
|
+
|
|
804
|
+
const buildUniformOpacityMatrix = (matrix = [], targetAlpha = 255) => {
|
|
805
|
+
const clampedAlpha = clampNumber(Number(targetAlpha) || 0, 0, 255);
|
|
806
|
+
let changedCells = 0;
|
|
807
|
+
const nextMatrix = matrix.map((row) =>
|
|
808
|
+
row.map((cell) => {
|
|
809
|
+
const nextCell = cell.slice();
|
|
810
|
+
if (isVisibleCell(nextCell) && nextCell[3] !== clampedAlpha) {
|
|
811
|
+
nextCell[3] = clampedAlpha;
|
|
812
|
+
changedCells++;
|
|
813
|
+
}
|
|
814
|
+
return nextCell;
|
|
815
|
+
}),
|
|
816
|
+
);
|
|
817
|
+
|
|
818
|
+
return { matrix: nextMatrix, changedCells, alpha: clampedAlpha };
|
|
819
|
+
};
|
|
820
|
+
|
|
821
|
+
const applyUniformOpacityToEditor = ({ captureUndo = false } = {}) => {
|
|
822
|
+
const ole = s('object-layer-engine');
|
|
823
|
+
if (!ole || !ObjectLayerEngineModal.uniformOpacityEnabled || uniformOpacitySyncInProgress) return false;
|
|
824
|
+
|
|
825
|
+
let currentFrame = null;
|
|
826
|
+
try {
|
|
827
|
+
const exportedMatrix = ole.exportMatrixJSON();
|
|
828
|
+
currentFrame = typeof exportedMatrix === 'string' ? JSON.parse(exportedMatrix) : exportedMatrix;
|
|
829
|
+
} catch (error) {
|
|
830
|
+
return false;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
const currentMatrix = currentFrame?.matrix;
|
|
834
|
+
if (!Array.isArray(currentMatrix) || !currentMatrix.length || !currentMatrix[0]?.length) {
|
|
835
|
+
return false;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
const targetAlpha = clampNumber(ole.getBrushAlpha?.() ?? ole.getBrushColor?.()?.[3] ?? 255, 0, 255);
|
|
839
|
+
const nextFrame = buildUniformOpacityMatrix(currentMatrix, targetAlpha);
|
|
840
|
+
if (!nextFrame.changedCells) return false;
|
|
841
|
+
|
|
842
|
+
const nextSnapshot = {
|
|
843
|
+
width: currentFrame.width || currentMatrix[0].length,
|
|
844
|
+
height: currentFrame.height || currentMatrix.length,
|
|
845
|
+
matrix: nextFrame.matrix,
|
|
846
|
+
};
|
|
847
|
+
const beforeSnapshot = captureUndo && typeof ole._snapshot === 'function' ? ole._snapshot() : null;
|
|
848
|
+
|
|
849
|
+
if (
|
|
850
|
+
captureUndo &&
|
|
851
|
+
beforeSnapshot &&
|
|
852
|
+
typeof ole._pushUndo === 'function' &&
|
|
853
|
+
typeof ole._matricesEqual === 'function' &&
|
|
854
|
+
!ole._matricesEqual(beforeSnapshot, nextSnapshot)
|
|
855
|
+
) {
|
|
856
|
+
ole._pushUndo(beforeSnapshot);
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
uniformOpacitySyncInProgress = true;
|
|
860
|
+
try {
|
|
861
|
+
ole.loadMatrix(nextFrame.matrix);
|
|
862
|
+
} finally {
|
|
863
|
+
uniformOpacitySyncInProgress = false;
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
return true;
|
|
867
|
+
};
|
|
868
|
+
|
|
216
869
|
// Helper function to update UI when entering edit mode
|
|
217
870
|
const enterEditMode = (frameId, directionCode) => {
|
|
218
871
|
editingFrameId = frameId;
|
|
@@ -331,7 +984,6 @@ const ObjectLayerEngineModal = {
|
|
|
331
984
|
}
|
|
332
985
|
}
|
|
333
986
|
if (objectLayerRenderFramesId) {
|
|
334
|
-
ObjectLayerEngineModal.renderIsStateless = objectLayerRenderFramesId.is_stateless || false;
|
|
335
987
|
ObjectLayerEngineModal.renderFrameDuration = objectLayerRenderFramesId.frame_duration || 100;
|
|
336
988
|
}
|
|
337
989
|
}
|
|
@@ -367,6 +1019,154 @@ const ObjectLayerEngineModal = {
|
|
|
367
1019
|
const pixelSize = parseInt(320 / Math.max(cellsW, cellsH));
|
|
368
1020
|
const idSectionA = 'template-section-a';
|
|
369
1021
|
const idSectionB = 'template-section-b';
|
|
1022
|
+
const colorPaletteClass = 'ol-color-palette';
|
|
1023
|
+
let directionPreviewRuntime = null;
|
|
1024
|
+
|
|
1025
|
+
const cleanupDirectionPreviewRuntime = () => {
|
|
1026
|
+
if (!directionPreviewRuntime) return;
|
|
1027
|
+
if (directionPreviewRuntime.intervalId) {
|
|
1028
|
+
clearInterval(directionPreviewRuntime.intervalId);
|
|
1029
|
+
}
|
|
1030
|
+
for (const frameUrl of directionPreviewRuntime.frameUrls || []) {
|
|
1031
|
+
URL.revokeObjectURL(frameUrl);
|
|
1032
|
+
}
|
|
1033
|
+
directionPreviewRuntime = null;
|
|
1034
|
+
};
|
|
1035
|
+
|
|
1036
|
+
const cleanupDirectionPreviewModal = () => {
|
|
1037
|
+
cleanupDirectionPreviewRuntime();
|
|
1038
|
+
if (s(`.${DIRECTION_PREVIEW_MODAL_ID}`)) {
|
|
1039
|
+
Modal.removeModal(DIRECTION_PREVIEW_MODAL_ID);
|
|
1040
|
+
}
|
|
1041
|
+
};
|
|
1042
|
+
|
|
1043
|
+
if (Modal.Data[options.idModal]) {
|
|
1044
|
+
Modal.Data[options.idModal].onCloseListener[`${options.idModal}-direction-preview-cleanup`] = () => {
|
|
1045
|
+
cleanupDirectionPreviewModal();
|
|
1046
|
+
delete Modal.Data[options.idModal]?.onCloseListener?.[`${options.idModal}-direction-preview-cleanup`];
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
const getCurrentFrameDuration = () => {
|
|
1051
|
+
const durationInput = s('.ol-input-render-frame-duration') || s('#ol-input-render-frame-duration');
|
|
1052
|
+
const parsedDuration = Number.parseInt(durationInput?.value, 10);
|
|
1053
|
+
return Math.max(
|
|
1054
|
+
100,
|
|
1055
|
+
Number.isFinite(parsedDuration) ? parsedDuration : ObjectLayerEngineModal.renderFrameDuration || 100,
|
|
1056
|
+
);
|
|
1057
|
+
};
|
|
1058
|
+
|
|
1059
|
+
const openDirectionPreviewModal = async (directionCode) => {
|
|
1060
|
+
const frames = ObjectLayerEngineModal.ObjectLayerData[directionCode] || [];
|
|
1061
|
+
if (!frames.length) {
|
|
1062
|
+
NotificationManager.Push({
|
|
1063
|
+
html: `No frames available yet for ${directionCodeLabels[directionCode] || directionCode}.`,
|
|
1064
|
+
status: 'warning',
|
|
1065
|
+
});
|
|
1066
|
+
return;
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
const frameUrls = frames
|
|
1070
|
+
.map((frame) => {
|
|
1071
|
+
if (!frame?.image) return null;
|
|
1072
|
+
return URL.createObjectURL(frame.image);
|
|
1073
|
+
})
|
|
1074
|
+
.filter(Boolean);
|
|
1075
|
+
|
|
1076
|
+
if (!frameUrls.length) {
|
|
1077
|
+
NotificationManager.Push({
|
|
1078
|
+
html: `Could not build a local preview for ${directionCodeLabels[directionCode] || directionCode}.`,
|
|
1079
|
+
status: 'error',
|
|
1080
|
+
});
|
|
1081
|
+
return;
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
cleanupDirectionPreviewModal();
|
|
1085
|
+
|
|
1086
|
+
const { barConfig } = await Themes[Css.currentTheme]();
|
|
1087
|
+
const frameDuration = getCurrentFrameDuration();
|
|
1088
|
+
const previewWidth = Math.max(180, cellsW * pixelSize);
|
|
1089
|
+
const previewHeight = Math.max(180, cellsH * pixelSize);
|
|
1090
|
+
|
|
1091
|
+
await Modal.instance({
|
|
1092
|
+
id: DIRECTION_PREVIEW_MODAL_ID,
|
|
1093
|
+
barConfig,
|
|
1094
|
+
title: `${directionCodeLabels[directionCode] || directionCode} Preview`,
|
|
1095
|
+
html: () => html`
|
|
1096
|
+
<div class="in section-mp" style="text-align: center; min-width: ${previewWidth}px;">
|
|
1097
|
+
<div class="in" style="font-size: 12px; color: #999; margin-bottom: 8px;">
|
|
1098
|
+
${frames.length} frame${frames.length === 1 ? '' : 's'} at ${frameDuration}ms
|
|
1099
|
+
</div>
|
|
1100
|
+
<div
|
|
1101
|
+
class="in direction-preview-stage"
|
|
1102
|
+
style="display: inline-flex; align-items: center; justify-content: center; min-height: ${previewHeight}px; min-width: ${previewWidth}px; background-image: linear-gradient(45deg, rgba(255,255,255,0.08) 25%, transparent 25%), linear-gradient(-45deg, rgba(255,255,255,0.08) 25%, transparent 25%), linear-gradient(45deg, transparent 75%, rgba(255,255,255,0.08) 75%), linear-gradient(-45deg, transparent 75%, rgba(255,255,255,0.08) 75%); background-size: 24px 24px; background-position: 0 0, 0 12px, 12px -12px, -12px 0; border: 1px solid rgba(255,255,255,0.15);"
|
|
1103
|
+
>
|
|
1104
|
+
<img
|
|
1105
|
+
class="direction-preview-image"
|
|
1106
|
+
src="${frameUrls[0]}"
|
|
1107
|
+
style="image-rendering: pixelated; width: ${previewWidth}px; height: ${previewHeight}px; object-fit: contain;"
|
|
1108
|
+
/>
|
|
1109
|
+
</div>
|
|
1110
|
+
<div class="fl" style="justify-content: center; gap: 6px; flex-wrap: wrap; margin-top: 10px;">
|
|
1111
|
+
${frameUrls.map(
|
|
1112
|
+
(frameUrl, frameIndex) => html`
|
|
1113
|
+
<img
|
|
1114
|
+
src="${frameUrl}"
|
|
1115
|
+
style="width: 48px; height: 48px; object-fit: contain; image-rendering: pixelated; border: 1px solid rgba(255,255,255,0.15); background: rgba(0,0,0,0.25);"
|
|
1116
|
+
title="Frame ${frameIndex + 1}"
|
|
1117
|
+
/>
|
|
1118
|
+
`,
|
|
1119
|
+
)}
|
|
1120
|
+
</div>
|
|
1121
|
+
</div>
|
|
1122
|
+
`,
|
|
1123
|
+
style: {
|
|
1124
|
+
width: `${Math.max(300, previewWidth + 40)}px`,
|
|
1125
|
+
height: `${Math.max(260, previewHeight + 150)}px`,
|
|
1126
|
+
border: '1px solid rgba(255,255,255,0.15)',
|
|
1127
|
+
'z-index': 10,
|
|
1128
|
+
},
|
|
1129
|
+
slideMenu: 'modal-menu',
|
|
1130
|
+
});
|
|
1131
|
+
|
|
1132
|
+
const previewImage = s(`.${DIRECTION_PREVIEW_MODAL_ID} .direction-preview-image`);
|
|
1133
|
+
if (!previewImage) {
|
|
1134
|
+
cleanupDirectionPreviewRuntime();
|
|
1135
|
+
return;
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
let currentFrameIndex = 0;
|
|
1139
|
+
const advancePreviewFrame = () => {
|
|
1140
|
+
if (!previewImage) return;
|
|
1141
|
+
currentFrameIndex = (currentFrameIndex + 1) % frameUrls.length;
|
|
1142
|
+
previewImage.src = frameUrls[currentFrameIndex];
|
|
1143
|
+
};
|
|
1144
|
+
|
|
1145
|
+
directionPreviewRuntime = {
|
|
1146
|
+
frameUrls,
|
|
1147
|
+
intervalId: frameUrls.length > 1 ? setInterval(advancePreviewFrame, frameDuration) : null,
|
|
1148
|
+
};
|
|
1149
|
+
|
|
1150
|
+
if (Modal.Data[DIRECTION_PREVIEW_MODAL_ID]) {
|
|
1151
|
+
Modal.Data[DIRECTION_PREVIEW_MODAL_ID].onCloseListener[DIRECTION_PREVIEW_MODAL_ID] = () => {
|
|
1152
|
+
cleanupDirectionPreviewRuntime();
|
|
1153
|
+
delete Modal.Data[DIRECTION_PREVIEW_MODAL_ID]?.onCloseListener?.[DIRECTION_PREVIEW_MODAL_ID];
|
|
1154
|
+
};
|
|
1155
|
+
}
|
|
1156
|
+
};
|
|
1157
|
+
|
|
1158
|
+
const rgbaToHex = (rgba = [0, 0, 0]) => {
|
|
1159
|
+
const red = Math.max(0, Math.min(255, rgba[0] || 0))
|
|
1160
|
+
.toString(16)
|
|
1161
|
+
.padStart(2, '0');
|
|
1162
|
+
const green = Math.max(0, Math.min(255, rgba[1] || 0))
|
|
1163
|
+
.toString(16)
|
|
1164
|
+
.padStart(2, '0');
|
|
1165
|
+
const blue = Math.max(0, Math.min(255, rgba[2] || 0))
|
|
1166
|
+
.toString(16)
|
|
1167
|
+
.padStart(2, '0');
|
|
1168
|
+
return `#${red}${green}${blue}`.toUpperCase();
|
|
1169
|
+
};
|
|
370
1170
|
|
|
371
1171
|
let directionsCodeBarRender = '';
|
|
372
1172
|
|
|
@@ -389,11 +1189,11 @@ const ObjectLayerEngineModal = {
|
|
|
389
1189
|
src="${URL.createObjectURL(image)}"
|
|
390
1190
|
data-direction-code="${capturedDirectionCode}"
|
|
391
1191
|
/>
|
|
392
|
-
${await BtnIcon.
|
|
1192
|
+
${await BtnIcon.instance({
|
|
393
1193
|
label: html`<i class="fa-solid fa-edit"></i>`,
|
|
394
1194
|
class: `abs direction-code-bar-edit-btn direction-code-bar-edit-btn-${id}`,
|
|
395
1195
|
})}
|
|
396
|
-
${await BtnIcon.
|
|
1196
|
+
${await BtnIcon.instance({
|
|
397
1197
|
label: html`<i class="fa-solid fa-trash"></i>`,
|
|
398
1198
|
class: `abs direction-code-bar-trash-btn direction-code-bar-trash-btn-${id}`,
|
|
399
1199
|
})}
|
|
@@ -538,14 +1338,18 @@ const ObjectLayerEngineModal = {
|
|
|
538
1338
|
<div class="fl">
|
|
539
1339
|
<div class="in fll">
|
|
540
1340
|
<div class="in direction-code-bar-frames-title">${directionCodeLabels[directionCode]}</div>
|
|
541
|
-
<div class="
|
|
542
|
-
${await BtnIcon.
|
|
1341
|
+
<div class="fl direction-code-bar-btn-row" style="gap: 6px;">
|
|
1342
|
+
${await BtnIcon.instance({
|
|
543
1343
|
label: html`
|
|
544
1344
|
<i class="fa-solid fa-plus direction-code-bar-frames-btn-icon-add-${directionCode}"></i>
|
|
545
1345
|
<i class="fa-solid fa-edit direction-code-bar-frames-btn-icon-edit-${directionCode} hide"></i>
|
|
546
1346
|
`,
|
|
547
1347
|
class: `direction-code-bar-frames-btn-add direction-code-bar-frames-btn-${directionCode}`,
|
|
548
1348
|
})}
|
|
1349
|
+
${await BtnIcon.instance({
|
|
1350
|
+
label: html`<i class="fa-solid fa-play"></i>`,
|
|
1351
|
+
class: `direction-code-bar-preview-btn direction-code-bar-preview-btn-${directionCode}`,
|
|
1352
|
+
})}
|
|
549
1353
|
</div>
|
|
550
1354
|
</div>
|
|
551
1355
|
<div class="frames-${directionCode}"></div>
|
|
@@ -560,7 +1364,7 @@ const ObjectLayerEngineModal = {
|
|
|
560
1364
|
const statValue = loadedData?.metadata?.data?.stats?.[statType] || 0;
|
|
561
1365
|
statsInputsRender += html`
|
|
562
1366
|
<div class="inl" style="margin-bottom: 10px; position: relative;">
|
|
563
|
-
${await Input.
|
|
1367
|
+
${await Input.instance({
|
|
564
1368
|
id: `ol-input-item-stats-${statType}`,
|
|
565
1369
|
label: html`<div
|
|
566
1370
|
title="${statInfo.description} ${statInfo.detail}"
|
|
@@ -651,8 +1455,13 @@ const ObjectLayerEngineModal = {
|
|
|
651
1455
|
}
|
|
652
1456
|
|
|
653
1457
|
const buttonSelector = `.direction-code-bar-frames-btn-${currentDirectionCode}`;
|
|
1458
|
+
const previewButtonSelector = `.direction-code-bar-preview-btn-${currentDirectionCode}`;
|
|
654
1459
|
console.log(`Registering click handler for: ${buttonSelector}`);
|
|
655
1460
|
|
|
1461
|
+
EventsUI.onClick(previewButtonSelector, async () => {
|
|
1462
|
+
await openDirectionPreviewModal(currentDirectionCode);
|
|
1463
|
+
});
|
|
1464
|
+
|
|
656
1465
|
EventsUI.onClick(buttonSelector, async () => {
|
|
657
1466
|
console.log(`Add frame button clicked for direction: ${currentDirectionCode}`);
|
|
658
1467
|
const ole = s('object-layer-engine');
|
|
@@ -738,7 +1547,165 @@ const ObjectLayerEngineModal = {
|
|
|
738
1547
|
await loadFrames();
|
|
739
1548
|
s('object-layer-engine').clear();
|
|
740
1549
|
|
|
741
|
-
|
|
1550
|
+
const editor = s('object-layer-engine');
|
|
1551
|
+
const colorPalette = s(`.${colorPaletteClass}`);
|
|
1552
|
+
|
|
1553
|
+
const syncPaletteFromEditor = (event = null) => {
|
|
1554
|
+
if (!colorPalette || !editor) {
|
|
1555
|
+
return;
|
|
1556
|
+
}
|
|
1557
|
+
const nextHex = event?.detail?.hex || rgbaToHex(editor.getBrushColor?.());
|
|
1558
|
+
if (nextHex && colorPalette.value !== nextHex) {
|
|
1559
|
+
colorPalette.value = nextHex;
|
|
1560
|
+
}
|
|
1561
|
+
};
|
|
1562
|
+
|
|
1563
|
+
const syncEditorFromPalette = (event) => {
|
|
1564
|
+
if (!editor) {
|
|
1565
|
+
return;
|
|
1566
|
+
}
|
|
1567
|
+
const nextHex = event.detail?.value || event.detail?.hex;
|
|
1568
|
+
if (!nextHex) {
|
|
1569
|
+
return;
|
|
1570
|
+
}
|
|
1571
|
+
const [red, green, blue] = hexToRgbA(nextHex);
|
|
1572
|
+
const alpha = editor.getBrushAlpha?.() ?? editor.getBrushColor?.()?.[3] ?? 255;
|
|
1573
|
+
editor.setBrushColor([red, green, blue, alpha]);
|
|
1574
|
+
};
|
|
1575
|
+
|
|
1576
|
+
if (colorPalette) {
|
|
1577
|
+
colorPalette.addEventListener('colorchange', syncEditorFromPalette);
|
|
1578
|
+
}
|
|
1579
|
+
if (editor) {
|
|
1580
|
+
editor.addEventListener('brushcolorchange', syncPaletteFromEditor);
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
const syncUniformOpacityFromEditor = () => {
|
|
1584
|
+
applyUniformOpacityToEditor();
|
|
1585
|
+
};
|
|
1586
|
+
|
|
1587
|
+
if (editor) {
|
|
1588
|
+
editor.addEventListener('brushcolorchange', syncUniformOpacityFromEditor);
|
|
1589
|
+
editor.addEventListener('matrixload', syncUniformOpacityFromEditor);
|
|
1590
|
+
}
|
|
1591
|
+
syncPaletteFromEditor();
|
|
1592
|
+
|
|
1593
|
+
const setDistortionStatus = (message, tone = 'muted') => {
|
|
1594
|
+
const statusNode = s(`.${distortionStatusClass}`);
|
|
1595
|
+
if (!statusNode) return;
|
|
1596
|
+
statusNode.style.color = tone === 'success' ? '#8fd18c' : tone === 'error' ? '#ff8a8a' : '#888';
|
|
1597
|
+
statusNode.innerHTML = message;
|
|
1598
|
+
};
|
|
1599
|
+
|
|
1600
|
+
const applyDistortionToCurrentFrame = async () => {
|
|
1601
|
+
const ole = s('object-layer-engine');
|
|
1602
|
+
if (!ole || typeof ole.exportMatrixJSON !== 'function' || typeof ole.loadMatrix !== 'function') return;
|
|
1603
|
+
|
|
1604
|
+
let currentJson = null;
|
|
1605
|
+
let currentFrame = null;
|
|
1606
|
+
|
|
1607
|
+
try {
|
|
1608
|
+
currentJson = ole.exportMatrixJSON();
|
|
1609
|
+
currentFrame = typeof currentJson === 'string' ? JSON.parse(currentJson) : currentJson;
|
|
1610
|
+
} catch (error) {
|
|
1611
|
+
setDistortionStatus('Could not read the current frame from the editor.', 'error');
|
|
1612
|
+
return;
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
const currentMatrix = currentFrame?.matrix;
|
|
1616
|
+
if (!Array.isArray(currentMatrix) || !currentMatrix.length || !currentMatrix[0]?.length) {
|
|
1617
|
+
setDistortionStatus('The current frame has no editable matrix data.', 'error');
|
|
1618
|
+
return;
|
|
1619
|
+
}
|
|
1620
|
+
const selectedDistortionType = ObjectLayerEngineModal.selectedDistortionType || DEFAULT_DISTORTION_TYPE;
|
|
1621
|
+
const selectedBehavior =
|
|
1622
|
+
CANVAS_BEHAVIOR_BY_VALUE[selectedDistortionType] || CANVAS_BEHAVIOR_BY_VALUE[DEFAULT_DISTORTION_TYPE];
|
|
1623
|
+
const distortionFactorA = readDistortionFactorA();
|
|
1624
|
+
const isMosaicMode = isMosaicBehavior(selectedDistortionType);
|
|
1625
|
+
|
|
1626
|
+
if (!isMosaicMode && !currentMatrix.some((row) => row.some((cell) => isVisibleCell(cell)))) {
|
|
1627
|
+
setDistortionStatus('Paint something on the current frame before applying a distortion.', 'error');
|
|
1628
|
+
return;
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
const baseFrame = currentFrame;
|
|
1632
|
+
let transformationResult = null;
|
|
1633
|
+
|
|
1634
|
+
if (isMosaicMode) {
|
|
1635
|
+
const activeBrushColor = normalizeColorCell(ole.getBrushColor?.() || [255, 0, 0, 255]);
|
|
1636
|
+
transformationResult = buildMosaicMatrix(
|
|
1637
|
+
baseFrame.matrix,
|
|
1638
|
+
selectedDistortionType,
|
|
1639
|
+
distortionFactorA,
|
|
1640
|
+
activeBrushColor,
|
|
1641
|
+
);
|
|
1642
|
+
} else {
|
|
1643
|
+
const seedBase = Date.now() + hashString(currentJson);
|
|
1644
|
+
for (let attempt = 0; attempt < 4; attempt++) {
|
|
1645
|
+
const distortionSeed = seedBase + attempt * 977;
|
|
1646
|
+
transformationResult = buildDistortedMatrix(
|
|
1647
|
+
baseFrame.matrix,
|
|
1648
|
+
selectedDistortionType,
|
|
1649
|
+
distortionFactorA,
|
|
1650
|
+
distortionSeed,
|
|
1651
|
+
);
|
|
1652
|
+
if (transformationResult.changedCells > 0) break;
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
if (!transformationResult || transformationResult.changedCells === 0) {
|
|
1657
|
+
setDistortionStatus(
|
|
1658
|
+
isMosaicMode
|
|
1659
|
+
? 'No visible mosaic was painted on the current frame. Try another factorA value or a different active color.'
|
|
1660
|
+
: 'No visible distortion was produced for the current frame. Try applying again or edit the frame shape first.',
|
|
1661
|
+
'error',
|
|
1662
|
+
);
|
|
1663
|
+
return;
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
const nextSnapshot = {
|
|
1667
|
+
width: baseFrame.width,
|
|
1668
|
+
height: baseFrame.height,
|
|
1669
|
+
matrix: transformationResult.matrix,
|
|
1670
|
+
};
|
|
1671
|
+
const beforeSnapshot = typeof ole._snapshot === 'function' ? ole._snapshot() : null;
|
|
1672
|
+
|
|
1673
|
+
if (
|
|
1674
|
+
beforeSnapshot &&
|
|
1675
|
+
typeof ole._pushUndo === 'function' &&
|
|
1676
|
+
typeof ole._matricesEqual === 'function' &&
|
|
1677
|
+
!ole._matricesEqual(beforeSnapshot, nextSnapshot)
|
|
1678
|
+
) {
|
|
1679
|
+
ole._pushUndo(beforeSnapshot);
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
ole.loadMatrix(transformationResult.matrix);
|
|
1683
|
+
|
|
1684
|
+
if (isMosaicMode) {
|
|
1685
|
+
setDistortionStatus(
|
|
1686
|
+
`${selectedBehavior.label} painted on the current frame with the active color. tileSize ${transformationResult.tileSize}, ${transformationResult.paintedCells || 0} pattern cells, ${transformationResult.changedCells} cells changed, factorA=${distortionFactorA.toFixed(2)}.`,
|
|
1687
|
+
'success',
|
|
1688
|
+
);
|
|
1689
|
+
} else {
|
|
1690
|
+
setDistortionStatus(
|
|
1691
|
+
`${selectedBehavior.label} applied to the current frame. ${transformationResult.appliedDistortions || 0} local shifts, ${transformationResult.changedCells} cells changed, factorA=${distortionFactorA.toFixed(2)}.`,
|
|
1692
|
+
'success',
|
|
1693
|
+
);
|
|
1694
|
+
}
|
|
1695
|
+
};
|
|
1696
|
+
|
|
1697
|
+
EventsUI.onClick(`.${distortionApplyBtnClass}`, async () => {
|
|
1698
|
+
await applyDistortionToCurrentFrame();
|
|
1699
|
+
});
|
|
1700
|
+
setDistortionStatus(DEFAULT_DISTORTION_STATUS);
|
|
1701
|
+
|
|
1702
|
+
EventsUI.onClick(`.${statsRandomizeBtnClass}`, async () => {
|
|
1703
|
+
randomizeStatInputs();
|
|
1704
|
+
});
|
|
1705
|
+
|
|
1706
|
+
const persistObjectLayer = async ({ clone = false } = {}) => {
|
|
1707
|
+
const isUpdateMode = Boolean(ObjectLayerEngineModal.existingObjectLayerId) && !clone;
|
|
1708
|
+
|
|
742
1709
|
// Validate minimum frame_duration 100ms
|
|
743
1710
|
const frameDuration = parseInt(s(`.ol-input-render-frame-duration`).value);
|
|
744
1711
|
if (!frameDuration || frameDuration < 100) {
|
|
@@ -776,7 +1743,6 @@ const ObjectLayerEngineModal = {
|
|
|
776
1743
|
frames: {},
|
|
777
1744
|
colors: [],
|
|
778
1745
|
frame_duration: ObjectLayerEngineModal.renderFrameDuration,
|
|
779
|
-
is_stateless: ObjectLayerEngineModal.renderIsStateless,
|
|
780
1746
|
};
|
|
781
1747
|
|
|
782
1748
|
const objectLayer = {
|
|
@@ -822,7 +1788,6 @@ const ObjectLayerEngineModal = {
|
|
|
822
1788
|
}
|
|
823
1789
|
}
|
|
824
1790
|
objectLayerRenderFramesData.frame_duration = parseInt(s(`.ol-input-render-frame-duration`).value);
|
|
825
|
-
objectLayerRenderFramesData.is_stateless = ObjectLayerEngineModal.renderIsStateless;
|
|
826
1791
|
objectLayer.data.stats = {
|
|
827
1792
|
effect: parseInt(s(`.ol-input-item-stats-effect`).value),
|
|
828
1793
|
resistance: parseInt(s(`.ol-input-item-stats-resistance`).value),
|
|
@@ -838,15 +1803,15 @@ const ObjectLayerEngineModal = {
|
|
|
838
1803
|
description: s(`.ol-input-item-description`).value,
|
|
839
1804
|
};
|
|
840
1805
|
|
|
841
|
-
// Add _id
|
|
842
|
-
if (
|
|
1806
|
+
// Add _id only when updating the existing object layer.
|
|
1807
|
+
if (isUpdateMode) {
|
|
843
1808
|
objectLayer._id = ObjectLayerEngineModal.existingObjectLayerId;
|
|
844
1809
|
}
|
|
845
1810
|
|
|
846
1811
|
console.warn(
|
|
847
1812
|
'objectLayer',
|
|
848
1813
|
objectLayer,
|
|
849
|
-
|
|
1814
|
+
clone ? '(CLONE MODE)' : isUpdateMode ? '(UPDATE MODE)' : '(CREATE MODE)',
|
|
850
1815
|
);
|
|
851
1816
|
|
|
852
1817
|
if (appStore.Data.user.main.model.user.role === 'guest') {
|
|
@@ -863,14 +1828,14 @@ const ObjectLayerEngineModal = {
|
|
|
863
1828
|
const directionCodesToUpload = Object.keys(ObjectLayerEngineModal.ObjectLayerData);
|
|
864
1829
|
|
|
865
1830
|
// In UPDATE mode, also include original direction codes that may have been cleared
|
|
866
|
-
const allDirectionCodes =
|
|
1831
|
+
const allDirectionCodes = isUpdateMode
|
|
867
1832
|
? [...new Set([...directionCodesToUpload, ...ObjectLayerEngineModal.originalDirectionCodes])]
|
|
868
1833
|
: directionCodesToUpload;
|
|
869
1834
|
|
|
870
1835
|
console.warn(
|
|
871
1836
|
`Uploading frames for ${allDirectionCodes.length} directions:`,
|
|
872
1837
|
allDirectionCodes,
|
|
873
|
-
|
|
1838
|
+
clone ? '(CLONE MODE)' : isUpdateMode ? '(UPDATE MODE)' : '(CREATE MODE)',
|
|
874
1839
|
);
|
|
875
1840
|
|
|
876
1841
|
for (const directionCode of allDirectionCodes) {
|
|
@@ -895,7 +1860,7 @@ const ObjectLayerEngineModal = {
|
|
|
895
1860
|
|
|
896
1861
|
// Send all frames for this direction in one request (even if empty, to remove frames)
|
|
897
1862
|
try {
|
|
898
|
-
if (
|
|
1863
|
+
if (isUpdateMode) {
|
|
899
1864
|
// UPDATE: use PUT endpoint with object layer ID
|
|
900
1865
|
const { status, data } = await ObjectLayerService.put({
|
|
901
1866
|
id: `${ObjectLayerEngineModal.existingObjectLayerId}/frame-image/${objectLayer.data.item.type}/${objectLayer.data.item.id}/${directionCode}`,
|
|
@@ -936,7 +1901,7 @@ const ObjectLayerEngineModal = {
|
|
|
936
1901
|
};
|
|
937
1902
|
|
|
938
1903
|
let response;
|
|
939
|
-
if (
|
|
1904
|
+
if (isUpdateMode) {
|
|
940
1905
|
// UPDATE existing object layer
|
|
941
1906
|
console.warn(
|
|
942
1907
|
'PUT path:',
|
|
@@ -957,22 +1922,28 @@ const ObjectLayerEngineModal = {
|
|
|
957
1922
|
const { status, data, message } = response;
|
|
958
1923
|
|
|
959
1924
|
if (status === 'success') {
|
|
1925
|
+
const successAction = clone ? 'cloned' : isUpdateMode ? 'updated' : 'created';
|
|
960
1926
|
NotificationManager.Push({
|
|
961
|
-
html: `Object layer "${objectLayer.data.item.id}" ${
|
|
962
|
-
ObjectLayerEngineModal.existingObjectLayerId ? 'updated' : 'created'
|
|
963
|
-
} successfully!`,
|
|
1927
|
+
html: `Object layer "${objectLayer.data.item.id}" ${successAction} successfully!`,
|
|
964
1928
|
status: 'success',
|
|
965
1929
|
});
|
|
966
1930
|
ObjectLayerEngineModal.toManagement(data?._id || ObjectLayerEngineModal.existingObjectLayerId);
|
|
967
1931
|
} else {
|
|
1932
|
+
const errorAction = clone ? 'cloning' : isUpdateMode ? 'updating' : 'creating';
|
|
968
1933
|
NotificationManager.Push({
|
|
969
|
-
html: `Error ${
|
|
970
|
-
ObjectLayerEngineModal.existingObjectLayerId ? 'updating' : 'creating'
|
|
971
|
-
} object layer: ${message}`,
|
|
1934
|
+
html: `Error ${errorAction} object layer: ${message}`,
|
|
972
1935
|
status: 'error',
|
|
973
1936
|
});
|
|
974
1937
|
}
|
|
975
1938
|
}
|
|
1939
|
+
};
|
|
1940
|
+
|
|
1941
|
+
EventsUI.onClick(`.ol-btn-save`, async () => {
|
|
1942
|
+
await persistObjectLayer();
|
|
1943
|
+
});
|
|
1944
|
+
|
|
1945
|
+
EventsUI.onClick(`.ol-btn-clone`, async () => {
|
|
1946
|
+
await persistObjectLayer({ clone: true });
|
|
976
1947
|
});
|
|
977
1948
|
|
|
978
1949
|
// Add reset button event listener
|
|
@@ -1040,6 +2011,10 @@ const ObjectLayerEngineModal = {
|
|
|
1040
2011
|
color: white;
|
|
1041
2012
|
border: none !important;
|
|
1042
2013
|
}
|
|
2014
|
+
.direction-code-bar-preview-btn {
|
|
2015
|
+
color: white;
|
|
2016
|
+
border: none !important;
|
|
2017
|
+
}
|
|
1043
2018
|
.direction-code-bar-trash-btn:hover {
|
|
1044
2019
|
background: none !important;
|
|
1045
2020
|
color: red;
|
|
@@ -1052,6 +2027,10 @@ const ObjectLayerEngineModal = {
|
|
|
1052
2027
|
background: none !important;
|
|
1053
2028
|
color: #c7ff58;
|
|
1054
2029
|
}
|
|
2030
|
+
.direction-code-bar-preview-btn:hover {
|
|
2031
|
+
background: none !important;
|
|
2032
|
+
color: #5ee6ff;
|
|
2033
|
+
}
|
|
1055
2034
|
.ol-btn-save {
|
|
1056
2035
|
width: 120px;
|
|
1057
2036
|
padding: 0.5rem;
|
|
@@ -1064,6 +2043,16 @@ const ObjectLayerEngineModal = {
|
|
|
1064
2043
|
font-size: 20px;
|
|
1065
2044
|
min-height: 50px;
|
|
1066
2045
|
}
|
|
2046
|
+
.ol-btn-clone {
|
|
2047
|
+
width: 120px;
|
|
2048
|
+
padding: 0.5rem;
|
|
2049
|
+
font-size: 20px;
|
|
2050
|
+
min-height: 50px;
|
|
2051
|
+
}
|
|
2052
|
+
.ol-btn-randomize-stats {
|
|
2053
|
+
min-height: 50px;
|
|
2054
|
+
padding: 0.5rem 0.75rem;
|
|
2055
|
+
}
|
|
1067
2056
|
.ol-number-label {
|
|
1068
2057
|
width: 120px;
|
|
1069
2058
|
font-size: 16px;
|
|
@@ -1102,6 +2091,7 @@ const ObjectLayerEngineModal = {
|
|
|
1102
2091
|
'.direction-code-bar-edit-btn',
|
|
1103
2092
|
'.direction-code-bar-trash-btn',
|
|
1104
2093
|
'.direction-code-bar-frames-btn-add',
|
|
2094
|
+
'.direction-code-bar-preview-btn',
|
|
1105
2095
|
])}
|
|
1106
2096
|
<div class="in frame-editor-container-loading">
|
|
1107
2097
|
<div class="abs center frame-editor-container-loading-center"></div>
|
|
@@ -1111,20 +2101,117 @@ const ObjectLayerEngineModal = {
|
|
|
1111
2101
|
|
|
1112
2102
|
<object-layer-engine id="ole" width="${cellsW}" height="${cellsH}" pixel-size="${pixelSize}">
|
|
1113
2103
|
</object-layer-engine>
|
|
2104
|
+
<div class="in section-mp-border" style="margin-top: 10px;">
|
|
2105
|
+
<div class="in sub-title-modal"><i class="fa-solid fa-palette"></i> Brush palette</div>
|
|
2106
|
+
<color-palette class="${colorPaletteClass}" value="#FF0000"></color-palette>
|
|
2107
|
+
<div class="fl" style="align-items: center; gap: 8px; margin-top: 8px;">
|
|
2108
|
+
${await ToggleSwitch.instance({
|
|
2109
|
+
id: UNIFORM_OPACITY_TOGGLE_ID,
|
|
2110
|
+
type: 'checkbox',
|
|
2111
|
+
displayMode: 'checkbox',
|
|
2112
|
+
containerClass: 'in fll',
|
|
2113
|
+
checked: ObjectLayerEngineModal.uniformOpacityEnabled,
|
|
2114
|
+
on: {
|
|
2115
|
+
checked: () => {
|
|
2116
|
+
ObjectLayerEngineModal.uniformOpacityEnabled = true;
|
|
2117
|
+
applyUniformOpacityToEditor({ captureUndo: true });
|
|
2118
|
+
},
|
|
2119
|
+
unchecked: () => {
|
|
2120
|
+
ObjectLayerEngineModal.uniformOpacityEnabled = false;
|
|
2121
|
+
},
|
|
2122
|
+
},
|
|
2123
|
+
})}
|
|
2124
|
+
<div class="section-mp" style="font-size: 14px;">
|
|
2125
|
+
Keep all visible cells at the current opacity bar value
|
|
2126
|
+
</div>
|
|
2127
|
+
</div>
|
|
2128
|
+
</div>
|
|
2129
|
+
<div class="in section-mp-border" style="margin-top: 10px;">
|
|
2130
|
+
<div class="in sub-title-modal"><i class="fa-solid fa-wand-magic-sparkles"></i> Canvas macro</div>
|
|
2131
|
+
<div class="fl" style="align-items: flex-start; gap: 8px; flex-wrap: wrap;">
|
|
2132
|
+
<div class="in fll" style="min-width: 240px;">
|
|
2133
|
+
${await DropDown.instance({
|
|
2134
|
+
id: distortionDropdownId,
|
|
2135
|
+
value: ObjectLayerEngineModal.selectedDistortionType,
|
|
2136
|
+
label: html`Select behavior`,
|
|
2137
|
+
disableSearchBox: true,
|
|
2138
|
+
data: [
|
|
2139
|
+
{
|
|
2140
|
+
kind: 'group',
|
|
2141
|
+
value: 'group-distortion-behaviors',
|
|
2142
|
+
display: html`<div style="padding: 0 6px; color: #9d9d9d;">Distortion behaviors</div>`,
|
|
2143
|
+
},
|
|
2144
|
+
...DISTORTION_TYPES.map((distortion) => ({
|
|
2145
|
+
value: distortion.value,
|
|
2146
|
+
display: html`<i class="${CANVAS_BEHAVIOR_ICON}"></i> ${distortion.label}`,
|
|
2147
|
+
onClick: async () => {
|
|
2148
|
+
ObjectLayerEngineModal.selectedDistortionType = distortion.value;
|
|
2149
|
+
readDistortionFactorA();
|
|
2150
|
+
const statusNode = s(`.${distortionStatusClass}`);
|
|
2151
|
+
if (statusNode) {
|
|
2152
|
+
statusNode.style.color = '#888';
|
|
2153
|
+
statusNode.innerHTML = `${distortion.label} ready for direct canvas apply. factorA controls local distortion density.`;
|
|
2154
|
+
}
|
|
2155
|
+
},
|
|
2156
|
+
})),
|
|
2157
|
+
{
|
|
2158
|
+
kind: 'group',
|
|
2159
|
+
value: 'group-mosaic-behaviors',
|
|
2160
|
+
display: html`<div style="padding: 0 6px; color: #9d9d9d;">Mosaic drawing behaviors</div>`,
|
|
2161
|
+
},
|
|
2162
|
+
...MOSAIC_TYPES.map((mosaic) => ({
|
|
2163
|
+
value: mosaic.value,
|
|
2164
|
+
display: html`<i class="${CANVAS_BEHAVIOR_ICON}"></i> ${mosaic.label}`,
|
|
2165
|
+
onClick: async () => {
|
|
2166
|
+
ObjectLayerEngineModal.selectedDistortionType = mosaic.value;
|
|
2167
|
+
readDistortionFactorA();
|
|
2168
|
+
const statusNode = s(`.${distortionStatusClass}`);
|
|
2169
|
+
if (statusNode) {
|
|
2170
|
+
statusNode.style.color = '#888';
|
|
2171
|
+
statusNode.innerHTML = `${mosaic.label} ready for direct canvas apply. factorA controls tile size and density.`;
|
|
2172
|
+
}
|
|
2173
|
+
},
|
|
2174
|
+
})),
|
|
2175
|
+
],
|
|
2176
|
+
})}
|
|
2177
|
+
</div>
|
|
2178
|
+
<div class="in fll" style="width: 120px;">
|
|
2179
|
+
${await Input.instance({
|
|
2180
|
+
id: `ol-input-distortion-factor-a`,
|
|
2181
|
+
label: html`factorA`,
|
|
2182
|
+
containerClass: 'inl',
|
|
2183
|
+
type: 'number',
|
|
2184
|
+
min: 0.01,
|
|
2185
|
+
max: 1,
|
|
2186
|
+
step: 0.01,
|
|
2187
|
+
value: ObjectLayerEngineModal.distortionFactorA,
|
|
2188
|
+
})}
|
|
2189
|
+
</div>
|
|
2190
|
+
<div class="in fll">
|
|
2191
|
+
${await BtnIcon.instance({
|
|
2192
|
+
class: distortionApplyBtnClass,
|
|
2193
|
+
label: html`<i class="fa-solid fa-bolt"></i> Apply To Frame`,
|
|
2194
|
+
})}
|
|
2195
|
+
</div>
|
|
2196
|
+
</div>
|
|
2197
|
+
<div class="in ${distortionStatusClass}" style="margin-top: 6px; font-size: 12px; color: #888;">
|
|
2198
|
+
${DEFAULT_DISTORTION_STATUS}
|
|
2199
|
+
</div>
|
|
2200
|
+
</div>
|
|
1114
2201
|
<object-layer-png-loader id="loader" editor-selector="#ole"></object-layer-png-loader>
|
|
1115
2202
|
</div>
|
|
1116
2203
|
|
|
1117
2204
|
<div class="in section-mp section-mp-border">
|
|
1118
|
-
<div class="in sub-title-modal"><i class="fa-solid fa-database"></i>
|
|
2205
|
+
<div class="in sub-title-modal"><i class="fa-solid fa-database"></i> instance data</div>
|
|
1119
2206
|
${dynamicCol({ containerSelector: options.idModal, id: idSectionA })}
|
|
1120
2207
|
|
|
1121
2208
|
<div class="fl">
|
|
1122
2209
|
<div class="in fll ${idSectionA}-col-a">
|
|
1123
2210
|
<div class="in section-mp">
|
|
1124
|
-
${await DropDown.
|
|
2211
|
+
${await DropDown.instance({
|
|
1125
2212
|
id: 'ol-dropdown-template',
|
|
1126
2213
|
value: ObjectLayerEngineModal.templates[0].id,
|
|
1127
|
-
label: html`${Translate.
|
|
2214
|
+
label: html`${Translate.instance('select-template')}`,
|
|
1128
2215
|
data: ObjectLayerEngineModal.templates.map((template) => {
|
|
1129
2216
|
return {
|
|
1130
2217
|
value: template.id,
|
|
@@ -1139,7 +2226,7 @@ const ObjectLayerEngineModal = {
|
|
|
1139
2226
|
</div>
|
|
1140
2227
|
<div class="in fll ${idSectionA}-col-b">
|
|
1141
2228
|
<div class="in section-mp-border" style="width: 135px;">
|
|
1142
|
-
${await Input.
|
|
2229
|
+
${await Input.instance({
|
|
1143
2230
|
id: `ol-input-render-frame-duration`,
|
|
1144
2231
|
label: html`<div class="inl ol-number-label">
|
|
1145
2232
|
<i class="fa-solid fa-chart-simple"></i> Frame duration
|
|
@@ -1152,25 +2239,6 @@ const ObjectLayerEngineModal = {
|
|
|
1152
2239
|
value: ObjectLayerEngineModal.renderFrameDuration,
|
|
1153
2240
|
})}
|
|
1154
2241
|
</div>
|
|
1155
|
-
<div class="in section-mp">
|
|
1156
|
-
${await ToggleSwitch.Render({
|
|
1157
|
-
id: 'ol-toggle-render-is-stateless',
|
|
1158
|
-
wrapper: true,
|
|
1159
|
-
wrapperLabel: html`${Translate.Render('is-stateless')}`,
|
|
1160
|
-
disabledOnClick: true,
|
|
1161
|
-
checked: ObjectLayerEngineModal.renderIsStateless,
|
|
1162
|
-
on: {
|
|
1163
|
-
unchecked: () => {
|
|
1164
|
-
ObjectLayerEngineModal.renderIsStateless = false;
|
|
1165
|
-
console.warn('renderIsStateless', ObjectLayerEngineModal.renderIsStateless);
|
|
1166
|
-
},
|
|
1167
|
-
checked: () => {
|
|
1168
|
-
ObjectLayerEngineModal.renderIsStateless = true;
|
|
1169
|
-
console.warn('renderIsStateless', ObjectLayerEngineModal.renderIsStateless);
|
|
1170
|
-
},
|
|
1171
|
-
},
|
|
1172
|
-
})}
|
|
1173
|
-
</div>
|
|
1174
2242
|
</div>
|
|
1175
2243
|
</div>
|
|
1176
2244
|
${directionsCodeBarRender}
|
|
@@ -1181,25 +2249,25 @@ const ObjectLayerEngineModal = {
|
|
|
1181
2249
|
<div class="in fll ${idSectionB}-col-a">
|
|
1182
2250
|
<div class="in section-mp section-mp-border">
|
|
1183
2251
|
<div class="in sub-title-modal"><i class="fa-solid fa-database"></i> Item data</div>
|
|
1184
|
-
${await Input.
|
|
2252
|
+
${await Input.instance({
|
|
1185
2253
|
id: `ol-input-item-id`,
|
|
1186
|
-
label: html`<i class="fa-solid fa-pen-to-square"></i> ${Translate.
|
|
2254
|
+
label: html`<i class="fa-solid fa-pen-to-square"></i> ${Translate.instance('item-id')}`,
|
|
1187
2255
|
containerClass: '',
|
|
1188
2256
|
placeholder: true,
|
|
1189
2257
|
value: loadedData?.metadata?.data?.item?.id || '',
|
|
1190
2258
|
})}
|
|
1191
|
-
${await Input.
|
|
2259
|
+
${await Input.instance({
|
|
1192
2260
|
id: `ol-input-item-description`,
|
|
1193
|
-
label: html`<i class="fa-solid fa-pen-to-square"></i> ${Translate.
|
|
2261
|
+
label: html`<i class="fa-solid fa-pen-to-square"></i> ${Translate.instance('item-description')}`,
|
|
1194
2262
|
containerClass: '',
|
|
1195
2263
|
placeholder: true,
|
|
1196
2264
|
value: loadedData?.metadata?.data?.item?.description || '',
|
|
1197
2265
|
})}
|
|
1198
2266
|
<div class="in section-mp">
|
|
1199
|
-
${await DropDown.
|
|
2267
|
+
${await DropDown.instance({
|
|
1200
2268
|
id: 'ol-dropdown-item-type',
|
|
1201
2269
|
value: ObjectLayerEngineModal.selectItemType,
|
|
1202
|
-
label: html`${Translate.
|
|
2270
|
+
label: html`${Translate.instance('select-item-type')}`,
|
|
1203
2271
|
data: itemTypes.map((itemType) => {
|
|
1204
2272
|
return {
|
|
1205
2273
|
value: itemType,
|
|
@@ -1213,10 +2281,10 @@ const ObjectLayerEngineModal = {
|
|
|
1213
2281
|
})}
|
|
1214
2282
|
</div>
|
|
1215
2283
|
<div class="in section-mp">
|
|
1216
|
-
${await ToggleSwitch.
|
|
2284
|
+
${await ToggleSwitch.instance({
|
|
1217
2285
|
id: 'ol-toggle-item-activable',
|
|
1218
2286
|
wrapper: true,
|
|
1219
|
-
wrapperLabel: html`${Translate.
|
|
2287
|
+
wrapperLabel: html`${Translate.instance('item-activable')}`,
|
|
1220
2288
|
disabledOnClick: true,
|
|
1221
2289
|
checked: ObjectLayerEngineModal.itemActivable,
|
|
1222
2290
|
on: {
|
|
@@ -1236,25 +2304,65 @@ const ObjectLayerEngineModal = {
|
|
|
1236
2304
|
<div class="in fll ${idSectionB}-col-b">
|
|
1237
2305
|
<div class="in section-mp section-mp-border">
|
|
1238
2306
|
<div class="in sub-title-modal"><i class="fa-solid fa-database"></i> Stats data</div>
|
|
2307
|
+
<div class="fl" style="align-items: flex-end; gap: 8px; flex-wrap: wrap; margin-bottom: 10px;">
|
|
2308
|
+
<div class="in fll" style="width: 110px;">
|
|
2309
|
+
${await Input.instance({
|
|
2310
|
+
id: statsRandomMinInputId,
|
|
2311
|
+
label: html`Random min`,
|
|
2312
|
+
containerClass: 'inl',
|
|
2313
|
+
type: 'number',
|
|
2314
|
+
min: 0,
|
|
2315
|
+
max: 10,
|
|
2316
|
+
placeholder: true,
|
|
2317
|
+
value: DEFAULT_STAT_RANDOM_MIN,
|
|
2318
|
+
})}
|
|
2319
|
+
</div>
|
|
2320
|
+
<div class="in fll" style="width: 110px;">
|
|
2321
|
+
${await Input.instance({
|
|
2322
|
+
id: statsRandomMaxInputId,
|
|
2323
|
+
label: html`Random max`,
|
|
2324
|
+
containerClass: 'inl',
|
|
2325
|
+
type: 'number',
|
|
2326
|
+
min: 0,
|
|
2327
|
+
max: 10,
|
|
2328
|
+
placeholder: true,
|
|
2329
|
+
value: DEFAULT_STAT_RANDOM_MAX,
|
|
2330
|
+
})}
|
|
2331
|
+
</div>
|
|
2332
|
+
<div class="in fll">
|
|
2333
|
+
${await BtnIcon.instance({
|
|
2334
|
+
label: html`<i class="fa-solid fa-dice"></i> Randomize`,
|
|
2335
|
+
class: statsRandomizeBtnClass,
|
|
2336
|
+
})}
|
|
2337
|
+
</div>
|
|
2338
|
+
</div>
|
|
1239
2339
|
${statsInputsRender}
|
|
1240
2340
|
</div>
|
|
1241
2341
|
</div>
|
|
1242
2342
|
</div>
|
|
1243
2343
|
|
|
1244
2344
|
<div class="fl section-mp">
|
|
1245
|
-
${await BtnIcon.
|
|
1246
|
-
label: html`<i class="submit-btn-icon fa-solid fa-folder-open"></i>
|
|
2345
|
+
${await BtnIcon.instance({
|
|
2346
|
+
label: html`<i class="submit-btn-icon fa-solid fa-folder-open"></i>
|
|
2347
|
+
${ObjectLayerEngineModal.existingObjectLayerId ? 'Update' : Translate.instance('save')}`,
|
|
1247
2348
|
class: `in flr ol-btn-save`,
|
|
1248
2349
|
})}
|
|
1249
|
-
${
|
|
1250
|
-
|
|
2350
|
+
${ObjectLayerEngineModal.existingObjectLayerId
|
|
2351
|
+
? await BtnIcon.instance({
|
|
2352
|
+
label: html`<i class="submit-btn-icon fa-solid fa-clone"></i> Clone`,
|
|
2353
|
+
class: `in flr ol-btn-clone`,
|
|
2354
|
+
})
|
|
2355
|
+
: ''}
|
|
2356
|
+
${await BtnIcon.instance({
|
|
2357
|
+
label: html`<i class="submit-btn-icon fa-solid fa-broom"></i> ${Translate.instance('reset')}`,
|
|
1251
2358
|
class: `in flr ol-btn-reset`,
|
|
1252
2359
|
})}
|
|
1253
2360
|
</div>
|
|
1254
2361
|
<div class="in section-mp"></div>
|
|
1255
2362
|
`;
|
|
1256
|
-
}
|
|
1257
|
-
|
|
2363
|
+
};
|
|
2364
|
+
|
|
2365
|
+
static getDirectionsFromDirectionCode(directionCode = '08') {
|
|
1258
2366
|
let objectLayerFrameDirections = [];
|
|
1259
2367
|
|
|
1260
2368
|
switch (directionCode) {
|
|
@@ -1285,8 +2393,9 @@ const ObjectLayerEngineModal = {
|
|
|
1285
2393
|
}
|
|
1286
2394
|
|
|
1287
2395
|
return objectLayerFrameDirections;
|
|
1288
|
-
}
|
|
1289
|
-
|
|
2396
|
+
}
|
|
2397
|
+
|
|
2398
|
+
static toManagement = async (id = null) => {
|
|
1290
2399
|
await ObjectLayerEngineModal.clearData();
|
|
1291
2400
|
const subModalId = 'management';
|
|
1292
2401
|
const modalId = `modal-object-layer-engine-${subModalId}`;
|
|
@@ -1313,8 +2422,9 @@ const ObjectLayerEngineModal = {
|
|
|
1313
2422
|
await DefaultManagement.waitGridReady(modalId);
|
|
1314
2423
|
await DefaultManagement.loadTable(modalId, { force: true, reload: true });
|
|
1315
2424
|
});
|
|
1316
|
-
}
|
|
1317
|
-
|
|
2425
|
+
};
|
|
2426
|
+
|
|
2427
|
+
static async Reload() {
|
|
1318
2428
|
// Clear data before reload to prevent contamination
|
|
1319
2429
|
ObjectLayerEngineModal.clearData();
|
|
1320
2430
|
const idModal = 'modal-object-layer-engine';
|
|
@@ -1323,7 +2433,7 @@ const ObjectLayerEngineModal = {
|
|
|
1323
2433
|
idModal,
|
|
1324
2434
|
html: await Modal.Data[idModal].options.html(),
|
|
1325
2435
|
});
|
|
1326
|
-
}
|
|
1327
|
-
}
|
|
2436
|
+
}
|
|
2437
|
+
}
|
|
1328
2438
|
|
|
1329
2439
|
export { ObjectLayerEngineModal };
|