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-strict": "Strict",
"settings-csp-none": "None", "settings-csp-none": "None",
"settings-csp-vanilla": "Vanilla", "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": "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-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", "settings-useSystemCssEditor": "Use system CSS editor",
@ -293,6 +295,7 @@
"themes-openThemesFolder": "Open themes folder", "themes-openThemesFolder": "Open themes folder",
"themes-import": "Import", "themes-import": "Import",
"themes-importUrlPlaceholder": "https://raw.githubusercontent.com/... [.theme.css]", "themes-importUrlPlaceholder": "https://raw.githubusercontent.com/... [.theme.css]",
"themes-installed": "Installed Themes",
"games-registeredGames": "Registered Games", "games-registeredGames": "Registered Games",
"games-refreshList": "Refresh list", "games-refreshList": "Refresh list",
"games-add": "Add", "games-add": "Add",

View file

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

167
pnpm-lock.yaml generated
View file

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

View file

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

View file

@ -77,4 +77,6 @@ export interface Settings {
extendedPluginAbilities: boolean; extendedPluginAbilities: boolean;
supportBannerDismissed: boolean; supportBannerDismissed: boolean;
pluginStates?: Record<string, 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, extendedPluginAbilities: false,
quickCss: true, quickCss: true,
supportBannerDismissed: false, supportBannerDismissed: false,
showExperimentalPluginMenu: false,
pluginStates: {}, pluginStates: {},
}; };
@ -80,6 +81,7 @@ const safeMode: Settings = {
vaapi: false, vaapi: false,
additionalArguments: "", additionalArguments: "",
extendedPluginAbilities: false, extendedPluginAbilities: false,
showExperimentalPluginMenu: false,
quickCss: 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 { export function injectThemesMain(browserWindow: BrowserWindow): void {
if (process.argv.includes("--safe-mode")) return; if (process.argv.includes("--safe-mode")) return;
if (!fs.existsSync(themesFolder)) { if (!fs.existsSync(themesFolder)) {
@ -123,11 +228,13 @@ export function injectThemesMain(browserWindow: BrowserWindow): void {
const files = fs.readdirSync(themesFolder); const files = fs.readdirSync(themesFolder);
for (const file of files) { for (const file of files) {
const themePath = path.join(themesFolder, file); 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}`); console.log(`[Theme Manager] Local theme detected: ${themePath}`);
installTheme(themePath).then(() => { const code = fs.readFileSync(themePath, "utf8");
installThemeFromCode(code);
try {
fs.unlinkSync(themePath); fs.unlinkSync(themePath);
}); } catch {}
} else { } else {
try { try {
const themeFile = getThemeManifest(file); const themeFile = getThemeManifest(file);
@ -168,10 +275,11 @@ export function uninstallTheme(id: string) {
fs.rmdirSync(path.join(themesFolder, `${id}-BD`), { recursive: true }); fs.rmdirSync(path.join(themesFolder, `${id}-BD`), { recursive: true });
console.log(`Removed ${id} folder`); console.log(`Removed ${id} folder`);
} }
themeManifestCache.delete(id);
invalidateThemeListCache();
} }
export function setThemeEnabled(id: string, enabled: boolean) { export function setThemeEnabled(id: string, enabled: boolean) {
// Performance optimization: Use cached manifest if available
let manifest = getThemeManifest(id); let manifest = getThemeManifest(id);
if (!manifest) { if (!manifest) {
manifest = JSON.parse(fs.readFileSync(path.join(themesFolder, id, "/manifest.json"), "utf8")) as ThemeManifest; 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; manifest.enabled = enabled;
fs.writeFileSync(`${themesFolder}/${id}/manifest.json`, JSON.stringify(manifest)); fs.writeFileSync(`${themesFolder}/${id}/manifest.json`, JSON.stringify(manifest));
// Performance optimization: Invalidate cache
themeManifestCache.delete(id); 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) { export async function installTheme(linkOrPath: string) {
@ -209,19 +334,7 @@ export async function installTheme(linkOrPath: string) {
} else { } else {
code = fs.readFileSync(linkOrPath, "utf8"); code = fs.readFileSync(linkOrPath, "utf8");
} }
const manifest = parseBDManifest(code); installThemeFromCode(code, isLinkImport ? linkOrPath : undefined);
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);
} }
export function initQuickCss(browserWindow: BrowserWindow) { 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 os from "node:os";
import path from "node:path"; import path from "node:path";
import type { Game } from "arrpc"; import type { Game } from "arrpc";
@ -21,7 +21,16 @@ import {
import { getConfig, getConfigLocation, setConfig, setConfigBulk } from "../common/config.js"; import { getConfig, getConfigLocation, setConfig, setConfigBulk } from "../common/config.js";
import { addDetectable, getDetectables, removeDetectable } from "../common/detectables.js"; import { addDetectable, getDetectables, removeDetectable } from "../common/detectables.js";
import { getLang, getLangName, getRawLang, setLang } from "../common/lang.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 { getDisplayVersion, getVersion } from "../common/version.js";
import { openCssEditor } from "../cssEditor/main.js"; import { openCssEditor } from "../cssEditor/main.js";
import { getAppliedFlags, handleRestart } from "../main.js"; import { getAppliedFlags, handleRestart } from "../main.js";
@ -65,6 +74,7 @@ let ipcRegistered = false;
export function registerIpc(passedWindow: BrowserWindow): void { export function registerIpc(passedWindow: BrowserWindow): void {
if (ipcRegistered) return; if (ipcRegistered) return;
ipcRegistered = true; ipcRegistered = true;
startThemeWatcher();
ipcMain.handle("getShelterBundle", () => { ipcMain.handle("getShelterBundle", () => {
return { return {
js: ifExistsRead(path.join(app.getPath("userData"), "shelter.js")), js: ifExistsRead(path.join(app.getPath("userData"), "shelter.js")),
@ -169,17 +179,11 @@ export function registerIpc(passedWindow: BrowserWindow): void {
}); });
ipcMain.on("getThemes", (event) => { ipcMain.on("getThemes", (event) => {
const themes = []; event.returnValue = getCachedThemeList();
const themeFolders = readdirSync(themesPath); });
for (const folder of themeFolders) { ipcMain.on("refreshThemesCache", (event) => {
if (existsSync(`${themesPath}/${folder}/manifest.json`)) { invalidateThemeListCache();
const manifest = JSON.parse( event.returnValue = getCachedThemeList();
readFileSync(`${themesPath}/${folder}/manifest.json`, "utf8"),
) as ThemeManifest;
themes.push({ ...manifest, id: folder });
}
}
event.returnValue = themes;
}); });
ipcMain.on("splashEnd", () => { ipcMain.on("splashEnd", () => {

View file

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

View file

@ -550,7 +550,12 @@ export function createWindow() {
const mainWindow = new BrowserWindow(browserWindowOptions); const mainWindow = new BrowserWindow(browserWindowOptions);
// Restore by position + size directly to match saveWindowState roundtrip. // 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.setPosition(storedBounds.x, storedBounds.y);
mainWindow.setSize(storedBounds.width, storedBounds.height); mainWindow.setSize(storedBounds.width, storedBounds.height);
} }

View file

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

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 type { ThemeManifest } from "../../../@types/themeManifest.js";
import { refreshThemes } from "../settings.js"; import { refreshThemes } from "../settings.js";
import classes from "./ThemesCard.module.css"; import classes from "./ThemesCard.module.css";
@ -14,7 +14,7 @@ export const ThemesCard = (props: { theme: ThemeManifest }) => {
function toggleTheme(state: boolean) { function toggleTheme(state: boolean) {
setSwitchState(state); setSwitchState(state);
if (props.theme.id) { if (props.theme.id) {
window.legcord.themes.set(props.theme.id, switchState()); window.legcord.themes.set(props.theme.id, state);
} }
refreshThemes(); refreshThemes();
} }
@ -47,35 +47,48 @@ export const ThemesCard = (props: { theme: ThemeManifest }) => {
window.legcord.themes.folder(props.theme.id); window.legcord.themes.folder(props.theme.id);
} }
} }
const showUpdate = !!props.theme.updateSrc;
return ( return (
<div class={classes.card}> <div class={classes.card}>
<div class={classes.info}> <div class={classes.topRow}>
<div class={classes.mainInfo}> <div class={classes.info}>
<Header tag={HeaderTags.H2} class={classes.title}> <Header tag={HeaderTags.H2} class={classes.title}>
{props.theme.name} {props.theme.name}
</Header> </Header>
<Header tag={HeaderTags.H3}>{store.i18n["themes-by"]}</Header> <div class={classes.author}>
<Header class={classes.eyebrow} tag={HeaderTags.EYEBROW}> {store.i18n["themes-by"]} {props.theme.author}
{props.theme.author}
</Header>
<div class={classes.switch}>
<Switch checked={switchState()} onChange={toggleTheme} />
</div> </div>
</div> </div>
<Header tag={HeaderTags.H5}>{props.theme.description}</Header> <Switch checked={switchState()} onChange={toggleTheme} />
</div>
<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"
/>
</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>
<button title={store.i18n["themes-delete"]} type="button" onClick={removeTheme} class={classes.btn}>
<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>
<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" />
</button>
<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)[] = []; let settingsCleanups: (() => void)[] = [];
function registerSections(): (() => void)[] { function registerSections(): (() => void)[] {
return [ const array = [
registerSection("divider"), registerSection("divider"),
registerSection("header", "Legcord"), registerSection("header", "Legcord"),
registerSection("section", "legcord-settings", "Settings", SettingsPage, { icon: SettingsSidebarIcon }), registerSection("section", "legcord-settings", "Settings", SettingsPage, { icon: SettingsSidebarIcon }),
registerSection("section", "legcord-themes", "Themes", ThemesPage, { icon: ThemesSidebarIcon }), 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-keybinds", "Keybinds", KeybindsPage, { icon: KeybindsSidebarIcon }),
registerSection("section", "legcord-games", "Games", RegisteredGamesPage, { icon: GamesSidebarIcon }), 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 } }) { 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}> <Header class={classes.category} tag={HeaderTags.H5}>
{store.i18n["settings-category-debug"]} {store.i18n["settings-category-debug"]}
</Header> </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"}> <Show when={window.legcord.platform === "linux"}>
<SwitchItem <SwitchItem
note={store.i18n["settings-venmic-deviceSelect-desc"]} note={store.i18n["settings-venmic-deviceSelect-desc"]}

View file

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

View file

@ -1,13 +1,23 @@
.buttonBox { .toolbar {
display: flex; display: flex;
justify-content: space-between; gap: 0.5rem;
border: 1px solid var(--background-modifier-accent); flex-wrap: wrap;
padding: 3px; margin-top: 0.75rem;
border-radius: 6px;
} }
.addBox { .addBox {
display: flex; display: flex;
justify-content: space-between; gap: 0.5rem;
padding: 3px; margin-top: 0.5rem;
margin-top: 1rem; 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);
} }