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"> id="bridgeBackupEnabled"
<input label={_("optionsBridgeEnabled")}
name="bridgeBackupEnabled" description={_("optionsBridgeBackupEnabledDescription")}
id="bridgeBackupEnabled" type="checkbox"
type="checkbox" bind:checked={opts.bridgeBackupEnabled}
bind:checked={opts.bridgeBackupEnabled} inline
/> >
</div> <svelte:fragment slot="label">
<label class="option__label" for="bridgeBackupEnabled">
{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"> id="bridgeBackupSecure"
<input label={_("optionsBridgeBackupSecure")}
id="bridgeBackupSecure" description={_("optionsBridgeBackupSecureDescription")}
type="checkbox" type="checkbox"
bind:checked={opts.bridgeBackupSecure} bind:checked={opts.bridgeBackupSecure}
/> inline
</div> />
<label class="option__label" for="bridgeBackupSecure"> <Option
{_("optionsBridgeBackupSecure")} id="bridgeBackupPassword"
</label> label={_("optionsBridgeBackupPassword")}
<div class="option__description"> description={_("optionsBridgeBackupPasswordDescription")}
{_("optionsBridgeBackupSecureDescription")} type="password"
</div> bind:value={opts.bridgeBackupPassword}
</div> />
<div class="option">
<label class="option__label" for="bridgeBackupPassword">
{_("optionsBridgeBackupPassword")}
</label>
<div class="option__control">
<input
id="bridgeBackupPassword"
placeholder="Password"
type="password"
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,383 +89,230 @@
> >
<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")} id="mediaEnabled"
</p> label={_("optionsMediaEnabled")}
type="checkbox"
<div class="option option--inline"> bind:checked={opts.mediaEnabled}
<div class="option__control"> inline
<input />
id="mediaEnabled"
type="checkbox"
bind:checked={opts.mediaEnabled}
/>
</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"> id="mediaSyncElement"
<input label={_("optionsMediaSyncElement")}
id="mediaSyncElement" 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"> id="mediaStopOnUnload"
<input label={_("optionsMediaStopOnUnload")}
id="mediaStopOnUnload" 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"> id="localMediaEnabled"
<input label={_("optionsLocalMediaEnabled")}
id="localMediaEnabled" 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"> id="localMediaServerPort"
{_("optionsLocalMediaServerPort")} label={_("optionsLocalMediaServerPort")}
</label> type="number"
<div class="option__control"> required
<input min="1025"
id="localMediaServerPort" max="65535"
type="number" bind:value={opts.localMediaServerPort}
required />
min="1025" </OptionsCategory>
max="65535"
bind:value={opts.localMediaServerPort}
/>
</div>
</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")} id="mirroringEnabled"
</p> label={_("optionsMirroringEnabled")}
type="checkbox"
bind:checked={opts.mirroringEnabled}
inline
/>
<div class="option option--inline"> <Option
<div class="option__control"> id="mirroringAppId"
<input label={_("optionsMirroringAppId")}
id="mirroringEnabled" description={_("optionsMirroringAppIdDescription")}
type="checkbox" required
bind:checked={opts.mirroringEnabled} bind:value={opts.mirroringAppId}
/> />
</div>
<label class="option__label" for="mirroringEnabled">
{_("optionsMirroringEnabled")}
</label>
</div>
<div class="option"> <div class="mirroring-stream">
<label class="option__label" for="mirroringAppId"> <details>
{_("optionsMirroringAppId")} <summary>
</label> {_("optionsMirroringStreamOptions")}
<div class="option__control"> </summary>
<input
id="mirroringAppId"
type="text"
required
bind:value={opts.mirroringAppId}
/>
<div class="option__description">
{_("optionsMirroringAppIdDescription")}
</div>
</div>
</div>
<details class="mirroring-stream"> <div class="mirroring-stream__options">
<summary> <Option
{_("optionsMirroringStreamOptions")} id="mirroringStreamUseMaxResolution"
</summary> label={_(
"optionsMirroringStreamUseMaxResolution"
<div class="mirroring-stream__options">
<div class="option option--inline scaling-resolution">
<div class="option__control">
<input
type="checkbox"
name="scaling"
id="mirroringStreamUseMaxResolution"
bind:checked={opts.mirroringStreamUseMaxResolution}
/>
</div>
<label
class="option__label"
for="mirroringStreamUseMaxResolution"
>
{_("optionsMirroringStreamMaxResolution")}
<input
type="number"
min="1"
placeholder={_(
"optionsMirroringStreamMaxResolutionWidthPlaceholder"
)}
bind:value={opts
.mirroringStreamMaxResolution.width}
/>
×
<input
type="number"
min="1"
placeholder={_(
"optionsMirroringStreamMaxResolutionHeightPlaceholder"
)}
bind:value={opts
.mirroringStreamMaxResolution.height}
/>
</label>
<p class="option__description">
{_(
"optionsMirroringStreamMaxResolutionDescription"
)} )}
</p> description={_(
</div> "optionsMirroringStreamUseMaxResolutionDescription"
)}
<div class="option scaling-downscale"> class="scaling-resolution"
<label type="checkbox"
class="option__label" bind:checked={opts.mirroringStreamUseMaxResolution}
for="mirroringStreamDownscaleFactor" inline
> >
{_("optionsMirroringStreamDownscaleFactor")} <svelte:fragment slot="label">
</label> <input
<div class="option__control"> type="number"
<input min="1"
id="mirroringStreamDownscaleFactor" placeholder={_(
type="number" "optionsMirroringStreamMaxResolutionWidthPlaceholder"
required )}
min="1" bind:value={opts
step="any" .mirroringStreamMaxResolution.width}
bind:value={opts.mirroringStreamDownscaleFactor} />
/> ×
<input
type="number"
min="1"
placeholder={_(
"optionsMirroringStreamMaxResolutionHeightPlaceholder"
)}
bind:value={opts
.mirroringStreamMaxResolution
.height}
/>
</svelte:fragment>
</Option>
<p class="option__description"> <Option
{_( id="mirroringStreamDownscaleFactor"
"optionsMirroringStreamDownscaleFactorDescription" label={_(
)} "optionsMirroringStreamDownscaleFactor"
</p> )}
</div> description={_(
</div> "optionsMirroringStreamDownscaleFactorDescription"
)}
type="number"
required
min="1"
step="any"
bind:value={opts.mirroringStreamDownscaleFactor}
class="scaling-downscale"
/>
<div class="option"> <Option
<label id="mirroringStreamMaxFrameRate"
class="option__label" label={_("optionsMirroringStreamFrameRate")}
for="mirroringStreamMaxFrameRate" type="number"
> required
{_("optionsMirroringStreamFrameRate")} min="1"
</label> bind:value={opts.mirroringStreamMaxFrameRate}
<div class="option__control"> />
<input
id="mirroringStreamMaxFrameRate"
type="number"
required
min="1"
bind:value={opts.mirroringStreamMaxFrameRate}
/>
</div>
</div>
<div class="option"> <Option
<label id="mirroringStreamMaxBitRate"
class="option__label" label={_("optionsMirroringStreamMaxBitRate")}
for="mirroringStreamMaxBitRate" description={_(
> "optionsMirroringStreamMaxBitRateDescription"
{_("optionsMirroringStreamMaxBitRate")} )}
</label> type="number"
<div class="option__control"> required
<input min="1"
id="mirroringStreamMaxBitRate" bind:value={opts.mirroringStreamMaxBitRate}
type="number" />
required
min="1"
bind:value={opts.mirroringStreamMaxBitRate}
/>
<p class="option__description">
{_(
"optionsMirroringStreamMaxBitRateDescription"
)}
</p>
</div>
</div> </div>
</div> </details>
</details> </div>
</fieldset> </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")} id="receiverSelectorWaitForConnection"
</p> label={_("optionsreceiverSelectorWaitForConnection")}
description={_("optionsReceiverSelectorWaitForConnectionDescription")}
type="checkbox"
bind:checked={opts.receiverSelectorWaitForConnection}
/> -->
<!-- <Option
<div class="option option--inline"> id="receiverSelectorExpandActive"
<div class="option__control"> label={_("optionsReceiverSelectorExpandActive")}
<input type="checkbox"
id="receiverSelectorWaitForConnection" bind:checked={opts.receiverSelectorExpandActive}
type="checkbox" inline
bind:checked={opts.receiverSelectorWaitForConnection} />
/> <Option
</div> id="receiverSelectorShowMediaImages"
<label label={_("optionsreceiverSelectorShowMediaImages")}
class="option__label" description={_(
for="receiverSelectorWaitForConnection" "optionsreceiverSelectorShowMediaImagesDescription"
> )}
{_("optionsReceiverSelectorWaitForConnection")} type="checkbox"
</label> bind:checked={opts.receiverSelectorShowMediaImages}
<div class="option__description"> inline
{_( />
"optionsReceiverSelectorWaitForConnectionDescription" <Option
)} id="receiverSelectorCloseIfFocusLost"
</div> label={_("optionsReceiverSelectorCloseIfFocusLost")}
</div> type="checkbox"
--> bind:checked={opts.receiverSelectorCloseIfFocusLost}
inline
<div class="option option--inline"> />
<div class="option__control"> </OptionsCategory>
<input
id="receiverSelectorExpandActive"
type="checkbox"
bind:checked={opts.receiverSelectorExpandActive}
/>
</div>
<label
class="option__label"
for="receiverSelectorExpandActive"
>
{_("optionsReceiverSelectorExpandActive")}
</label>
</div>
<div class="option option--inline">
<div class="option__control">
<input
id="receiverSelectorShowMediaImages"
type="checkbox"
bind:checked={opts.receiverSelectorShowMediaImages}
/>
</div>
<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"
type="checkbox"
bind:checked={opts.receiverSelectorCloseIfFocusLost}
/>
</div>
<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"> id="siteWhitelistEnabled"
<input label={_("optionsSiteWhitelistEnabled")}
id="siteWhitelistEnabled" description={_("optionsSiteWhitelistEnabledDescription")}
type="checkbox" type="checkbox"
bind:checked={opts.siteWhitelistEnabled} bind:checked={opts.siteWhitelistEnabled}
/> recommended
</div> inline
<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 id="siteWhitelistCustomUserAgent"
class="option__label" label={_("optionsSiteWhitelistCustomUserAgent")}
for="siteWhitelistCustomUserAgent" description={_(
> "optionsSiteWhitelistCustomUserAgentDescription"
{_("optionsSiteWhitelistCustomUserAgent")} )}
</label> bind:value={opts.siteWhitelistCustomUserAgent}
<div class="option__control"> placeholder={defaultUserAgent}
<input />
id="siteWhitelistCustomUserAgent"
type="text"
bind:value={opts.siteWhitelistCustomUserAgent}
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"> id="showAdvancedOptions"
<input label={_("optionsShowAdvancedOptions")}
id="showAdvancedOptions" 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"> id="isUserAgentDisabled-{i}"
<input label={_(
id="isUserAgentDisabled-{i}" "optionsSiteWhitelistUserAgentDisabled"
type="checkbox" )}
bind:checked={item.isUserAgentDisabled} description={_(
/> "optionsSiteWhitelistUserAgentDisabledDescription"
</div> )}
<label type="checkbox"
class="option__label" bind:checked={item.isUserAgentDisabled}
for="isUserAgentDisabled-{i}" inline
> />
{_("optionsSiteWhitelistUserAgentDisabled")}
</label>
<div class="option__description">
{_(
"optionsSiteWhitelistUserAgentDisabledDescription"
)}
</div>
</div>
<div class="option"> <Option
<label id="customUserAgentString-{i}"
class="option__label" label={_(
for="customUserAgentString-{i}" "optionsSiteWhitelistSiteSpecificUserAgent"
> )}
{_( description={_(
"optionsSiteWhitelistSiteSpecificUserAgent" "optionsSiteWhitelistSiteSpecificUserAgentDescription"
)} )}
</label> bind:value={item.customUserAgent}
<div class="option__control"> placeholder={opts.siteWhitelistCustomUserAgent ||
<input defaultUserAgent}
id="customUserAgentString-{i}" />
type="text"
bind:value={item.customUserAgent}
placeholder={opts.siteWhitelistCustomUserAgent ||
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;