Reduce options UI code duplication

This commit is contained in:
hensm
2025-12-29 15:55:12 +00:00
parent 1f200bef9a
commit 1e3d7c21e6
7 changed files with 370 additions and 470 deletions

View File

@@ -106,4 +106,4 @@ export default {
siteWhitelistCustomUserAgent: "", siteWhitelistCustomUserAgent: "",
showAdvancedOptions: false showAdvancedOptions: false
} satisfies Options as Options; } as Options;

View File

@@ -13,6 +13,7 @@
import type { Options } from "../../lib/options"; import type { Options } from "../../lib/options";
import messaging from "../../messaging"; import messaging from "../../messaging";
import Option from "./Option.svelte";
const _ = browser.i18n.getMessage; const _ = browser.i18n.getMessage;
@@ -294,16 +295,15 @@
{/if} {/if}
<div class="bridge__options"> <div class="bridge__options">
<div class="option option--inline"> <Option
<div class="option__control">
<input
name="bridgeBackupEnabled"
id="bridgeBackupEnabled" id="bridgeBackupEnabled"
label={_("optionsBridgeEnabled")}
description={_("optionsBridgeBackupEnabledDescription")}
type="checkbox" type="checkbox"
bind:checked={opts.bridgeBackupEnabled} bind:checked={opts.bridgeBackupEnabled}
/> inline
</div> >
<label class="option__label" for="bridgeBackupEnabled"> <svelte:fragment slot="label">
{backupMessageStart} {backupMessageStart}
<input <input
class="bridge__backup-host" class="bridge__backup-host"
@@ -323,48 +323,30 @@
bind:value={opts.bridgeBackupPort} bind:value={opts.bridgeBackupPort}
/> />
{backupMessageEnd} {backupMessageEnd}
</label> </svelte:fragment>
<div class="option__description"> </Option>
{_("optionsBridgeBackupEnabledDescription")}
</div>
</div>
{#if opts.showAdvancedOptions} {#if opts.showAdvancedOptions}
<fieldset <fieldset
class="bridge__daemon-options" class="bridge__daemon-options"
disabled={!opts.bridgeBackupEnabled} disabled={!opts.bridgeBackupEnabled}
> >
<div class="option option--inline"> <Option
<div class="option__control">
<input
id="bridgeBackupSecure" id="bridgeBackupSecure"
label={_("optionsBridgeBackupSecure")}
description={_("optionsBridgeBackupSecureDescription")}
type="checkbox" type="checkbox"
bind:checked={opts.bridgeBackupSecure} bind:checked={opts.bridgeBackupSecure}
inline
/> />
</div> <Option
<label class="option__label" for="bridgeBackupSecure">
{_("optionsBridgeBackupSecure")}
</label>
<div class="option__description">
{_("optionsBridgeBackupSecureDescription")}
</div>
</div>
<div class="option">
<label class="option__label" for="bridgeBackupPassword">
{_("optionsBridgeBackupPassword")}
</label>
<div class="option__control">
<input
id="bridgeBackupPassword" id="bridgeBackupPassword"
placeholder="Password" label={_("optionsBridgeBackupPassword")}
description={_("optionsBridgeBackupPasswordDescription")}
type="password" type="password"
bind:value={opts.bridgeBackupPassword} bind:value={opts.bridgeBackupPassword}
/> />
<div class="option__description">
{_("optionsBridgeBackupPasswordDescription")}
</div>
</div>
</div>
</fieldset> </fieldset>
{/if} {/if}
</div> </div>

View File

@@ -0,0 +1,87 @@
<script lang="ts">
import type { HTMLInputAttributes } from "svelte/elements";
const _ = browser.i18n.getMessage;
interface $$Props extends HTMLInputAttributes {
id: string;
label: string;
description?: string | undefined;
recommended?: boolean;
inline?: boolean;
value?: any;
checked?: boolean;
}
export let id: string;
export let label: string;
export let description: string | undefined = undefined;
export let recommended = false;
export let inline = false;
// Bindables
export let value: any = undefined;
export let checked: boolean | undefined = undefined;
let computedClassName: string;
$: {
computedClassName = "option";
if (inline) computedClassName += " option--inline";
if ($$restProps.class) computedClassName += ` ${$$restProps.class}`;
}
</script>
<div class={computedClassName}>
<!--
This is pretty awful, not a good fit for CSS reordering. Would use snippets,
but this Svelte is too old!
-->
{#if inline}
<div class="option__control">
{#if $$restProps.type === "checkbox"}
<input {id} type="checkbox" bind:checked={checked} {...$$restProps} />
{:else}
<input {id} bind:value={value} {...$$restProps} />
{/if}
</div>
<label class="option__label" for={id}>
{label}
{#if recommended}
<span class="option__recommended">
{_("optionsOptionRecommended")}
</span>
{/if}
<slot name="label" />
</label>
{#if description}
<div class="option__description">
{description}
<slot name="description" />
</div>
{/if}
{:else}
<label class="option__label" for={id}>
{label}
{#if recommended}
<span class="option__recommended">
{_("optionsOptionRecommended")}
</span>
{/if}
<slot name="label" />
</label>
<div class="option__control">
{#if $$restProps.type === "checkbox"}
<input {id} type="checkbox" bind:checked={checked} {...$$restProps} />
{:else}
<input {id} bind:value={value} {...$$restProps} />
{/if}
{#if description}
<div class="option__description">
{description}
<slot name="description" />
</div>
{/if}
</div>
{/if}
</div>

View File

@@ -11,6 +11,8 @@
import defaultOptions from "../../defaultOptions"; import defaultOptions from "../../defaultOptions";
import { getChromeUserAgentString } from "../../lib/userAgents"; import { getChromeUserAgentString } from "../../lib/userAgents";
import Option from "./Option.svelte";
import OptionsCategory from "./OptionsCategory.svelte";
const _ = browser.i18n.getMessage; const _ = browser.i18n.getMessage;
@@ -87,152 +89,101 @@
> >
<Bridge bind:opts /> <Bridge bind:opts />
<fieldset class="category"> <OptionsCategory
<legend class="category__name"> name={_("optionsMediaCategoryName")}
<h2>{_("optionsMediaCategoryName")}</h2> description={_("optionsMediaCategoryDescription")}
</legend> >
<p class="category__description"> <Option
{_("optionsMediaCategoryDescription")}
</p>
<div class="option option--inline">
<div class="option__control">
<input
id="mediaEnabled" id="mediaEnabled"
label={_("optionsMediaEnabled")}
type="checkbox" type="checkbox"
bind:checked={opts.mediaEnabled} bind:checked={opts.mediaEnabled}
inline
/> />
</div>
<label class="option__label" for="mediaEnabled">
{_("optionsMediaEnabled")}
</label>
</div>
{#if opts.showAdvancedOptions} {#if opts.showAdvancedOptions}
<div class="option option--inline"> <Option
<div class="option__control">
<input
id="mediaSyncElement" id="mediaSyncElement"
label={_("optionsMediaSyncElement")}
description={_("optionsMediaSyncElementDescription")}
type="checkbox" type="checkbox"
bind:checked={opts.mediaSyncElement} bind:checked={opts.mediaSyncElement}
inline
/> />
</div>
<label class="option__label" for="mediaSyncElement">
{_("optionsMediaSyncElement")}
</label>
<div class="option__description">
{_("optionsMediaSyncElementDescription")}
</div>
</div>
{/if} {/if}
<div class="option option--inline"> <Option
<div class="option__control">
<input
id="mediaStopOnUnload" id="mediaStopOnUnload"
label={_("optionsMediaStopOnUnload")}
type="checkbox" type="checkbox"
bind:checked={opts.mediaStopOnUnload} bind:checked={opts.mediaStopOnUnload}
inline
/> />
</div>
<label class="option__label" for="mediaStopOnUnload">
{_("optionsMediaStopOnUnload")}
</label>
</div>
<hr /> <hr />
<div class="option option--inline"> <Option
<div class="option__control">
<input
id="localMediaEnabled" id="localMediaEnabled"
label={_("optionsLocalMediaEnabled")}
description={_("optionsLocalMediaCategoryDescription")}
type="checkbox" type="checkbox"
bind:checked={opts.localMediaEnabled} bind:checked={opts.localMediaEnabled}
inline
/> />
</div>
<label class="option__label" for="localMediaEnabled">
{_("optionsLocalMediaEnabled")}
</label>
<div class="option__description">
{_("optionsLocalMediaCategoryDescription")}
</div>
</div>
<div class="option"> <Option
<label class="option__label" for="localMediaServerPort">
{_("optionsLocalMediaServerPort")}
</label>
<div class="option__control">
<input
id="localMediaServerPort" id="localMediaServerPort"
label={_("optionsLocalMediaServerPort")}
type="number" type="number"
required required
min="1025" min="1025"
max="65535" max="65535"
bind:value={opts.localMediaServerPort} bind:value={opts.localMediaServerPort}
/> />
</div> </OptionsCategory>
</div>
</fieldset>
{#if opts.showAdvancedOptions} {#if opts.showAdvancedOptions}
<fieldset class="category"> <OptionsCategory
<legend class="category__name"> name={_("optionsMirroringCategoryName")}
<h2>{_("optionsMirroringCategoryName")}</h2> description={_("optionsMirroringCategoryDescription")}
</legend> >
<p class="category__description"> <Option
{_("optionsMirroringCategoryDescription")}
</p>
<div class="option option--inline">
<div class="option__control">
<input
id="mirroringEnabled" id="mirroringEnabled"
label={_("optionsMirroringEnabled")}
type="checkbox" type="checkbox"
bind:checked={opts.mirroringEnabled} bind:checked={opts.mirroringEnabled}
inline
/> />
</div>
<label class="option__label" for="mirroringEnabled">
{_("optionsMirroringEnabled")}
</label>
</div>
<div class="option"> <Option
<label class="option__label" for="mirroringAppId">
{_("optionsMirroringAppId")}
</label>
<div class="option__control">
<input
id="mirroringAppId" id="mirroringAppId"
type="text" label={_("optionsMirroringAppId")}
description={_("optionsMirroringAppIdDescription")}
required required
bind:value={opts.mirroringAppId} bind:value={opts.mirroringAppId}
/> />
<div class="option__description">
{_("optionsMirroringAppIdDescription")}
</div>
</div>
</div>
<details class="mirroring-stream"> <div class="mirroring-stream">
<details>
<summary> <summary>
{_("optionsMirroringStreamOptions")} {_("optionsMirroringStreamOptions")}
</summary> </summary>
<div class="mirroring-stream__options"> <div class="mirroring-stream__options">
<div class="option option--inline scaling-resolution"> <Option
<div class="option__control">
<input
type="checkbox"
name="scaling"
id="mirroringStreamUseMaxResolution" id="mirroringStreamUseMaxResolution"
label={_(
"optionsMirroringStreamUseMaxResolution"
)}
description={_(
"optionsMirroringStreamUseMaxResolutionDescription"
)}
class="scaling-resolution"
type="checkbox"
bind:checked={opts.mirroringStreamUseMaxResolution} bind:checked={opts.mirroringStreamUseMaxResolution}
/> inline
</div>
<label
class="option__label"
for="mirroringStreamUseMaxResolution"
> >
{_("optionsMirroringStreamMaxResolution")} <svelte:fragment slot="label">
<input <input
type="number" type="number"
min="1" min="1"
@@ -250,220 +201,118 @@
"optionsMirroringStreamMaxResolutionHeightPlaceholder" "optionsMirroringStreamMaxResolutionHeightPlaceholder"
)} )}
bind:value={opts bind:value={opts
.mirroringStreamMaxResolution.height} .mirroringStreamMaxResolution
.height}
/> />
</label> </svelte:fragment>
<p class="option__description"> </Option>
{_(
"optionsMirroringStreamMaxResolutionDescription"
)}
</p>
</div>
<div class="option scaling-downscale"> <Option
<label
class="option__label"
for="mirroringStreamDownscaleFactor"
>
{_("optionsMirroringStreamDownscaleFactor")}
</label>
<div class="option__control">
<input
id="mirroringStreamDownscaleFactor" id="mirroringStreamDownscaleFactor"
label={_(
"optionsMirroringStreamDownscaleFactor"
)}
description={_(
"optionsMirroringStreamDownscaleFactorDescription"
)}
type="number" type="number"
required required
min="1" min="1"
step="any" step="any"
bind:value={opts.mirroringStreamDownscaleFactor} bind:value={opts.mirroringStreamDownscaleFactor}
class="scaling-downscale"
/> />
<p class="option__description"> <Option
{_(
"optionsMirroringStreamDownscaleFactorDescription"
)}
</p>
</div>
</div>
<div class="option">
<label
class="option__label"
for="mirroringStreamMaxFrameRate"
>
{_("optionsMirroringStreamFrameRate")}
</label>
<div class="option__control">
<input
id="mirroringStreamMaxFrameRate" id="mirroringStreamMaxFrameRate"
label={_("optionsMirroringStreamFrameRate")}
type="number" type="number"
required required
min="1" min="1"
bind:value={opts.mirroringStreamMaxFrameRate} bind:value={opts.mirroringStreamMaxFrameRate}
/> />
</div>
</div>
<div class="option"> <Option
<label
class="option__label"
for="mirroringStreamMaxBitRate"
>
{_("optionsMirroringStreamMaxBitRate")}
</label>
<div class="option__control">
<input
id="mirroringStreamMaxBitRate" id="mirroringStreamMaxBitRate"
label={_("optionsMirroringStreamMaxBitRate")}
description={_(
"optionsMirroringStreamMaxBitRateDescription"
)}
type="number" type="number"
required required
min="1" min="1"
bind:value={opts.mirroringStreamMaxBitRate} bind:value={opts.mirroringStreamMaxBitRate}
/> />
<p class="option__description">
{_(
"optionsMirroringStreamMaxBitRateDescription"
)}
</p>
</div>
</div>
</div> </div>
</details> </details>
</fieldset> </div>
</OptionsCategory>
{/if} {/if}
{#if opts.showAdvancedOptions} {#if opts.showAdvancedOptions}
<fieldset class="category"> <OptionsCategory
<legend class="category__name"> name={_("optionsReceiverSelectorCategoryName")}
<h2>{_("optionsReceiverSelectorCategoryName")}</h2> description={_("optionsReceiverSelectorCategoryDescription")}
</legend> >
<p class="category__description"> <!-- <Option
{_("optionsReceiverSelectorCategoryDescription")}
</p>
<!--
<div class="option option--inline">
<div class="option__control">
<input
id="receiverSelectorWaitForConnection" id="receiverSelectorWaitForConnection"
label={_("optionsreceiverSelectorWaitForConnection")}
description={_("optionsReceiverSelectorWaitForConnectionDescription")}
type="checkbox" type="checkbox"
bind:checked={opts.receiverSelectorWaitForConnection} bind:checked={opts.receiverSelectorWaitForConnection}
/> /> -->
</div>
<label
class="option__label"
for="receiverSelectorWaitForConnection"
>
{_("optionsReceiverSelectorWaitForConnection")}
</label>
<div class="option__description">
{_(
"optionsReceiverSelectorWaitForConnectionDescription"
)}
</div>
</div>
-->
<div class="option option--inline"> <Option
<div class="option__control">
<input
id="receiverSelectorExpandActive" id="receiverSelectorExpandActive"
label={_("optionsReceiverSelectorExpandActive")}
type="checkbox" type="checkbox"
bind:checked={opts.receiverSelectorExpandActive} bind:checked={opts.receiverSelectorExpandActive}
inline
/> />
</div> <Option
<label
class="option__label"
for="receiverSelectorExpandActive"
>
{_("optionsReceiverSelectorExpandActive")}
</label>
</div>
<div class="option option--inline">
<div class="option__control">
<input
id="receiverSelectorShowMediaImages" id="receiverSelectorShowMediaImages"
label={_("optionsreceiverSelectorShowMediaImages")}
description={_(
"optionsreceiverSelectorShowMediaImagesDescription"
)}
type="checkbox" type="checkbox"
bind:checked={opts.receiverSelectorShowMediaImages} bind:checked={opts.receiverSelectorShowMediaImages}
inline
/> />
</div> <Option
<label
class="option__label"
for="receiverSelectorShowMediaImages"
>
{_("optionsReceiverSelectorShowMediaImages")}
</label>
<div class="option__description">
{_("optionsReceiverSelectorShowMediaImagesDescription")}
</div>
</div>
<div class="option option--inline">
<div class="option__control">
<input
id="receiverSelectorCloseIfFocusLost" id="receiverSelectorCloseIfFocusLost"
label={_("optionsReceiverSelectorCloseIfFocusLost")}
type="checkbox" type="checkbox"
bind:checked={opts.receiverSelectorCloseIfFocusLost} bind:checked={opts.receiverSelectorCloseIfFocusLost}
inline
/> />
</div> </OptionsCategory>
<label
class="option__label"
for="receiverSelectorCloseIfFocusLost"
>
{_("optionsReceiverSelectorCloseIfFocusLost")}
</label>
</div>
</fieldset>
{/if} {/if}
<fieldset class="category"> <OptionsCategory
<legend class="category__name"> name={_("optionsSiteWhitelistCategoryName")}
<h2>{_("optionsSiteWhitelistCategoryName")}</h2> description={_("optionsSiteWhitelistCategoryDescription")}
</legend> >
<p class="category__description">
{_("optionsSiteWhitelistCategoryDescription")}
</p>
{#if opts.showAdvancedOptions} {#if opts.showAdvancedOptions}
<div class="option option--inline"> <Option
<div class="option__control">
<input
id="siteWhitelistEnabled" id="siteWhitelistEnabled"
label={_("optionsSiteWhitelistEnabled")}
description={_("optionsSiteWhitelistEnabledDescription")}
type="checkbox" type="checkbox"
bind:checked={opts.siteWhitelistEnabled} bind:checked={opts.siteWhitelistEnabled}
recommended
inline
/> />
</div>
<label class="option__label" for="siteWhitelistEnabled">
{_("optionsSiteWhitelistEnabled")}
<span class="option__recommended">
{_("optionsOptionRecommended")}
</span>
</label>
<div class="option__description">
{_("optionsSiteWhitelistEnabledDescription")}
</div>
</div>
<div class="option"> <Option
<label
class="option__label"
for="siteWhitelistCustomUserAgent"
>
{_("optionsSiteWhitelistCustomUserAgent")}
</label>
<div class="option__control">
<input
id="siteWhitelistCustomUserAgent" id="siteWhitelistCustomUserAgent"
type="text" label={_("optionsSiteWhitelistCustomUserAgent")}
description={_(
"optionsSiteWhitelistCustomUserAgentDescription"
)}
bind:value={opts.siteWhitelistCustomUserAgent} bind:value={opts.siteWhitelistCustomUserAgent}
placeholder={defaultUserAgent} placeholder={defaultUserAgent}
/> />
<div class="option__description">
{_(
"optionsSiteWhitelistCustomUserAgentDescription"
)}
</div>
</div>
</div>
{/if} {/if}
<div class="option"> <div class="option">
@@ -478,21 +327,16 @@
/> />
</div> </div>
</div> </div>
</fieldset> </OptionsCategory>
<div class="form__footer"> <div class="form__footer">
<div class="option option--inline"> <Option
<div class="option__control">
<input
id="showAdvancedOptions" id="showAdvancedOptions"
label={_("optionsShowAdvancedOptions")}
type="checkbox" type="checkbox"
bind:checked={opts.showAdvancedOptions} bind:checked={opts.showAdvancedOptions}
inline
/> />
</div>
<label class="option__label" for="showAdvancedOptions">
{_("optionsShowAdvancedOptions")}
</label>
</div>
<div class="form__buttons"> <div class="form__buttons">
{#if isSavedIndicatorVisible} {#if isSavedIndicatorVisible}

View File

@@ -0,0 +1,15 @@
<script lang="ts">
export let name: string;
export let description: string;
</script>
<fieldset class="category">
<legend class="category__name">
<h2>{name}</h2>
</legend>
<p class="category__description">
{description}
</p>
<slot />
</fieldset>

View File

@@ -7,6 +7,7 @@
import type { Options } from "../../lib/options"; import type { Options } from "../../lib/options";
import knownApps, { KnownApp } from "../../cast/knownApps"; import knownApps, { KnownApp } from "../../cast/knownApps";
import Option from "./Option.svelte";
const _ = browser.i18n.getMessage; const _ = browser.i18n.getMessage;
@@ -217,51 +218,31 @@
{#if isItemExpanded} {#if isItemExpanded}
<div class="whitelist__expanded"> <div class="whitelist__expanded">
<div class="option option--inline"> <Option
<div class="option__control">
<input
id="isUserAgentDisabled-{i}" id="isUserAgentDisabled-{i}"
type="checkbox" label={_(
bind:checked={item.isUserAgentDisabled} "optionsSiteWhitelistUserAgentDisabled"
/> )}
</div> description={_(
<label
class="option__label"
for="isUserAgentDisabled-{i}"
>
{_("optionsSiteWhitelistUserAgentDisabled")}
</label>
<div class="option__description">
{_(
"optionsSiteWhitelistUserAgentDisabledDescription" "optionsSiteWhitelistUserAgentDisabledDescription"
)} )}
</div> type="checkbox"
</div> bind:checked={item.isUserAgentDisabled}
inline
/>
<div class="option"> <Option
<label id="customUserAgentString-{i}"
class="option__label" label={_(
for="customUserAgentString-{i}"
>
{_(
"optionsSiteWhitelistSiteSpecificUserAgent" "optionsSiteWhitelistSiteSpecificUserAgent"
)} )}
</label> description={_(
<div class="option__control"> "optionsSiteWhitelistSiteSpecificUserAgentDescription"
<input )}
id="customUserAgentString-{i}"
type="text"
bind:value={item.customUserAgent} bind:value={item.customUserAgent}
placeholder={opts.siteWhitelistCustomUserAgent || placeholder={opts.siteWhitelistCustomUserAgent ||
defaultUserAgent} defaultUserAgent}
/> />
<div class="option__description">
{_(
"optionsSiteWhitelistSiteSpecificUserAgentDescription"
)}
</div>
</div>
</div>
</div> </div>
{/if} {/if}
{/if} {/if}

View File

@@ -237,20 +237,6 @@ input:placeholder-shown {
grid-column: span 2; grid-column: span 2;
width: 100%; width: 100%;
} }
.category > details {
display: contents;
}
.category > details > summary {
grid-column: 2;
margin-top: 5px;
}
.category > details:not([open]) > summary ~ * {
display: none;
}
.category > details:not([open]) > summary,
.category > details[open] > :last-child {
margin-bottom: 5px;
}
.category__name { .category__name {
float: left; float: left;
@@ -336,9 +322,14 @@ fieldset:disabled .option__description {
margin-inline-start: initial; margin-inline-start: initial;
} }
.mirroring-stream > summary { .mirroring-stream {
color: var(--secondary-color); grid-column: 2;
} }
.mirroring-stream > details > summary {
color: var(--secondary-color);
margin: 5px 0;
}
.mirroring-stream__options { .mirroring-stream__options {
background-color: var(--overlay-color); background-color: var(--overlay-color);
border-radius: 4px; border-radius: 4px;