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: "",
showAdvancedOptions: false
} satisfies Options as Options;
} as Options;

View File

@@ -13,6 +13,7 @@
import type { Options } from "../../lib/options";
import messaging from "../../messaging";
import Option from "./Option.svelte";
const _ = browser.i18n.getMessage;
@@ -294,16 +295,15 @@
{/if}
<div class="bridge__options">
<div class="option option--inline">
<div class="option__control">
<input
name="bridgeBackupEnabled"
<Option
id="bridgeBackupEnabled"
label={_("optionsBridgeEnabled")}
description={_("optionsBridgeBackupEnabledDescription")}
type="checkbox"
bind:checked={opts.bridgeBackupEnabled}
/>
</div>
<label class="option__label" for="bridgeBackupEnabled">
inline
>
<svelte:fragment slot="label">
{backupMessageStart}
<input
class="bridge__backup-host"
@@ -323,48 +323,30 @@
bind:value={opts.bridgeBackupPort}
/>
{backupMessageEnd}
</label>
<div class="option__description">
{_("optionsBridgeBackupEnabledDescription")}
</div>
</div>
</svelte:fragment>
</Option>
{#if opts.showAdvancedOptions}
<fieldset
class="bridge__daemon-options"
disabled={!opts.bridgeBackupEnabled}
>
<div class="option option--inline">
<div class="option__control">
<input
<Option
id="bridgeBackupSecure"
label={_("optionsBridgeBackupSecure")}
description={_("optionsBridgeBackupSecureDescription")}
type="checkbox"
bind:checked={opts.bridgeBackupSecure}
inline
/>
</div>
<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
<Option
id="bridgeBackupPassword"
placeholder="Password"
label={_("optionsBridgeBackupPassword")}
description={_("optionsBridgeBackupPasswordDescription")}
type="password"
bind:value={opts.bridgeBackupPassword}
/>
<div class="option__description">
{_("optionsBridgeBackupPasswordDescription")}
</div>
</div>
</div>
</fieldset>
{/if}
</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 { getChromeUserAgentString } from "../../lib/userAgents";
import Option from "./Option.svelte";
import OptionsCategory from "./OptionsCategory.svelte";
const _ = browser.i18n.getMessage;
@@ -87,152 +89,101 @@
>
<Bridge bind:opts />
<fieldset class="category">
<legend class="category__name">
<h2>{_("optionsMediaCategoryName")}</h2>
</legend>
<p class="category__description">
{_("optionsMediaCategoryDescription")}
</p>
<div class="option option--inline">
<div class="option__control">
<input
<OptionsCategory
name={_("optionsMediaCategoryName")}
description={_("optionsMediaCategoryDescription")}
>
<Option
id="mediaEnabled"
label={_("optionsMediaEnabled")}
type="checkbox"
bind:checked={opts.mediaEnabled}
inline
/>
</div>
<label class="option__label" for="mediaEnabled">
{_("optionsMediaEnabled")}
</label>
</div>
{#if opts.showAdvancedOptions}
<div class="option option--inline">
<div class="option__control">
<input
<Option
id="mediaSyncElement"
label={_("optionsMediaSyncElement")}
description={_("optionsMediaSyncElementDescription")}
type="checkbox"
bind:checked={opts.mediaSyncElement}
inline
/>
</div>
<label class="option__label" for="mediaSyncElement">
{_("optionsMediaSyncElement")}
</label>
<div class="option__description">
{_("optionsMediaSyncElementDescription")}
</div>
</div>
{/if}
<div class="option option--inline">
<div class="option__control">
<input
<Option
id="mediaStopOnUnload"
label={_("optionsMediaStopOnUnload")}
type="checkbox"
bind:checked={opts.mediaStopOnUnload}
inline
/>
</div>
<label class="option__label" for="mediaStopOnUnload">
{_("optionsMediaStopOnUnload")}
</label>
</div>
<hr />
<div class="option option--inline">
<div class="option__control">
<input
<Option
id="localMediaEnabled"
label={_("optionsLocalMediaEnabled")}
description={_("optionsLocalMediaCategoryDescription")}
type="checkbox"
bind:checked={opts.localMediaEnabled}
inline
/>
</div>
<label class="option__label" for="localMediaEnabled">
{_("optionsLocalMediaEnabled")}
</label>
<div class="option__description">
{_("optionsLocalMediaCategoryDescription")}
</div>
</div>
<div class="option">
<label class="option__label" for="localMediaServerPort">
{_("optionsLocalMediaServerPort")}
</label>
<div class="option__control">
<input
<Option
id="localMediaServerPort"
label={_("optionsLocalMediaServerPort")}
type="number"
required
min="1025"
max="65535"
bind:value={opts.localMediaServerPort}
/>
</div>
</div>
</fieldset>
</OptionsCategory>
{#if opts.showAdvancedOptions}
<fieldset class="category">
<legend class="category__name">
<h2>{_("optionsMirroringCategoryName")}</h2>
</legend>
<p class="category__description">
{_("optionsMirroringCategoryDescription")}
</p>
<div class="option option--inline">
<div class="option__control">
<input
<OptionsCategory
name={_("optionsMirroringCategoryName")}
description={_("optionsMirroringCategoryDescription")}
>
<Option
id="mirroringEnabled"
label={_("optionsMirroringEnabled")}
type="checkbox"
bind:checked={opts.mirroringEnabled}
inline
/>
</div>
<label class="option__label" for="mirroringEnabled">
{_("optionsMirroringEnabled")}
</label>
</div>
<div class="option">
<label class="option__label" for="mirroringAppId">
{_("optionsMirroringAppId")}
</label>
<div class="option__control">
<input
<Option
id="mirroringAppId"
type="text"
label={_("optionsMirroringAppId")}
description={_("optionsMirroringAppIdDescription")}
required
bind:value={opts.mirroringAppId}
/>
<div class="option__description">
{_("optionsMirroringAppIdDescription")}
</div>
</div>
</div>
<details class="mirroring-stream">
<div class="mirroring-stream">
<details>
<summary>
{_("optionsMirroringStreamOptions")}
</summary>
<div class="mirroring-stream__options">
<div class="option option--inline scaling-resolution">
<div class="option__control">
<input
type="checkbox"
name="scaling"
<Option
id="mirroringStreamUseMaxResolution"
label={_(
"optionsMirroringStreamUseMaxResolution"
)}
description={_(
"optionsMirroringStreamUseMaxResolutionDescription"
)}
class="scaling-resolution"
type="checkbox"
bind:checked={opts.mirroringStreamUseMaxResolution}
/>
</div>
<label
class="option__label"
for="mirroringStreamUseMaxResolution"
inline
>
{_("optionsMirroringStreamMaxResolution")}
<svelte:fragment slot="label">
<input
type="number"
min="1"
@@ -250,220 +201,118 @@
"optionsMirroringStreamMaxResolutionHeightPlaceholder"
)}
bind:value={opts
.mirroringStreamMaxResolution.height}
.mirroringStreamMaxResolution
.height}
/>
</label>
<p class="option__description">
{_(
"optionsMirroringStreamMaxResolutionDescription"
)}
</p>
</div>
</svelte:fragment>
</Option>
<div class="option scaling-downscale">
<label
class="option__label"
for="mirroringStreamDownscaleFactor"
>
{_("optionsMirroringStreamDownscaleFactor")}
</label>
<div class="option__control">
<input
<Option
id="mirroringStreamDownscaleFactor"
label={_(
"optionsMirroringStreamDownscaleFactor"
)}
description={_(
"optionsMirroringStreamDownscaleFactorDescription"
)}
type="number"
required
min="1"
step="any"
bind:value={opts.mirroringStreamDownscaleFactor}
class="scaling-downscale"
/>
<p class="option__description">
{_(
"optionsMirroringStreamDownscaleFactorDescription"
)}
</p>
</div>
</div>
<div class="option">
<label
class="option__label"
for="mirroringStreamMaxFrameRate"
>
{_("optionsMirroringStreamFrameRate")}
</label>
<div class="option__control">
<input
<Option
id="mirroringStreamMaxFrameRate"
label={_("optionsMirroringStreamFrameRate")}
type="number"
required
min="1"
bind:value={opts.mirroringStreamMaxFrameRate}
/>
</div>
</div>
<div class="option">
<label
class="option__label"
for="mirroringStreamMaxBitRate"
>
{_("optionsMirroringStreamMaxBitRate")}
</label>
<div class="option__control">
<input
<Option
id="mirroringStreamMaxBitRate"
label={_("optionsMirroringStreamMaxBitRate")}
description={_(
"optionsMirroringStreamMaxBitRateDescription"
)}
type="number"
required
min="1"
bind:value={opts.mirroringStreamMaxBitRate}
/>
<p class="option__description">
{_(
"optionsMirroringStreamMaxBitRateDescription"
)}
</p>
</div>
</div>
</div>
</details>
</fieldset>
</div>
</OptionsCategory>
{/if}
{#if opts.showAdvancedOptions}
<fieldset class="category">
<legend class="category__name">
<h2>{_("optionsReceiverSelectorCategoryName")}</h2>
</legend>
<p class="category__description">
{_("optionsReceiverSelectorCategoryDescription")}
</p>
<!--
<div class="option option--inline">
<div class="option__control">
<input
<OptionsCategory
name={_("optionsReceiverSelectorCategoryName")}
description={_("optionsReceiverSelectorCategoryDescription")}
>
<!-- <Option
id="receiverSelectorWaitForConnection"
label={_("optionsreceiverSelectorWaitForConnection")}
description={_("optionsReceiverSelectorWaitForConnectionDescription")}
type="checkbox"
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">
<div class="option__control">
<input
<Option
id="receiverSelectorExpandActive"
label={_("optionsReceiverSelectorExpandActive")}
type="checkbox"
bind:checked={opts.receiverSelectorExpandActive}
inline
/>
</div>
<label
class="option__label"
for="receiverSelectorExpandActive"
>
{_("optionsReceiverSelectorExpandActive")}
</label>
</div>
<div class="option option--inline">
<div class="option__control">
<input
<Option
id="receiverSelectorShowMediaImages"
label={_("optionsreceiverSelectorShowMediaImages")}
description={_(
"optionsreceiverSelectorShowMediaImagesDescription"
)}
type="checkbox"
bind:checked={opts.receiverSelectorShowMediaImages}
inline
/>
</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
<Option
id="receiverSelectorCloseIfFocusLost"
label={_("optionsReceiverSelectorCloseIfFocusLost")}
type="checkbox"
bind:checked={opts.receiverSelectorCloseIfFocusLost}
inline
/>
</div>
<label
class="option__label"
for="receiverSelectorCloseIfFocusLost"
>
{_("optionsReceiverSelectorCloseIfFocusLost")}
</label>
</div>
</fieldset>
</OptionsCategory>
{/if}
<fieldset class="category">
<legend class="category__name">
<h2>{_("optionsSiteWhitelistCategoryName")}</h2>
</legend>
<p class="category__description">
{_("optionsSiteWhitelistCategoryDescription")}
</p>
<OptionsCategory
name={_("optionsSiteWhitelistCategoryName")}
description={_("optionsSiteWhitelistCategoryDescription")}
>
{#if opts.showAdvancedOptions}
<div class="option option--inline">
<div class="option__control">
<input
<Option
id="siteWhitelistEnabled"
label={_("optionsSiteWhitelistEnabled")}
description={_("optionsSiteWhitelistEnabledDescription")}
type="checkbox"
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">
<label
class="option__label"
for="siteWhitelistCustomUserAgent"
>
{_("optionsSiteWhitelistCustomUserAgent")}
</label>
<div class="option__control">
<input
<Option
id="siteWhitelistCustomUserAgent"
type="text"
label={_("optionsSiteWhitelistCustomUserAgent")}
description={_(
"optionsSiteWhitelistCustomUserAgentDescription"
)}
bind:value={opts.siteWhitelistCustomUserAgent}
placeholder={defaultUserAgent}
/>
<div class="option__description">
{_(
"optionsSiteWhitelistCustomUserAgentDescription"
)}
</div>
</div>
</div>
{/if}
<div class="option">
@@ -478,21 +327,16 @@
/>
</div>
</div>
</fieldset>
</OptionsCategory>
<div class="form__footer">
<div class="option option--inline">
<div class="option__control">
<input
<Option
id="showAdvancedOptions"
label={_("optionsShowAdvancedOptions")}
type="checkbox"
bind:checked={opts.showAdvancedOptions}
inline
/>
</div>
<label class="option__label" for="showAdvancedOptions">
{_("optionsShowAdvancedOptions")}
</label>
</div>
<div class="form__buttons">
{#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 knownApps, { KnownApp } from "../../cast/knownApps";
import Option from "./Option.svelte";
const _ = browser.i18n.getMessage;
@@ -217,51 +218,31 @@
{#if isItemExpanded}
<div class="whitelist__expanded">
<div class="option option--inline">
<div class="option__control">
<input
<Option
id="isUserAgentDisabled-{i}"
type="checkbox"
bind:checked={item.isUserAgentDisabled}
/>
</div>
<label
class="option__label"
for="isUserAgentDisabled-{i}"
>
{_("optionsSiteWhitelistUserAgentDisabled")}
</label>
<div class="option__description">
{_(
label={_(
"optionsSiteWhitelistUserAgentDisabled"
)}
description={_(
"optionsSiteWhitelistUserAgentDisabledDescription"
)}
</div>
</div>
type="checkbox"
bind:checked={item.isUserAgentDisabled}
inline
/>
<div class="option">
<label
class="option__label"
for="customUserAgentString-{i}"
>
{_(
<Option
id="customUserAgentString-{i}"
label={_(
"optionsSiteWhitelistSiteSpecificUserAgent"
)}
</label>
<div class="option__control">
<input
id="customUserAgentString-{i}"
type="text"
description={_(
"optionsSiteWhitelistSiteSpecificUserAgentDescription"
)}
bind:value={item.customUserAgent}
placeholder={opts.siteWhitelistCustomUserAgent ||
defaultUserAgent}
/>
<div class="option__description">
{_(
"optionsSiteWhitelistSiteSpecificUserAgentDescription"
)}
</div>
</div>
</div>
</div>
{/if}
{/if}

View File

@@ -237,20 +237,6 @@ input:placeholder-shown {
grid-column: span 2;
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 {
float: left;
@@ -336,9 +322,14 @@ fieldset:disabled .option__description {
margin-inline-start: initial;
}
.mirroring-stream > summary {
color: var(--secondary-color);
.mirroring-stream {
grid-column: 2;
}
.mirroring-stream > details > summary {
color: var(--secondary-color);
margin: 5px 0;
}
.mirroring-stream__options {
background-color: var(--overlay-color);
border-radius: 4px;