feat(node): re-write using node

This commit is contained in:
Haoyu Xu
2023-01-16 14:06:14 -05:00
parent 4b834fe0ff
commit 6d54eb068c
95 changed files with 1341 additions and 2486 deletions

View File

@@ -1,10 +1,11 @@
VITE_OPERATOR=chen
VITE_TITLE="Arknights: Ch'en/Chen the Holungday - 明日方舟:假日威龙陈"
VITE_FILENAME=dyn_illust_char_1013_chen2
VITE_LOGO=logo_rhodes_override
VITE_OPACITY=100
VITE_LOGO_FILENAME=logo_rhodes_override
VITE_FALLBACK_FILENAME=char_1013_chen2_2
VITE_VIEWPORT_LEFT=0
VITE_VIEWPORT_RIGHT=0
VITE_VIEWPORT_TOP=1
VITE_VIEWPORT_BOTTOM=1
VITE_INVERT_FILTER=false
VITE_IMAGE_WIDTH=2048
VITE_IMAGE_HEIGHT=2048

26
.gitignore vendored
View File

@@ -12,3 +12,29 @@ test.db
download
*.user.*
.wip
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
node_modules

15
Pipfile
View File

@@ -1,15 +0,0 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
pillow = "*"
pyyaml = "*"
pyyaml-include = "*"
[dev-packages]
autopep8 = "*"
[requires]
python_version = "3.10"

169
Pipfile.lock generated
View File

@@ -1,169 +0,0 @@
{
"_meta": {
"hash": {
"sha256": "b630762e087910062c726a8b7b09ae028f57619798e709018d2c23e500760325"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.10"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"pillow": {
"hashes": [
"sha256:0845adc64fe9886db00f5ab68c4a8cd933ab749a87747555cec1c95acea64b0b",
"sha256:0884ba7b515163a1a05440a138adeb722b8a6ae2c2b33aea93ea3118dd3a899e",
"sha256:09b89ddc95c248ee788328528e6a2996e09eaccddeeb82a5356e92645733be35",
"sha256:0dd4c681b82214b36273c18ca7ee87065a50e013112eea7d78c7a1b89a739153",
"sha256:0e51f608da093e5d9038c592b5b575cadc12fd748af1479b5e858045fff955a9",
"sha256:0f3269304c1a7ce82f1759c12ce731ef9b6e95b6df829dccd9fe42912cc48569",
"sha256:16a8df99701f9095bea8a6c4b3197da105df6f74e6176c5b410bc2df2fd29a57",
"sha256:19005a8e58b7c1796bc0167862b1f54a64d3b44ee5d48152b06bb861458bc0f8",
"sha256:28676836c7796805914b76b1837a40f76827ee0d5398f72f7dcc634bae7c6264",
"sha256:2968c58feca624bb6c8502f9564dd187d0e1389964898f5e9e1fbc8533169157",
"sha256:3fa1284762aacca6dc97474ee9c16f83990b8eeb6697f2ba17140d54b453e133",
"sha256:451f10ef963918e65b8869e17d67db5e2f4ab40e716ee6ce7129b0cde2876eab",
"sha256:46c259e87199041583658457372a183636ae8cd56dbf3f0755e0f376a7f9d0e6",
"sha256:46f39cab8bbf4a384ba7cb0bc8bae7b7062b6a11cfac1ca4bc144dea90d4a9f5",
"sha256:519e14e2c49fcf7616d6d2cfc5c70adae95682ae20f0395e9280db85e8d6c4df",
"sha256:53dcb50fbdc3fb2c55431a9b30caeb2f7027fcd2aeb501459464f0214200a503",
"sha256:54614444887e0d3043557d9dbc697dbb16cfb5a35d672b7a0fcc1ed0cf1c600b",
"sha256:575d8912dca808edd9acd6f7795199332696d3469665ef26163cd090fa1f8bfa",
"sha256:5dd5a9c3091a0f414a963d427f920368e2b6a4c2f7527fdd82cde8ef0bc7a327",
"sha256:5f532a2ad4d174eb73494e7397988e22bf427f91acc8e6ebf5bb10597b49c493",
"sha256:60e7da3a3ad1812c128750fc1bc14a7ceeb8d29f77e0a2356a8fb2aa8925287d",
"sha256:653d7fb2df65efefbcbf81ef5fe5e5be931f1ee4332c2893ca638c9b11a409c4",
"sha256:6663977496d616b618b6cfa43ec86e479ee62b942e1da76a2c3daa1c75933ef4",
"sha256:6abfb51a82e919e3933eb137e17c4ae9c0475a25508ea88993bb59faf82f3b35",
"sha256:6c6b1389ed66cdd174d040105123a5a1bc91d0aa7059c7261d20e583b6d8cbd2",
"sha256:6d9dfb9959a3b0039ee06c1a1a90dc23bac3b430842dcb97908ddde05870601c",
"sha256:765cb54c0b8724a7c12c55146ae4647e0274a839fb6de7bcba841e04298e1011",
"sha256:7a21222644ab69ddd9967cfe6f2bb420b460dae4289c9d40ff9a4896e7c35c9a",
"sha256:7ac7594397698f77bce84382929747130765f66406dc2cd8b4ab4da68ade4c6e",
"sha256:7cfc287da09f9d2a7ec146ee4d72d6ea1342e770d975e49a8621bf54eaa8f30f",
"sha256:847b114580c5cc9ebaf216dd8c8dbc6b00a3b7ab0131e173d7120e6deade1f57",
"sha256:8f127e7b028900421cad64f51f75c051b628db17fb00e099eb148761eed598c9",
"sha256:94cdff45173b1919350601f82d61365e792895e3c3a3443cf99819e6fbf717a5",
"sha256:9a3049a10261d7f2b6514d35bbb7a4dfc3ece4c4de14ef5876c4b7a23a0e566d",
"sha256:a1c2d7780448eb93fbcc3789bf3916aa5720d942e37945f4056680317f1cd23e",
"sha256:a2e0f87144fcbbe54297cae708c5e7f9da21a4646523456b00cc956bd4c65815",
"sha256:a4dfdae195335abb4e89cc9762b2edc524f3c6e80d647a9a81bf81e17e3fb6f0",
"sha256:a96e6e23f2b79433390273eaf8cc94fec9c6370842e577ab10dabdcc7ea0a66b",
"sha256:aabdab8ec1e7ca7f1434d042bf8b1e92056245fb179790dc97ed040361f16bfd",
"sha256:b222090c455d6d1a64e6b7bb5f4035c4dff479e22455c9eaa1bdd4c75b52c80c",
"sha256:b52ff4f4e002f828ea6483faf4c4e8deea8d743cf801b74910243c58acc6eda3",
"sha256:b9b752ab91e78234941e44abdecc07f1f0d8f51fb62941d32995b8161f68cfe5",
"sha256:ba6612b6548220ff5e9df85261bddc811a057b0b465a1226b39bfb8550616aee",
"sha256:bd752c5ff1b4a870b7661234694f24b1d2b9076b8bf337321a814c612665f343",
"sha256:c3c4ed2ff6760e98d262e0cc9c9a7f7b8a9f61aa4d47c58835cdaf7b0b8811bb",
"sha256:c5c1362c14aee73f50143d74389b2c158707b4abce2cb055b7ad37ce60738d47",
"sha256:cb362e3b0976dc994857391b776ddaa8c13c28a16f80ac6522c23d5257156bed",
"sha256:d197df5489004db87d90b918033edbeee0bd6df3848a204bca3ff0a903bef837",
"sha256:d3b56206244dc8711f7e8b7d6cad4663917cd5b2d950799425076681e8766286",
"sha256:d5b2f8a31bd43e0f18172d8ac82347c8f37ef3e0b414431157718aa234991b28",
"sha256:d7081c084ceb58278dd3cf81f836bc818978c0ccc770cbbb202125ddabec6628",
"sha256:db74f5562c09953b2c5f8ec4b7dfd3f5421f31811e97d1dbc0a7c93d6e3a24df",
"sha256:df41112ccce5d47770a0c13651479fbcd8793f34232a2dd9faeccb75eb5d0d0d",
"sha256:e1339790c083c5a4de48f688b4841f18df839eb3c9584a770cbd818b33e26d5d",
"sha256:e621b0246192d3b9cb1dc62c78cfa4c6f6d2ddc0ec207d43c0dedecb914f152a",
"sha256:e8c5cf126889a4de385c02a2c3d3aba4b00f70234bfddae82a5eaa3ee6d5e3e6",
"sha256:e9d7747847c53a16a729b6ee5e737cf170f7a16611c143d95aa60a109a59c336",
"sha256:eaef5d2de3c7e9b21f1e762f289d17b726c2239a42b11e25446abf82b26ac132",
"sha256:ed3e4b4e1e6de75fdc16d3259098de7c6571b1a6cc863b1a49e7d3d53e036070",
"sha256:ef21af928e807f10bf4141cad4746eee692a0dd3ff56cfb25fce076ec3cc8abe",
"sha256:f09598b416ba39a8f489c124447b007fe865f786a89dbfa48bb5cf395693132a",
"sha256:f6e78171be3fb7941f9910ea15b4b14ec27725865a73c15277bc39f5ca4f8391",
"sha256:f715c32e774a60a337b2bb8ad9839b4abf75b267a0f18806f6f4f5f1688c4b5a"
],
"index": "pypi",
"version": "==9.4.0"
},
"pyyaml": {
"hashes": [
"sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf",
"sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293",
"sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b",
"sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57",
"sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b",
"sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4",
"sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07",
"sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba",
"sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9",
"sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287",
"sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513",
"sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0",
"sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782",
"sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0",
"sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92",
"sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f",
"sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2",
"sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc",
"sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1",
"sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c",
"sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86",
"sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4",
"sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c",
"sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34",
"sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b",
"sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d",
"sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c",
"sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb",
"sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7",
"sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737",
"sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3",
"sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d",
"sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358",
"sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53",
"sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78",
"sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803",
"sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a",
"sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f",
"sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174",
"sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"
],
"index": "pypi",
"version": "==6.0"
},
"pyyaml-include": {
"hashes": [
"sha256:5142a0b0f93d9b1e5872d5a814f3681ffbc70638128ced1acfd9fb57da7825ca",
"sha256:f7fbeb8e71b50be0e6e07472f7c79dbfb1a15bade9c93a078369ff49e57e6771"
],
"index": "pypi",
"version": "==1.3"
}
},
"develop": {
"autopep8": {
"hashes": [
"sha256:be5bc98c33515b67475420b7b1feafc8d32c1a69862498eda4983b45bffd2687",
"sha256:d27a8929d8dcd21c0f4b3859d2d07c6c25273727b98afc984c039df0f0d86566"
],
"index": "pypi",
"version": "==2.0.1"
},
"pycodestyle": {
"hashes": [
"sha256:347187bdb476329d98f695c213d7295a846d1152ff4fe9bacb8a9590b8ee7053",
"sha256:8a4eaf0d0495c7395bdab3589ac2db602797d76207242c17d470186815706610"
],
"markers": "python_version >= '3.6'",
"version": "==2.10.0"
},
"tomli": {
"hashes": [
"sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc",
"sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"
],
"markers": "python_version < '3.11'",
"version": "==2.0.1"
}
}
}

246
README.md
View File

@@ -2,6 +2,113 @@
A project that builds showcase webpage for Arknights Live2D-equipped operators. Showcase webpage can be used as a wallpaper for Wallpaper Engine on Windows or [Plash](https://github.com/sindresorhus/Plash) on macOS (not tested).
## Softwares
- For Windows users: Use [Wallpaper Engine](https://www.wallpaperengine.io/en) or other softwares that support using webpage as desktop wallpaper.
- For macOS users: Use [Plash](https://github.com/sindresorhus/Plash), however, I don't have macOS machine, so your mileage may vary.
- For Linux users: You power user should be able to find your solutions!
## Usage
### Command Line Tool
``` bash
$ O={operator_name} node preprocessing.js
To generate operator assets for showcase page
```
``` bash
$ O={operator_name} node preprocessing.js -i
To initialize folder and config file for an operator
```
``` bash
$ O={operator_name} pnpm run dev
Live showcase page server for development
```
``` bash
$ O={operator_name} pnpm run build
Build showcase webpage for an operator
```
``` bash
$ node build_all.js
Build showcase webpages for all operators
```
### Webpage & JavaScript
Add query string `settings` to bring up the settings panel to adjust your settings. Then use appropriate JavaScript code to load your settings
``` javascript
settings.setFPS(integer) // set FPS
settings.displayLogo(boolean) // display logo or not
settings.resizeLogo(float) // the ratio of the logo
settings.opacityLogo(float) // the opacity of the logo
settings.setLogo(url) // change the logo, url: image url, removeInvert: boolean
settings.setBackground(url) // change the background, url: image url
settings.positionPadding("left", integer) // left padding
settings.positionPadding("right", integer) // right padding
settings.positionPadding("top", integer) // top padding
settings.positionPadding("bottom", integer) // bottom padding
settings.open() // open settings panel
settings.close() // close settings panel
settings.reset() // reset settings
```
## Config
### General Config
``` yaml
folder:
operator: ./operator/ # folder for operator assets
release: ./release/ # folder for released showcase page
operators:
chen: !include config/chen.yaml # include the config for the operator under folder `config/chen.yaml`
dusk: !include config/dusk.yaml
dusk_everything_is_a_miracle: !include config/dusk_everything_is_a_miracle.yaml
ling: !include config/ling.yaml
nearl: !include config/nearl.yaml
nian: !include config/nian.yaml
nian_unfettered_freedom: !include config/nian_unfettered_freedom.yaml
phatom_focus: !include config/phatom_focus.yaml
rosmontis: !include config/rosmontis.yaml
skadi: !include config/skadi.yaml
skadi_sublimation: !include config/skadi_sublimation.yaml
w: !include config/w.yaml
w_fugue: !include config/w_fugue.yaml
specter: !include config/specter.yaml
gavial: !include config/gavial.yaml
surtr_colorful_wonderland: !include config/surtr_colorful_wonderland.yaml
lee_trust_your_eyes: !include config/lee_trust_your_eyes.yaml
texas_the_omertosa: !include config/texas_the_omertosa.yaml
nearl_relight: !include config/nearl_relight.yaml
rosmontis_become_anew: !include config/rosmontis_become_anew.yaml
passager_dream_in_a_moment: !include config/passager_dream_in_a_moment.yaml
mizuki_summer_feast: !include config/mizuki_summer_feast.yaml
```
### Operator Config
```yaml
link: chen # the link to access showcase page for this operator
type: operator # operator live2d or skin live2d
date: 2021/08 # release date
title: 'Arknights: Ch''en/Chen the Holungday - 明日方舟:假日威龙陈' # page title
filename: dyn_illust_char_1013_chen2 # live2d assets name
logo: logo_rhodes_override # operator logo
fallback_name: char_1013_chen2_2 # fallback image name
viewport_left: 0 # live2d view port settings
viewport_right: 0
viewport_top: 1
viewport_bottom: 1
invert_filter: false # operator logo invert filter
```
## LICENSE
The `LICENSE` file applies to all files unless listed specifically.
`LICENSE_SPINE` file applies to following files including adapted code for this repo:
- `src/libs/spine-player.css`
- `src/libs/spine-player.js`
`Copyright © 2017 - 2023 Arknights/Hypergryph Co., Ltd` applies to following files:
- all files under `operator` folder and its sub-folders
## Supported Operators
| Operator | Live Preview | Steam Workshop |
@@ -28,142 +135,3 @@ A project that builds showcase webpage for Arknights Live2D-equipped operators.
| Become Anew / Rosmontis | [Link](https://arknights.halyul.dev/rosmontis_become_anew/) | [Link](https://steamcommunity.com/sharedfiles/filedetails/?id=2883012349) |
| Dream in a Moment / Passager | [Link](https://arknights.halyul.dev/passager_dream_in_a_moment/) | [Link](https://steamcommunity.com/sharedfiles/filedetails/?id=2883021565) |
| Summer Feast / Mizuki | [Link](https://arknights.halyul.dev/mizuki_summer_feast/) | [Link](https://steamcommunity.com/sharedfiles/filedetails/?id=2895953271) |
- For Windows users: Use [Wallpaper Engine](https://www.wallpaperengine.io/en) or other softwares that support using webpage as desktop wallpaper.
- For macOS users: Use [Plash](https://github.com/sindresorhus/Plash), however, I don't have macOS machine, so your mileage may vary.
- For Linux users: You power user should be able to find your solutions!
## Usage
### Command Line Tool
``` bash
$ python3 aklive2d.py -h
usage: aklive2d [-h] {server,s,build,b,init,i} ...
Arknights Live 2D Wallpaper Builder
optional arguments:
-h, --help show this help message and exit
Available commands:
{server,s,build,b,init,i}
<Required> Select the command to run
server (s) Development Server
build (b) Build releases
init (i) Initialize a new operator
```
``` bash
$ python3 aklive2d.py s -h
usage: aklive2d server [-h] [-p PORT] -o OPERATOR_NAME [-r]
optional arguments:
-h, --help show this help message and exit
-p PORT, --port PORT Development server port (default: 8080)
-o OPERATOR_NAME, --operator OPERATOR_NAME
<Required> Operator to develop (default: None)
-r, --rebuild Rebuild assets (default: False)
```
``` bash
$ python3 aklive2d.py b -h
usage: aklive2d build [-h] [-o OPERATOR_NAMES [OPERATOR_NAMES ...]] [-r]
optional arguments:
-h, --help show this help message and exit
-o OPERATOR_NAMES [OPERATOR_NAMES ...], --operators OPERATOR_NAMES [OPERATOR_NAMES ...]
Operators to build (default: ['all'])
-r, --rebuild Rebuild assets (default: False)
```
``` bash
$ python3 aklive2d.py i -h
usage: aklive2d init [-h] [-c OPERATOR_NAME]
optional arguments:
-h, --help show this help message and exit
-c OPERATOR_NAME, --copy OPERATOR_NAME
YAML pre-defined Operator assets to copy (default: None)
```
### Webpage & JavaScript
Add query string `settings` to bring up the settings panel to adjust your settings. Then use appropriate JavaScript code to load your settings
``` javascript
settings.setFPS(integer) // set FPS
settings.displayLogo(boolean) // display logo or not
settings.resizeLogo(float) // the ratio of the logo
settings.opacityLogo(float) // the opacity of the logo
settings.setLogo(url, removeInvert) // change the logo, url: image url, removeInvert: boolean
settings.setBackground(url) // change the background, url: image url
settings.positionPadding("padLeft", integer) // left padding
settings.positionPadding("padRight", integer) // right padding
settings.positionPadding("padTop", integer) // top padding
settings.positionPadding("padBottom", integer) // bottom padding
settings.open() // open settings panel
settings.close() // close settings panel
settings.reset() // reset settings
```
## Config
``` yaml
# share properties for all operators
operator:
preview: preview.jpg # Steam workshop preview image file
project_json: project.json # Steam workshop project file
source_folder: ./operator/{name}/extracted/ # The folder that stores extracted game files
target_folder: ./operator/{name}/processed/ # The folder that stores processed game files
# Development server settings
# List all the supported operators under <operators> block
operators:
chen: # <operator name>/<folder name under "operator" folder>, will be used to replace <{name}> above
_operator_settings.js: # refer to char_1013_chen2_2_settings.js under operator folder
fallbackImage_height: 2048 # fallback image height
fallbackImage_width: 2048 # fallback image width
filename: dyn_illust_char_1013_chen2 # common file name
fps: 60 # default fps target in the webpage
opacity: 100 # optional property, can be used in the file
viewport_bottom: 1 # bottom padding of the model
viewport_left: 0 # left padding of the model
viewport_right: 0 # right padding of the model
viewport_top: 1 # top padding of the model
index.html: # refer to index.html under template folder
fallback_name: char_1013_chen2_2 # fallback image name
id: char_1013_chen2 # id of the operator
operator_logo: logo_rhodes_override # operator logo
title: Ch'en the Holungday # webpage title
version: __get_version # eval __get_version() function
project.json: # refer to project.json under operator folder
description: 'Arknights: Ch''en/Chen the Holungday Live 2D\n明日方舟假日威龙陈 Live
2D\nThe model is extracted from game with Spine support.\n模型来自游戏内提取支持Spine\nPlease
set your FPS target in Wallpaper Engine > Settings > Performance > FPS\n请在
Wallpaper Engine > 设置 > 性能 > FPS 下设置FPS\n\nLive preview on: https://arknights.halyul.dev/chen\nGithub:
https://github.com/Halyul/aklive2d' # Steam Workshop description
title: 'Arknights: Ch''en/Chen the Holungday - 明日方舟:假日威龙陈' # Steam Workshop title
ui_logo_opacity: 100 # logo opacity setting in WE
ui_logo_ratio: 61.8 # logo ratio setting in WE
ui_operator_logo: 'true' # enable/disable logo in WE
ui_position_padding_bottom: 1 # bottom padding of the model in WE
ui_position_padding_left: 0 # left padding of the model in WE
ui_position_padding_right: 0 # right padding of the model in WE
ui_position_padding_top: 1 # top padding of the model in WE
workshopid: 2564643862 # Steam Workshop id
server:
operator_folder: ./operator/ # The path that the showcase webpage accesses game files
release_folder: ./release/ # The folder that stores the showcase webpage
template_folder: ./template/ # The folder that stores the showcase template
```
## LICENSE
The `LICENSE` file applies to all files unless listed specifically.
`LICENSE_SPINE` file applies to following files including adapted code for this repo:
- `template/assets/spine-player.css`
- `template/assets/spine-player.js`
- `release/*/assets/spine-player.css`
- `release/*/assets/spine-player.js`
`Copyright © 2017 - 2021 Arknights/Hypergryph Co., Ltd` applies to following files:
- all files under `operator` folder and its sub-folders
- all files under `release/*/operator/*` folder
- `release/*/operator/operator_assets.js`

View File

@@ -1 +1 @@
2.2.4
3.0.0

View File

@@ -1,115 +0,0 @@
#!/usr/bin/env python3
import argparse
import sys
from lib.config import Config
from lib.server import Server
from lib.builder import Builder
from lib.initializer import Initializer
class AkLive2D:
def __init__(self) -> None:
self.config = Config().read()
self.args = None
self.running = None
def start(self):
parser = argparse.ArgumentParser(
prog="aklive2d",
description="Arknights Live 2D Wallpaper Builder",
formatter_class=argparse.ArgumentDefaultsHelpFormatter
)
subprasers = parser.add_subparsers(
title="Available commands",
dest="command",
required=True,
help="<Required> Select the command to run"
)
server = subprasers.add_parser(
"server",
help="Development Server",
aliases=['s'],
formatter_class=argparse.ArgumentDefaultsHelpFormatter
)
server.add_argument(
"-p",
"--port",
dest="port",
type=int,
default=8080,
help="Development server port"
)
server.add_argument(
"-o",
"--operator",
dest="operator_name",
type=str,
required=True,
help="<Required> Operator to develop",
)
server.add_argument(
"-r",
"--rebuild",
dest="rebuild",
action='store_true',
help="Rebuild assets"
)
build = subprasers.add_parser(
"build",
help="Build releases",
aliases=['b'],
formatter_class=argparse.ArgumentDefaultsHelpFormatter
)
build.add_argument(
"-o",
"--operators",
dest="operator_names",
type=str,
default=["all"],
nargs='+',
help="Operators to build"
)
build.add_argument(
"-r",
"--rebuild",
dest="rebuild",
action='store_true',
help="Rebuild assets"
)
initializer = subprasers.add_parser(
"init",
help="Initialize a new operator",
aliases=['i'],
formatter_class=argparse.ArgumentDefaultsHelpFormatter
)
initializer.add_argument(
"-o",
"--operator",
dest="operator_name",
type=str,
required=True,
help="YAML pre-defined Operator assets to copy",
)
self.args = parser.parse_args()
if self.args.command == "server" or self.args.command == "s":
self.running = Server(self.args.port, self.args.operator_name, self.config, self.args.rebuild)
elif self.args.command == "build" or self.args.command == "b":
self.running = Builder(self.config, self.args.operator_names, self.args.rebuild)
elif self.args.command == "init" or self.args.command == "i":
self.running = Initializer(self.config, self.args.operator_name)
self.running.start()
if __name__ == "__main__":
aklive2d = AkLive2D()
try:
aklive2d.start()
except KeyboardInterrupt:
print("\nInterrupted, exiting...")
sys.exit()

15
build_all.js Normal file
View File

@@ -0,0 +1,15 @@
import path from 'path'
import fs from 'fs'
import { execSync } from 'child_process'
import { fileURLToPath } from 'url'
let __dirname
__dirname = __dirname || path.dirname(fileURLToPath(import.meta.url))
const directory = path.join(__dirname, 'operator');
fs.readdir(directory, function (err, files) {
files.forEach(function (file) {
if (file.startsWith('_')) return;
console.log(execSync(`O=${file} node preprocessing.js && O=${file} pnpm run build`).toString());
});
});

View File

@@ -1,8 +1,6 @@
operator:
preview: preview.jpg
project_json: project.json
source_folder: ./operator/{name}/extracted/
target_folder: ./operator/{name}/processed/
folder:
operator: ./operator/
release: ./release/
operators:
chen: !include config/chen.yaml
dusk: !include config/dusk.yaml
@@ -26,9 +24,4 @@ operators:
rosmontis_become_anew: !include config/rosmontis_become_anew.yaml
passager_dream_in_a_moment: !include config/passager_dream_in_a_moment.yaml
mizuki_summer_feast: !include config/mizuki_summer_feast.yaml
server:
operator_folder: ./operator/
release_folder: ./release/
template_folder: ./template/operator/
index:
src_folder: ./template/index/src/assets

View File

@@ -1,9 +0,0 @@
title: ${var:index.html->title}
description: '${func:split("project.json->title" ," - ")[0]} Live 2D\n${func:split("project.json->title" ," - ")[1]} Live 2D\nThe model is extracted from game with Spine support.\n模型来自游戏内提取支持Spine\nPlease set your FPS target in Wallpaper Engine > Settings > Performance > FPS\n请在 Wallpaper Engine > 设置 > 性能 > FPS 下设置FPS\n\nLive preview on: https://arknights.halyul.dev/${var:link}\nGithub: https://github.com/Halyul/aklive2d'
ui_logo_opacity: ${var:_operator_settings.js->opacity}
ui_logo_ratio: 61.8
ui_operator_logo: 'true'
ui_position_padding_bottom: ${var:_operator_settings.js->viewport_bottom}
ui_position_padding_left: ${var:_operator_settings.js->viewport_left}
ui_position_padding_right: ${var:_operator_settings.js->viewport_right}
ui_position_padding_top: ${var:_operator_settings.js->viewport_top}

12
config/_template.yaml Normal file
View File

@@ -0,0 +1,12 @@
link: chen
type: operator
date: 2021/08
title: 'Arknights: Ch''en/Chen the Holungday - 明日方舟:假日威龙陈'
filename: dyn_illust_char_1013_chen2
logo: logo_rhodes_override
fallback_name: char_1013_chen2_2
viewport_left: 0
viewport_right: 0
viewport_top: 1
viewport_bottom: 1
invert_filter: false

View File

@@ -1,21 +1,12 @@
_operator_settings.js:
fallbackImage_height: 2048
fallbackImage_width: 2048
filename: dyn_illust_char_1013_chen2
fps: 60
opacity: 100
viewport_bottom: 1
viewport_left: 0
viewport_right: 0
viewport_top: 1
invert_filter: false
index.html:
fallback_name: char_1013_chen2_2
id: char_1013_chen2
operator_logo: logo_rhodes_override
title: 'Arknights: Ch''en/Chen the Holungday - 明日方舟:假日威龙陈'
version: ${func:get_version()}
project.json: !include config/_project.json.yaml
link: chen
type: operator
date: 2021/08
title: 'Arknights: Ch''en/Chen the Holungday - 明日方舟:假日威龙陈'
filename: dyn_illust_char_1013_chen2
logo: logo_rhodes_override
fallback_name: char_1013_chen2_2
viewport_left: 0
viewport_right: 0
viewport_top: 1
viewport_bottom: 1
invert_filter: false

View File

@@ -1,21 +1,12 @@
_operator_settings.js:
fallbackImage_height: 2048
fallbackImage_width: 2048
filename: dyn_illust_char_2015_dusk
fps: 60
opacity: 30
viewport_bottom: 0
viewport_left: 0
viewport_right: 0
viewport_top: 0
invert_filter: true
index.html:
fallback_name: char_2015_dusk_2
id: char_2015_dusk
operator_logo: logo_sui
title: 'Arknights: Dusk - 明日方舟:夕'
version: ${func:get_version()}
project.json: !include config/_project.json.yaml
link: dusk
type: operator
date: 2021/02
title: 'Arknights: Dusk - 明日方舟:夕'
filename: dyn_illust_char_2015_dusk
logo: logo_sui
fallback_name: char_2015_dusk_2
viewport_left: 0
viewport_right: 0
viewport_top: 0
viewport_bottom: 0
invert_filter: true

View File

@@ -1,20 +1,12 @@
_operator_settings.js:
fallbackImage_height: 2048
fallbackImage_width: 2048
filename: dyn_illust_char_2015_dusk_nian#7
fps: 60
opacity: 30
viewport_bottom: 0
viewport_left: 10
viewport_right: 0
viewport_top: 0
index.html:
fallback_name: char_2015_dusk_nian%237
id: char_2015_dusk_nian%237
operator_logo: logo_sui
title: 'Arknights: Everything is a Miracle / Dusk - 明日方舟:染尘烟·夕'
version: ${func:get_version()}
project.json: !include config/_project.json.yaml
link: dusk_everything_is_a_miracle
type: skin
date: 2022/01
title: 'Arknights: Everything is a Miracle / Dusk - 明日方舟:染尘烟·夕'
filename: dyn_illust_char_2015_dusk_nian#7
logo: logo_sui
fallback_name: char_2015_dusk_nian#7
viewport_left: 10
viewport_right: 0
viewport_top: 0
viewport_bottom: 0
invert_filter: true

View File

@@ -1,21 +1,12 @@
_operator_settings.js:
fallbackImage_height: 2048
fallbackImage_width: 2048
filename: dyn_illust_char_1026_gvial2
fps: 60
opacity: 30
viewport_bottom: 0
viewport_left: 0
viewport_right: 0
viewport_top: 0
invert_filter: false
index.html:
fallback_name: char_1026_gvial2_2
id: char_1026_gvial2_2
operator_logo: logo_rhodes_override
title: 'Arknights: Gavial the Invincible - 明日方舟:百练嘉维尔'
version: ${func:get_version()}
project.json: !include config/_project.json.yaml
link: gavial
type: operator
date: 2022/08
title: 'Arknights: Gavial the Invincible - 明日方舟:百练嘉维尔'
filename: dyn_illust_char_1026_gvial2
logo: logo_rhodes_override
fallback_name: char_1026_gvial2_2
viewport_left: 0
viewport_right: 0
viewport_top: 0
viewport_bottom: 0
invert_filter: false

View File

@@ -1,21 +1,12 @@
_operator_settings.js:
fallbackImage_height: 2048
fallbackImage_width: 2048
filename: dyn_illust_char_322_lmlee_witch#3
fps: 60
opacity: 30
viewport_bottom: 0
viewport_left: 0
viewport_right: 0
viewport_top: 0
invert_filter: true
index.html:
fallback_name: char_322_lmlee_witch%233
id: char_322_lmlee_witch%233
operator_logo: logo_lee
title: 'Arknights: Trust Your Eyes / Lee - 明日方舟:手到牌来·老鲤'
version: ${func:get_version()}
project.json: !include config/_project.json.yaml
link: lee_trust_your_eyes
type: skin
date: 2022/10
title: 'Arknights: Trust Your Eyes / Lee - 明日方舟:手到牌来·老鲤'
filename: dyn_illust_char_322_lmlee_witch#3
logo: logo_lee
fallback_name: char_322_lmlee_witch#3
viewport_left: 0
viewport_right: 0
viewport_top: 0
viewport_bottom: 0
invert_filter: true

View File

@@ -1,21 +1,12 @@
_operator_settings.js:
fallbackImage_height: 2048
fallbackImage_width: 2048
filename: dyn_illust_char_2023_ling
fps: 60
opacity: 30
viewport_bottom: 0
viewport_left: 0
viewport_right: 0
viewport_top: 0
invert_filter: true
index.html:
fallback_name: char_2023_ling_2
id: char_2023_ling
operator_logo: logo_sui
title: 'Arknights: Ling - 明日方舟:令'
version: ${func:get_version()}
project.json: !include config/_project.json.yaml
link: ling
type: operator
date: 2022/01
title: 'Arknights: Ling - 明日方舟:令'
filename: dyn_illust_char_2023_ling
logo: logo_sui
fallback_name: char_2023_ling_2
viewport_left: 0
viewport_right: 0
viewport_top: 0
viewport_bottom: 0
invert_filter: true

View File

@@ -1,21 +1,12 @@
_operator_settings.js:
fallbackImage_height: 2048
fallbackImage_width: 2048
filename: dyn_illust_char_437_mizuki_sale#7
fps: 60
opacity: 30
viewport_bottom: 0
viewport_left: 0
viewport_right: 0
viewport_top: 0
invert_filter: true
index.html:
fallback_name: char_437_mizuki_sale%237
id: char_437_mizuki_sale%237
operator_logo: logo_higashi
title: 'Arknights: Summer Feast / Mizuki - 明日方舟:夏日餮宴·水月'
version: ${func:get_version()}
project.json: !include config/_project.json.yaml
link: mizuki_summer_feast
type: skin
date: 2022/12
title: 'Arknights: Summer Feast / Mizuki - 明日方舟:夏日餮宴·水月'
filename: dyn_illust_char_437_mizuki_sale#7
logo: logo_higashi
fallback_name: char_437_mizuki_sale#7
viewport_left: 0
viewport_right: 0
viewport_top: 0
viewport_bottom: 0
invert_filter: true

View File

@@ -1,21 +1,12 @@
_operator_settings.js:
fallbackImage_height: 2048
fallbackImage_width: 2048
filename: dyn_illust_char_1014_nearl2
fps: 60
opacity: 100
viewport_bottom: 0
viewport_left: 2
viewport_right: 3
viewport_top: 10
invert_filter: true
index.html:
fallback_name: char_1014_nearl2_2
id: char_1014_nearl2
operator_logo: logo_kazimierz
title: 'Arknights: Nearl the Radiant Knight - 明日方舟:耀骑士临光'
version: ${func:get_version()}
project.json: !include config/_project.json.yaml
link: nearl
type: operator
date: 2021/11
title: 'Arknights: Nearl the Radiant Knight - 明日方舟:耀骑士临光'
filename: dyn_illust_char_1014_nearl2
logo: logo_kazimierz
fallback_name: char_1014_nearl2_2
viewport_left: 2
viewport_right: 3
viewport_top: 10
viewport_bottom: 0
invert_filter: true

View File

@@ -1,21 +1,12 @@
_operator_settings.js:
fallbackImage_height: 2048
fallbackImage_width: 2048
filename: dyn_illust_char_1014_nearl2_epoque#17
fps: 60
opacity: 30
viewport_bottom: 0
viewport_left: 0
viewport_right: 0
viewport_top: 0
invert_filter: true
index.html:
fallback_name: char_1014_nearl2_epoque%2317
id: char_1014_nearl2%2317
operator_logo: logo_kazimierz
title: 'Arknights: Relight / Nearl - 明日方舟:复现荣光·耀骑士临光'
version: ${func:get_version()}
project.json: !include config/_project.json.yaml
link: nearl_relight
type: skin
date: 2022/11
title: 'Arknights: Relight / Nearl - 明日方舟:复现荣光·耀骑士临光'
filename: dyn_illust_char_1014_nearl2_epoque#17
logo: logo_kazimierz
fallback_name: char_1014_nearl2_epoque#17
viewport_left: 0
viewport_right: 0
viewport_top: 0
viewport_bottom: 0
invert_filter: true

View File

@@ -1,21 +1,12 @@
_operator_settings.js:
fallbackImage_height: 2048
fallbackImage_width: 2048
filename: dyn_illust_char_2014_nian
fps: 60
opacity: 30
viewport_bottom: 5
viewport_left: 2
viewport_right: 2
viewport_top: 3
invert_filter: true
index.html:
fallback_name: char_2014_nian_2
id: char_2014_nian
operator_logo: logo_sui
title: 'Arknights: Nian - 明日方舟:年'
version: ${func:get_version()}
project.json: !include config/_project.json.yaml
link: nian
type: operator
date: 2020/01
title: 'Arknights: Nian - 明日方舟:年'
filename: dyn_illust_char_2014_nian
logo: logo_sui
fallback_name: char_2014_nian_2
viewport_left: 2
viewport_right: 2
viewport_top: 3
viewport_bottom: 5
invert_filter: true

View File

@@ -1,21 +1,12 @@
_operator_settings.js:
fallbackImage_height: 2048
fallbackImage_width: 2048
filename: dyn_illust_char_2014_nian_nian#4
fps: 60
opacity: 30
viewport_bottom: 0
viewport_left: 0
viewport_right: 0
viewport_top: 0
invert_filter: true
index.html:
fallback_name: char_2014_nian_nian%234
id: char_2014_nian_nian%234
operator_logo: logo_sui
title: 'Arknights: Unfettered Freedom / Nian - 明日方舟:乐逍遥·年'
version: ${func:get_version()}
project.json: !include config/_project.json.yaml
link: nian_unfettered_freedom
type: skin
date: 2021/02
title: 'Arknights: Unfettered Freedom / Nian - 明日方舟:乐逍遥·年'
filename: dyn_illust_char_2014_nian_nian#4
logo: logo_sui
fallback_name: char_2014_nian_nian#4
viewport_left: 0
viewport_right: 0
viewport_top: 0
viewport_bottom: 0
invert_filter: true

View File

@@ -1,21 +1,12 @@
_operator_settings.js:
fallbackImage_height: 2048
fallbackImage_width: 2048
filename: dyn_illust_char_472_pasngr_epoque#17
fps: 60
opacity: 30
viewport_bottom: 0
viewport_left: 0
viewport_right: 0
viewport_top: 0
invert_filter: true
index.html:
fallback_name: char_472_pasngr_epoque%2317
id: char_472_pasngr%2317
operator_logo: logo_sargon
title: 'Arknights: Dream in a Moment / Passager - 明日方舟:今昔须臾之梦 · 异客'
version: ${func:get_version()}
project.json: !include config/_project.json.yaml
link: passager_dream_in_a_moment
type: skin
date: 2022/11
title: 'Arknights: Dream in a Moment / Passager - 明日方舟:今昔须臾之梦 · 异客'
filename: dyn_illust_char_472_pasngr_epoque#17
logo: logo_sargon
fallback_name: char_472_pasngr_epoque#17
viewport_left: 0
viewport_right: 0
viewport_top: 0
viewport_bottom: 0
invert_filter: true

View File

@@ -1,21 +1,12 @@
_operator_settings.js:
fallbackImage_height: 2048
fallbackImage_width: 2048
filename: dyn_illust_char_250_phatom_sale#4
fps: 60
opacity: 100
viewport_bottom: 1
viewport_left: 0
viewport_right: 0
viewport_top: 5
invert_filter: false
index.html:
fallback_name: char_250_phatom_sale%234
id: char_250_phatom_sale%234
operator_logo: logo_victoria
title: 'Arknights: Focus / Phatom - 明日方舟:焦点·傀影'
version: ${func:get_version()}
project.json: !include config/_project.json.yaml
link: phatom_focus
type: skin
date: 2022/04
title: 'Arknights: Focus / Phatom - 明日方舟:焦点·傀影'
filename: dyn_illust_char_250_phatom_sale#4
logo: logo_victoria
fallback_name: char_250_phatom_sale#4
viewport_left: 0
viewport_right: 0
viewport_top: 5
viewport_bottom: 1
invert_filter: true

View File

@@ -1,21 +1,12 @@
_operator_settings.js:
fallbackImage_height: 2048
fallbackImage_width: 2048
filename: dyn_illust_char_391_rosmon
fps: 60
opacity: 100
viewport_bottom: -1
viewport_left: 0
viewport_right: -14
viewport_top: -38
invert_filter: false
index.html:
fallback_name: char_391_rosmon_2
id: char_391_rosmon
operator_logo: logo_elite
title: 'Arknights: Rosmontis - 明日方舟:迷迭香'
version: ${func:get_version()}
project.json: !include config/_project.json.yaml
link: rosmontis
type: operator
date: 2020/11
title: 'Arknights: Rosmontis - 明日方舟:迷迭香'
filename: dyn_illust_char_391_rosmon
logo: logo_elite
fallback_name: char_391_rosmon_2
viewport_left: 0
viewport_right: -14
viewport_top: -38
viewport_bottom: -1
invert_filter: true

View File

@@ -1,21 +1,12 @@
_operator_settings.js:
fallbackImage_height: 2048
fallbackImage_width: 2048
filename: dyn_illust_char_391_rosmon_epoque#17
fps: 60
opacity: 30
viewport_bottom: 0
viewport_left: 0
viewport_right: 0
viewport_top: 0
invert_filter: true
index.html:
fallback_name: char_391_rosmon_epoque%2317
id: char_391_rosmon%2317
operator_logo: logo_elite
title: 'Arknights: Become Anew / Rosmontis - 明日方舟:拥抱新生·迷迭香'
version: ${func:get_version()}
project.json: !include config/_project.json.yaml
link: rosmontis_become_anew
type: skin
date: 2022/11
title: 'Arknights: Become Anew / Rosmontis - 明日方舟:拥抱新生·迷迭香'
filename: dyn_illust_char_391_rosmon_epoque#17
logo: logo_elite
fallback_name: char_391_rosmon_epoque#17
viewport_left: 0
viewport_right: 0
viewport_top: 0
viewport_bottom: 0
invert_filter: true

View File

@@ -1,21 +1,12 @@
_operator_settings.js:
fallbackImage_height: 2048
fallbackImage_width: 2048
filename: dyn_illust_char_1012_skadi2
fps: 60
opacity: 30
viewport_bottom: -12
viewport_left: -5
viewport_right: -10
viewport_top: 0
invert_filter: true
index.html:
fallback_name: char_1012_skadi2_2
id: char_1012_skadi2
operator_logo: logo_egir
title: 'Arknights: Skadi the Corrupting Heart - 明日方舟:浊心斯卡蒂'
version: ${func:get_version()}
project.json: !include config/_project.json.yaml
link: skadi
type: operator
date: 2021/05
title: 'Arknights: Skadi the Corrupting Heart - 明日方舟:浊心斯卡蒂'
filename: dyn_illust_char_1012_skadi2
logo: logo_egir
fallback_name: char_1012_skadi2_2
viewport_left: -5
viewport_right: -10
viewport_top: 0
viewport_bottom: -12
invert_filter: true

View File

@@ -1,21 +1,12 @@
_operator_settings.js:
fallbackImage_height: 2048
fallbackImage_width: 2048
filename: dyn_illust_char_1012_skadi2_boc#4
fps: 60
opacity: 30
viewport_bottom: 0
viewport_left: 0
viewport_right: 0
viewport_top: 0
invert_filter: true
index.html:
fallback_name: char_1012_skadi2_boc%234
id: char_1012_skadi2_boc%234
operator_logo: logo_egir
title: 'Arknights: Sublimation / Skadi the Corrupting Heart - 明日方舟:升华·浊心斯卡蒂'
version: ${func:get_version()}
project.json: !include config/_project.json.yaml
link: skadi_sublimation
type: skin
date: 2022/05
title: 'Arknights: Sublimation / Skadi the Corrupting Heart - 明日方舟:升华·浊心斯卡蒂'
filename: dyn_illust_char_1012_skadi2_boc#4
logo: logo_egir
fallback_name: char_1012_skadi2_boc#4
viewport_left: 0
viewport_right: 0
viewport_top: 0
viewport_bottom: 0
invert_filter: true

View File

@@ -1,21 +1,12 @@
_operator_settings.js:
fallbackImage_height: 2048
fallbackImage_width: 2048
filename: dyn_illust_char_1023_ghost2
fps: 60
opacity: 100
viewport_bottom: 0
viewport_left: 0
viewport_right: 0
viewport_top: 0
invert_filter: false
index.html:
fallback_name: char_1023_ghost2_2
id: char_1023_ghost2
operator_logo: logo_abyssal
title: 'Arknights: Specter the Unchained - 明日方舟:归溟幽灵鲨'
version: ${func:get_version()}
project.json: !include config/_project.json.yaml
link: specter
type: operator
date: 2022/05
title: 'Arknights: Specter the Unchained - 明日方舟:归溟幽灵鲨'
filename: dyn_illust_char_1023_ghost2
logo: logo_abyssal
fallback_name: char_1023_ghost2_2
viewport_left: 0
viewport_right: 0
viewport_top: 0
viewport_bottom: 0
invert_filter: true

View File

@@ -1,21 +1,12 @@
_operator_settings.js:
fallbackImage_height: 2048
fallbackImage_width: 2048
filename: dyn_illust_char_350_surtr_summer#9
fps: 60
opacity: 30
viewport_bottom: 0
viewport_left: 0
viewport_right: 6
viewport_top: 1
invert_filter: false
index.html:
fallback_name: char_350_surtr_summer%239
id: char_350_surtr_summer%239
operator_logo: logo_rhodes_override
title: 'Arknights: Colorful Wonderland CW03 / Surtr - 明日方舟:缤纷奇境 CW03·史尔特尔'
version: ${func:get_version()}
project.json: !include config/_project.json.yaml
link: surtr_colorful_wonderland
type: skin
date: 2022/08
title: 'Arknights: Colorful Wonderland CW03 / Surtr - 明日方舟:缤纷奇境 CW03·史尔特尔'
filename: dyn_illust_char_350_surtr_summer#9
logo: logo_rhodes_override
fallback_name: char_350_surtr_summer#9
viewport_left: 0
viewport_right: 6
viewport_top: 1
viewport_bottom: 0
invert_filter: false

View File

@@ -1,21 +1,12 @@
_operator_settings.js:
fallbackImage_height: 2048
fallbackImage_width: 2048
filename: dyn_illust_char_1028_texas2
fps: 60
opacity: 30
viewport_bottom: 0
viewport_left: 0
viewport_right: 0
viewport_top: 0
invert_filter: true
index.html:
fallback_name: char_1028_texas2_2
id: char_1028_texas2
operator_logo: logo_penguin
title: 'Arknights: Texas the Omertosa - 明日方舟:缄默德克萨斯'
version: ${func:get_version()}
project.json: !include config/_project.json.yaml
link: texas_the_omertosa
type: operator
date: 2022/11
title: 'Arknights: Texas the Omertosa - 明日方舟:缄默德克萨斯'
filename: dyn_illust_char_1028_texas2
logo: logo_penguin
fallback_name: char_1028_texas2_2
viewport_left: 0
viewport_right: 0
viewport_top: 0
viewport_bottom: 0
invert_filter: true

View File

@@ -1,21 +1,12 @@
_operator_settings.js:
fallbackImage_height: 2048
fallbackImage_width: 2048
filename: dyn_illust_char_113_cqbw
fps: 60
opacity: 30
viewport_bottom: 1
viewport_left: 3
viewport_right: -3
viewport_top: 0
invert_filter: true
index.html:
fallback_name: char_113_cqbw_2
id: char_113_cqbw
operator_logo: logo_babel
title: 'Arknights: W - 明日方舟W'
version: ${func:get_version()}
project.json: !include config/_project.json.yaml
link: w
type: operator
date: 2020/05
title: 'Arknights: W - 明日方舟: W'
filename: dyn_illust_char_113_cqbw
logo: logo_babel
fallback_name: char_113_cqbw_2
viewport_left: 3
viewport_right: -3
viewport_top: 0
viewport_bottom: 1
invert_filter: true

View File

@@ -1,21 +1,12 @@
_operator_settings.js:
fallbackImage_height: 2048
fallbackImage_width: 2048
filename: dyn_illust_char_113_cqbw_epoque#7
fps: 60
opacity: 30
viewport_left: 0
viewport_right: 0
viewport_top: 1
viewport_bottom: -4
invert_filter: true
index.html:
fallback_name: char_113_cqbw_epoque%237
id: char_113_cqbw_epoque%237
operator_logo: logo_babel
title: 'Arknights: Fugue / W - 明日方舟恍惚·W'
version: ${func:get_version()}
project.json: !include config/_project.json.yaml
link: w_fugue
type: skin
date: 2020/11
title: 'Arknights: Fugue / W - 明日方舟恍惚·W'
filename: dyn_illust_char_113_cqbw_epoque#7
logo: logo_babel
fallback_name: char_113_cqbw_epoque#7
viewport_left: 0
viewport_right: 0
viewport_top: 1
viewport_bottom: -4
invert_filter: true

View File

@@ -4,5 +4,6 @@
"paths": {
"@/*": ["src/*"]
}
}
},
"exclude": ["node_modules", "**/node_modules", "dist", "operator", "release"]
}

31
lib/alpha_composite.js Normal file
View File

@@ -0,0 +1,31 @@
import sharp from "sharp";
import path from "path";
export default class AlphaComposite {
#config
#operatorName
#operatorSourceFolder
constructor(config, operatorName, rootDir) {
this.#config = config
this.#operatorName = operatorName
this.#operatorSourceFolder = path.join(rootDir, this.#config.folder.operator, this.#operatorName)
}
async process(filename, extractedDir) {
const image = sharp(path.join(extractedDir, filename))
.removeAlpha()
const imageMeta = await image.metadata()
const imageBuffer = await image.toBuffer()
const mask = await sharp(path.join(extractedDir, `${path.parse(filename).name}[alpha].png`))
.extractChannel("blue")
.resize(imageMeta.width, imageMeta.height)
.toBuffer();
return sharp(imageBuffer)
.joinChannel(mask)
.toBuffer()
}
}

View File

@@ -1,39 +0,0 @@
import pathlib
from PIL import Image
class AlphaComposite:
def __init__(self, image_path: str, save_path) -> None:
if image_path.strip().endswith(".png") is True:
image_path = image_path.replace(".png", "")
if save_path.strip().endswith(".png") is False:
save_path += ".png"
self.image_path = pathlib.Path.cwd().joinpath(image_path + ".png")
self.mask_path = pathlib.Path.cwd().joinpath(image_path + "[alpha].png")
self.save_path = save_path
self.image = None
self.mask = None
self.loaded_image = None
self.loaded_mask = None
self.__open_image()
self.__alpha_composite()
pass
def __open_image(self):
self.image = Image.open(self.image_path)
self.mask = Image.open(self.mask_path)
if self.image.size != self.mask.size:
# resize mask
self.mask = self.__resize(self.mask, self.image.size[0], self.image.size[1])
self.loaded_image = self.image.load()
self.loaded_mask = self.mask.load()
def __alpha_composite(self):
for y in range(self.mask.size[1]):
for x in range(self.mask.size[0]):
self.loaded_image[x, y] = (self.loaded_image[x, y][0], self.loaded_image[x, y][1], self.loaded_image[x, y][2], self.loaded_mask[x, y][2])
self.image.save(pathlib.Path.cwd().joinpath(self.save_path))
# resize image
def __resize(self, image, width: int, height: int):
return image.resize((width, height))

44
lib/assets_processor.js Normal file
View File

@@ -0,0 +1,44 @@
import path from 'path'
import { copy, read, write } from './file.js'
import AlphaComposite from './alpha_composite.js'
export default class AssetsProcessor {
#config
#operatorName
#operatorSourceFolder
#alphaCompositer
constructor(config, operatorName, rootDir) {
this.#config = config
this.#operatorName = operatorName
this.#operatorSourceFolder = path.join(rootDir, this.#config.folder.operator)
this.#alphaCompositer = new AlphaComposite(config, operatorName, rootDir)
}
async process(publicAssetsDir, extractedDir) {
const BASE64_BINARY_PREFIX = 'data:application/octet-stream;base64,'
const BASE64_PNG_PREFIX = 'data:image/png;base64,'
const assetsJson = {}
const skelFilename = `${this.#config.operators[this.#operatorName].filename}.skel`
const skel = await read(path.join(extractedDir, skelFilename), null)
const atlasFilename = `${this.#config.operators[this.#operatorName].filename}.atlas`
const atlas = await read(path.join(extractedDir, atlasFilename))
const dimensions = atlas.match(new RegExp(/^size:(.*),(.*)/gm))[0].replace('size: ', '').split(',')
const matches = atlas.match(new RegExp(/(.*).png/g))
for (const item of matches) {
const buffer = await this.#alphaCompositer.process(item, extractedDir)
assetsJson[`./assets/${item}`] = BASE64_PNG_PREFIX + buffer.toString('base64')
}
assetsJson[`./assets/${skelFilename.replace('#', '%23')}`] = BASE64_BINARY_PREFIX + skel.toString('base64')
assetsJson[`./assets/${atlasFilename.replace('#', '%23')}`] = BASE64_BINARY_PREFIX + Buffer.from(atlas).toString('base64')
const fallbackFilename = `${this.#config.operators[this.#operatorName].fallback_name}.png`
const fallbackBuffer = await this.#alphaCompositer.process(fallbackFilename, extractedDir)
await write(fallbackBuffer, path.join(this.#operatorSourceFolder, this.#operatorName, fallbackFilename))
await copy(path.join(this.#operatorSourceFolder, this.#operatorName, fallbackFilename), path.join(publicAssetsDir, fallbackFilename))
return {
dimensions,
assetsJson
}
}
}

View File

@@ -1,31 +0,0 @@
import pathlib
import shutil
class AtlasReader:
def __init__(self, file_path: str, save_path: str) -> None:
if file_path.strip().endswith(".atlas") is False:
file_path += ".atlas"
if save_path.strip().endswith(".atlas") is False:
save_path += ".atlas"
self.file_path = pathlib.Path.cwd().joinpath(file_path)
self.save_path = pathlib.Path.cwd().joinpath(save_path)
self.images = list()
def get_images(self):
with open(self.file_path, "r") as f:
line = f.readline()
while line:
line = line.strip()
if line.endswith(".png") is True:
self.images.append(line)
line = f.readline()
self.copy()
return self.images
def copy(self):
shutil.copyfile(
self.file_path,
self.save_path
)

View File

@@ -1,49 +0,0 @@
import base64
import pathlib
def encode_binary(data=None, type="application/octet-stream", path=None, prefix=True):
if data is None and path is None:
raise ValueError("Both data and path arguments are None")
if data is not None:
bytes = data
elif path is not None:
with open(pathlib.Path.cwd().joinpath(path), "rb") as f:
bytes = f.read()
encoded_bytes = base64.b64encode(bytes)
humanreadable_data = encoded_bytes.decode("utf-8")
if prefix is True:
result = "data:{};base64,".format(type) + humanreadable_data
else:
result = humanreadable_data
return result
def decode_binary(data: str, path):
if data.strip().startswith("data:") is True:
data = data.split(",")[1]
encoded_bytes = data.encode("utf-8")
with open(pathlib.Path.cwd().joinpath(path), "wb") as f:
bytes = base64.decodebytes(encoded_bytes)
f.write(bytes)
def encode_string(data=None, type="text/plain", path=None, prefix=True, encoding="utf-8"):
if data is None and path is None:
raise ValueError("Both data and path arguments are None")
if data is not None:
bytes = data.encode(encoding)
elif path is not None:
with open(pathlib.Path.cwd().joinpath(path), "r") as f:
bytes = f.read()
encoded_bytes = base64.b64encode(bytes)
humanreadable_data = encoded_bytes.decode(encoding)
if prefix is True:
result = "data:{};base64,".format(type) + humanreadable_data
else:
result = humanreadable_data
return result
def decode_string(data:str, encoding="utf-8"):
if data.strip().startswith("data:") is True:
data = data.split(",")[1]
encoded_bytes = data.encode(encoding)
bytes = base64.decodebytes(encoded_bytes)
return bytes.decode(encoding)

View File

@@ -1,286 +0,0 @@
from multiprocessing import Process, Manager
import shutil
import json
import operator
from lib.alpha_composite import AlphaComposite
from lib.atlas_reader import AtlasReader
from lib.base64_util import *
from lib.content_processor import ContentProcessor
class Builder:
def __init__(self, config, operator_names=list(), rebuild=False) -> None:
self.operator_names = operator_names
self.config = config
self.rebuild = rebuild
self.content_processor = None
def start(self):
if "all" in self.operator_names:
self.operator_names = [operator_name for operator_name in self.config["operators"]]
for operator_name in self.operator_names:
self.build(operator_name)
self.__release_file(operator_name)
return
def stop(self):
return
def build(self, operator_name):
thread_map = [
dict(
target=self.__build_assets,
args=(operator_name,),
),
dict(
target=self.__build_settings,
args=(operator_name,),
)
]
threads = list()
self.content_processor = ContentProcessor(self.config, operator_name)
for item in thread_map:
thread = Process(**item)
threads.append(thread)
thread.start()
for thread in threads:
thread.join()
# use content processor to generate operator settings
def __build_settings(self, operator_name):
source_path = pathlib.Path.cwd().joinpath("operator", operator_name, "config")
target_path = pathlib.Path.cwd().joinpath("operator", operator_name)
for file in source_path.iterdir():
if file.is_file() is True:
file_path = pathlib.Path.cwd().joinpath(target_path, file.name)
self.content_processor.build(
file,
file_path
)
return
def __build_assets(self, operator_name):
file_paths = dict(
source=self.config["operator"]["source_folder"].format(name=operator_name),
target=self.config["operator"]["target_folder"].format(name=operator_name),
common_name=self.config["operators"][operator_name]["_operator_settings.js"]["filename"],
fallback_name=self.config["operators"][operator_name]["index.html"]["fallback_name"].replace("%23", "#") if self.config["operators"][operator_name]["index.html"]["fallback_name"] is not None else None,
id_name=self.config["operators"][operator_name]["index.html"]["id"].replace("%23", "#")
)
operator_file = pathlib.Path.cwd().joinpath(file_paths["target"], "..", "{}_assets.js".format(file_paths["id_name"]))
if operator_file.exists() is False or self.rebuild is True:
print("Building operator data for {}...".format(operator_name))
prefix = "window.operatorAssets = "
manager = Manager()
data = manager.dict()
thread_map = [
dict(
target=self.__ar_thread,
args=(
file_paths,
data
),
),
dict(
target=self.__skeleton_binary_thread,
args=(
file_paths,
data
),
),
dict(
target=self.__fallback_thread,
args=(
file_paths,
data
),
),
]
threads = list()
for item in thread_map:
thread = Process(**item)
threads.append(thread)
thread.start()
for thread in threads:
thread.join()
sorted_data = dict()
for i in sorted(data.keys()):
sorted_data[i] = data[i]
json_content = prefix + str(sorted_data)
with open(operator_file, "w") as f:
f.write(json_content)
print("Finished building operator data for {}.".format(operator_name))
else:
print("Operator data for {} has been built.".format(operator_name))
return
def __ar_thread(self, file_paths, data):
source_path = file_paths["source"]
target_path = file_paths["target"]
common_name = file_paths["common_name"]
png_to_base64_threads = list()
alpha_composite_threads = list()
ar = AtlasReader(source_path + common_name, target_path + common_name)
images = ar.get_images()
atlas_to_base64_thread = Process(
target=self.__atlas_to_base64,
args=(
target_path + common_name + ".atlas",
data,
self.config["server"]["operator_folder"] + common_name + ".atlas",
),
)
atlas_to_base64_thread.start()
for item in images:
alpha_composite_thread = Process(
target=AlphaComposite,
args=(source_path + item, target_path + item),
)
alpha_composite_threads.append(alpha_composite_thread)
alpha_composite_thread.start()
for thread in alpha_composite_threads:
thread.join()
for item in images:
png_to_base64_thread = Process(
target=self.__png_to_base64,
args=(
target_path + item,
data,
self.config["server"]["operator_folder"] + item,
),
)
png_to_base64_threads.append(png_to_base64_thread)
png_to_base64_thread.start()
for thread in png_to_base64_threads:
thread.join()
atlas_to_base64_thread.join()
def __skeleton_binary_thread(self, file_paths, data):
source_path = file_paths["source"]
target_path = file_paths["target"]
common_name = file_paths["common_name"]
if common_name.strip().endswith(".skel") is False:
common_name += ".skel"
file_path = pathlib.Path.cwd().joinpath(source_path + common_name)
save_path = pathlib.Path.cwd().joinpath(target_path + common_name)
shutil.copyfile(
file_path,
save_path
)
self.__skel_to_base64(
save_path,
data,
self.config["server"]["operator_folder"] + common_name,
)
def __fallback_thread(self, file_paths, data):
source_path = file_paths["source"]
target_path = file_paths["target"]
fallback_name = file_paths["fallback_name"]
if fallback_name is not None:
AlphaComposite(source_path + fallback_name, target_path + "../{}".format(fallback_name))
def __json_to_base64(self, path, dict=None, key=None):
with open(pathlib.Path.cwd().joinpath(path), "r") as f:
data = f.read()
result = encode_string(data, type="application/json")
if dict is not None and key is not None:
dict[key] = result
else:
return result
def __skel_to_base64(self, path, dict=None, key=None):
result = encode_binary(
path=path
)
if dict is not None and key is not None:
dict[key] = result
else:
return result
def __atlas_to_base64(self, path, dict=None, key=None):
with open(pathlib.Path.cwd().joinpath(path), "r") as f:
data = f.read()
result = encode_string(data, type="application/octet-stream")
if dict is not None and key is not None:
dict[key] = result
else:
return result
def __png_to_base64(self, path, dict=None, key=None):
result = encode_binary(
path=path,
type="image/png"
)
if dict is not None and key is not None:
dict[key] = result
else:
return result
def __release_file(self, operator_name):
target_path = self.config["server"]["release_folder"]
operator_release_path = pathlib.Path.cwd().joinpath(target_path, operator_name)
release_operator_assets_path = pathlib.Path.cwd().joinpath(operator_release_path, self.config["server"]["operator_folder"])
operator_assets_path = pathlib.Path.cwd().joinpath(self.config["operator"]["target_folder"].format(name=operator_name), "..")
template_path = pathlib.Path.cwd().joinpath(self.config["server"]["template_folder"])
if operator_release_path.exists() is True:
shutil.rmtree(operator_release_path)
operator_release_path.mkdir()
release_operator_assets_path.mkdir()
for file in operator_assets_path.iterdir():
if file.is_file() is True:
filename = file.name
if filename == self.config["operator"]["project_json"] or filename == self.config["operator"]["preview"]:
file_path = pathlib.Path.cwd().joinpath(operator_release_path, filename)
else:
file_path = pathlib.Path.cwd().joinpath(release_operator_assets_path, filename)
shutil.copyfile(
file,
file_path
)
# template folder uses content processor to generate files
for file in template_path.iterdir():
if file.is_file() is True:
file_path = pathlib.Path.cwd().joinpath(operator_release_path, file.name)
self.content_processor.build(file, file_path)
elif file.is_dir() is True:
file_path = pathlib.Path.cwd().joinpath(operator_release_path, file.name)
shutil.copytree(
file,
file_path
)
# generate a directory.json for index page
save_path = pathlib.Path.cwd().joinpath(
self.config["index"]["src_folder"], "directory.json")
directory_json = []
for key, value in self.config["operators"].items():
directory_json.append(dict(
name=key,
link=value["link"],
type=value["type"],
date=value["date"]
))
directory_json.sort(key=operator.itemgetter("date", "name"), reverse=True)
with open(save_path, 'w', encoding='utf8') as fp:
json.dump(directory_json, fp)
return

9
lib/config.js Normal file
View File

@@ -0,0 +1,9 @@
import path from 'path'
import { read } from './yaml.js'
export default function (dirname) {
return {
basedir: dirname,
...read(path.join(dirname, 'config.yaml'))
}
}

View File

@@ -1,17 +0,0 @@
import pathlib, yaml
from yamlinclude import YamlIncludeConstructor
class Config:
def __init__(self) -> None:
self.config_path = pathlib.Path.cwd().joinpath("config.yaml")
def read(self):
return self.__read_config()
def __read_config(self):
try:
YamlIncludeConstructor.add_to_loader_class(loader_class=yaml.FullLoader, base_dir=pathlib.Path.cwd())
return yaml.load(open(self.config_path, "r"), Loader=yaml.FullLoader)
except Exception as e:
raise

72
lib/content_processor.js Normal file
View File

@@ -0,0 +1,72 @@
export default class Matcher {
#start
#end
#content
#reExp
#config
constructor(content, start, end, config) {
this.#start = start
this.#end = end
this.#content = content
this.#reExp = new RegExp(`\\${start}.+?${end}`, 'g')
this.#config = config
}
match() {
return this.#content.match(this.#reExp)
}
process() {
const matches = this.match()
if (matches !== null) {
matches.forEach((match) => {
const matchTypeName = match.replace(this.#start, '').replace(this.#end, '')
const type = matchTypeName.split(':')[0]
const name = matchTypeName.split(':')[1]
switch (type) {
case 'var':
let replaceValue = this.#config
name.split('->').forEach((item) => {
try {
replaceValue = replaceValue[item]
} catch (e) {
throw new Error(`Cannot find variable ${name}.`)
}
this.#content = this.#content.replace(match, replaceValue)
})
break
case 'func':
try {
this.#content = this.#content.replace(match, (new Function('Evalable', 'config', `return new Evalable(config).${name}`))(Evalable, this.#config))
} catch (e) {
throw new Error(e)
}
break
default:
throw new Error(`Cannot find type ${type}.`)
}
})
}
return this.#content
}
}
class Evalable {
#config
constructor(config) {
this.#config = config
}
split(varName, separator) {
varName.split("->").forEach((item) => {
try {
this.#config = this.#config[item]
} catch (e) {
throw new Error(`Cannot split ${varName} with separator ${separator}.`)
}
})
return this.#config.split(separator)
}
}

View File

@@ -1,101 +0,0 @@
import pathlib
import shutil
import re
class ContentProcessor:
def __init__(self, config, operator_name):
self.config = config["operators"][operator_name]
self.file_to_process = [key for key, value in self.config.items() if key.startswith("_") is False]
self.settings = self.config
self.__process_value()
def process(self, file_path):
file_path = pathlib.Path.cwd().joinpath(file_path)
with open(file_path, "r") as f:
content = f.read()
if file_path.name in self.file_to_process:
content = Matcher(content, "${format:", "}", self.settings)
return content.format(file_path.name)
else:
return content
def build(self, source_path, target_path):
if source_path.name in self.file_to_process:
content = self.process(source_path)
with open(pathlib.Path.cwd().joinpath(target_path), "w") as f:
f.write(content)
else:
shutil.copyfile(
source_path,
target_path
)
def __process_value(self):
for item_key, item_value in self.settings.items():
for key, value in item_value.items() if type(item_value) == dict else {}:
matcher = Matcher(value, "${", "}", self.settings)
if matcher.match():
replace_value = matcher.process()
else:
replace_value = value
self.settings[item_key][key] = replace_value
# copy dict value _operator_settings.js to {id}_settings.js
settings_filename = "{}_settings.js".format(self.settings["index.html"]["id"].replace("%23", "#"))
self.settings[settings_filename] = self.settings["_operator_settings.js"]
self.file_to_process.append(settings_filename)
class Evalable:
def __init__(self, settings):
self.settings = settings
def get_version(self):
with open(pathlib.Path.cwd().joinpath("Version"), "r") as f:
version = f.read()
return version
def split(self, var_name, separator):
for var in var_name.split("->"):
try:
self.settings = self.settings[var]
except Exception as e:
raise e
return self.settings.split(separator)
class Matcher:
def __init__(self, content, start, end, settings):
self.start = start
self.end = end
self.content = str(content)
self.settings = settings
self.re_exp = re.compile("\{}.+?{}".format(start, end))
def match(self):
return re.search(self.re_exp, self.content) is not None
def process(self):
for match in re.findall(self.re_exp, self.content):
type = match.replace(self.start, "").replace(self.end, "").split(":")[0]
name = match.replace(self.start, "").replace(self.end, "").split(":")[1]
if type == "func":
try:
self.content = self.content.replace(match, eval("Evalable(self.settings)." + name))
except Exception as e:
raise e
elif type == "var":
replace_value = self.settings
for var in name.split("->"):
try:
replace_value = replace_value[var]
except Exception as e:
raise e
self.content = self.content.replace(match, str(replace_value))
else:
raise Exception("Unsupported type: {}".format(type))
return self.content
def format(self, filename):
for key, value in self.settings[filename].items():
identifier = self.start + key + self.end
self.content = self.content.replace(identifier, str(value))
return self.content

32
lib/env_generator.js Normal file
View File

@@ -0,0 +1,32 @@
import path from 'path'
export default class EnvGenerator {
#config
constructor(config, operatorName) {
this.#config = config.operators[operatorName]
}
async generate(dimensions) {
return await this.#promise(dimensions)
}
#promise(dimensions) {
return new Promise((resolve, reject) => {
resolve([
`VITE_TITLE="${this.#config.title}"`,
`VITE_FILENAME=${this.#config.filename.replace('#', '%23')}`,
`VITE_LOGO_FILENAME=${this.#config.logo}`,
`VITE_FALLBACK_FILENAME=${this.#config.fallback_name.replace('#', '%23')}`,
`VITE_VIEWPORT_LEFT=${this.#config.viewport_left}`,
`VITE_VIEWPORT_RIGHT=${this.#config.viewport_right}`,
`VITE_VIEWPORT_TOP=${this.#config.viewport_top}`,
`VITE_VIEWPORT_BOTTOM=${this.#config.viewport_bottom}`,
`VITE_INVERT_FILTER=${this.#config.invert_filter}`,
`VITE_IMAGE_WIDTH=${dimensions[0]}`,
`VITE_IMAGE_HEIGHT=${dimensions[1]}`,
].join('\n'))
})
}
}

42
lib/file.js Normal file
View File

@@ -0,0 +1,42 @@
import fs, { promises as fsP } from 'fs'
import path from 'path'
export async function write(content, filePath) {
mkdir(path.dirname(filePath))
return await fsP.writeFile(filePath, content, { flag: 'w' })
}
export async function read(filePath, encoding = 'utf8') {
return await fsP.readFile(filePath, encoding, { flag: 'r' })
}
export function exists(filePath) {
return fs.existsSync(filePath)
}
export function rmdir(dir) {
if (exists(dir)) {
fs.rmSync(dir, { recursive: true })
}
}
export function rm(file) {
if (exists(file)) {
fs.rmSync(file)
}
}
export function mkdir(dir) {
if (!exists(dir)) {
fs.mkdirSync(dir, { recursive: true })
}
}
export async function copy(sourcePath, targetPath) {
if (!exists(sourcePath)) {
console.warn(`Source file ${sourcePath} does not exist.`)
return
}
mkdir(path.dirname(targetPath))
return await fsP.copyFile(sourcePath, targetPath)
}

7
lib/initializer.js Normal file
View File

@@ -0,0 +1,7 @@
import path from 'path'
import { mkdir, copy } from './file.js'
export default function init(operatorName, __dirname, extractedDir) {
mkdir(extractedDir)
copy(path.join(__dirname, 'config', '_template.yaml'), path.join(__dirname, 'config', `${operatorName}.yaml`))
}

View File

@@ -1,64 +0,0 @@
import pathlib, shutil
class Initializer:
def __init__(self, config, operator_name) -> None:
self.config = config
self.operator_name = operator_name
pass
def start(self):
self.__copy_files()
return
def __copy_files(self):
# ./operator/<operator_name>
operator_assets_path = pathlib.Path.cwd().joinpath(self.config["server"]["operator_folder"], self.operator_name)
if operator_assets_path.exists() is True:
shutil.rmtree(operator_assets_path)
operator_assets_path.mkdir()
dir_map = dict(
config=pathlib.Path.cwd().joinpath(operator_assets_path, "config"),
extracted=pathlib.Path.cwd().joinpath(operator_assets_path, "extracted"),
processed=pathlib.Path.cwd().joinpath(operator_assets_path, "processed")
)
for key, path in dir_map.items():
path.mkdir()
# copy file
operator_settings_path = pathlib.Path.cwd().joinpath(self.config["server"]["operator_folder"], "_share")
logo_path = pathlib.Path.cwd().joinpath(operator_settings_path, "logo")
copy_map = [
dict(
source_name="operator_settings.js",
target_name="{}_settings.js".format(self.config["operators"][self.operator_name]["index.html"]["id"].replace("%23", "#")),
source_path=operator_settings_path,
target_path=dir_map["config"],
),
dict(
source_name="project.json",
target_name="project.json",
source_path=operator_settings_path,
target_path=dir_map["config"],
),
dict(
source_name="operator_bg.png",
target_name="operator_bg.png",
source_path=operator_settings_path,
target_path=operator_assets_path,
),
dict(
source_name="{}.png".format(self.config["operators"][self.operator_name]["index.html"]["operator_logo"]),
target_name="{}.png".format(self.config["operators"][self.operator_name]["index.html"]["operator_logo"]),
source_path=logo_path,
target_path=operator_assets_path,
),
]
for item in copy_map:
shutil.copy(
pathlib.Path.cwd().joinpath(item["source_path"], item["source_name"]),
pathlib.Path.cwd().joinpath(item["target_path"], item["target_name"])
)
return

70
lib/project_json.js Normal file
View File

@@ -0,0 +1,70 @@
import path from 'path'
import Matcher from './content_processor.js'
import { read, exists } from './file.js'
export default class ProjectJson {
#json
#config
#operatorName
#operatorSourceFolder
#operatorShareFolder
constructor(config, operatorName, __dirname, operatorShareFolder) {
this.#config = config
this.#operatorName = operatorName
this.#operatorSourceFolder = path.join(__dirname, this.#config.folder.operator)
this.#operatorShareFolder = operatorShareFolder
}
async load() {
// load json from file
this.#json = JSON.parse(await read(this.#getPath()))
this.#process()
return this.#json
}
#getPath() {
// if exists, do not use the template
const defaultPath = path.join(this.#operatorSourceFolder, this.#operatorName, 'project.json')
if (exists(defaultPath)) {
return defaultPath
} else {
return path.join(this.#operatorShareFolder, 'project.json')
}
}
#process() {
const matcher = new Matcher(this.#json.description, '${', '}', this.#config.operators[this.#operatorName])
if (matcher.match() !== null) {
this.#json.description = matcher.process()
}
this.#json = {
...this.#json,
description: this.#json.description,
title: this.#config.operators[this.#operatorName].title,
general: {
...this.#json.general,
properties: {
...this.#json.general.properties,
paddingbottom: {
...this.#json.general.properties.paddingbottom,
value: this.#config.operators[this.#operatorName].viewport_bottom
},
paddingleft: {
...this.#json.general.properties.paddingleft,
value: this.#config.operators[this.#operatorName].viewport_left
},
paddingright: {
...this.#json.general.properties.paddingright,
value: this.#config.operators[this.#operatorName].viewport_right
},
paddingtop: {
...this.#json.general.properties.paddingtop,
value: this.#config.operators[this.#operatorName].viewport_top
},
}
},
}
}
}

View File

@@ -1,60 +0,0 @@
import pathlib
from http.server import SimpleHTTPRequestHandler
from socketserver import TCPServer
from lib.builder import Builder
from lib.content_processor import ContentProcessor
class Server:
def __init__(self, port, operator, config, rebuild) -> None:
self.config = config
self.operator = operator
self.port = port
self.rebuild = rebuild
self.httpd = TCPServer(("", port), httpd(operator, config, directory=str(pathlib.Path.cwd())))
def start(self):
# build assets first
Builder(self.config, rebuild=self.rebuild).build(self.operator)
print("Server is up at 0.0.0.0:{port}".format(port=self.port))
self.httpd.serve_forever()
return
def stop(self):
self.httpd.server_close()
return
class httpd(SimpleHTTPRequestHandler):
def __init__(self, operator, config, directory):
self.config = config["server"]
self.operator = operator
self.template_path = directory
self.content_processor = ContentProcessor(config, operator)
def __call__(self, *args, **kwds):
super().__init__(*args, directory=self.template_path, **kwds)
def do_GET(self):
# ignore query string
if "?" in self.path:
self.path = self.path.split("?")[0]
split_path = self.path.split("/")
access_path = "./{}/".format(split_path[1])
if self.path == "/":
# self.path = self.config["template_folder"] + "index.html"
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()
html = self.content_processor.process(self.config["template_folder"] + "index.html")
self.wfile.write(bytes(html, "utf8"))
return
elif access_path == "./assets/":
# assets folder
self.path = self.config["template_folder"] + "assets/" + "/".join([i for i in split_path[2:]])
elif self.config["operator_folder"] == access_path:
# operator folder
self.path = self.config["operator_folder"] + "{}/".format(self.operator) + split_path[-1]
return SimpleHTTPRequestHandler.do_GET(self)

19
lib/yaml.js Normal file
View File

@@ -0,0 +1,19 @@
import path from 'path'
import { parse } from 'yaml'
import fs from 'fs'
export function read(file_dir) {
const include = {
identify: value => value.startsWith('!include'),
tag: '!include',
resolve(str) {
const dir = path.resolve(path.dirname(file_dir), str)
const data = read(dir)
return data
}
}
const file = fs.readFileSync(file_dir, 'utf8')
return parse(file, {
customTags: [include],
})
}

View File

@@ -9,7 +9,9 @@
"preview": "vite preview"
},
"dependencies": {
"preact": "^10.11.3"
"preact": "^10.11.3",
"sharp": "^0.31.3",
"yaml": "^2.2.1"
},
"devDependencies": {
"@preact/preset-vite": "^2.4.0",

View File

@@ -3,10 +3,14 @@ lockfileVersion: 5.4
specifiers:
'@preact/preset-vite': ^2.4.0
preact: ^10.11.3
sharp: ^0.31.3
vite: ^4.0.0
yaml: ^2.2.1
dependencies:
preact: 10.11.3
sharp: 0.31.3
yaml: 2.2.1
devDependencies:
'@preact/preset-vite': 2.5.0_6lv424qzfa5w4k4pp4rg6latw4
@@ -575,6 +579,18 @@ packages:
'@babel/core': 7.20.7
dev: true
/base64-js/1.5.1:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
dev: false
/bl/4.1.0:
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
dependencies:
buffer: 5.7.1
inherits: 2.0.4
readable-stream: 3.6.0
dev: false
/browserslist/4.21.4:
resolution: {integrity: sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==}
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
@@ -586,6 +602,13 @@ packages:
update-browserslist-db: 1.0.10_browserslist@4.21.4
dev: true
/buffer/5.7.1:
resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
dependencies:
base64-js: 1.5.1
ieee754: 1.2.1
dev: false
/caniuse-lite/1.0.30001441:
resolution: {integrity: sha512-OyxRR4Vof59I3yGWXws6i908EtGbMzVUi3ganaZQHmydk1iwDhRnvaPG2WaR0KcqrDFKrxVZHULT396LEPhXfg==}
dev: true
@@ -599,15 +622,44 @@ packages:
supports-color: 5.5.0
dev: true
/chownr/1.1.4:
resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==}
dev: false
/color-convert/1.9.3:
resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
dependencies:
color-name: 1.1.3
dev: true
/color-convert/2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
dependencies:
color-name: 1.1.4
dev: false
/color-name/1.1.3:
resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==}
dev: true
/color-name/1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
dev: false
/color-string/1.9.1:
resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==}
dependencies:
color-name: 1.1.3
simple-swizzle: 0.2.2
dev: false
/color/4.2.3:
resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==}
engines: {node: '>=12.5.0'}
dependencies:
color-convert: 2.0.1
color-string: 1.9.1
dev: false
/convert-source-map/1.9.0:
resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==}
@@ -625,10 +677,33 @@ packages:
ms: 2.1.2
dev: true
/decompress-response/6.0.0:
resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==}
engines: {node: '>=10'}
dependencies:
mimic-response: 3.1.0
dev: false
/deep-extend/0.6.0:
resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==}
engines: {node: '>=4.0.0'}
dev: false
/detect-libc/2.0.1:
resolution: {integrity: sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==}
engines: {node: '>=8'}
dev: false
/electron-to-chromium/1.4.284:
resolution: {integrity: sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==}
dev: true
/end-of-stream/1.4.4:
resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==}
dependencies:
once: 1.4.0
dev: false
/esbuild/0.16.13:
resolution: {integrity: sha512-oYwFdSEIoKM1oYzyem1osgKJAvg5447XF+05ava21fOtilyb2HeQQh26/74K4WeAk5dZmj/Mx10zUqUnI14jhA==}
engines: {node: '>=12'}
@@ -673,6 +748,15 @@ packages:
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
dev: true
/expand-template/2.0.3:
resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==}
engines: {node: '>=6'}
dev: false
/fs-constants/1.0.0:
resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==}
dev: false
/fsevents/2.3.2:
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
@@ -690,6 +774,10 @@ packages:
engines: {node: '>=6.9.0'}
dev: true
/github-from-package/0.0.0:
resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==}
dev: false
/globals/11.12.0:
resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
engines: {node: '>=4'}
@@ -707,6 +795,22 @@ packages:
function-bind: 1.1.1
dev: true
/ieee754/1.2.1:
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
dev: false
/inherits/2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
dev: false
/ini/1.3.8:
resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==}
dev: false
/is-arrayish/0.3.2:
resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==}
dev: false
/is-core-module/2.11.0:
resolution: {integrity: sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==}
dependencies:
@@ -739,6 +843,26 @@ packages:
yallist: 3.1.1
dev: true
/lru-cache/6.0.0:
resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
engines: {node: '>=10'}
dependencies:
yallist: 4.0.0
dev: false
/mimic-response/3.1.0:
resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==}
engines: {node: '>=10'}
dev: false
/minimist/1.2.7:
resolution: {integrity: sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==}
dev: false
/mkdirp-classic/0.5.3:
resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==}
dev: false
/ms/2.1.2:
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
dev: true
@@ -749,10 +873,31 @@ packages:
hasBin: true
dev: true
/napi-build-utils/1.0.2:
resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==}
dev: false
/node-abi/3.31.0:
resolution: {integrity: sha512-eSKV6s+APenqVh8ubJyiu/YhZgxQpGP66ntzUb3lY1xB9ukSRaGnx0AIxI+IM+1+IVYC1oWobgG5L3Lt9ARykQ==}
engines: {node: '>=10'}
dependencies:
semver: 7.3.8
dev: false
/node-addon-api/5.1.0:
resolution: {integrity: sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==}
dev: false
/node-releases/2.0.8:
resolution: {integrity: sha512-dFSmB8fFHEH/s81Xi+Y/15DQY6VHW81nXRj86EMSL3lmuTmK1e+aT4wrFCkTbm+gSwkw4KpX+rT/pMM2c1mF+A==}
dev: true
/once/1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
dependencies:
wrappy: 1.0.2
dev: false
/path-parse/1.0.7:
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
dev: true
@@ -778,6 +923,51 @@ packages:
/preact/10.11.3:
resolution: {integrity: sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==}
/prebuild-install/7.1.1:
resolution: {integrity: sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==}
engines: {node: '>=10'}
hasBin: true
dependencies:
detect-libc: 2.0.1
expand-template: 2.0.3
github-from-package: 0.0.0
minimist: 1.2.7
mkdirp-classic: 0.5.3
napi-build-utils: 1.0.2
node-abi: 3.31.0
pump: 3.0.0
rc: 1.2.8
simple-get: 4.0.1
tar-fs: 2.1.1
tunnel-agent: 0.6.0
dev: false
/pump/3.0.0:
resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==}
dependencies:
end-of-stream: 1.4.4
once: 1.4.0
dev: false
/rc/1.2.8:
resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
hasBin: true
dependencies:
deep-extend: 0.6.0
ini: 1.3.8
minimist: 1.2.7
strip-json-comments: 2.0.1
dev: false
/readable-stream/3.6.0:
resolution: {integrity: sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==}
engines: {node: '>= 6'}
dependencies:
inherits: 2.0.4
string_decoder: 1.3.0
util-deprecate: 1.0.2
dev: false
/resolve/1.22.1:
resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==}
hasBin: true
@@ -795,16 +985,72 @@ packages:
fsevents: 2.3.2
dev: true
/safe-buffer/5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
dev: false
/semver/6.3.0:
resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==}
hasBin: true
dev: true
/semver/7.3.8:
resolution: {integrity: sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==}
engines: {node: '>=10'}
hasBin: true
dependencies:
lru-cache: 6.0.0
dev: false
/sharp/0.31.3:
resolution: {integrity: sha512-XcR4+FCLBFKw1bdB+GEhnUNXNXvnt0tDo4WsBsraKymuo/IAuPuCBVAL2wIkUw2r/dwFW5Q5+g66Kwl2dgDFVg==}
engines: {node: '>=14.15.0'}
requiresBuild: true
dependencies:
color: 4.2.3
detect-libc: 2.0.1
node-addon-api: 5.1.0
prebuild-install: 7.1.1
semver: 7.3.8
simple-get: 4.0.1
tar-fs: 2.1.1
tunnel-agent: 0.6.0
dev: false
/simple-concat/1.0.1:
resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==}
dev: false
/simple-get/4.0.1:
resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==}
dependencies:
decompress-response: 6.0.0
once: 1.4.0
simple-concat: 1.0.1
dev: false
/simple-swizzle/0.2.2:
resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==}
dependencies:
is-arrayish: 0.3.2
dev: false
/source-map-js/1.0.2:
resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
engines: {node: '>=0.10.0'}
dev: true
/string_decoder/1.3.0:
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
dependencies:
safe-buffer: 5.2.1
dev: false
/strip-json-comments/2.0.1:
resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==}
engines: {node: '>=0.10.0'}
dev: false
/supports-color/5.5.0:
resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}
engines: {node: '>=4'}
@@ -817,11 +1063,37 @@ packages:
engines: {node: '>= 0.4'}
dev: true
/tar-fs/2.1.1:
resolution: {integrity: sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==}
dependencies:
chownr: 1.1.4
mkdirp-classic: 0.5.3
pump: 3.0.0
tar-stream: 2.2.0
dev: false
/tar-stream/2.2.0:
resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==}
engines: {node: '>=6'}
dependencies:
bl: 4.1.0
end-of-stream: 1.4.4
fs-constants: 1.0.0
inherits: 2.0.4
readable-stream: 3.6.0
dev: false
/to-fast-properties/2.0.0:
resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==}
engines: {node: '>=4'}
dev: true
/tunnel-agent/0.6.0:
resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==}
dependencies:
safe-buffer: 5.2.1
dev: false
/update-browserslist-db/1.0.10_browserslist@4.21.4:
resolution: {integrity: sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==}
hasBin: true
@@ -833,6 +1105,10 @@ packages:
picocolors: 1.0.0
dev: true
/util-deprecate/1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
dev: false
/vite/4.0.4:
resolution: {integrity: sha512-xevPU7M8FU0i/80DMR+YhgrzR5KS2ORy1B4xcX/cXLsvnUWvfHuqMmVU6N0YiJ4JWGRJJsLCgjEzKjG9/GKoSw==}
engines: {node: ^14.18.0 || >=16.0.0}
@@ -866,6 +1142,19 @@ packages:
fsevents: 2.3.2
dev: true
/wrappy/1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
dev: false
/yallist/3.1.1:
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
dev: true
/yallist/4.0.0:
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
dev: false
/yaml/2.2.1:
resolution: {integrity: sha512-e0WHiYql7+9wr4cWMx3TVQrNwejKaEe7/rHNmQmqRjazfOP5W8PB6Jpebb5o6fIapbz9o9+2ipcaTM2ZwDI6lw==}
engines: {node: '>= 14'}
dev: false

89
preprocessing.js Normal file
View File

@@ -0,0 +1,89 @@
import assert from 'assert'
import getConfig from './lib/config.js'
import ProjectJson from './lib/project_json.js'
import EnvGenerator from './lib/env_generator.js'
import { write, rmdir, copy } from './lib/file.js'
import AssetsProcessor from './lib/assets_processor.js'
import path from 'path'
import { fileURLToPath } from 'url'
import init from './lib/initializer.js'
let mode = null
const OPERATOR_NAME = process.env.O;
if (process.argv[1].endsWith('vite.js')) {
mode = "VITE"
} else {
mode = "NODE"
}
assert(OPERATOR_NAME !== undefined, 'Please set the environment variable O to the operator name.')
if (mode === null) {
console.log('Please set the environment variable O to the operator name.')
console.log('Or use the -o flag to specify the operator name.')
process.exit(1)
}
let __dirname
__dirname = __dirname || path.dirname(fileURLToPath(import.meta.url))
const config = getConfig(__dirname)
const OPERATOR_SOURCE_FOLDER = path.join(__dirname, config.folder.operator)
const OPERATOR_RELEASE_FOLDER = path.join(__dirname, config.folder.release, OPERATOR_NAME)
const SHOWCASE_PUBLIC_FOLDER = path.join(__dirname, "public")
const SHOWCASE_PUBLIC_ASSSETS_FOLDER = path.join(SHOWCASE_PUBLIC_FOLDER, "assets")
const EXTRACTED_FOLDER = path.join(OPERATOR_SOURCE_FOLDER, OPERATOR_NAME, 'extracted')
const OPERATOR_SHARE_FOLDER = path.join(OPERATOR_SOURCE_FOLDER, '_share')
if (mode === 'NODE') {
if (process.argv[2] === '-i') {
init(OPERATOR_NAME, __dirname, EXTRACTED_FOLDER)
process.exit(0)
}
rmdir(OPERATOR_RELEASE_FOLDER)
rmdir(SHOWCASE_PUBLIC_FOLDER)
const projectJson = new ProjectJson(config, OPERATOR_NAME, __dirname, OPERATOR_SHARE_FOLDER)
projectJson.load().then((content) => {
write(JSON.stringify(content, null, 2), path.join(OPERATOR_RELEASE_FOLDER, 'project.json'))
})
const assetsProcessor = new AssetsProcessor(config, OPERATOR_NAME, __dirname)
assetsProcessor.process(SHOWCASE_PUBLIC_ASSSETS_FOLDER, EXTRACTED_FOLDER).then((content) => {
const envGenerator = new EnvGenerator(config, OPERATOR_NAME)
envGenerator.generate(content.dimensions).then((value) => {
write(value, path.join(__dirname, '.env'))
})
write(JSON.stringify(content.assetsJson, null), path.join(OPERATOR_SOURCE_FOLDER, OPERATOR_NAME, `${config.operators[OPERATOR_NAME].filename}.json`))
})
}
const filesToCopy = [
{
filename: 'preview.jpg',
source: path.join(OPERATOR_SOURCE_FOLDER, OPERATOR_NAME),
target: path.join(OPERATOR_RELEASE_FOLDER)
},
{
filename: 'operator_bg.png',
source: OPERATOR_SHARE_FOLDER,
target: path.join(SHOWCASE_PUBLIC_FOLDER)
},
{
filename: `${config.operators[OPERATOR_NAME].logo}.png`,
source: path.join(OPERATOR_SHARE_FOLDER, 'logo'),
target: path.join(SHOWCASE_PUBLIC_ASSSETS_FOLDER)
},
{
filename: `${config.operators[OPERATOR_NAME].fallback_name}.png`,
source: path.join(OPERATOR_SOURCE_FOLDER, OPERATOR_NAME),
target: path.join(SHOWCASE_PUBLIC_ASSSETS_FOLDER)
},
]
filesToCopy.forEach((file) => {
copy(path.join(file.source, file.filename), path.join(file.target, file.filename))
})
export default {
OPERATOR_NAME,
config,
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

View File

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

View File

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

View File

@@ -1,11 +0,0 @@
#
# These requirements were autogenerated by pipenv
# To regenerate from the project's Pipfile, run:
#
# pipenv lock --requirements
#
-i https://pypi.org/simple/
pillow==9.3.0
pyyaml-include==1.3
pyyaml==6.0

View File

@@ -1,16 +0,0 @@
# https://github.com/DavHau/mach-nix/blob/master/examples.md
{ pkgs ? import <nixpkgs> {} }:
let
mach-nix = import (
builtins.fetchGit {
url = "https://github.com/DavHau/mach-nix/";
ref = "refs/tags/3.3.0";
}
) {
python = "python39";
};
in
mach-nix.mkPythonShell {
requirements = builtins.readFile ./requirements.txt;
}

View File

@@ -1,7 +1,6 @@
import { useState, useEffect, useRef } from 'preact/hooks'
import '@/app.css'
import '@/libs/wallpaper_engine'
import logo from 'logo'
import check_web_gl from '@/libs/check_web_gl'
import Player from '@/components/player'
import Fallback from '@/components/fallback'
@@ -14,7 +13,7 @@ export function App() {
const [spinePlayer, setSpinePlayer] = useState(null);
const [showControls, setShowControls] = useState(params.has("controls"));
const [showSettings, setShowSettings] = useState(params.has("settings"));
const [showSettings, setShowSettings] = useState(params.has("settings") || import.meta.env.MODE === 'development');
useEffect(() => {
document.title = import.meta.env.VITE_TITLE
@@ -23,7 +22,7 @@ export function App() {
return (
<>
<img src={logo} class="logo invert-filter" id="logo" alt="operator logo" ref={logoRef} width={16} />
<img src={`./assets/${import.meta.env.VITE_LOGO_FILENAME}.png `} class="logo invert-filter" id="logo" alt="operator logo" ref={logoRef} width={16} />
<Settings
spinePlayer={spinePlayer}
setShowSettings={setShowSettings}

View File

@@ -1,5 +1,4 @@
import { useRef, useEffect } from 'preact/hooks'
import fallback from 'fallback'
import '@/components/fallback.css'
export default function Fallback() {
@@ -11,9 +10,9 @@ export default function Fallback() {
return { x: window.innerWidth / width, y: window.innerHeight / height };
}
const fallback = () => {
const scale = calculateScale(2048, 2048);
fallbackRef.current.style.width = 2048 * (scale.x > scale.y ? scale.y : scale.x) + "px";
fallbackRef.current.style.height = 2048 * (scale.x > scale.y ? scale.y : scale.x) + "px";
const scale = calculateScale(import.meta.env.VITE_IMAGE_WIDTH, import.meta.env.VITE_IMAGE_HEIGHT);
fallbackRef.current.style.width = import.meta.env.VITE_IMAGE_WIDTH * (scale.x > scale.y ? scale.y : scale.x) + "px";
fallbackRef.current.style.height = import.meta.env.VITE_IMAGE_HEIGHT * (scale.x > scale.y ? scale.y : scale.x) + "px";
}
fallback();
window.addEventListener('resize', fallback, true);
@@ -23,7 +22,7 @@ export default function Fallback() {
return (
<div id="fallback"
style={{
backgroundImage: `url(${fallback})`,
backgroundImage: `url(./assets/${import.meta.env.VITE_FALLBACK_FILENAME}.png)`,
}}
ref={fallbackRef}
/>

View File

@@ -1,7 +1,7 @@
import { useRef, useEffect } from 'preact/hooks'
import '@/libs/spine-player.css'
import spine from '@/libs/spine-player'
import assets from '@/assets/assets.json'
import assets from '#'
import '@/components/player.css'
export default function Player({ showControls, setSpinePlayer }) {
@@ -44,7 +44,7 @@ export default function Player({ showControls, setSpinePlayer }) {
if (window.performance.now() - resetTime >= 8 * 1000 && Math.random() < 0.3) {
resetTime = window.performance.now();
let entry = widget.animationState.setAnimation(0, "Special", false, 0);
entry.mixDuration = 0.8;
entry.mixDuration = 0.3;
widget.animationState.addAnimation(0, "Idle", true, 0);
}
},
@@ -55,7 +55,7 @@ export default function Player({ showControls, setSpinePlayer }) {
}
isPlayingInteract = true;
let entry = widget.animationState.setAnimation(0, "Interact", false, 0);
entry.mixDuration = 0.8;
entry.mixDuration = 0.3;
widget.animationState.addAnimation(0, "Idle", true, 0);
}
},

View File

@@ -1,6 +1,7 @@
import { useState, useEffect } from 'preact/hooks'
import '@/components/settings.css'
import { subscribe, unsubscribe, publish } from '@/libs/events'
import '@/libs/setting_hooks'
const getPercentage = (value) => parseInt(value.replace("%", ""))
const defaultBackgroundImage = getComputedStyle(document.body).backgroundImage
@@ -10,7 +11,7 @@ export default function Settings({
}) {
const defaultFps = 60
const defaultRatio = 61.8
const defaultOpacity = import.meta.env.VITE_OPACITY
const defaultOpacity = 30
const defaultShowLogo = false
const defaultInvertFilter = import.meta.env.VITE_INVERT_FILTER === "true"
const [defaultLogoImage, setDefaultLogoImage] = useState(null)
@@ -43,18 +44,69 @@ export default function Settings({
const [isPlaying, setIsPlaying] = useState(true)
const resize = (value) => {
logoEl.width = window.innerWidth / 2 * (value || ratio) / 100
}
const [eventFPSDelay, setEventFPSDelay] = useState(-1)
const [eventLogoDelay, setEventLogoDelay] = useState(null)
const [eventRatioDelay, setEventRatioDelay] = useState(-1)
const [eventOpacityDelay, setEventOpacityDelay] = useState(-1)
const [eventImageDelay, setEventImageDelay] = useState(null)
const [eventPadLeftDelay, setEventPadLeftDelay] = useState(-1)
const [eventPadRightDelay, setEventPadRightDelay] = useState(-1)
const [eventPadTopDelay, setEventPadTopDelay] = useState(-1)
const [eventPadBottomDelay, setEventPadBottomDelay] = useState(-1)
const [eventPositionResetDelay, setEventPositionResetDelay] = useState(null)
const [eventLogoResetDelay, setEventLogoResetDelay] = useState(null)
const readFile = (e, onload, callback) => {
const file = e.target.files[0]
if (!file) return
const reader = new FileReader()
reader.readAsDataURL(file);
reader.onload = readerEvent => onload(readerEvent)
callback()
useEffect(() => {
if (spinePlayer === null) return;
if (eventFPSDelay !== -1) {
setFPS(eventFPSDelay)
setEventFPSDelay(-1)
}
if (eventPadLeftDelay !== -1) {
positionPadding("left", eventPadLeftDelay)
setEventPadLeftDelay(-1)
}
if (eventPadRightDelay !== -1) {
positionPadding("right", eventPadRightDelay)
setEventPadRightDelay(-1)
}
if (eventPadTopDelay !== -1) {
positionPadding("top", eventPadTopDelay)
setEventPadTopDelay(-1)
}
if (eventPadBottomDelay !== -1) {
positionPadding("bottom", eventPadBottomDelay)
setEventPadBottomDelay(-1)
}
if (eventPositionResetDelay) {
positionReset()
setEventPositionResetDelay(null)
}
}, [spinePlayer])
useEffect(() => {
if (logoEl === null) return;
if (eventLogoDelay !== null) {
setLogoDisplay(eventLogoDelay)
setEventLogoDelay(null)
}
if (eventRatioDelay !== -1) {
setLogoRatio(eventRatioDelay)
setEventRatioDelay(-1)
}
if (eventOpacityDelay !== -1) {
setLogoOpacity(eventOpacityDelay)
setEventOpacityDelay(-1)
}
if (eventImageDelay !== null) {
setLogo(eventImageDelay)
setEventImageDelay(null)
}
if (eventLogoResetDelay) {
resetLogoImage()
setEventLogoResetDelay(null)
}
}, [logoEl])
const setFPS = (value) => {
setFps(value)
@@ -63,6 +115,10 @@ export default function Settings({
useEffect(() => {
const handleListener = (e) => {
if (spinePlayer === null) {
setEventFPSDelay(e.detail)
return
}
setFPS(e.detail)
}
subscribe("settings:fps", handleListener)
@@ -76,18 +132,35 @@ export default function Settings({
useEffect(() => {
const handleListener = (e) => {
if (logoEl === null) {
setEventLogoDelay(e.detail)
return
}
setLogoDisplay(e.detail)
}
subscribe("settings:logo", handleListener)
return () => unsubscribe("settings:logo", handleListener)
}, [logoEl])
const resize = (value) => {
logoEl.width = window.innerWidth / 2 * (value || ratio) / 100
}
const setLogo = (src, invert_filter) => {
logoEl.src = src
resize()
setLogoInvertFilter(invert_filter)
}
const readFile = (e, onload, callback) => {
const file = e.target.files[0]
if (!file) return
const reader = new FileReader()
reader.readAsDataURL(file);
reader.onload = readerEvent => onload(readerEvent)
callback()
}
const setLogoImage = (e) => {
readFile(
e,
@@ -101,6 +174,10 @@ export default function Settings({
useEffect(() => {
const handleListener = (e) => {
if (logoEl === null) {
setEventImageDelay(e.detail)
return
}
setLogo(e.detail)
}
subscribe("settings:image:set", handleListener)
@@ -113,8 +190,15 @@ export default function Settings({
}
useEffect(() => {
subscribe("settings:image:reset", resetLogoImage)
return () => unsubscribe("settings:image:reset", resetLogoImage)
const handleListener = () => {
if (logoEl === null) {
setEventLogoResetDelay(true)
return
}
resetLogoImage()
}
subscribe("settings:image:reset", handleListener)
return () => unsubscribe("settings:image:reset", handleListener)
}, [logoEl])
const setLogoRatio = (value) => {
@@ -124,6 +208,10 @@ export default function Settings({
useEffect(() => {
const handleListener = (e) => {
if (logoEl === null) {
setEventRatioDelay(e.detail)
return
}
setLogoRatio(e.detail)
}
subscribe("settings:ratio", handleListener)
@@ -137,6 +225,10 @@ export default function Settings({
useEffect(() => {
const handleListener = (e) => {
if (logoEl === null) {
setEventOpacityDelay(e.detail)
return
}
setLogoOpacity(e.detail)
}
subscribe("settings:opacity", handleListener)
@@ -168,7 +260,7 @@ export default function Settings({
useEffect(() => {
const handleListener = (e) => {
setBackgoundImage(e.detail)
setBackgoundImage(`url("${e.detail}")`)
}
subscribe("settings:background:set", handleListener)
return () => unsubscribe("settings:background:set", handleListener)
@@ -180,8 +272,8 @@ export default function Settings({
}
useEffect(() => {
subscribe("settings:background:reset", resetLogoImage)
return () => unsubscribe("settings:background:reset", resetLogoImage)
subscribe("settings:background:reset", resetBackground)
return () => unsubscribe("settings:background:reset", resetBackground)
}, [])
const positionPadding = (key, value) => {
@@ -219,6 +311,23 @@ export default function Settings({
useEffect(() => {
const handleListener = (e) => {
if (spinePlayer === null) {
switch (e.detail.key) {
case "left":
setEventPadLeftDelay(e.detail.value)
break;
case "right":
setEventPadRightDelay(e.detail.value)
break;
case "top":
setEventPadTopDelay(e.detail.value)
break;
case "bottom":
setEventPadBottomDelay(e.detail.value)
break;
}
return
}
positionPadding(e.detail.key, e.detail.value)
}
subscribe("settings:position:set", handleListener)
@@ -234,10 +343,52 @@ export default function Settings({
}
useEffect(() => {
subscribe("settings:position:reset", positionReset)
return () => unsubscribe("settings:position:reset", positionReset)
const handleListener = () => {
if (spinePlayer === null) {
setEventPositionResetDelay(true)
return
}
positionReset()
}
subscribe("settings:position:reset", handleListener)
return () => unsubscribe("settings:position:reset", handleListener)
}, [spinePlayer])
useEffect(() => {
const handleListener = () => {
setShowSettings(true)
}
subscribe("settings:open", handleListener)
return () => unsubscribe("settings:open", handleListener)
}, [])
useEffect(() => {
const handleListener = () => {
setShowSettings(false)
}
subscribe("settings:close", handleListener)
return () => unsubscribe("settings:close", handleListener)
}, [])
const settingsReset = () => {
setFPS(defaultFps)
setLogoDisplay(defaultShowLogo)
resetLogoImage()
setLogoRatio(defaultRatio)
setLogoOpacity(defaultOpacity)
resetBackground()
positionReset()
spinePlayer.play()
}
useEffect(() => {
const handleListener = () => {
settingsReset()
}
subscribe("settings:reset", handleListener)
return () => unsubscribe("settings:reset", handleListener)
}, [])
useEffect(() => {
if (logoEl) {
resize()
@@ -257,7 +408,7 @@ export default function Settings({
return () => {
window.removeEventListener("resize", resize, true);
}
}, [])
}, [logoEl])
return (
<div class="website-settings" hidden={hidden}>
@@ -338,16 +489,7 @@ export default function Settings({
>
Pause
</button>
<button type="button" onClick={() => {
setFPS(defaultFps)
setLogoDisplay(defaultShowLogo)
resetLogoImage()
setLogoRatio(defaultRatio)
setLogoOpacity(defaultOpacity)
resetBackground()
positionReset()
spinePlayer.play()
}}>
<button type="button" onClick={() => settingsReset()}>
Reset
</button>
<button type="button" onClick={() => setShowSettings(false)}>Close</button>

37
src/libs/setting_hooks.js Normal file
View File

@@ -0,0 +1,37 @@
import { publish } from '@/libs/events'
window.settings = {
setFPS: (fps) => {
publish('settings:fps', fps)
},
displayLogo: (flag) => {
publish('settings:logo', !flag)
},
resizeLogo: (value) => {
publish('settings:ratio', value)
},
opacityLogo: (value) => {
publish('settings:opacity', value)
},
setLogo: (url) => {
publish('settings:image:set', 'file:///' + url)
},
setBackground: (url) => {
publish('settings:background:set', 'file:///' + url)
},
positionPadding: (location, value) => {
publish('settings:position:set', {
key: location,
value: value
})
},
open: () => {
publish('settings:open')
},
close: () => {
publish('settings:close')
},
reset: () => {
publish('settings:reset')
}
}

View File

@@ -3,7 +3,6 @@ import { publish } from '@/libs/events'
window.wallpaperPropertyListener = {
applyGeneralProperties: function (properties) {
if (properties.fps) {
// use custom event
publish('settings:fps', properties.fps.value)
}
},

View File

@@ -1,24 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@@ -1,27 +0,0 @@
import path from 'path'
import { parse } from 'yaml'
import fs from 'fs'
function read_yaml(file_dir) {
const include = {
identify: value => value.startsWith('!include'),
tag: '!include',
resolve(str) {
const dir = path.resolve(BASEDIR, str)
const data = read_yaml(dir)
return data
}
}
const file = fs.readFileSync(file_dir, 'utf8')
return parse(file, {
customTags: [include],
})
}
const BASEDIR = path.resolve(__dirname, '..', '..')
const CONFIG = read_yaml(path.join(BASEDIR, 'config.yaml'))
export default {
basedir: BASEDIR,
...CONFIG
}

View File

@@ -1,13 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/_index/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>aklive2d directory</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>

View File

@@ -1,23 +0,0 @@
{
"name": "index",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.6.1",
"yaml": "^2.2.1"
},
"devDependencies": {
"@types/react": "^18.0.26",
"@types/react-dom": "^18.0.9",
"@vitejs/plugin-react-swc": "^3.0.0",
"vite": "^4.0.0"
}
}

View File

@@ -1,571 +0,0 @@
lockfileVersion: 5.4
specifiers:
'@types/react': ^18.0.26
'@types/react-dom': ^18.0.9
'@vitejs/plugin-react-swc': ^3.0.0
react: ^18.2.0
react-dom: ^18.2.0
react-router-dom: ^6.6.1
vite: ^4.0.0
yaml: ^2.2.1
dependencies:
react: 18.2.0
react-dom: 18.2.0_react@18.2.0
react-router-dom: 6.6.1_biqbaboplfbrettd7655fr4n2y
yaml: 2.2.1
devDependencies:
'@types/react': 18.0.26
'@types/react-dom': 18.0.10
'@vitejs/plugin-react-swc': 3.0.1_vite@4.0.3
vite: 4.0.3
packages:
/@esbuild/android-arm/0.16.12:
resolution: {integrity: sha512-CTWgMJtpCyCltrvipZrrcjjRu+rzm6pf9V8muCsJqtKujR3kPmU4ffbckvugNNaRmhxAF1ZI3J+0FUIFLFg8KA==}
engines: {node: '>=12'}
cpu: [arm]
os: [android]
requiresBuild: true
dev: true
optional: true
/@esbuild/android-arm64/0.16.12:
resolution: {integrity: sha512-0LacmiIW+X0/LOLMZqYtZ7d4uY9fxYABAYhSSOu+OGQVBqH4N5eIYgkT7bBFnR4Nm3qo6qS3RpHKVrDASqj/uQ==}
engines: {node: '>=12'}
cpu: [arm64]
os: [android]
requiresBuild: true
dev: true
optional: true
/@esbuild/android-x64/0.16.12:
resolution: {integrity: sha512-sS5CR3XBKQXYpSGMM28VuiUnbX83Z+aWPZzClW+OB2JquKqxoiwdqucJ5qvXS8pM6Up3RtJfDnRQZkz3en2z5g==}
engines: {node: '>=12'}
cpu: [x64]
os: [android]
requiresBuild: true
dev: true
optional: true
/@esbuild/darwin-arm64/0.16.12:
resolution: {integrity: sha512-Dpe5hOAQiQRH20YkFAg+wOpcd4PEuXud+aGgKBQa/VriPJA8zuVlgCOSTwna1CgYl05lf6o5els4dtuyk1qJxQ==}
engines: {node: '>=12'}
cpu: [arm64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/@esbuild/darwin-x64/0.16.12:
resolution: {integrity: sha512-ApGRA6X5txIcxV0095X4e4KKv87HAEXfuDRcGTniDWUUN+qPia8sl/BqG/0IomytQWajnUn4C7TOwHduk/FXBQ==}
engines: {node: '>=12'}
cpu: [x64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/@esbuild/freebsd-arm64/0.16.12:
resolution: {integrity: sha512-AMdK2gA9EU83ccXCWS1B/KcWYZCj4P3vDofZZkl/F/sBv/fphi2oUqUTox/g5GMcIxk8CF1CVYTC82+iBSyiUg==}
engines: {node: '>=12'}
cpu: [arm64]
os: [freebsd]
requiresBuild: true
dev: true
optional: true
/@esbuild/freebsd-x64/0.16.12:
resolution: {integrity: sha512-KUKB9w8G/xaAbD39t6gnRBuhQ8vIYYlxGT2I+mT6UGRnCGRr1+ePFIGBQmf5V16nxylgUuuWVW1zU2ktKkf6WQ==}
engines: {node: '>=12'}
cpu: [x64]
os: [freebsd]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-arm/0.16.12:
resolution: {integrity: sha512-vhDdIv6z4eL0FJyNVfdr3C/vdd/Wc6h1683GJsFoJzfKb92dU/v88FhWdigg0i6+3TsbSDeWbsPUXb4dif2abg==}
engines: {node: '>=12'}
cpu: [arm]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-arm64/0.16.12:
resolution: {integrity: sha512-29HXMLpLklDfmw7T2buGqq3HImSUaZ1ArmrPOMaNiZZQptOSZs32SQtOHEl8xWX5vfdwZqrBfNf8Te4nArVzKQ==}
engines: {node: '>=12'}
cpu: [arm64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-ia32/0.16.12:
resolution: {integrity: sha512-JFDuNDTTfgD1LJg7wHA42o2uAO/9VzHYK0leAVnCQE/FdMB599YMH73ux+nS0xGr79pv/BK+hrmdRin3iLgQjg==}
engines: {node: '>=12'}
cpu: [ia32]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-loong64/0.16.12:
resolution: {integrity: sha512-xTGzVPqm6WKfCC0iuj1fryIWr1NWEM8DMhAIo+4rFgUtwy/lfHl+Obvus4oddzRDbBetLLmojfVZGmt/g/g+Rw==}
engines: {node: '>=12'}
cpu: [loong64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-mips64el/0.16.12:
resolution: {integrity: sha512-zI1cNgHa3Gol+vPYjIYHzKhU6qMyOQrvZ82REr5Fv7rlh5PG6SkkuCoH7IryPqR+BK2c/7oISGsvPJPGnO2bHQ==}
engines: {node: '>=12'}
cpu: [mips64el]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-ppc64/0.16.12:
resolution: {integrity: sha512-/C8OFXExoMmvTDIOAM54AhtmmuDHKoedUd0Otpfw3+AuuVGemA1nQK99oN909uZbLEU6Bi+7JheFMG3xGfZluQ==}
engines: {node: '>=12'}
cpu: [ppc64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-riscv64/0.16.12:
resolution: {integrity: sha512-qeouyyc8kAGV6Ni6Isz8hUsKMr00EHgVwUKWNp1r4l88fHEoNTDB8mmestvykW6MrstoGI7g2EAsgr0nxmuGYg==}
engines: {node: '>=12'}
cpu: [riscv64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-s390x/0.16.12:
resolution: {integrity: sha512-s9AyI/5vz1U4NNqnacEGFElqwnHusWa81pskAf8JNDM2eb6b2E6PpBmT8RzeZv6/TxE6/TADn2g9bb0jOUmXwQ==}
engines: {node: '>=12'}
cpu: [s390x]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-x64/0.16.12:
resolution: {integrity: sha512-e8YA7GQGLWhvakBecLptUiKxOk4E/EPtSckS1i0MGYctW8ouvNUoh7xnU15PGO2jz7BYl8q1R6g0gE5HFtzpqQ==}
engines: {node: '>=12'}
cpu: [x64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/netbsd-x64/0.16.12:
resolution: {integrity: sha512-z2+kUxmOqBS+6SRVd57iOLIHE8oGOoEnGVAmwjm2aENSP35HPS+5cK+FL1l+rhrsJOFIPrNHqDUNechpuG96Sg==}
engines: {node: '>=12'}
cpu: [x64]
os: [netbsd]
requiresBuild: true
dev: true
optional: true
/@esbuild/openbsd-x64/0.16.12:
resolution: {integrity: sha512-PAonw4LqIybwn2/vJujhbg1N9W2W8lw9RtXIvvZoyzoA/4rA4CpiuahVbASmQohiytRsixbNoIOUSjRygKXpyA==}
engines: {node: '>=12'}
cpu: [x64]
os: [openbsd]
requiresBuild: true
dev: true
optional: true
/@esbuild/sunos-x64/0.16.12:
resolution: {integrity: sha512-+wr1tkt1RERi+Zi/iQtkzmMH4nS8+7UIRxjcyRz7lur84wCkAITT50Olq/HiT4JN2X2bjtlOV6vt7ptW5Gw60Q==}
engines: {node: '>=12'}
cpu: [x64]
os: [sunos]
requiresBuild: true
dev: true
optional: true
/@esbuild/win32-arm64/0.16.12:
resolution: {integrity: sha512-XEjeUSHmjsAOJk8+pXJu9pFY2O5KKQbHXZWQylJzQuIBeiGrpMeq9sTVrHefHxMOyxUgoKQTcaTS+VK/K5SviA==}
engines: {node: '>=12'}
cpu: [arm64]
os: [win32]
requiresBuild: true
dev: true
optional: true
/@esbuild/win32-ia32/0.16.12:
resolution: {integrity: sha512-eRKPM7e0IecUAUYr2alW7JGDejrFJXmpjt4MlfonmQ5Rz9HWpKFGCjuuIRgKO7W9C/CWVFXdJ2GjddsBXqQI4A==}
engines: {node: '>=12'}
cpu: [ia32]
os: [win32]
requiresBuild: true
dev: true
optional: true
/@esbuild/win32-x64/0.16.12:
resolution: {integrity: sha512-iPYKN78t3op2+erv2frW568j1q0RpqX6JOLZ7oPPaAV1VaF7dDstOrNw37PVOYoTWE11pV4A1XUitpdEFNIsPg==}
engines: {node: '>=12'}
cpu: [x64]
os: [win32]
requiresBuild: true
dev: true
optional: true
/@remix-run/router/1.2.1:
resolution: {integrity: sha512-XiY0IsyHR+DXYS5vBxpoBe/8veTeoRpMHP+vDosLZxL5bnpetzI0igkxkLZS235ldLzyfkxF+2divEwWHP3vMQ==}
engines: {node: '>=14'}
dev: false
/@swc/core-darwin-arm64/1.3.24:
resolution: {integrity: sha512-rR+9UpWm+fGXcipsjCst2hIL1GYIbo0YTLhJZWdIpQD6KRHHJMFXiydMgQQkDj2Ml7HpqUVgxj6m4ZWYL8b0OA==}
engines: {node: '>=10'}
cpu: [arm64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/@swc/core-darwin-x64/1.3.24:
resolution: {integrity: sha512-px+5vkGtgPH0m3FkkTBHynlRdS5rRz+lK+wiXIuBZFJSySWFl6RkKbvwkD+sf0MpazQlqwlv/rTOGJBw6oDffg==}
engines: {node: '>=10'}
cpu: [x64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/@swc/core-linux-arm-gnueabihf/1.3.24:
resolution: {integrity: sha512-jLs8ZOdTV4UW4J12E143QJ4mOMONQtqgAnuhBbRuWFzQ3ny1dfoC3P1jNWAJ2Xi59XdxAIXn0PggPNH4Kh34kw==}
engines: {node: '>=10'}
cpu: [arm]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@swc/core-linux-arm64-gnu/1.3.24:
resolution: {integrity: sha512-A/v0h70BekrwGpp1DlzIFGcHQ3QQ2PexXcnnuIBZeMc9gNmHlcZmg3EcwAnaUDiokhNuSUFA/wV94yk1OqmSkw==}
engines: {node: '>=10'}
cpu: [arm64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@swc/core-linux-arm64-musl/1.3.24:
resolution: {integrity: sha512-pbc9eArWPTiMrbpS/pJo0IiQNAKAQBcBIDjWBGP1tcw2iDXYLw4bruwz9kI/VjakbshWb8MoE4T5ClkeuULvSw==}
engines: {node: '>=10'}
cpu: [arm64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@swc/core-linux-x64-gnu/1.3.24:
resolution: {integrity: sha512-pP5pOLlY1xd352qo7rTlpVPUI9/9VhOd4b3Lk+LzfZDq9bTL2NDlGfyrPiwa5DGHMSzrugH56K2J68eutkxYVA==}
engines: {node: '>=10'}
cpu: [x64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@swc/core-linux-x64-musl/1.3.24:
resolution: {integrity: sha512-phNbP7zGp+Wcyxq1Qxlpe5KkxO7WLT2kVQUC7aDFGlVdCr+xdXsfH1MzheHtnr0kqTVQX1aiM8XXXHfFxR0oNA==}
engines: {node: '>=10'}
cpu: [x64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@swc/core-win32-arm64-msvc/1.3.24:
resolution: {integrity: sha512-qhbiJTWAOqyR+K9xnGmCkOWSz2EmWpDBstEJCEOTc6FZiEdbiTscDmqTcMbCKaTHGu8t+6erVA4t65/Eg6uWPA==}
engines: {node: '>=10'}
cpu: [arm64]
os: [win32]
requiresBuild: true
dev: true
optional: true
/@swc/core-win32-ia32-msvc/1.3.24:
resolution: {integrity: sha512-JfghIlscE4Rz+Lc08lSoDh+R0cWxrISed5biogFfE6vZqhaDnw3E5Qshqw7O3pIaiq8L2u1nmzuyP581ZmpbRA==}
engines: {node: '>=10'}
cpu: [ia32]
os: [win32]
requiresBuild: true
dev: true
optional: true
/@swc/core-win32-x64-msvc/1.3.24:
resolution: {integrity: sha512-3AmJRr0hwciwDBbzUNqaftvppzS8v9X/iv/Wl7YaVLBVpPfQvaZzfqLycvNMGLZb5vIKXR/u58txg3dRBGsJtw==}
engines: {node: '>=10'}
cpu: [x64]
os: [win32]
requiresBuild: true
dev: true
optional: true
/@swc/core/1.3.24:
resolution: {integrity: sha512-QMOTd0AgiUT3K1crxLRqd3gw0f3FC8hhH1vvlIlryvYqU4c+FJ/T2G4ZhMKLxQlZ/jX6Rhk0gKINZRBxy2GFyQ==}
engines: {node: '>=10'}
hasBin: true
requiresBuild: true
optionalDependencies:
'@swc/core-darwin-arm64': 1.3.24
'@swc/core-darwin-x64': 1.3.24
'@swc/core-linux-arm-gnueabihf': 1.3.24
'@swc/core-linux-arm64-gnu': 1.3.24
'@swc/core-linux-arm64-musl': 1.3.24
'@swc/core-linux-x64-gnu': 1.3.24
'@swc/core-linux-x64-musl': 1.3.24
'@swc/core-win32-arm64-msvc': 1.3.24
'@swc/core-win32-ia32-msvc': 1.3.24
'@swc/core-win32-x64-msvc': 1.3.24
dev: true
/@types/prop-types/15.7.5:
resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==}
dev: true
/@types/react-dom/18.0.10:
resolution: {integrity: sha512-E42GW/JA4Qv15wQdqJq8DL4JhNpB3prJgjgapN3qJT9K2zO5IIAQh4VXvCEDupoqAwnz0cY4RlXeC/ajX5SFHg==}
dependencies:
'@types/react': 18.0.26
dev: true
/@types/react/18.0.26:
resolution: {integrity: sha512-hCR3PJQsAIXyxhTNSiDFY//LhnMZWpNNr5etoCqx/iUfGc5gXWtQR2Phl908jVR6uPXacojQWTg4qRpkxTuGug==}
dependencies:
'@types/prop-types': 15.7.5
'@types/scheduler': 0.16.2
csstype: 3.1.1
dev: true
/@types/scheduler/0.16.2:
resolution: {integrity: sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==}
dev: true
/@vitejs/plugin-react-swc/3.0.1_vite@4.0.3:
resolution: {integrity: sha512-3GQ2oruZO9j8dSHcI0MUeOZQBhjYyDQsF/pKY4Px+CJxn0M16OhgFeEzUjeuwci4zhhjoNIDE9aFNaV5GMQ09g==}
peerDependencies:
vite: ^4
dependencies:
'@swc/core': 1.3.24
vite: 4.0.3
dev: true
/csstype/3.1.1:
resolution: {integrity: sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==}
dev: true
/esbuild/0.16.12:
resolution: {integrity: sha512-eq5KcuXajf2OmivCl4e89AD3j8fbV+UTE9vczEzq5haA07U9oOTzBWlh3+6ZdjJR7Rz2QfWZ2uxZyhZxBgJ4+g==}
engines: {node: '>=12'}
hasBin: true
requiresBuild: true
optionalDependencies:
'@esbuild/android-arm': 0.16.12
'@esbuild/android-arm64': 0.16.12
'@esbuild/android-x64': 0.16.12
'@esbuild/darwin-arm64': 0.16.12
'@esbuild/darwin-x64': 0.16.12
'@esbuild/freebsd-arm64': 0.16.12
'@esbuild/freebsd-x64': 0.16.12
'@esbuild/linux-arm': 0.16.12
'@esbuild/linux-arm64': 0.16.12
'@esbuild/linux-ia32': 0.16.12
'@esbuild/linux-loong64': 0.16.12
'@esbuild/linux-mips64el': 0.16.12
'@esbuild/linux-ppc64': 0.16.12
'@esbuild/linux-riscv64': 0.16.12
'@esbuild/linux-s390x': 0.16.12
'@esbuild/linux-x64': 0.16.12
'@esbuild/netbsd-x64': 0.16.12
'@esbuild/openbsd-x64': 0.16.12
'@esbuild/sunos-x64': 0.16.12
'@esbuild/win32-arm64': 0.16.12
'@esbuild/win32-ia32': 0.16.12
'@esbuild/win32-x64': 0.16.12
dev: true
/fsevents/2.3.2:
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
requiresBuild: true
dev: true
optional: true
/function-bind/1.1.1:
resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==}
dev: true
/has/1.0.3:
resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==}
engines: {node: '>= 0.4.0'}
dependencies:
function-bind: 1.1.1
dev: true
/is-core-module/2.11.0:
resolution: {integrity: sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==}
dependencies:
has: 1.0.3
dev: true
/js-tokens/4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
dev: false
/loose-envify/1.4.0:
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
hasBin: true
dependencies:
js-tokens: 4.0.0
dev: false
/nanoid/3.3.4:
resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
dev: true
/path-parse/1.0.7:
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
dev: true
/picocolors/1.0.0:
resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
dev: true
/postcss/8.4.20:
resolution: {integrity: sha512-6Q04AXR1212bXr5fh03u8aAwbLxAQNGQ/Q1LNa0VfOI06ZAlhPHtQvE4OIdpj4kLThXilalPnmDSOD65DcHt+g==}
engines: {node: ^10 || ^12 || >=14}
dependencies:
nanoid: 3.3.4
picocolors: 1.0.0
source-map-js: 1.0.2
dev: true
/react-dom/18.2.0_react@18.2.0:
resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==}
peerDependencies:
react: ^18.2.0
dependencies:
loose-envify: 1.4.0
react: 18.2.0
scheduler: 0.23.0
dev: false
/react-router-dom/6.6.1_biqbaboplfbrettd7655fr4n2y:
resolution: {integrity: sha512-u+8BKUtelStKbZD5UcY0NY90WOzktrkJJhyhNg7L0APn9t1qJNLowzrM9CHdpB6+rcPt6qQrlkIXsTvhuXP68g==}
engines: {node: '>=14'}
peerDependencies:
react: '>=16.8'
react-dom: '>=16.8'
dependencies:
'@remix-run/router': 1.2.1
react: 18.2.0
react-dom: 18.2.0_react@18.2.0
react-router: 6.6.1_react@18.2.0
dev: false
/react-router/6.6.1_react@18.2.0:
resolution: {integrity: sha512-YkvlYRusnI/IN0kDtosUCgxqHeulN5je+ew8W+iA1VvFhf86kA+JEI/X/8NqYcr11hCDDp906S+SGMpBheNeYQ==}
engines: {node: '>=14'}
peerDependencies:
react: '>=16.8'
dependencies:
'@remix-run/router': 1.2.1
react: 18.2.0
dev: false
/react/18.2.0:
resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==}
engines: {node: '>=0.10.0'}
dependencies:
loose-envify: 1.4.0
dev: false
/resolve/1.22.1:
resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==}
hasBin: true
dependencies:
is-core-module: 2.11.0
path-parse: 1.0.7
supports-preserve-symlinks-flag: 1.0.0
dev: true
/rollup/3.9.0:
resolution: {integrity: sha512-nGGylpmblyjTpF4lEUPgmOw6OVxRvnI6Iuuh6Lz4O/X66cVOX1XJSsqP1YamxQ+mPuFE7qJxLFDSCk8rNv5dDw==}
engines: {node: '>=14.18.0', npm: '>=8.0.0'}
hasBin: true
optionalDependencies:
fsevents: 2.3.2
dev: true
/scheduler/0.23.0:
resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==}
dependencies:
loose-envify: 1.4.0
dev: false
/source-map-js/1.0.2:
resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
engines: {node: '>=0.10.0'}
dev: true
/supports-preserve-symlinks-flag/1.0.0:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'}
dev: true
/vite/4.0.3:
resolution: {integrity: sha512-HvuNv1RdE7deIfQb8mPk51UKjqptO/4RXZ5yXSAvurd5xOckwS/gg8h9Tky3uSbnjYTgUm0hVCet1cyhKd73ZA==}
engines: {node: ^14.18.0 || >=16.0.0}
hasBin: true
peerDependencies:
'@types/node': '>= 14'
less: '*'
sass: '*'
stylus: '*'
sugarss: '*'
terser: ^5.4.0
peerDependenciesMeta:
'@types/node':
optional: true
less:
optional: true
sass:
optional: true
stylus:
optional: true
sugarss:
optional: true
terser:
optional: true
dependencies:
esbuild: 0.16.12
postcss: 8.4.20
resolve: 1.22.1
rollup: 3.9.0
optionalDependencies:
fsevents: 2.3.2
dev: true
/yaml/2.2.1:
resolution: {integrity: sha512-e0WHiYql7+9wr4cWMx3TVQrNwejKaEe7/rHNmQmqRjazfOP5W8PB6Jpebb5o6fIapbz9o9+2ipcaTM2ZwDI6lw==}
engines: {node: '>= 14'}
dev: false

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1,41 +0,0 @@
#root {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.react:hover {
filter: drop-shadow(0 0 2em #61dafbaa);
}
@keyframes logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@media (prefers-reduced-motion: no-preference) {
a:nth-of-type(2) .logo {
animation: logo-spin infinite 20s linear;
}
}
.card {
padding: 2em;
}
.read-the-docs {
color: #888;
}

View File

@@ -1,34 +0,0 @@
import { useState } from 'react'
import reactLogo from './assets/react.svg'
import './App.css'
function App() {
const [count, setCount] = useState(0)
return (
<div className="App">
<div>
<a href="https://vitejs.dev" target="_blank">
<img src="/vite.svg" className="logo" alt="Vite logo" />
</a>
<a href="https://reactjs.org" target="_blank">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
</div>
<h1>Vite + React</h1>
<div className="card">
<button onClick={() => setCount((count) => count + 1)}>
count is {count}
</button>
<p>
Edit <code>src/App.jsx</code> and save to test HMR
</p>
</div>
<p className="read-the-docs">
Click on the Vite and React logos to learn more
</p>
</div>
)
}
export default App

View File

@@ -1 +0,0 @@
[{"name": "mizuki_summer_feast", "link": "mizuki_summer_feast", "type": "skin", "date": "2022/12"}, {"name": "texas_the_omertosa", "link": "texas_the_omertosa", "type": "operator", "date": "2022/11"}, {"name": "rosmontis_become_anew", "link": "rosmontis_become_anew", "type": "skin", "date": "2022/11"}, {"name": "passager_dream_in_a_moment", "link": "passager_dream_in_a_moment", "type": "skin", "date": "2022/11"}, {"name": "nearl_relight", "link": "nearl_relight", "type": "skin", "date": "2022/11"}, {"name": "lee_trust_your_eyes", "link": "lee_trust_your_eyes", "type": "skin", "date": "2022/10"}, {"name": "surtr_colorful_wonderland", "link": "surtr_colorful_wonderland", "type": "skin", "date": "2022/08"}, {"name": "gavial", "link": "gavial", "type": "operator", "date": "2022/08"}, {"name": "specter", "link": "specter", "type": "operator", "date": "2022/05"}, {"name": "skadi_sublimation", "link": "skadi_sublimation", "type": "skin", "date": "2022/05"}, {"name": "phatom_focus", "link": "phatom_focus", "type": "skin", "date": "2022/04"}, {"name": "ling", "link": "ling", "type": "operator", "date": "2022/01"}, {"name": "dusk_everything_is_a_miracle", "link": "dusk_everything_is_a_miracle", "type": "skin", "date": "2022/01"}, {"name": "nearl", "link": "nearl", "type": "operator", "date": "2021/11"}, {"name": "chen", "link": "chen", "type": "operator", "date": "2021/08"}, {"name": "skadi", "link": "skadi", "type": "operator", "date": "2021/05"}, {"name": "nian_unfettered_freedom", "link": "nian_unfettered_freedom", "type": "skin", "date": "2021/02"}, {"name": "dusk", "link": "dusk", "type": "operator", "date": "2021/02"}, {"name": "w_fugue", "link": "w_fugue", "type": "skin", "date": "2020/11"}, {"name": "rosmontis", "link": "rosmontis", "type": "operator", "date": "2020/11"}, {"name": "w", "link": "w", "type": "operator", "date": "2020/05"}, {"name": "nian", "link": "nian", "type": "operator", "date": "2020/01"}]

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>

Before

Width:  |  Height:  |  Size: 4.0 KiB

View File

@@ -1,70 +0,0 @@
:root {
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
font-size: 16px;
line-height: 24px;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}

View File

@@ -1,10 +0,0 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import './index.css'
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)

View File

@@ -1,25 +0,0 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
import path from 'path'
import config from './config'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
build: {
outDir: path.resolve(config.basedir, config.server.release_folder),
assetsDir: '_index',
emptyOutDir: false,
rollupOptions: {
output: {
manualChunks: {
},
}
}
},
})

View File

@@ -1,24 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

View File

@@ -1,20 +0,0 @@
import { defineConfig } from 'vite'
import preact from '@preact/preset-vite'
import path from 'path'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [preact()],
base: "",
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
'logo': path.resolve(__dirname, `./src/assets/logo_rhodes_override.png`), // TODO: use env
'fallback': path.resolve(__dirname, `./src/assets/char_1013_chen2_2.png`), // TODO
},
},
build: {
chunkSizeWarningLimit: 10000,
// outDir: path.resolve(config.basedir, config.server.release_folder),
},
})

22
vite.config.js Normal file
View File

@@ -0,0 +1,22 @@
import { defineConfig } from 'vite'
import preact from '@preact/preset-vite'
import path from 'path'
import data from './preprocessing'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [preact()],
base: "",
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
'!': path.resolve(data.config.basedir, data.config.folder.operator, data.OPERATOR_NAME),
'#': path.resolve(data.config.basedir, data.config.folder.operator, data.OPERATOR_NAME, `${data.config.operators[data.OPERATOR_NAME].filename}.json`),
},
},
build: {
outDir: path.resolve(data.config.basedir, data.config.folder.release, data.OPERATOR_NAME),
emptyOutDir: false,
chunkSizeWarningLimit: 10000,
},
})