diff --git a/index.html b/index.html index 8b1a8e43..3a52b1df 100644 --- a/index.html +++ b/index.html @@ -2,7 +2,12 @@ - + + + + + + CeraUI diff --git a/package-lock.json b/package-lock.json index 9309d37a..3fe9a35b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "@macfja/svelte-persistent-store": "^2.4.2", "@sveltejs/enhanced-img": "^0.4.1", + "@sveltejs/kit": "^2.19.0", "lodash-es": "^4.17.21", "lodash.template": "^4.5.0", "lucide-svelte": "^0.456.0", @@ -1426,6 +1427,12 @@ "url": "https://opencollective.com/unts" } }, + "node_modules/@polka/url": { + "version": "1.0.0-next.28", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.28.tgz", + "integrity": "sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==", + "license": "MIT" + }, "node_modules/@poppinss/macroable": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@poppinss/macroable/-/macroable-1.0.4.tgz", @@ -1814,6 +1821,36 @@ "vite": ">= 5.0.0" } }, + "node_modules/@sveltejs/kit": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.19.0.tgz", + "integrity": "sha512-UTx28Ad4sYsLU//gqkEo5aFOPFBRT2uXCmXTsURqhurDCvzkVwXruJgBcHDaMiK6RKKpYRteDUaXYqZyGPgCXQ==", + "license": "MIT", + "dependencies": { + "@types/cookie": "^0.6.0", + "cookie": "^0.6.0", + "devalue": "^5.1.0", + "esm-env": "^1.2.2", + "import-meta-resolve": "^4.1.0", + "kleur": "^4.1.5", + "magic-string": "^0.30.5", + "mrmime": "^2.0.0", + "sade": "^1.8.1", + "set-cookie-parser": "^2.6.0", + "sirv": "^3.0.0" + }, + "bin": { + "svelte-kit": "svelte-kit.js" + }, + "engines": { + "node": ">=18.13" + }, + "peerDependencies": { + "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0", + "svelte": "^4.0.0 || ^5.0.0-next.0", + "vite": "^5.0.3 || ^6.0.0" + } + }, "node_modules/@sveltejs/vite-plugin-svelte": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-4.0.4.tgz", @@ -1920,6 +1957,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", @@ -2918,6 +2961,15 @@ "dev": true, "license": "MIT" }, + "node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -3123,7 +3175,6 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.1.1.tgz", "integrity": "sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==", - "dev": true, "license": "MIT" }, "node_modules/didyoumean": { @@ -3828,7 +3879,6 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", - "dev": true, "license": "MIT" }, "node_modules/esniff": { @@ -4576,6 +4626,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/import-meta-resolve": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz", + "integrity": "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -5169,7 +5229,6 @@ "version": "4.1.5", "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -5450,12 +5509,20 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", - "dev": true, "license": "MIT", "engines": { "node": ">=4" } }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -6440,7 +6507,6 @@ "version": "1.8.1", "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", - "dev": true, "license": "MIT", "dependencies": { "mri": "^1.1.0" @@ -6516,6 +6582,12 @@ "node": ">=10" } }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "license": "MIT" + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -6725,6 +6797,20 @@ "is-arrayish": "^0.3.1" } }, + "node_modules/sirv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.1.tgz", + "integrity": "sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==", + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/sjcl-bit-array": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/sjcl-bit-array/-/sjcl-bit-array-1.0.0.tgz", @@ -7614,6 +7700,15 @@ "license": "MIT", "optional": true }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/ts-algebra": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz", diff --git a/package.json b/package.json index adb69dbc..bac3aae1 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,7 @@ "dependencies": { "@macfja/svelte-persistent-store": "^2.4.2", "@sveltejs/enhanced-img": "^0.4.1", + "@sveltejs/kit": "^2.19.0", "lodash-es": "^4.17.21", "lodash.template": "^4.5.0", "lucide-svelte": "^0.456.0", diff --git a/public/vite.svg b/public/vite.svg deleted file mode 100644 index e7b8dfb1..00000000 --- a/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/app.css b/src/app.css index a6664b67..2d1ab808 100644 --- a/src/app.css +++ b/src/app.css @@ -60,15 +60,10 @@ } @layer base { - @font-face { - font-family: 'geist-sans'; - src: url('./assets/fonts/Geist/geist.woff2') format('woff2'); - font-display: swap; - } - * { @apply border-border; } + body { @apply bg-background text-foreground; /* font-feature-settings: "rlig" 1, "calt" 1; */ diff --git a/src/assets/apple-touch-icon.png b/src/assets/apple-touch-icon.png new file mode 100644 index 00000000..cc960581 Binary files /dev/null and b/src/assets/apple-touch-icon.png differ diff --git a/src/assets/favicon-96x96.png b/src/assets/favicon-96x96.png new file mode 100644 index 00000000..f5be8593 Binary files /dev/null and b/src/assets/favicon-96x96.png differ diff --git a/src/assets/favicon.ico b/src/assets/favicon.ico new file mode 100644 index 00000000..49dfc02b Binary files /dev/null and b/src/assets/favicon.ico differ diff --git a/src/assets/favicon.svg b/src/assets/favicon.svg new file mode 100644 index 00000000..6eae9869 --- /dev/null +++ b/src/assets/favicon.svg @@ -0,0 +1,10 @@ + + + + + \ No newline at end of file diff --git a/src/assets/fonts/Geist/LICENSE.TXT b/src/assets/fonts/Geist/LICENSE.TXT deleted file mode 100644 index df71062c..00000000 --- a/src/assets/fonts/Geist/LICENSE.TXT +++ /dev/null @@ -1,92 +0,0 @@ -Geist Sans and Geist Mono Font -(C) 2023 Vercel, made in collaboration with basement.studio - -This Font Software is licensed under the SIL Open Font License, Version 1.1. -This license is available with a FAQ at: http://scripts.sil.org/OFL and copied below - ------------------------------------------------------------ -SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ------------------------------------------------------------ - -PREAMBLE -The goals of the Open Font License (OFL) are to stimulate worldwide -development of collaborative font projects, to support the font creation -efforts of academic and linguistic communities, and to provide a free and -open framework in which fonts may be shared and improved in partnership -with others. - -The OFL allows the licensed fonts to be used, studied, modified and -redistributed freely as long as they are not sold by themselves. The -fonts, including any derivative works, can be bundled, embedded, -redistributed and/or sold with any software provided that any reserved -names are not used by derivative works. The fonts and derivatives, -however, cannot be released under any other type of license. The -requirement for fonts to remain under this license does not apply -to any document created using the fonts or their derivatives. - -DEFINITIONS -"Font Software" refers to the set of files released by the Copyright -Holder(s) under this license and clearly marked as such. This may -include source files, build scripts and documentation. - -"Reserved Font Name" refers to any names specified as such after the -copyright statement(s). - -"Original Version" refers to the collection of Font Software components as -distributed by the Copyright Holder(s). - -"Modified Version" refers to any derivative made by adding to, deleting, -or substituting -- in part or in whole -- any of the components of the -Original Version, by changing formats or by porting the Font Software to a -new environment. - -"Author" refers to any designer, engineer, programmer, technical -writer or other person who contributed to the Font Software. - -PERMISSION AND CONDITIONS -Permission is hereby granted, free of charge, to any person obtaining -a copy of the Font Software, to use, study, copy, merge, embed, modify, -redistribute, and sell modified and unmodified copies of the Font -Software, subject to the following conditions: - -1) Neither the Font Software nor any of its individual components, -in Original or Modified Versions, may be sold by itself. - -2) Original or Modified Versions of the Font Software may be bundled, -redistributed and/or sold with any software, provided that each copy -contains the above copyright notice and this license. These can be -included either as stand-alone text files, human-readable headers or -in the appropriate machine-readable metadata fields within text or -binary files as long as those fields can be easily viewed by the user. - -3) No Modified Version of the Font Software may use the Reserved Font -Name(s) unless explicit written permission is granted by the corresponding -Copyright Holder. This restriction only applies to the primary font name as -presented to the users. - -4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font -Software shall not be used to promote, endorse or advertise any -Modified Version, except to acknowledge the contribution(s) of the -Copyright Holder(s) and the Author(s) or with their explicit written -permission. - -5) The Font Software, modified or unmodified, in part or in whole, -must be distributed entirely under this license, and must not be -distributed under any other license. The requirement for fonts to -remain under this license does not apply to any document created -using the Font Software. - -TERMINATION -This license becomes null and void if any of the above conditions are -not met. - -DISCLAIMER -THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT -OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE -COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL -DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM -OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/src/assets/fonts/Geist/geist.woff2 b/src/assets/fonts/Geist/geist.woff2 deleted file mode 100644 index fa004e70..00000000 Binary files a/src/assets/fonts/Geist/geist.woff2 and /dev/null differ diff --git a/src/assets/images/logo.png b/src/assets/images/logo.png deleted file mode 100644 index 4f914a2a..00000000 Binary files a/src/assets/images/logo.png and /dev/null differ diff --git a/src/assets/images/logo.svg b/src/assets/images/logo.svg deleted file mode 100644 index 7dc22b99..00000000 --- a/src/assets/images/logo.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/src/assets/site.webmanifest b/src/assets/site.webmanifest new file mode 100644 index 00000000..01dc8b61 --- /dev/null +++ b/src/assets/site.webmanifest @@ -0,0 +1,21 @@ +{ + "name": "CeraUI for BELABOX©", + "short_name": "CeraUI", + "icons": [ + { + "src": "/web-app-manifest-192x192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "/web-app-manifest-512x512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} \ No newline at end of file diff --git a/src/assets/svelte.svg b/src/assets/svelte.svg deleted file mode 100644 index c5e08481..00000000 --- a/src/assets/svelte.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/assets/web-app-manifest-192x192.png b/src/assets/web-app-manifest-192x192.png new file mode 100644 index 00000000..e4d725eb Binary files /dev/null and b/src/assets/web-app-manifest-192x192.png differ diff --git a/src/assets/web-app-manifest-512x512.png b/src/assets/web-app-manifest-512x512.png new file mode 100644 index 00000000..1c379624 Binary files /dev/null and b/src/assets/web-app-manifest-512x512.png differ diff --git a/src/lib/components/icons/apple.svelte b/src/lib/components/icons/apple.svelte deleted file mode 100644 index 949631b9..00000000 --- a/src/lib/components/icons/apple.svelte +++ /dev/null @@ -1,11 +0,0 @@ - - - - - diff --git a/src/lib/components/icons/aria.svelte b/src/lib/components/icons/aria.svelte deleted file mode 100644 index e0727bbe..00000000 --- a/src/lib/components/icons/aria.svelte +++ /dev/null @@ -1,10 +0,0 @@ - - - - - diff --git a/src/lib/components/icons/github.svelte b/src/lib/components/icons/github.svelte deleted file mode 100644 index 1790637d..00000000 --- a/src/lib/components/icons/github.svelte +++ /dev/null @@ -1,11 +0,0 @@ - - - - - diff --git a/src/lib/components/icons/google.svelte b/src/lib/components/icons/google.svelte deleted file mode 100644 index 1c49e21f..00000000 --- a/src/lib/components/icons/google.svelte +++ /dev/null @@ -1,11 +0,0 @@ - - - - - diff --git a/src/lib/components/icons/index.ts b/src/lib/components/icons/index.ts index 23be5d5e..ed7e4d50 100644 --- a/src/lib/components/icons/index.ts +++ b/src/lib/components/icons/index.ts @@ -1,16 +1,5 @@ -import Apple from './apple.svelte'; -import Aria from './aria.svelte'; -import GitHub from './github.svelte'; -import Google from './google.svelte'; import Hamburger from './hamburger.svelte'; import Logo from './logo.svelte'; -import Npm from './npm.svelte'; -import PayPal from './paypal.svelte'; -import Pnpm from './pnpm.svelte'; -import RadixSvelte from './radix-svelte.svelte'; -import Tailwind from './tailwind.svelte'; -import Twitter from './twitter.svelte'; -import Yarn from './yarn.svelte'; import ArrowRight from 'lucide-svelte/icons/arrow-right'; import Check from 'lucide-svelte/icons/check'; import ChevronLeft from 'lucide-svelte/icons/chevron-left'; @@ -58,23 +47,11 @@ export const Icons = { arrowRight: ArrowRight, help: CircleHelp, pizza: Pizza, - twitter: Twitter, check: Check, copy: Copy, copyDone: ClipboardCheck, sun: SunMedium, moon: Moon, laptop: Laptop, - gitHub: GitHub, - radix: RadixSvelte, - 'Radix Svelte': RadixSvelte, - aria: Aria, - npm: Npm, - yarn: Yarn, - pnpm: Pnpm, - tailwind: Tailwind, - google: Google, - apple: Apple, - paypal: PayPal, Hamburger, }; diff --git a/src/lib/components/icons/npm.svelte b/src/lib/components/icons/npm.svelte deleted file mode 100644 index 64f90e49..00000000 --- a/src/lib/components/icons/npm.svelte +++ /dev/null @@ -1,11 +0,0 @@ - - - - - diff --git a/src/lib/components/icons/paypal.svelte b/src/lib/components/icons/paypal.svelte deleted file mode 100644 index 9a4c2035..00000000 --- a/src/lib/components/icons/paypal.svelte +++ /dev/null @@ -1,11 +0,0 @@ - - - - - diff --git a/src/lib/components/icons/pnpm.svelte b/src/lib/components/icons/pnpm.svelte deleted file mode 100644 index 30dccae1..00000000 --- a/src/lib/components/icons/pnpm.svelte +++ /dev/null @@ -1,11 +0,0 @@ - - - - - diff --git a/src/lib/components/icons/radix-svelte.svelte b/src/lib/components/icons/radix-svelte.svelte deleted file mode 100644 index 5777c5d7..00000000 --- a/src/lib/components/icons/radix-svelte.svelte +++ /dev/null @@ -1,13 +0,0 @@ - - - - - diff --git a/src/lib/components/icons/radix.svelte b/src/lib/components/icons/radix.svelte deleted file mode 100644 index de09cf22..00000000 --- a/src/lib/components/icons/radix.svelte +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - diff --git a/src/lib/components/icons/svelte-logo.svelte b/src/lib/components/icons/svelte-logo.svelte deleted file mode 100644 index 12449a53..00000000 --- a/src/lib/components/icons/svelte-logo.svelte +++ /dev/null @@ -1,6 +0,0 @@ - diff --git a/src/lib/components/icons/tailwind.svelte b/src/lib/components/icons/tailwind.svelte deleted file mode 100644 index 575b1cd0..00000000 --- a/src/lib/components/icons/tailwind.svelte +++ /dev/null @@ -1,11 +0,0 @@ - - - - - diff --git a/src/lib/components/icons/twitter.svelte b/src/lib/components/icons/twitter.svelte deleted file mode 100644 index a759f76b..00000000 --- a/src/lib/components/icons/twitter.svelte +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/src/lib/components/icons/yarn.svelte b/src/lib/components/icons/yarn.svelte deleted file mode 100644 index c4279bd8..00000000 --- a/src/lib/components/icons/yarn.svelte +++ /dev/null @@ -1,11 +0,0 @@ - - - - - diff --git a/src/lib/helpers/NetworkHelper.ts b/src/lib/helpers/NetworkHelper.ts index c71f32c9..82345662 100644 --- a/src/lib/helpers/NetworkHelper.ts +++ b/src/lib/helpers/NetworkHelper.ts @@ -21,22 +21,26 @@ export const networkRenameWithError = (name: string, error?: string) => { } return name; }; + export const networkRename = (name: string) => { - const originalName = name; + let numberSuffix = ''; + const number = name.match(/\d+$/g)?.[0]; + if (number) { + numberSuffix = ` ${Number.parseInt(number) + 1}`; + name = name.slice(0, -number.length).trim(); + } + if (name.startsWith('wl')) { - name = 'WiFI'; + name = 'WiFi'; } else if (name.startsWith('eth') || name.startsWith('en')) { - if (Number.parseInt(name[name.length - 1]) < 2) { - name = 'Ethernet'; - } else { - name = 'ETH-Modem'; - } + name = 'Ethernet'; } else if (name.startsWith('ww')) { name = 'Modem'; + } else if (name.startsWith('usb')) { + name = 'USB'; } - name += ' ' + (Number.parseInt(originalName[originalName.length - 1]) + name === 'ETH-Modem' ? -1 : +1); - return name; + return name + numberSuffix; }; export const getModemNetworkName = (name: string) => { diff --git a/src/lib/helpers/PipelineHelper.ts b/src/lib/helpers/PipelineHelper.ts index 7b710199..4d4948b6 100644 --- a/src/lib/helpers/PipelineHelper.ts +++ b/src/lib/helpers/PipelineHelper.ts @@ -27,17 +27,31 @@ export type GroupedPipelines = { }; export function parsePipelineName(name: string): PipelineInfo { + // Basic device extraction const deviceMatch = name.match(/^([^/]+)/); + + // Extract encoder (h264 or h265) const encoderMatch = name.match(/(h264|h265)/); - - const formatMatch = name.match(/(?:h264|h265)_([^_]+(?:_[^_\d]+)*)/); + + // Format extraction - comes after h264/h265_ prefix + const formatMatch = name.match(/(?:h264|h265)_([^_]+)/); + + // Extract resolution - typically NNNp format (like 720p, 1080p) const resolutionMatch = name.match(/(\d{3,4}p)/); - const fpsMatch = name.match(/(\d+(?:\.\d+)?)(?:fps)?$/); - + + // Extract framerate - typically pNN format (like p30, p60) + // Handle both underscore separated and inline formats + const fpsMatch = name.match(/p(\d+(?:\.\d+)?)/); + + // Special case for libuvch264 + const isLibUVC = name.includes('libuvch264'); + return { device: deviceMatch ? deviceMatch[0] : null, encoder: encoderMatch ? encoderMatch[0] : null, - format: formatMatch ? formatMatch[1].replace(/_/g, ' ').replace('libuvch264', 'USB-libuvch264') : null, + format: formatMatch + ? (isLibUVC ? 'usb-libuvch264' : formatMatch[1].replace(/_/g, ' ')) + : null, resolution: resolutionMatch ? resolutionMatch[0] : '[Match device resolution]', fps: fpsMatch ? parseFloat(fpsMatch[1]) : '[Match device output]', }; @@ -77,4 +91,4 @@ export const groupPipelinesByDeviceAndFormat = (pipelines: PipelinesMessage): Gr }); return groupedPipelines; -}; +}; \ No newline at end of file diff --git a/src/locale/ar.json b/src/locale/ar.json index 239665a5..a9984d66 100644 --- a/src/locale/ar.json +++ b/src/locale/ar.json @@ -46,6 +46,7 @@ "settings": { "encoderSettings": "إعدادات المرمز", "inputMode": "وضع الإدخال", + "djiCameraMessage": "قد تعمل كاميرات DJI بشكل أفضل باستخدام وضع الإدخال USB-LIBUVCH264", "selectInputMode": "اختر وضع الإدخال", "selectEncodingOutputFormat": "اختر ترميز الإخراج", "selectEncodingFormat": "حدد تنسيق إخراج الترميز", @@ -108,6 +109,7 @@ "enabled": "مفعّل", "connected": "متصل", "disconnected": "غير متصل", + "disconnecting": "جارِ الانفصال", "connecting": "جاري الاتصال", "scanning": "جارٍ المسح" } diff --git a/src/locale/de.json b/src/locale/de.json index 62a7a47f..37d52bef 100644 --- a/src/locale/de.json +++ b/src/locale/de.json @@ -46,6 +46,7 @@ "settings": { "encoderSettings": "Encoder-Einstellungen", "inputMode": "Eingabemodus", + "djiCameraMessage": "DJI-Kameras funktionieren möglicherweise besser im USB-LIBUVCH264-Eingabemodus", "selectInputMode": "Wähle den Eingabemodus", "selectEncodingOutputFormat": "Wähle den Ausgabe-Codec", "selectEncodingFormat": "Kodierungsausgabeformat auswählen", @@ -108,6 +109,7 @@ "enabled": "Aktiviert", "connected": "Verbunden", "disconnected": "Getrennt", + "disconnecting": "Trennen", "connecting": "Verbinde", "scanning": "Wird gescannt" } diff --git a/src/locale/en.json b/src/locale/en.json index f3bb3389..bf62745b 100644 --- a/src/locale/en.json +++ b/src/locale/en.json @@ -46,6 +46,7 @@ "settings": { "encoderSettings": "Encoder Settings", "inputMode": "Input Mode", + "djiCameraMessage": "DJI cameras may work better using the USB-LIBUVCH264 input mode", "selectInputMode": "Select the input mode", "selectEncodingOutputFormat": "Select output codec", "encodingFormat": "Encoding format", @@ -108,6 +109,7 @@ "enabled": "Enabled", "connected": "Connected", "disconnected": "Disconnected", + "disconnecting": "Disconnecting", "connecting": "Connecting", "scanning": "Scanning" diff --git a/src/locale/es.json b/src/locale/es.json index e5ce3c32..73d51c0d 100644 --- a/src/locale/es.json +++ b/src/locale/es.json @@ -45,6 +45,7 @@ "settings": { "encoderSettings": "Configuración del codificador", "inputMode": "Modo de entrada", + "djiCameraMessage": "Las cámaras DJI suelen funcionar mejor usando el modo de entrada USB-LIBUVCH264", "selectInputMode": "Seleccione el modo de Entrada", "selectEncodingOutputFormat": "Seleccione un formato de salida", "encodingFormat": "Formato de codificación", @@ -107,6 +108,7 @@ "connected": "Conectado", "enabled": "Habilitada", "disconnected": "Desconectado", + "disconnecting": "Desconectándose", "connecting": "Conectando", "scanning": "Buscando" } diff --git a/src/locale/fr.json b/src/locale/fr.json index 02db00c5..79ccf2e7 100644 --- a/src/locale/fr.json +++ b/src/locale/fr.json @@ -48,6 +48,7 @@ "settings": { "encoderSettings": "Paramètres de l'encodeur", "inputMode": "Mode d'entrée", + "djiCameraMessage": "Les caméras DJI peuvent mieux fonctionner en utilisant le mode d’entrée USB-LIBUVCH264", "selectInputMode": "Sélectionnez le mode d'entrée", "selectEncodingOutputFormat": "Sélectionnez le codec de sortie", "encodingFormat": "Format de codage", @@ -108,6 +109,7 @@ "registered": "Enregistré", "connected": "Connecté", "disconnected": "Déconnecté", + "disconnecting": "Déconnexion en cours", "connecting": "Connexion en cours", "scanning": "Scan en cours" } diff --git a/src/locale/hi.json b/src/locale/hi.json index 18bbcc84..2a075ba5 100644 --- a/src/locale/hi.json +++ b/src/locale/hi.json @@ -47,6 +47,7 @@ "encoderSettings": "एनकोडर सेटिंग्स", "inputMode": "इनपुट मोड", "selectInputMode": "इनपुट मोड चुनें", + "djiCameraMessage": "DJI कैमरे USB-LIBUVCH264 इनपुट मोड का उपयोग करके बेहतर काम कर सकते हैं", "selectEncodingOutputFormat": "आउटपुट कोडेक चुनें", "selectEncodingFormat": "एनकोडिंग आउटपुट फॉर्मेट चुनें", "encodingResolution": "एनकोडिंग रिज़ॉल्यूशन", @@ -108,6 +109,7 @@ "enabled": "सक्षम", "connected": "जुड़ा हुआ", "disconnected": "डिस्कनेक्ट किया गया", + "disconnecting": "डिसकनेक्ट हो रहा है", "connecting": "कनेक्ट हो रहा है", "scanning": "स्कैनिंग" } diff --git a/src/locale/ja.json b/src/locale/ja.json index 36385226..34acfa8a 100644 --- a/src/locale/ja.json +++ b/src/locale/ja.json @@ -46,6 +46,7 @@ "settings": { "encoderSettings": "エンコーダー設定", "inputMode": "入力モード", + "djiCameraMessage": "DJIカメラはUSB-LIBUVCH264入力モードを使用するとより良く動作する可能性があります", "selectInputMode": "入力モードを選択", "selectEncodingOutputFormat": "出力コーデックを選択", "selectEncodingFormat": "エンコーディング出力フォーマットを選択", @@ -108,6 +109,7 @@ "enabled": "有効", "connected": "接続済み", "disconnected": "切断済み", + "disconnecting": "切断中", "connecting": "接続中", "scanning": "スキャン中" } diff --git a/src/locale/ko.json b/src/locale/ko.json index 84e7f78f..66086338 100644 --- a/src/locale/ko.json +++ b/src/locale/ko.json @@ -46,6 +46,7 @@ "settings": { "encoderSettings": "인코더 설정", "inputMode": "입력 모드", + "djiCameraMessage": "DJI 카메라는 USB-LIBUVCH264 입력 모드를 사용하면 더 잘 작동할 수 있습니다", "selectInputMode": "입력 모드를 선택", "selectEncodingOutputFormat": "출력 코덱을 선택", "selectEncodingFormat": "인코딩 출력 형식 선택", @@ -108,6 +109,7 @@ "enabled": "활성화됨", "connected": "연결됨", "disconnected": "연결 해제됨", + "disconnecting": "연결 해제 중", "connecting": "연결 중", "scanning": "스캔 중" } diff --git a/src/locale/pt-BR.json b/src/locale/pt-BR.json index 52168f4f..6b5b0b9b 100644 --- a/src/locale/pt-BR.json +++ b/src/locale/pt-BR.json @@ -48,6 +48,7 @@ "settings": { "encoderSettings": "Configurações do Codificador", "inputMode": "Modo de Entrada", + "djiCameraMessage": "As câmeras DJI podem funcionar melhor usando o modo de entrada USB-LIBUVCH264", "selectInputMode": "Selecione o modo de entrada", "selectEncodingOutputFormat": "Selecione o codec de saída", "selectEncodingFormat": "Selecione o formato de saída de codificação", @@ -107,6 +108,7 @@ "registered": "Registrado", "connected": "Conectado", "disconnected": "Desconectado", + "disconnecting": "Desconectando", "connecting": "Conectando", "scanning": "Escaneando" diff --git a/src/locale/zh.json b/src/locale/zh.json index 7f76603d..ddac9333 100644 --- a/src/locale/zh.json +++ b/src/locale/zh.json @@ -46,6 +46,7 @@ "settings": { "encoderSettings": "编码器设置", "inputMode": "输入模式", + "djiCameraMessage": "DJI 相机在使用 USB-LIBUVCH264 输入模式时可能表现更好", "selectInputMode": "选择输入模式", "selectEncodingOutputFormat": "选择输出编解码器", "selectEncodingFormat": "选择编码输出格式", @@ -108,6 +109,7 @@ "enabled": "已启用", "connected": "已连接", "disconnected": "已断开", + "disconnecting": "正在断开", "connecting": "连接中", "scanning": "扫描中" diff --git a/src/main/Layout.svelte b/src/main/Layout.svelte index e03960ff..0048ef05 100644 --- a/src/main/Layout.svelte +++ b/src/main/Layout.svelte @@ -8,11 +8,196 @@ import { Toaster } from '$lib/components/ui/sonner'; import UpdatingOverlay from '$lib/components/updating-overlay.svelte'; import { authStatusStore } from '$lib/stores/auth-status'; import { AuthMessages, NotificationsMessages, StatusMessages, sendAuthMessage } from '$lib/stores/websocket-store'; +import { startStreaming as startStreamingFn, stopStreaming as stopStreamingFn } from '$lib/helpers/SystemHelper'; let authStatus = $state(false); let isCheckingAuthStatus = $state(true); let updatingStatus: StatusMessage['updating'] = $state(false); const setupLocaleResult = setupLocale(); + +// Toast tracking system for duplicates +interface ToastInfo { + id: string; + timestamp: number; + duration: number; + notificationKey: string; // Unique key for identifying similar notifications +} +let activeToasts = $state>({}); +// Simple flag to prevent recursive updates +let isUpdatingToasts = false; + +// Function to dismiss all non-persistent toasts +const dismissAllNonPersistentToasts = () => { + // Guard against recursion + if (isUpdatingToasts) return; + + try { + isUpdatingToasts = true; + + // Use Sonner's built-in dismissAll method first which is more reliable + toast.dismiss(); + + // Reset tracking state safely + setTimeout(() => { + activeToasts = {}; + }, 0); + } finally { + isUpdatingToasts = false; + } +}; + +// Override original SystemHelper functions to add toast clearing +const startStreaming = (config: { [key: string]: string | number }) => { + // Guard against infinite updates + if (isUpdatingToasts) { + startStreamingFn(config); + return; + } + + try { + isUpdatingToasts = true; + + // Force clear all toasts completely + toast.dismiss(); + + // Clear all persistent notification timers + Object.values(persistentNotificationTimers).forEach(timer => { + clearTimeout(timer); + }); + + // Reset tracking states safely using setTimeout to avoid reactive updates + setTimeout(() => { + activeToasts = {}; + persistentNotificationTimers = {}; + }, 0); + + // Now call the original function + startStreamingFn(config); + } finally { + // Ensure flag is reset + isUpdatingToasts = false; + } +}; + +const stopStreaming = () => { + // Guard against infinite updates + if (isUpdatingToasts) { + stopStreamingFn(); + return; + } + + try { + isUpdatingToasts = true; + + // Force clear all toasts completely + toast.dismiss(); + + // Clear all persistent notification timers + Object.values(persistentNotificationTimers).forEach(timer => { + clearTimeout(timer); + }); + + // Reset tracking states safely using setTimeout to avoid reactive updates + setTimeout(() => { + activeToasts = {}; + persistentNotificationTimers = {}; + }, 0); + + // Now call the original function + stopStreamingFn(); + } finally { + // Ensure flag is reset + isUpdatingToasts = false; + } +}; + +// Show a toast, extending duration if a duplicate exists +const showToast = (type: NotificationType, name: string, options: any) => { + // Prevent recursive calls that could cause infinite loops + if (isUpdatingToasts) return; + + try { + isUpdatingToasts = true; + + // Generate a message-only key to identify toasts with the same content + const messageKey = options.description; + const now = Date.now(); + + // For persistent notifications, don't create duplicates + if (options.isPersistent) { + // Check if we already have this persistent notification + const existingPersistentToastEntries = Object.entries(activeToasts).filter(([_, toast]) => + toast.notificationKey === messageKey && toast.duration === Infinity + ); + + if (existingPersistentToastEntries.length > 0) { + // We already have this persistent notification showing + // The timer has already been reset in the subscription, so just skip creating a duplicate + return; + } + } + + // Create a unique ID for this toast + const id = `toast-${now}-${Math.random().toString(36).substr(2, 9)}`; + options.id = id; + + // Simplified onDismiss handler + const originalOnDismiss = options.onDismiss; + options.onDismiss = () => { + // Call original onDismiss if it exists + if (originalOnDismiss) originalOnDismiss(); + + // Safely update our tracking + if (activeToasts[id]) { + setTimeout(() => { + const newActiveToasts = { ...activeToasts }; + delete newActiveToasts[id]; + activeToasts = newActiveToasts; + }, 0); + } + }; + + // Display the toast + toast[type](name, options); + + // Track this toast + const toastInfo = { + id, + timestamp: now, + duration: options.duration, + notificationKey: messageKey + }; + + // Use a non-reactive way to update activeToasts to avoid triggering effects + setTimeout(() => { + activeToasts = { ...activeToasts, [id]: toastInfo }; + }, 0); + + // Clean up the toast tracking after it expires (except for persistent toasts) + if (options.duration !== Infinity) { + setTimeout(() => { + try { + // Safely dismiss and remove tracking + toast.dismiss(id); + + setTimeout(() => { + if (activeToasts[id]) { + const newActiveToasts = { ...activeToasts }; + delete newActiveToasts[id]; + activeToasts = newActiveToasts; + } + }, 0); + } catch (e) { + console.error('Error cleaning up toast:', e); + } + }, options.duration + 1000); // Add 1 second buffer + } + } finally { + // Always make sure we reset the flag + isUpdatingToasts = false; + } +}; + StatusMessages.subscribe(status => { updatingStatus = status?.updating && typeof status.updating !== 'boolean' && status.updating.result !== 0; }); @@ -28,9 +213,10 @@ if (auth) { AuthMessages.subscribe(message => { if (message?.success) { isCheckingAuthStatus = false; - toast.success('AUTH', { + showToast('success', 'AUTH', { duration: 5000, description: 'Successfully authenticated', + dismissable: true }); authStatusStore.set(true); } @@ -39,15 +225,76 @@ AuthMessages.subscribe(message => { authStatus = status; }); }); + +// Time after which we'll automatically clear a persistent notification if no new updates arrive +const PERSISTENT_AUTO_CLEAR_TIMEOUT = 5000; // 5 seconds + +// Track timers for auto-clearing persistent notifications +let persistentNotificationTimers = $state>({}); + NotificationsMessages.subscribe(notifications => { notifications?.show?.forEach(notification => { - toast[notification.type as NotificationType](notification.name.toUpperCase(), { + const toastKey = `${notification.type}-${notification.msg}`; + + // If this is a persistent notification, reset/create its auto-clear timer + if (notification.is_persistent) { + // Clear any existing timer for this notification + if (persistentNotificationTimers[toastKey]) { + clearTimeout(persistentNotificationTimers[toastKey]); + } + + // Set a new timer to auto-clear this notification if no new updates arrive + const timerId = window.setTimeout(() => { + // Find any toasts with this key + Object.entries(activeToasts).forEach(([id, toast]) => { + if (toast.notificationKey === notification.msg && toast.duration === Infinity) { + // Auto-clear this toast since no new updates have arrived + toast.dismiss(toast.id); + + // Update our tracking + setTimeout(() => { + const newActiveToasts = { ...activeToasts }; + delete newActiveToasts[id]; + activeToasts = newActiveToasts; + }, 0); + } + }); + + // Remove this timer from tracking + setTimeout(() => { + const newTimers = { ...persistentNotificationTimers }; + delete newTimers[toastKey]; + persistentNotificationTimers = newTimers; + }, 0); + }, PERSISTENT_AUTO_CLEAR_TIMEOUT); + + // Update the timers object + const newTimers = { ...persistentNotificationTimers }; + newTimers[toastKey] = timerId; + persistentNotificationTimers = newTimers; + } + + // Show the toast + showToast(notification.type as NotificationType, notification.name.toUpperCase(), { description: notification.msg, duration: notification.is_persistent ? Infinity : notification.duration * 2500, dismissable: !notification.is_dismissable, + isPersistent: notification.is_persistent }); }); }); + +// TypeScript interface for global window object +declare global { + interface Window { + startStreamingWithNotificationClear: typeof startStreaming; + stopStreamingWithNotificationClear: typeof stopStreaming; + } +} + +// Export our functions to the global scope to make them available to other components +window.startStreamingWithNotificationClear = startStreaming; +window.stopStreamingWithNotificationClear = stopStreaming; {#await setupLocaleResult} diff --git a/src/main/Tabs/Settings.svelte b/src/main/Tabs/Settings.svelte index 0b8de7db..84db87b1 100644 --- a/src/main/Tabs/Settings.svelte +++ b/src/main/Tabs/Settings.svelte @@ -1,6 +1,7 @@
- {#if isStreaming} - - {:else} - - {/if} +
+ {#if isStreaming} + + {:else} + + {/if} +
@@ -271,131 +376,193 @@ const startStreamingWithCurrentConfig = () => { - -
- - { - selectedResolution = { value: undefined, label: undefined }; - selectedFramerate = { value: undefined, label: undefined }; - selectedInputMode = value; - }}> - - - - - - {#if groupedPipelines} - {#each Object.entries(groupedPipelines) as [pipelineKey, _]} - {@const label = pipelineKey.toUpperCase().split(' ')[0]} - - {/each} - {/if} - - - -
+
+ +
+ + { + selectedEncoder = { value: undefined, label: undefined }; + selectedResolution = { value: undefined, label: undefined }; + selectedFramerate = { value: undefined, label: undefined }; + selectedInputMode = value; + + // Auto-select the next level if there's only one option + if (value) { + autoSelectNextOption('inputMode'); + } + }}> + + + + + + {#if groupedPipelines} + {#each Object.entries(groupedPipelines) as [pipelineKey, _]} + {@const label = pipelineKey.toUpperCase().split(' ')[0]} + + {/each} + {/if} + + + + {#if selectedInputMode?.value && selectedInputMode.value.toLowerCase().includes('usb')} +

+ {$_('settings.djiCameraMessage')} +

+ {/if} +
- -
- - (selectedEncoder = value)}> - - - - - - {#if selectedInputMode?.value && groupedPipelines?.[selectedInputMode.value]} - {#each Object.keys(groupedPipelines[selectedInputMode.value]) as encoder} - - {/each} - {/if} - - - -
+ +
+ + { + selectedEncoder = value; + selectedResolution = { value: undefined, label: undefined }; + selectedFramerate = { value: undefined, label: undefined }; + + // Auto-select the next level if there's only one option + if (value) { + autoSelectNextOption('encoder'); + } + }}> + + + + + + {#if selectedInputMode?.value && groupedPipelines?.[selectedInputMode.value]} + {#each Object.keys(groupedPipelines[selectedInputMode.value]) as encoder} + + {/each} + {/if} + + + +
- -
- - { - selectedFramerate = { value: undefined, label: undefined }; - selectedResolution = value; - }}> - - - - - - {#if selectedEncoder?.value && selectedInputMode?.value && groupedPipelines?.[selectedInputMode.value]?.[selectedEncoder.value]} - {@const resolutions = Object.keys(groupedPipelines[selectedInputMode.value][selectedEncoder.value])} - {#each resolutions as resolution} - - {/each} - {/if} - - - -
+ +
+ + { + selectedResolution = value; + selectedFramerate = { value: undefined, label: undefined }; + + // Auto-select the next level if there's only one option + if (value) { + autoSelectNextOption('resolution'); + } + }}> + + + + + + {#if selectedEncoder?.value && selectedInputMode?.value && groupedPipelines?.[selectedInputMode.value]?.[selectedEncoder.value]} + {@const resolutions = Object.keys( + groupedPipelines[selectedInputMode.value][selectedEncoder.value], + )} + {@const sortedResolutions = [...resolutions].sort((a, b) => { + // Put "match device resolution" or similar special options first + if (a.toLowerCase().includes('match') || a.toLowerCase().includes('device')) return -1; + if (b.toLowerCase().includes('match') || b.toLowerCase().includes('device')) return 1; + + // Extract numeric values (like "720" from "720p") + const numA = parseInt(a.match(/\d+/)?.[0] || '0', 10); + const numB = parseInt(b.match(/\d+/)?.[0] || '0', 10); + + // Sort by numeric value + return numA - numB; + })} + {#each sortedResolutions as resolution} + + {/each} + {/if} + + + +
- -
- - (selectedFramerate = value)}> - - - - - - {#if selectedEncoder?.value && selectedInputMode?.value && selectedResolution?.value && groupedPipelines?.[selectedInputMode.value]?.[selectedEncoder.value][selectedResolution.value]} - {@const framerates = - groupedPipelines[selectedInputMode.value][selectedEncoder.value][selectedResolution.value]} - {#each framerates as framerate} - - {/each} - {/if} - - - -
- - { - setTimeout(() => { - selectedBitrate = value[0]; - updateMaxBitrate(); - }); - }} /> - { - selectedBitrate = normalizeValue(selectedBitrate, 2000, 12000, 50); - updateMaxBitrate(); - }}> - {#if isStreaming} -

{$_('settings.changeBitrateNotice')}

+ +
+ + (selectedFramerate = value)}> + + + + + + {#if selectedEncoder?.value && selectedInputMode?.value && selectedResolution?.value && groupedPipelines?.[selectedInputMode.value]?.[selectedEncoder.value][selectedResolution.value]} + {@const framerates = + groupedPipelines[selectedInputMode.value][selectedEncoder.value][selectedResolution.value]} + {@const sortedFramerates = [...framerates].sort((a, b) => { + // Put "match device output" or similar special options first + const fpsA = a.extraction.fps; + const fpsB = b.extraction.fps; + + if (typeof fpsA === 'string' && fpsA.toLowerCase().includes('match')) return -1; + if (typeof fpsB === 'string' && fpsB.toLowerCase().includes('match')) return 1; + + // Convert to numbers for numeric comparison + const numA = typeof fpsA === 'number' ? fpsA : parseFloat(String(fpsA)) || 0; + const numB = typeof fpsB === 'number' ? fpsB : parseFloat(String(fpsB)) || 0; + + // Sort by numeric value + return numA - numB; + })} + {#each sortedFramerates as framerate} + + {/each} + {/if} + + + + + {#if formErrors.pipeline} +

{formErrors.pipeline}

{/if} + +
+ + { + setTimeout(() => { + selectedBitrate = value[0]; + updateMaxBitrate(); + }); + }} /> + { + selectedBitrate = normalizeValue(selectedBitrate, 2000, 12000, 50); + updateMaxBitrate(); + }}> + {#if isStreaming} +

{$_('settings.changeBitrateNotice')}

+ {/if} +
@@ -407,73 +574,86 @@ const startStreamingWithCurrentConfig = () => { - {#if audioCodecs && unparsedPipelines && selectedPipeline && unparsedPipelines[selectedPipeline].asrc} - - (selectedAudioSource = value)}> - - - - - - {#if audioSources} - {#each audioSources as audioSource} - - {/each} - {/if} - - - - {/if} - - {#if audioCodecs && unparsedPipelines && selectedPipeline && unparsedPipelines[selectedPipeline].acodec} - - (selectedAudioCodec = value)}> - - - - - - {#each Object.entries(audioCodecs) as [codec, label]} - - {/each} - - - -
- - (selectedAudioDelay = value[0])} - disabled={isStreaming} - max={2000} - min={-2000} - step={5}> - { - selectedAudioDelay = normalizeValue(selectedAudioDelay, 2000, 12000, 50); - }}> -
- {/if} - - {#if audioCodecs && unparsedPipelines && selectedPipeline && !unparsedPipelines[selectedPipeline].acodec && !unparsedPipelines[selectedPipeline].asrc} -

{$_('settings.noAudioSettingSupport')}

- {/if} - - {#if audioCodecs && unparsedPipelines && !selectedPipeline} -

{$_('settings.audioSettingsMessage')}

- {/if} +
+ {#if audioCodecs && unparsedPipelines && selectedPipeline && unparsedPipelines[selectedPipeline].asrc} +
+ + (selectedAudioSource = value)}> + + + + + + {#if audioSources} + {#each audioSources as audioSource} + + {/each} + {/if} + + + +
+ {/if} + + {#if audioCodecs && unparsedPipelines && selectedPipeline && unparsedPipelines[selectedPipeline].acodec} +
+ + (selectedAudioCodec = value)}> + + + + + + {#each Object.entries(audioCodecs) as [codec, label]} + + {/each} + + + +
+ + (selectedAudioDelay = value[0])} + disabled={isStreaming} + max={2000} + min={-2000} + step={5}> + { + selectedAudioDelay = normalizeValue(selectedAudioDelay, 2000, 12000, 50); + }}> +
+
+ {/if} + + {#if audioCodecs && unparsedPipelines && selectedPipeline && !unparsedPipelines[selectedPipeline].acodec && !unparsedPipelines[selectedPipeline].asrc} +
+

{$_('settings.noAudioSettingSupport')}

+
+ {/if} + + {#if audioCodecs && unparsedPipelines && !selectedPipeline} +
+

{$_('settings.audioSettingsMessage')}

+
+ {/if} +
@@ -483,73 +663,95 @@ const startStreamingWithCurrentConfig = () => { - - value && (selectedRelayServer = value)}> - - - - - - {$_('settings.manualConfiguration')} - {#if relayMessage?.servers} - {#each Object.entries(relayMessage?.servers) as [server, serverInfo]} - - {/each} - {/if} - - - - {#if selectedRelayServer?.value === '-1' || selectedRelayServer?.value === undefined} - - - {/if} - {#if selectedRelayServer?.value !== '-1' && selectedRelayServer?.value !== undefined} - - value && (selectedRelayAccount = value)}> - - - - - - {$_('settings.manualConfiguration')} - {#if relayMessage?.servers} - {#each Object.entries(relayMessage?.accounts) as [account, accountInfo]} - - {/each} - {/if} - - - - {/if} - {#if selectedRelayAccount?.value === '-1' || selectedRelayAccount?.value === undefined} - - - - - {/if} - {#if srtLatency !== undefined} - - (srtLatency = value[0])}> - { - srtLatency = normalizeValue(srtLatency, 2000, 12000, 50); - }}> - {/if} +
+
+ + value && (selectedRelayServer = value)}> + + + + + + {$_('settings.manualConfiguration')} + {#if relayMessage?.servers} + {#each Object.entries(relayMessage?.servers) as [server, serverInfo]} + + {/each} + {/if} + + + +
+ + {#if selectedRelayServer?.value === '-1' || selectedRelayServer?.value === undefined} +
+ + +
+ {/if} + + {#if selectedRelayServer?.value !== '-1' && selectedRelayServer?.value !== undefined} +
+ + value && (selectedRelayAccount = value)}> + + + + + + {$_('settings.manualConfiguration')} + {#if relayMessage?.servers} + {#each Object.entries(relayMessage?.accounts) as [account, accountInfo]} + + {/each} + {/if} + + + +
+ {/if} + + {#if selectedRelayAccount?.value === '-1' || selectedRelayAccount?.value === undefined} +
+ + +
+
+ + +
+ {/if} + + {#if srtLatency !== undefined} +
+ + (srtLatency = value[0])} + disabled={isStreaming}> + { + srtLatency = normalizeValue(srtLatency, 2000, 12000, 50); + }}> +
+ {/if} +
diff --git a/src/main/shared/ModemConfigurator.svelte b/src/main/shared/ModemConfigurator.svelte index 62721bb1..ce023655 100644 --- a/src/main/shared/ModemConfigurator.svelte +++ b/src/main/shared/ModemConfigurator.svelte @@ -1,11 +1,11 @@
-
- - { - if (val) modemProperties.selectedNetwork = val; - }}> - - - - - - {#each modem.network_type.supported as networkType} - {renameSupportedModemNetwork(networkType)} - {/each} - - - -
-
- - {#if modemProperties.roaming} - - {:else} - - {/if} - -
-

- {$_('network.modem.enableRoaming')} -

-
-
-
-
- +
+
+ { - if (val) modemProperties.network = val; + if (val) { + formData.selectedNetwork = { ...val }; // Create a new object to ensure reactivity + errors.selectedNetwork = undefined; + } }}> - - + + - - - {#if modem.available_networks} - {#each Object.entries(modem.available_networks) as [key, availableNetwork]} - {#if availableNetwork.availability === 'available'} - - {/if} - {/each} - {/if} + {#each modem.network_type.supported as networkType} + {renameSupportedModemNetwork(networkType)} + {/each} -
- -
-
-
- -
- - {#if modemProperties.autoconfig} - - {:else} - + {#if errors.selectedNetwork} +

{errors.selectedNetwork}

{/if} -
- -
-

- {$_('network.modem.autoapn')} -

-
- {#if !modemProperties.autoconfig} -
- - -
-
- - + +
+ (formData.roaming = val)}> + {#if formData.roaming} + + {:else} + + {/if} + +
+

+ {$_('network.modem.enableRoaming')} +

+
-
- - + + {#if formData.roaming} +
+ +
+
+ { + if (val) formData.network = { ...val }; // Create a new object to ensure reactivity + }}> + + + {Number(formData.network.value) === -1 + ? $_('network.modem.automaticRoamingNetwork') + : formData.network.label} + + + + + + {#if modem.available_networks} + {#each Object.entries(modem.available_networks) as [key, availableNetwork]} + {#if availableNetwork.availability === 'available'} + + {/if} + {/each} + {/if} + + + +
+ +
+
+ {/if} + +
+ (formData.autoconfig = val)}> + {#if formData.autoconfig} + + {:else} + + {/if} + +
+

+ {$_('network.modem.autoapn')} +

+
- {/if} - + {#if !formData.autoconfig} +
+ + (errors.apn = undefined)} /> + {#if errors.apn} +

{errors.apn}

+ {/if} +
+ +
+ + +
+ +
+ + +
+ {/if} + +
+ + +
+