Compare commits

...

8 commits

Author SHA1 Message Date
smartfrigde
a934aa50b1 feat: add showExperimentalPluginMenu toggle 2026-07-19 22:34:30 +02:00
smartfrigde
5963a451ca fix: type check 2026-07-19 21:15:40 +02:00
dependabot[bot]
1bedb9e431
chore(deps): bump adm-zip from 0.5.17 to 0.6.0 in the npm_and_yarn group across 1 directory (#1124)
Signed-off-by: dependabot[bot] <support@github.com>
2026-07-19 19:05:33 +02:00
youtsuho
d27b05f85a
refactor(themes): redesigned themes page UI with modern card layout (#1108) 2026-07-19 19:05:19 +02:00
youtsuho
526d6dcce8
perf: cache theme list with fs.watch to avoid readdirSync (#1118) 2026-07-19 19:03:43 +02:00
youtsuho
0d80100e44
ci: fix biome lint errors and apply formatting (#1117) 2026-07-19 19:03:28 +02:00
Patryk Żurek
4b8394f196
Revert "feat: custom invite modal with shelter plugin" (#1126) 2026-07-19 18:54:29 +02:00
youtsuho
7e92800c12
feat: custom invite modal with shelter plugin (#1125) 2026-07-19 18:53:36 +02:00
16 changed files with 388 additions and 189 deletions

View file

@ -64,6 +64,8 @@
"settings-csp-strict": "Strict",
"settings-csp-none": "None",
"settings-csp-vanilla": "Vanilla",
"settings-showExperimentalPluginMenu": "Show experimental plugin menu",
"settings-showExperimentalPluginMenu-desc": "Show the experimental plugin menu in settings. This is a temporary setting until the plugin system is fully implemented.",
"settings-firstTimeCrash": "We're setting things up for you!",
"settings-firstTimeCrash-desc": "Settings are not available on a first-time launch. Please use a button below to restart and settings should be ready after the restart.",
"settings-useSystemCssEditor": "Use system CSS editor",
@ -293,6 +295,7 @@
"themes-openThemesFolder": "Open themes folder",
"themes-import": "Import",
"themes-importUrlPlaceholder": "https://raw.githubusercontent.com/... [.theme.css]",
"themes-installed": "Installed Themes",
"games-registeredGames": "Registered Games",
"games-refreshList": "Refresh list",
"games-add": "Add",

View file

@ -43,18 +43,18 @@
"@xmldom/xmldom": "^0.9.10",
"app-builder-lib": "^26.8.1",
"babel-preset-solid": "^1.9.12",
"electron": "42.3.3",
"electron": "43.1.1",
"electron-builder": "26.8.1",
"lucide-solid": "^1.16.0",
"rolldown": "1.0.3",
"rollup-plugin-copy": "^3.5.0",
"solid-js": "^1.9.13",
"solid-js": "^1.9.14",
"solid-motionone": "^1.0.4",
"typescript": "^6.0.3",
"xml-formatter": "^3.7.0"
},
"dependencies": {
"adm-zip": "^0.5.17",
"adm-zip": "^0.6.0",
"arrpc": "github:Legcord/arrpc#efe7589762470d32b9ba10d529be5acc23cd0e19",
"electron-context-menu": "^4.1.2",
"electron-is-dev": "^3.0.1",
@ -67,6 +67,6 @@
"optionalDependencies": {
"@vencord/venmic": "^6.1.0"
},
"packageManager": "pnpm@11.4.0+sha512.f0febc7e37552ab485494a914241b338e0b3580b93d54ce31f00933015880863129038a1b4ae4e414a0ee63ac35bf21197e990172c4a68256450b5636310968f",
"packageManager": "pnpm@11.10.0",
"package-manager-strict": false
}

167
pnpm-lock.yaml generated
View file

@ -12,8 +12,8 @@ importers:
.:
dependencies:
adm-zip:
specifier: ^0.5.17
version: 0.5.17
specifier: ^0.6.0
version: 0.6.0
arrpc:
specifier: github:Legcord/arrpc#efe7589762470d32b9ba10d529be5acc23cd0e19
version: https://codeload.github.com/Legcord/arrpc/tar.gz/efe7589762470d32b9ba10d529be5acc23cd0e19
@ -44,7 +44,7 @@ importers:
version: 2.4.16
'@rolldown-plugin/solid':
specifier: ^0.0.4
version: 0.0.4(csstype@3.2.3)(rolldown@1.0.3)(seroval@1.5.4)(solid-js@1.9.13)
version: 0.0.4(csstype@3.2.3)(rolldown@1.0.3)(seroval@1.5.5)(solid-js@1.9.14)
'@rollup/plugin-esm-shim':
specifier: ^0.1.8
version: 0.1.8
@ -71,16 +71,16 @@ importers:
version: 26.8.1(dmg-builder@26.8.1)(electron-builder-squirrel-windows@26.8.1)
babel-preset-solid:
specifier: ^1.9.12
version: 1.9.12(@babel/core@8.0.0-rc.6)(solid-js@1.9.13)
version: 1.9.12(@babel/core@8.0.0-rc.6)(solid-js@1.9.14)
electron:
specifier: 42.3.3
version: 42.3.3
specifier: 43.1.1
version: 43.1.1
electron-builder:
specifier: 26.8.1
version: 26.8.1(electron-builder-squirrel-windows@26.8.1)
lucide-solid:
specifier: ^1.16.0
version: 1.16.0(solid-js@1.9.13)
version: 1.16.0(solid-js@1.9.14)
rolldown:
specifier: 1.0.3
version: 1.0.3
@ -88,11 +88,11 @@ importers:
specifier: ^3.5.0
version: 3.5.0
solid-js:
specifier: ^1.9.13
version: 1.9.13
specifier: ^1.9.14
version: 1.9.14
solid-motionone:
specifier: ^1.0.4
version: 1.0.4(solid-js@1.9.13)
version: 1.0.4(solid-js@1.9.14)
typescript:
specifier: ^6.0.3
version: 6.0.3
@ -370,6 +370,10 @@ packages:
resolution: {integrity: sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig==}
engines: {node: '>= 8.9.0'}
'@electron-internal/extract-zip@1.0.4':
resolution: {integrity: sha512-Zr1Vs7E9tpCNhZHDAbFVXc2gEVCG9RqPDjrno5+bdgB6LRAuvgyMHJut4NCVyYwtAieapMzc3fiQ3CSTi75ARg==}
engines: {node: '>=22.12.0'}
'@electron/asar@3.4.1':
resolution: {integrity: sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA==}
engines: {node: '>=10.12.0'}
@ -975,9 +979,6 @@ packages:
'@types/ws@8.18.1':
resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==}
'@types/yauzl@2.10.3':
resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==}
'@uwu/lune@1.6.2':
resolution: {integrity: sha512-0/e1zFGm7pBISyuNvhKMqdg4gEQOpBtf8wdquJaUDqA0enrV55iwMWrrCdJHlfx77K1D8hjnAUGvCXxwFebg5Q==}
hasBin: true
@ -1007,9 +1008,9 @@ packages:
engines: {node: '>=0.4.0'}
hasBin: true
adm-zip@0.5.17:
resolution: {integrity: sha512-+Ut8d9LLqwEvHHJl1+PIHqoyDxFgVN847JTVM3Izi3xHDWPE4UtzzXysMZQs64DMcrJfBeS/uoEP4AD3HQHnQQ==}
engines: {node: '>=12.0'}
adm-zip@0.6.0:
resolution: {integrity: sha512-XleryMhbuksdKtofnWZ9Sk+4CUTbms4Mb/EU32SZwToAyZ5RgVos/ki8n+yr0LWHOGKuakbXTuuYNHLQjhddgg==}
engines: {node: '>=14.0'}
agent-base@6.0.2:
resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
@ -1073,7 +1074,7 @@ packages:
engines: {node: '>=8'}
arrpc@https://codeload.github.com/Legcord/arrpc/tar.gz/efe7589762470d32b9ba10d529be5acc23cd0e19:
resolution: {gitHosted: true, tarball: https://codeload.github.com/Legcord/arrpc/tar.gz/efe7589762470d32b9ba10d529be5acc23cd0e19}
resolution: {gitHosted: true, integrity: sha512-oxxPz88l9/VDvmgkepTGlhstBkt2kAO4SqxUjRu+pql4mQ2GSagDCjsYv4Lg3KnuazbuqRkl0K5KzOUzdBBJZQ==, tarball: https://codeload.github.com/Legcord/arrpc/tar.gz/efe7589762470d32b9ba10d529be5acc23cd0e19}
version: 3.5.0
hasBin: true
@ -1158,9 +1159,6 @@ packages:
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true
buffer-crc32@0.2.13:
resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==}
buffer-from@1.1.2:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
@ -1410,8 +1408,8 @@ packages:
resolution: {integrity: sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg==}
engines: {node: '>=8.0.0'}
electron@42.3.3:
resolution: {integrity: sha512-0MwYp9wTb7TrtTalOYqeW+suqd9T/Znstr/nDLKqFGIjHdBZX339guo3mQqTPURRZ/UQmYM4uMpzKpI5wLptfQ==}
electron@43.1.1:
resolution: {integrity: sha512-I5c5vfuVvaXpWx3IZdwvXgxQW44+e7OP1wXGVQkogLeSFSkUZ6sLCcWV05AdEcs65AO5tAIJJwbp7ixw+LdarA==}
engines: {node: '>= 22.12.0'}
hasBin: true
@ -1494,11 +1492,6 @@ packages:
resolution: {integrity: sha512-yblEwXAbGv1VQDmow7s38W77hzAgJAO50ztBLMcUyUBfxv1HC+LGwtiEN+Co6LtlqT/5uwVOxsD4TNIilWhwdQ==}
engines: {node: '>=4'}
extract-zip@2.0.1:
resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==}
engines: {node: '>= 10.17.0'}
hasBin: true
extsprintf@1.4.1:
resolution: {integrity: sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==}
engines: {'0': node >=0.6.0}
@ -1557,6 +1550,10 @@ packages:
resolution: {integrity: sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==}
engines: {node: '>=14.14'}
fs-extra@11.3.6:
resolution: {integrity: sha512-w8ZNZr2mKIc7qeNaQ9AVPT1+iFaI+Avd4xudVOvdDJ8VytREi1Ft5Ih7hd9jjehod8vAM5GMsfQ/TpPf4EyoEA==}
engines: {node: '>=14.14'}
fs-extra@7.0.1:
resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==}
engines: {node: '>=6 <7 || >=8'}
@ -2150,9 +2147,6 @@ packages:
resolution: {integrity: sha512-eRWB5LBz7PpDu4PUlwT0PhnQfTQJlDDdPa35urV4Osrm0t0AqQFGn+UIkU3klZvwJ8KPO3VbBFsXquA6p6kqZw==}
engines: {node: '>=12', npm: '>=6'}
pend@1.2.0:
resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==}
picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
@ -2342,14 +2336,14 @@ packages:
resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==}
engines: {node: '>=10'}
seroval-plugins@1.5.4:
resolution: {integrity: sha512-S0xQPhUTefAhNvNWFg0c1J8qJArHt5KdtJ/cFAofo06KD1MVSeFWyl4iiu+ApDIuw0WhjpOfCdgConOfAnLgkw==}
seroval-plugins@1.5.5:
resolution: {integrity: sha512-+BDhqYM6CEn3x09v44dpa9p6974FuUB2dxk+Ctn04k0cO1Zt6QODTXfmEZK0eBaTe/fJBvP4NMGuNJ+R8T+QMg==}
engines: {node: '>=10'}
peerDependencies:
seroval: ^1.0
seroval@1.5.4:
resolution: {integrity: sha512-46uFvgrXTVxZcUorgSSRZ4y+ieqLLQRMlG4bnCZKW3qI6BZm7Rg4ntMW4p1mILEEBZWrFlcpp0AyIIlM6jD9iw==}
seroval@1.5.5:
resolution: {integrity: sha512-bSjOuPcwPKLSJNhr9+bZxA20nQxVle5J5MNsYRVE6cIg7KpRLXGupymePavu0jrxlPiPsr4xGZSB8yUY2sH2sw==}
engines: {node: '>=10'}
set-blocking@2.0.0:
@ -2389,8 +2383,8 @@ packages:
solid-js@1.6.16:
resolution: {integrity: sha512-Ng4CahvLlpGA3BXIMiiMdwhI8WpObZ8gXqm97GCKR4+MpnODs6Pdpco+tmVCY/4ZDFaEKDxz7WRLAAdoXdlY7w==}
solid-js@1.9.13:
resolution: {integrity: sha512-6hJeJMOcEX8ktqjpDoJZEmld3ijvcvWBDtiXBm7f4332SiFN66QeAQI1REQshvyUoISsSeJ4PHDauKYbwao9JQ==}
solid-js@1.9.14:
resolution: {integrity: sha512-sAEXC0Kk0S1EDg+8ysEWJDbYhA3RRoEjwuySUGlKIemeo0I5YZfOyumNjNs9Sv3y2nmhD+0rW66ag2HsMuQiGQ==}
solid-motionone@1.0.4:
resolution: {integrity: sha512-aqEjgecoO9raDFznu/dEci7ORSmA26Kjj9J4Cn1Gyr0GZuOVdvsNxdxClTL9J40Aq/uYFx4GLwC8n70fMLHiuA==}
@ -2646,10 +2640,6 @@ packages:
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
engines: {node: '>=12'}
yauzl@3.3.1:
resolution: {integrity: sha512-RNPCUkiE/ZgO4w8i9U5yDQVHaFDdnzaFANElRvpJteCspvmv2VqrRb9lvS6odVD+jqI/zDsxAHJVsafpcheVQQ==}
engines: {node: '>=12'}
yocto-queue@0.1.0:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
@ -2986,6 +2976,8 @@ snapshots:
ajv: 6.15.0
ajv-keywords: 3.5.2(ajv@6.15.0)
'@electron-internal/extract-zip@1.0.4': {}
'@electron/asar@3.4.1':
dependencies:
commander: 5.1.0
@ -3071,7 +3063,7 @@ snapshots:
dependencies:
cross-dirname: 0.1.0
debug: 4.4.3
fs-extra: 11.3.5
fs-extra: 11.3.6
minimist: 1.2.8
postject: 1.0.0-alpha.6
transitivePeerDependencies:
@ -3320,7 +3312,7 @@ snapshots:
'@parcel/watcher-win32-x64': 2.5.6
optional: true
'@rolldown-plugin/solid@0.0.4(csstype@3.2.3)(rolldown@1.0.3)(seroval@1.5.4)(solid-js@1.9.13)':
'@rolldown-plugin/solid@0.0.4(csstype@3.2.3)(rolldown@1.0.3)(seroval@1.5.5)(solid-js@1.9.14)':
dependencies:
'@babel/core': 8.0.0-rc.6
'@babel/helper-module-imports': 8.0.0-rc.6
@ -3333,8 +3325,8 @@ snapshots:
html-entities: 2.6.0
parse5: 8.0.1
rolldown: 1.0.3
seroval: 1.5.4
solid-js: 1.9.13
seroval: 1.5.5
solid-js: 1.9.14
'@rolldown/binding-android-arm64@1.0.3':
optional: true
@ -3432,23 +3424,23 @@ snapshots:
'@sindresorhus/is@4.6.0': {}
'@solid-primitives/props@3.2.3(solid-js@1.9.13)':
'@solid-primitives/props@3.2.3(solid-js@1.9.14)':
dependencies:
'@solid-primitives/utils': 6.4.0(solid-js@1.9.13)
solid-js: 1.9.13
'@solid-primitives/utils': 6.4.0(solid-js@1.9.14)
solid-js: 1.9.14
'@solid-primitives/refs@1.1.3(solid-js@1.9.13)':
'@solid-primitives/refs@1.1.3(solid-js@1.9.14)':
dependencies:
'@solid-primitives/utils': 6.4.0(solid-js@1.9.13)
solid-js: 1.9.13
'@solid-primitives/utils': 6.4.0(solid-js@1.9.14)
solid-js: 1.9.14
'@solid-primitives/transition-group@1.1.2(solid-js@1.9.13)':
'@solid-primitives/transition-group@1.1.2(solid-js@1.9.14)':
dependencies:
solid-js: 1.9.13
solid-js: 1.9.14
'@solid-primitives/utils@6.4.0(solid-js@1.9.13)':
'@solid-primitives/utils@6.4.0(solid-js@1.9.14)':
dependencies:
solid-js: 1.9.13
solid-js: 1.9.14
'@szmarczak/http-timer@4.0.6':
dependencies:
@ -3528,11 +3520,6 @@ snapshots:
dependencies:
'@types/node': 25.9.1
'@types/yauzl@2.10.3':
dependencies:
'@types/node': 25.9.1
optional: true
'@uwu/lune@1.6.2':
dependencies:
'@babel/core': 7.29.7
@ -3575,7 +3562,7 @@ snapshots:
acorn@8.16.0: {}
adm-zip@0.5.17: {}
adm-zip@0.6.0: {}
agent-base@6.0.2:
dependencies:
@ -3712,12 +3699,12 @@ snapshots:
html-entities: 2.3.3
parse5: 7.3.0
babel-preset-solid@1.9.12(@babel/core@8.0.0-rc.6)(solid-js@1.9.13):
babel-preset-solid@1.9.12(@babel/core@8.0.0-rc.6)(solid-js@1.9.14):
dependencies:
'@babel/core': 8.0.0-rc.6
babel-plugin-jsx-dom-expressions: 0.40.7(@babel/core@8.0.0-rc.6)
optionalDependencies:
solid-js: 1.9.13
solid-js: 1.9.14
balanced-match@1.0.2: {}
@ -3757,8 +3744,6 @@ snapshots:
node-releases: 2.0.46
update-browserslist-db: 1.2.3(browserslist@4.28.2)
buffer-crc32@0.2.13: {}
buffer-from@1.1.2: {}
buffer@5.7.1:
@ -4103,11 +4088,11 @@ snapshots:
transitivePeerDependencies:
- supports-color
electron@42.3.3:
electron@43.1.1:
dependencies:
'@electron-internal/extract-zip': 1.0.4
'@electron/get': 5.0.0
'@types/node': 24.12.4
extract-zip: 2.0.1
transitivePeerDependencies:
- supports-color
@ -4192,16 +4177,6 @@ snapshots:
ext-list: 2.2.2
sort-keys-length: 1.0.1
extract-zip@2.0.1:
dependencies:
debug: 4.4.3
get-stream: 5.2.0
yauzl: 3.3.1
optionalDependencies:
'@types/yauzl': 2.10.3
transitivePeerDependencies:
- supports-color
extsprintf@1.4.1:
optional: true
@ -4260,6 +4235,13 @@ snapshots:
jsonfile: 6.2.1
universalify: 2.0.1
fs-extra@11.3.6:
dependencies:
graceful-fs: 4.2.11
jsonfile: 6.2.1
universalify: 2.0.1
optional: true
fs-extra@7.0.1:
dependencies:
graceful-fs: 4.2.11
@ -4634,9 +4616,9 @@ snapshots:
lru-cache@7.18.3: {}
lucide-solid@1.16.0(solid-js@1.9.13):
lucide-solid@1.16.0(solid-js@1.9.14):
dependencies:
solid-js: 1.9.13
solid-js: 1.9.14
magic-string@0.30.21:
dependencies:
@ -4819,8 +4801,6 @@ snapshots:
pe-library@0.4.1: {}
pend@1.2.0: {}
picocolors@1.1.1: {}
picomatch@2.3.2: {}
@ -5037,11 +5017,11 @@ snapshots:
type-fest: 0.13.1
optional: true
seroval-plugins@1.5.4(seroval@1.5.4):
seroval-plugins@1.5.5(seroval@1.5.5):
dependencies:
seroval: 1.5.4
seroval: 1.5.5
seroval@1.5.4: {}
seroval@1.5.5: {}
set-blocking@2.0.0:
optional: true
@ -5079,21 +5059,21 @@ snapshots:
dependencies:
csstype: 3.2.3
solid-js@1.9.13:
solid-js@1.9.14:
dependencies:
csstype: 3.2.3
seroval: 1.5.4
seroval-plugins: 1.5.4(seroval@1.5.4)
seroval: 1.5.5
seroval-plugins: 1.5.5(seroval@1.5.5)
solid-motionone@1.0.4(solid-js@1.9.13):
solid-motionone@1.0.4(solid-js@1.9.14):
dependencies:
'@motionone/dom': 10.18.0
'@motionone/utils': 10.18.0
'@solid-primitives/props': 3.2.3(solid-js@1.9.13)
'@solid-primitives/refs': 1.1.3(solid-js@1.9.13)
'@solid-primitives/transition-group': 1.1.2(solid-js@1.9.13)
'@solid-primitives/props': 3.2.3(solid-js@1.9.14)
'@solid-primitives/refs': 1.1.3(solid-js@1.9.14)
'@solid-primitives/transition-group': 1.1.2(solid-js@1.9.14)
csstype: 3.2.3
solid-js: 1.9.13
solid-js: 1.9.14
sort-keys-length@1.0.1:
dependencies:
@ -5326,11 +5306,6 @@ snapshots:
y18n: 5.0.8
yargs-parser: 21.1.1
yauzl@3.3.1:
dependencies:
buffer-crc32: 0.2.13
pend: 1.2.0
yocto-queue@0.1.0: {}
zod@3.25.76: {}

View file

@ -78,6 +78,7 @@ export interface LegcordWindow {
uninstall: (id: string) => void;
set: (id: string, state: boolean) => void;
getThemes: () => Readonly<ThemeManifest[]>;
refresh: () => Readonly<ThemeManifest[]>;
openQuickCss: () => void;
edit: (id: string) => void;
folder: (id: string) => void;

View file

@ -77,4 +77,6 @@ export interface Settings {
extendedPluginAbilities: boolean;
supportBannerDismissed: boolean;
pluginStates?: Record<string, boolean>;
// Remove below once the plugin system is fully implemented.
showExperimentalPluginMenu: boolean;
}

View file

@ -67,6 +67,7 @@ const defaults: Settings = {
extendedPluginAbilities: false,
quickCss: true,
supportBannerDismissed: false,
showExperimentalPluginMenu: false,
pluginStates: {},
};
@ -80,6 +81,7 @@ const safeMode: Settings = {
vaapi: false,
additionalArguments: "",
extendedPluginAbilities: false,
showExperimentalPluginMenu: false,
quickCss: false,
};

View file

@ -112,6 +112,111 @@ function getThemeManifest(themeId: string): ThemeManifest | null {
}
}
// Performance optimization: Cached theme list with directory watcher
const THEME_LIST_CACHE_TTL = 2000;
let themeListCache: ThemeManifest[] | null = null;
let themeListCacheTime = 0;
let themeWatcher: fs.FSWatcher | null = null;
let themeListRefreshTimeout: NodeJS.Timeout | null = null;
function importLooseThemeFiles(): void {
try {
if (!fs.existsSync(themesFolder)) return;
const entries = fs.readdirSync(themesFolder);
for (const entry of entries) {
const entryPath = path.join(themesFolder, entry);
const stat = fs.statSync(entryPath);
if (stat.isFile() && (entry.endsWith(".css") || entry.endsWith(".theme.css"))) {
const code = fs.readFileSync(entryPath, "utf8");
installThemeFromCode(code);
try {
fs.unlinkSync(entryPath);
} catch {}
}
}
} catch (err) {
console.error("[Theme Manager] Failed to import loose theme files:", err);
}
}
function refreshThemeListCache(): ThemeManifest[] {
try {
if (!fs.existsSync(themesFolder)) {
themeListCache = [];
themeListCacheTime = Date.now();
return themeListCache;
}
const themes: ThemeManifest[] = [];
const entries = fs.readdirSync(themesFolder);
for (const entry of entries) {
const manifestPath = path.join(themesFolder, entry, "manifest.json");
if (fs.existsSync(manifestPath)) {
const manifest = getThemeManifest(entry);
if (manifest) {
themes.push({ ...manifest, id: entry });
}
}
}
themeListCache = themes;
themeListCacheTime = Date.now();
return themes;
} catch (err) {
console.error("[Theme Manager] Failed to refresh theme list cache:", err);
return themeListCache ?? [];
}
}
export function getCachedThemeList(): ThemeManifest[] {
const now = Date.now();
if (themeListCache && now - themeListCacheTime < THEME_LIST_CACHE_TTL) {
return themeListCache;
}
importLooseThemeFiles();
return refreshThemeListCache();
}
export function invalidateThemeListCache(): void {
themeListCache = null;
}
function debouncedRefreshThemeList(): void {
if (themeListRefreshTimeout) {
clearTimeout(themeListRefreshTimeout);
}
themeListRefreshTimeout = setTimeout(() => {
refreshThemeListCache();
themeListRefreshTimeout = null;
}, 500);
}
export function startThemeWatcher(): void {
if (themeWatcher) return;
try {
if (!fs.existsSync(themesFolder)) {
fs.mkdirSync(themesFolder, { recursive: true });
}
themeWatcher = fs.watch(themesFolder, { recursive: true }, (eventType, filename) => {
if (filename && (filename.endsWith("manifest.json") || eventType === "rename")) {
debouncedRefreshThemeList();
}
});
refreshThemeListCache();
} catch (err) {
console.error("[Theme Manager] Failed to start theme watcher:", err);
}
}
export function stopThemeWatcher(): void {
if (themeWatcher) {
themeWatcher.close();
themeWatcher = null;
}
if (themeListRefreshTimeout) {
clearTimeout(themeListRefreshTimeout);
themeListRefreshTimeout = null;
}
}
export function injectThemesMain(browserWindow: BrowserWindow): void {
if (process.argv.includes("--safe-mode")) return;
if (!fs.existsSync(themesFolder)) {
@ -123,11 +228,13 @@ export function injectThemesMain(browserWindow: BrowserWindow): void {
const files = fs.readdirSync(themesFolder);
for (const file of files) {
const themePath = path.join(themesFolder, file);
if (fs.statSync(themePath).isFile() && file.endsWith(".DS_Store")) {
if (fs.statSync(themePath).isFile() && (file.endsWith(".css") || file.endsWith(".theme.css"))) {
console.log(`[Theme Manager] Local theme detected: ${themePath}`);
installTheme(themePath).then(() => {
const code = fs.readFileSync(themePath, "utf8");
installThemeFromCode(code);
try {
fs.unlinkSync(themePath);
});
} catch {}
} else {
try {
const themeFile = getThemeManifest(file);
@ -168,10 +275,11 @@ export function uninstallTheme(id: string) {
fs.rmdirSync(path.join(themesFolder, `${id}-BD`), { recursive: true });
console.log(`Removed ${id} folder`);
}
themeManifestCache.delete(id);
invalidateThemeListCache();
}
export function setThemeEnabled(id: string, enabled: boolean) {
// Performance optimization: Use cached manifest if available
let manifest = getThemeManifest(id);
if (!manifest) {
manifest = JSON.parse(fs.readFileSync(path.join(themesFolder, id, "/manifest.json"), "utf8")) as ThemeManifest;
@ -196,8 +304,25 @@ export function setThemeEnabled(id: string, enabled: boolean) {
manifest.enabled = enabled;
fs.writeFileSync(`${themesFolder}/${id}/manifest.json`, JSON.stringify(manifest));
// Performance optimization: Invalidate cache
themeManifestCache.delete(id);
invalidateThemeListCache();
}
function installThemeFromCode(code: string, linkOrPath?: string): void {
const manifest = parseBDManifest(code);
const themePath = path.join(themesFolder, `${manifest.name?.replace(" ", "-")}-BD`);
if (!fs.existsSync(themePath)) {
fs.mkdirSync(themePath);
console.log(`Created ${manifest.name} folder`);
}
if (linkOrPath && manifest.updateSrc === undefined) {
manifest.updateSrc = linkOrPath;
}
if (code.includes(".titlebar")) manifest.supportsLegcordTitlebar = true;
else manifest.supportsLegcordTitlebar = false;
fs.writeFileSync(path.join(themePath, "manifest.json"), JSON.stringify(manifest));
fs.writeFileSync(path.join(themePath, "src.css"), code);
invalidateThemeListCache();
}
export async function installTheme(linkOrPath: string) {
@ -209,19 +334,7 @@ export async function installTheme(linkOrPath: string) {
} else {
code = fs.readFileSync(linkOrPath, "utf8");
}
const manifest = parseBDManifest(code);
const themePath = path.join(themesFolder, `${manifest.name?.replace(" ", "-")}-BD`);
if (!fs.existsSync(themePath)) {
fs.mkdirSync(themePath);
console.log(`Created ${manifest.name} folder`);
}
if (isLinkImport && manifest.updateSrc === undefined) {
manifest.updateSrc = linkOrPath;
}
if (code.includes(".titlebar")) manifest.supportsLegcordTitlebar = true;
else manifest.supportsLegcordTitlebar = false;
fs.writeFileSync(path.join(themePath, "manifest.json"), JSON.stringify(manifest));
fs.writeFileSync(path.join(themePath, "src.css"), code);
installThemeFromCode(code, isLinkImport ? linkOrPath : undefined);
}
export function initQuickCss(browserWindow: BrowserWindow) {

View file

@ -1,4 +1,4 @@
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import os from "node:os";
import path from "node:path";
import type { Game } from "arrpc";
@ -21,7 +21,16 @@ import {
import { getConfig, getConfigLocation, setConfig, setConfigBulk } from "../common/config.js";
import { addDetectable, getDetectables, removeDetectable } from "../common/detectables.js";
import { getLang, getLangName, getRawLang, setLang } from "../common/lang.js";
import { disableQuickCss, initQuickCss, installTheme, setThemeEnabled, uninstallTheme } from "../common/themes.js";
import {
disableQuickCss,
getCachedThemeList,
initQuickCss,
installTheme,
invalidateThemeListCache,
setThemeEnabled,
startThemeWatcher,
uninstallTheme,
} from "../common/themes.js";
import { getDisplayVersion, getVersion } from "../common/version.js";
import { openCssEditor } from "../cssEditor/main.js";
import { getAppliedFlags, handleRestart } from "../main.js";
@ -65,6 +74,7 @@ let ipcRegistered = false;
export function registerIpc(passedWindow: BrowserWindow): void {
if (ipcRegistered) return;
ipcRegistered = true;
startThemeWatcher();
ipcMain.handle("getShelterBundle", () => {
return {
js: ifExistsRead(path.join(app.getPath("userData"), "shelter.js")),
@ -169,17 +179,11 @@ export function registerIpc(passedWindow: BrowserWindow): void {
});
ipcMain.on("getThemes", (event) => {
const themes = [];
const themeFolders = readdirSync(themesPath);
for (const folder of themeFolders) {
if (existsSync(`${themesPath}/${folder}/manifest.json`)) {
const manifest = JSON.parse(
readFileSync(`${themesPath}/${folder}/manifest.json`, "utf8"),
) as ThemeManifest;
themes.push({ ...manifest, id: folder });
}
}
event.returnValue = themes;
event.returnValue = getCachedThemeList();
});
ipcMain.on("refreshThemesCache", (event) => {
invalidateThemeListCache();
event.returnValue = getCachedThemeList();
});
ipcMain.on("splashEnd", () => {

View file

@ -102,6 +102,7 @@ contextBridge.exposeInMainWorld("legcord", {
uninstall: (id: string) => ipcRenderer.send("uninstallTheme", id),
edit: (id: string) => ipcRenderer.send("editTheme", id),
getThemes: () => ipcRenderer.sendSync("getThemes") as ThemeManifest[],
refresh: () => ipcRenderer.sendSync("refreshThemesCache") as ThemeManifest[],
openImportPicker: () => ipcRenderer.send("openImportPicker"),
set: (id: string, state: boolean) => ipcRenderer.send("setThemeEnabled", id, state),
folder: (id: string) => ipcRenderer.send("openThemeFolder", id),

View file

@ -550,7 +550,12 @@ export function createWindow() {
const mainWindow = new BrowserWindow(browserWindowOptions);
// Restore by position + size directly to match saveWindowState roundtrip.
if (storedBounds.x !== undefined && storedBounds.y !== undefined) {
if (
storedBounds.x !== undefined &&
storedBounds.y !== undefined &&
storedBounds.width !== undefined &&
storedBounds.height !== undefined
) {
mainWindow.setPosition(storedBounds.x, storedBounds.y);
mainWindow.setSize(storedBounds.width, storedBounds.height);
}

View file

@ -1,45 +1,95 @@
.card {
border: 1px solid var(--background-accent);
border-radius: 4px;
padding: 0.5rem;
margin-top: 0.5rem;
flex-wrap: wrap;
border: 1px solid var(--background-modifier-accent);
border-radius: 8px;
padding: 1rem;
margin-top: 0.75rem;
background: var(--background-secondary);
transition:
box-shadow 0.2s ease,
border-color 0.2s ease;
}
.switch {
margin-left: auto;
.card:hover {
border-color: var(--background-modifier-selected);
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
}
.topRow {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 1rem;
}
.info {
align-self: flex-start;
flex: 1;
min-width: 0;
}
.eyebrow {
margin-bottom: 0px;
color: var(--text-strong);
.title {
margin: 0;
word-break: break-word;
}
.author {
font-size: 0.85rem;
color: var(--text-muted);
margin-top: 0.15rem;
}
.description {
margin-top: 0.5rem;
font-size: 0.9rem;
color: var(--text-normal);
line-height: 1.4;
}
.actions {
display: flex;
gap: 0.25rem;
margin-top: 0.75rem;
padding-top: 0.75rem;
border-top: 1px solid var(--background-modifier-accent);
}
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
border: none;
background: none;
color: var(--interactive-text-active);
transition: opacity 250ms;
&:hover {
color: var(--interactive-text-hover);
background: transparent;
width: 32px;
height: 32px;
border-radius: 6px;
cursor: pointer;
color: var(--interactive-normal);
transition:
background 0.15s ease,
color 0.15s ease;
}
.btn:hover {
background: var(--background-modifier-hover);
color: var(--interactive-hover);
}
.btnDanger:hover {
background: rgba(218, 55, 60, 0.15);
color: #da373c;
}
.icon {
height: 20px;
margin-top: 2px;
height: 18px;
width: 18px;
filter: brightness(0) invert(0.7);
pointer-events: none;
}
.btn:hover .icon {
filter: brightness(0) invert(1);
}
.mainInfo {
flex-wrap: wrap;
align-items: baseline;
justify-content: center;
gap: 5px;
margin-bottom: -0.5em;
display: flex;
.btnDanger:hover .icon {
filter: brightness(0) saturate(1) invert(27%) sepia(82%) saturate(3000%) hue-rotate(346deg) brightness(94%)
contrast(90%);
}

View file

@ -1,4 +1,4 @@
import { createSignal } from "solid-js";
import { createSignal, Show } from "solid-js";
import type { ThemeManifest } from "../../../@types/themeManifest.js";
import { refreshThemes } from "../settings.js";
import classes from "./ThemesCard.module.css";
@ -14,7 +14,7 @@ export const ThemesCard = (props: { theme: ThemeManifest }) => {
function toggleTheme(state: boolean) {
setSwitchState(state);
if (props.theme.id) {
window.legcord.themes.set(props.theme.id, switchState());
window.legcord.themes.set(props.theme.id, state);
}
refreshThemes();
}
@ -47,35 +47,48 @@ export const ThemesCard = (props: { theme: ThemeManifest }) => {
window.legcord.themes.folder(props.theme.id);
}
}
const showUpdate = !!props.theme.updateSrc;
return (
<div class={classes.card}>
<div class={classes.topRow}>
<div class={classes.info}>
<div class={classes.mainInfo}>
<Header tag={HeaderTags.H2} class={classes.title}>
{props.theme.name}
</Header>
<Header tag={HeaderTags.H3}>{store.i18n["themes-by"]}</Header>
<Header class={classes.eyebrow} tag={HeaderTags.EYEBROW}>
{props.theme.author}
</Header>
<div class={classes.switch}>
<div class={classes.author}>
{store.i18n["themes-by"]} {props.theme.author}
</div>
</div>
<Switch checked={switchState()} onChange={toggleTheme} />
</div>
</div>
<Header tag={HeaderTags.H5}>{props.theme.description}</Header>
</div>
<button title={store.i18n["themes-delete"]} type="button" onClick={removeTheme} class={classes.btn}>
<Show when={props.theme.description}>
<div class={classes.description}>{props.theme.description}</div>
</Show>
<div class={classes.actions}>
<button
title={store.i18n["themes-delete"]}
type="button"
onClick={removeTheme}
class={`${classes.btn} ${classes.btnDanger}`}
>
<img class={classes.icon} alt={store.i18n["themes-delete"]} src="legcord://assets/Trash.png" />
</button>
<button title={store.i18n["themes-edit"]} type="button" onClick={editTheme} class={classes.btn}>
<img class={classes.icon} alt={store.i18n["themes-edit"]} src="legcord://assets/Edit.png" />
</button>
<Show when={showUpdate}>
<button title={store.i18n["themes-update"]} type="button" onClick={updateTheme} class={classes.btn}>
<img class={classes.icon} alt={store.i18n["themes-update"]} src="legcord://assets/UpgradeArrow.png" />
<img
class={classes.icon}
alt={store.i18n["themes-update"]}
src="legcord://assets/UpgradeArrow.png"
/>
</button>
</Show>
<button title={store.i18n["themes-open"]} type="button" onClick={openThemesFolder} class={classes.btn}>
<img class={classes.icon} alt={store.i18n["themes-open"]} src="legcord://assets/Folder.png" />
</button>
</div>
</div>
);
};

View file

@ -25,15 +25,18 @@ const {
let settingsCleanups: (() => void)[] = [];
function registerSections(): (() => void)[] {
return [
const array = [
registerSection("divider"),
registerSection("header", "Legcord"),
registerSection("section", "legcord-settings", "Settings", SettingsPage, { icon: SettingsSidebarIcon }),
registerSection("section", "legcord-themes", "Themes", ThemesPage, { icon: ThemesSidebarIcon }),
registerSection("section", "legcord-plugins", "Plugins", PluginsPage, { icon: PluginsSidebarIcon }),
registerSection("section", "legcord-keybinds", "Keybinds", KeybindsPage, { icon: KeybindsSidebarIcon }),
registerSection("section", "legcord-games", "Games", RegisteredGamesPage, { icon: GamesSidebarIcon }),
];
if (window.legcord.settings.getConfig().showExperimentalPluginMenu) {
array.push(registerSection("section", "legcord-plugins", "Plugins", PluginsPage, { icon: PluginsSidebarIcon }));
}
return array;
}
function restartRequired(payload: { event: string; properties: { origin_pane: string } }) {

View file

@ -383,6 +383,13 @@ export function SettingsPage() {
<Header class={classes.category} tag={HeaderTags.H5}>
{store.i18n["settings-category-debug"]}
</Header>
<SwitchItem
note={store.i18n["settings-showExperimentalPluginMenu-desc"]}
value={settings.showExperimentalPluginMenu}
onChange={(e: boolean) => setConfig("showExperimentalPluginMenu", e, true)}
>
{store.i18n["settings-showExperimentalPluginMenu"]}
</SwitchItem>
<Show when={window.legcord.platform === "linux"}>
<SwitchItem
note={store.i18n["settings-venmic-deviceSelect-desc"]}

View file

@ -46,7 +46,7 @@ export function ThemesPage() {
>
{store.i18n["settings-quickCss"]}
</SwitchItem>
<div class={classes.buttonBox}>
<div class={classes.toolbar}>
<Button
size={ButtonSizes.LARGE}
onClick={window.legcord.themes.openQuickCss}
@ -60,6 +60,14 @@ export function ThemesPage() {
<Button size={ButtonSizes.LARGE} onClick={window.legcord.settings.openThemesFolder}>
{t["themes-openThemesFolder"]}
</Button>
<Button
size={ButtonSizes.LARGE}
onClick={() => {
store.themes = window.legcord.themes.refresh();
}}
>
{t["themes-refresh"] ?? "Refresh"}
</Button>
</div>
<div class={classes.addBox}>
<TextBox
@ -71,6 +79,8 @@ export function ThemesPage() {
{t["themes-import"]}
</Button>
</div>
<hr class={classes.divider} />
<Header tag={HeaderTags.H3}>{t["themes-installed"]}</Header>
<For each={store.themes}>{(theme: ThemeManifest) => <ThemesCard theme={theme} />}</For>
</>
);

View file

@ -1,13 +1,23 @@
.buttonBox {
.toolbar {
display: flex;
justify-content: space-between;
border: 1px solid var(--background-modifier-accent);
padding: 3px;
border-radius: 6px;
gap: 0.5rem;
flex-wrap: wrap;
margin-top: 0.75rem;
}
.addBox {
display: flex;
justify-content: space-between;
padding: 3px;
margin-top: 1rem;
gap: 0.5rem;
margin-top: 0.5rem;
align-items: center;
}
.addBox > :first-child {
flex: 1;
}
.divider {
margin: 1.5rem 0 0.5rem;
border: none;
border-top: 1px solid var(--background-modifier-accent);
}