mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-10-30 04:53:21 +00:00
refactor: consume Commonlib as a package
This commit is contained in:
1 parent
6475d32769
commit
a721d3b602
665 files changed
+3438
-31592
No files matched your search
@@ -0,0 +1,162 @@
|
||||
<script lang="ts">
|
||||
import type { DialogHostProps } from "@vrtmrz/livesync-commonlib/compat/services/implements/base/SvelteDialog";
|
||||
import { type DialogSvelteComponentBaseProps } from "./svelteDialog";
|
||||
// type Props = DialogSvelteComponentBaseProps & {
|
||||
// /**
|
||||
// * The Svelte component to mount inside the dialog host
|
||||
// */
|
||||
// mountComponent: ComponentHasResult<any>;
|
||||
// /**
|
||||
// * Callback function to setup the dialog context
|
||||
// * @param props
|
||||
// */
|
||||
// onSetupContext?(props: DialogSvelteComponentBaseProps): void;
|
||||
// };
|
||||
const { setTitle, closeDialog, setResult, mountComponent, getInitialData, onSetupContext }: DialogHostProps =
|
||||
$props();
|
||||
const contextProps = {
|
||||
setTitle,
|
||||
closeDialog,
|
||||
setResult,
|
||||
getInitialData,
|
||||
} satisfies DialogSvelteComponentBaseProps<any,any>
|
||||
|
||||
// Call the onSetupContext function to setup the dialog context
|
||||
onSetupContext?.(contextProps);
|
||||
|
||||
/**
|
||||
* Wrapper around setResult to also close the dialog
|
||||
* @param result
|
||||
*/
|
||||
const setResultWrapper = (result: any) => {
|
||||
setResult(result);
|
||||
closeDialog();
|
||||
};
|
||||
|
||||
const Component = mountComponent;
|
||||
let thisElement: HTMLElement;
|
||||
</script>
|
||||
|
||||
<div class="dialog-host" bind:this={thisElement}>
|
||||
<Component setResult={setResultWrapper} {getInitialData}></Component>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.dialog-host {
|
||||
padding: 20px;
|
||||
gap: 0.5em;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-bottom: var(--keyboard-height, 0px);
|
||||
}
|
||||
|
||||
.dialog-host :global(button) {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.dialog-host :global(.button-group) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.dialog-host :global(.row) {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-items: center;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.dialog-host :global(.row > input[type="text"]),
|
||||
.dialog-host :global(.row > input[type="password"]),
|
||||
.dialog-host :global(.row > textarea),
|
||||
.dialog-host :global(.row > select) {
|
||||
flex: 1;
|
||||
margin-left: 10px;
|
||||
min-width: 10em;
|
||||
}
|
||||
.dialog-host :global(.row > input[type="password"]) {
|
||||
-webkit-text-security: disc;
|
||||
}
|
||||
|
||||
.dialog-host :global(.row > input[type="checkbox"]) {
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.dialog-host :global(label > span) {
|
||||
display: block;
|
||||
width: 8em;
|
||||
}
|
||||
|
||||
.dialog-host :global(.note),
|
||||
.dialog-host :global(.note-important),
|
||||
.dialog-host :global(.note-error) {
|
||||
padding: 10px;
|
||||
margin-top: 4px;
|
||||
margin-bottom: 0.5lh;
|
||||
border-left: 4px solid;
|
||||
}
|
||||
|
||||
.dialog-host :global(.note) {
|
||||
background-color: var(--interactive-hover);
|
||||
border-left-color: var(--interactive-accent);
|
||||
}
|
||||
.dialog-host :global(.note-important) {
|
||||
background-color: var(--interactive-hover);
|
||||
border-left-color: var(--text-warning);
|
||||
}
|
||||
.dialog-host :global(.note-error) {
|
||||
background-color: var(--interactive-hover);
|
||||
border-left-color: var(--text-error);
|
||||
}
|
||||
.dialog-host :global(hr) {
|
||||
margin: 0.7lh 0;
|
||||
}
|
||||
.dialog-host :global(details) {
|
||||
gap: 0.5em;
|
||||
padding-left: 0.5em;
|
||||
border-left: 2px solid var(--interactive-accent);
|
||||
}
|
||||
.dialog-host :global(summary::marker) {
|
||||
display: none;
|
||||
content: "";
|
||||
}
|
||||
|
||||
.dialog-host :global(summary) {
|
||||
border-left: 4px solid var(--interactive-accent);
|
||||
padding-left: 0.5em;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
}
|
||||
.dialog-host :global(details > summary::after) {
|
||||
content: "⏷";
|
||||
float: right;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
.dialog-host :global(details[open] > summary::after) {
|
||||
content: "⏶";
|
||||
float: right;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
.dialog-host :global(input:invalid),
|
||||
.dialog-host :global(textarea:invalid) {
|
||||
border-color: var(--background-modifier-error);
|
||||
}
|
||||
.dialog-host :global(.sub-section) {
|
||||
margin-left: 1em;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5em;
|
||||
}
|
||||
|
||||
.dialog-host :global(.row > input[type="text"]:disabled),
|
||||
.dialog-host :global(.row > input[type="password"]:disabled),
|
||||
.dialog-host :global(.row > textarea:disabled),
|
||||
.dialog-host :global(.row > select:disabled) {
|
||||
background-color: var(--background-secondary);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,53 @@
|
||||
<script lang="ts">
|
||||
import { translateIfAvailable as translate } from "@vrtmrz/livesync-commonlib/compat/common/i18n";
|
||||
|
||||
type Props = {
|
||||
title: string;
|
||||
value: boolean;
|
||||
noteOnSelected?: () => any;
|
||||
noteOnUnselected?: () => any;
|
||||
children?: () => any;
|
||||
};
|
||||
|
||||
let { title, value = $bindable(), noteOnSelected, noteOnUnselected, children }: Props = $props();
|
||||
const translatedTitle = $derived.by(() => translate(title));
|
||||
</script>
|
||||
|
||||
<label class="choice-row">
|
||||
<input type="checkbox" bind:checked={value} />
|
||||
<span class="choice-title">{translatedTitle}</span>
|
||||
</label>
|
||||
<div class="choice-notes">
|
||||
<!-- TODO Highlight selected option -->
|
||||
{#if value && noteOnSelected}
|
||||
{@render noteOnSelected()}
|
||||
{:else if !value && noteOnUnselected}
|
||||
{@render noteOnUnselected()}
|
||||
{/if}
|
||||
{@render children?.()}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.choice-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-top: 1rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
.choice-row span.choice-title {
|
||||
width: auto;
|
||||
}
|
||||
.choice-row input[type="checkbox"] {
|
||||
/* width: 1.2rem;
|
||||
height: 1.2rem; */
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.choice-notes {
|
||||
margin-left: 2rem;
|
||||
margin-top: 0.25rem;
|
||||
color: var(--text-muted);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,24 @@
|
||||
<script lang="ts">
|
||||
import { translateIfAvailable as translate } from "@vrtmrz/livesync-commonlib/compat/common/i18n";
|
||||
import { fireAndForget } from "octagonal-wheels/promises";
|
||||
|
||||
type Props = {
|
||||
title: string;
|
||||
commit: () => Promise<void> | void;
|
||||
important?: boolean;
|
||||
destructive?: boolean;
|
||||
additionalClasses?: string;
|
||||
disabled?: boolean;
|
||||
};
|
||||
let { title, commit, additionalClasses, important, disabled = $bindable(), destructive }: Props = $props();
|
||||
const translatedTitle = $derived.by(() => translate(title));
|
||||
function onclick() {
|
||||
fireAndForget(async () => commit());
|
||||
}
|
||||
</script>
|
||||
|
||||
<button
|
||||
class="button {additionalClasses} {important ? 'mod-cta' : ''} {destructive ? 'mod-destructive' : ''}"
|
||||
{onclick}
|
||||
{disabled}>{translatedTitle}</button
|
||||
>
|
||||
@@ -0,0 +1,41 @@
|
||||
<script lang="ts">
|
||||
import { onMount, tick } from "svelte";
|
||||
import { translateIfAvailable as translate } from "@vrtmrz/livesync-commonlib/compat/common/i18n";
|
||||
import { getDialogContext } from "@/modules/services/LiveSyncUI/svelteDialog";
|
||||
import { _activeDocument } from "@vrtmrz/livesync-commonlib/compat/common/coreEnvFunctions";
|
||||
|
||||
type Props = {
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
children?: () => unknown;
|
||||
};
|
||||
let { title = $bindable(), subtitle }: Props = $props();
|
||||
const context = getDialogContext();
|
||||
const translatedTitle = $derived.by(() => translate(title));
|
||||
const translatedSubtitle = $derived.by(() => (subtitle ? translate(subtitle) : ""));
|
||||
const modalTitle = $derived.by(() => `${translatedTitle}${translatedSubtitle ? ` - ${translatedSubtitle}` : ""}`);
|
||||
|
||||
$effect(() => {
|
||||
if (translatedTitle) {
|
||||
context.setTitle(modalTitle);
|
||||
}
|
||||
});
|
||||
onMount(async () => {
|
||||
context.setTitle(modalTitle);
|
||||
await tick();
|
||||
_activeDocument.querySelector(".modal")?.scrollTo(0, 0);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="dialog-header">
|
||||
<h2>{translatedTitle}</h2>
|
||||
{#if translatedSubtitle}
|
||||
<h4>{translatedSubtitle}</h4>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.dialog-header {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { translateIfAvailable as translate } from "@vrtmrz/livesync-commonlib/compat/common/i18n";
|
||||
|
||||
type Props = {
|
||||
title?: string;
|
||||
children?: () => any;
|
||||
};
|
||||
const { children, title }: Props = $props();
|
||||
const translatedTitle = $derived.by(() => (title ? translate(title) : ""));
|
||||
</script>
|
||||
|
||||
<details>
|
||||
<summary>{translatedTitle}</summary>
|
||||
<div class="sub-section">
|
||||
{@render children?.()}
|
||||
</div>
|
||||
</details>
|
||||
@@ -0,0 +1,21 @@
|
||||
<script lang="ts">
|
||||
import { translateIfAvailable as translate } from "@vrtmrz/livesync-commonlib/compat/common/i18n";
|
||||
|
||||
type Props = {
|
||||
children?: () => any;
|
||||
important?: boolean;
|
||||
title?: string;
|
||||
};
|
||||
const { children, important, title }: Props = $props();
|
||||
const cssClass = $derived.by(() => {
|
||||
return important ? "guidance important" : "guidance";
|
||||
});
|
||||
const translatedTitle = $derived.by(() => (title ? translate(title) : ""));
|
||||
</script>
|
||||
|
||||
<div class={cssClass}>
|
||||
{#if translatedTitle}
|
||||
<h3>{translatedTitle}</h3>
|
||||
{/if}
|
||||
{@render children?.()}
|
||||
</div>
|
||||
@@ -0,0 +1,87 @@
|
||||
<script lang="ts">
|
||||
import { translateIfAvailable as translate } from "@vrtmrz/livesync-commonlib/compat/common/i18n";
|
||||
|
||||
type SignalWord = "danger" | "warning" | "caution" | "notice";
|
||||
|
||||
type Props = {
|
||||
title?: string;
|
||||
message?: string;
|
||||
children?: () => any;
|
||||
cssClass?: string;
|
||||
warning?: boolean;
|
||||
caution?: boolean;
|
||||
error?: boolean;
|
||||
notice?: boolean;
|
||||
info?: boolean;
|
||||
signalWord?: string | false;
|
||||
visible?: boolean;
|
||||
};
|
||||
const {
|
||||
title,
|
||||
message,
|
||||
children,
|
||||
cssClass,
|
||||
warning: isWarning,
|
||||
caution: isCaution,
|
||||
error: isError,
|
||||
notice: isNotice,
|
||||
info: isInfo = true,
|
||||
signalWord,
|
||||
visible,
|
||||
}: Props = $props();
|
||||
const derivedCssClass = $derived.by(() => {
|
||||
if (isError) {
|
||||
return "note-error sls-info-note sls-info-note-danger";
|
||||
} else if (isWarning) {
|
||||
return "note-important sls-info-note sls-info-note-warning";
|
||||
} else if (isCaution) {
|
||||
return "note-important sls-info-note sls-info-note-caution";
|
||||
} else if (isNotice) {
|
||||
return "note sls-info-note sls-info-note-notice";
|
||||
} else if (isInfo) {
|
||||
return "note sls-info-note";
|
||||
} else {
|
||||
return "sls-info-note";
|
||||
}
|
||||
});
|
||||
|
||||
const signalWordKind = $derived.by((): SignalWord | undefined => {
|
||||
if (isError) return "danger";
|
||||
if (isWarning) return "warning";
|
||||
if (isCaution) return "caution";
|
||||
if (isNotice) return "notice";
|
||||
return undefined;
|
||||
});
|
||||
const defaultSignalWord = $derived.by(() => {
|
||||
switch (signalWordKind) {
|
||||
case "danger":
|
||||
return "Ui.Common.Signal.Danger";
|
||||
case "warning":
|
||||
return "Ui.Common.Signal.Warning";
|
||||
case "caution":
|
||||
return "Ui.Common.Signal.Caution";
|
||||
case "notice":
|
||||
return "Ui.Common.Signal.Notice";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
});
|
||||
const signalWordText = $derived.by(() => {
|
||||
if (signalWord === false) return "";
|
||||
return signalWord ? translate(signalWord) : defaultSignalWord ? translate(defaultSignalWord) : "";
|
||||
});
|
||||
const signalWordCssKind = $derived.by(() => signalWordKind ?? "custom");
|
||||
const translatedTitle = $derived.by(() => (title ? translate(title) : ""));
|
||||
const translatedMessage = $derived.by(() => (message ? translate(message) : ""));
|
||||
</script>
|
||||
|
||||
{#if visible === undefined || visible === true}
|
||||
<div class={(cssClass ?? "") + " " + derivedCssClass}>
|
||||
{#if signalWordText}
|
||||
<div class="sls-signal-word sls-signal-word-{signalWordCssKind}">{signalWordText}</div>
|
||||
{/if}
|
||||
{#if translatedTitle}<h3>{translatedTitle}</h3>{/if}
|
||||
{#if translatedMessage}<p>{translatedMessage}</p>{/if}
|
||||
{@render children?.()}
|
||||
</div>
|
||||
{/if}
|
||||
@@ -0,0 +1,74 @@
|
||||
<script lang="ts">
|
||||
type Props = {
|
||||
info: Record<string, any>;
|
||||
};
|
||||
const { info }: Props = $props();
|
||||
const infoEntries = $derived.by(() => Object.entries(info ?? {}));
|
||||
</script>
|
||||
|
||||
<div class="info-panel">
|
||||
<div class="info-grid" role="list">
|
||||
{#each infoEntries as [key, value]}
|
||||
<div class="info-entry info-key" role="listitem" aria-label={key}>
|
||||
<div class="key">{key}</div>
|
||||
</div>
|
||||
<div class="info-entry info-item" role="listitem" aria-label={key}>
|
||||
<div class="value">{value}</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.info-panel {
|
||||
padding: 0.6rem;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
/* Main Grid (Info Items) 120px to 1fr, repeat */
|
||||
.info-grid {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(120px, 1fr) 1fr;
|
||||
column-count: 2;
|
||||
gap: 0.6rem;
|
||||
margin-top: 0.5rem;
|
||||
grid-area: "info-key" "info-value";
|
||||
}
|
||||
.info-entry {
|
||||
display: grid;
|
||||
gap: 0.5rem;
|
||||
border-radius: 6px;
|
||||
box-sizing: border-box;
|
||||
min-height: 1.2em;
|
||||
}
|
||||
|
||||
.info-key {
|
||||
font-weight: 600;
|
||||
align-items: center;
|
||||
border-top: 1px solid var(--background-modifier-hover);
|
||||
border-bottom: 1px solid var(--background-modifier-hover);
|
||||
grid-area: "info-key";
|
||||
}
|
||||
.info-item {
|
||||
align-items: start;
|
||||
padding: 0.5rem;
|
||||
background: var(--background-modifier-hover, rgba(0, 0, 0, 0.03));
|
||||
grid-area: "info-value";
|
||||
}
|
||||
|
||||
.value {
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
color: var(--text-normal, #e6e6e6);
|
||||
min-height: 1em;
|
||||
}
|
||||
|
||||
@container (max-width: 340px) {
|
||||
.info-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.info-item {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,15 @@
|
||||
<script lang="ts">
|
||||
import { translateIfAvailable as translate } from "@vrtmrz/livesync-commonlib/compat/common/i18n";
|
||||
|
||||
type Props = {
|
||||
label: string;
|
||||
children?: () => any;
|
||||
};
|
||||
const { label, children }: Props = $props();
|
||||
const translatedLabel = $derived.by(() => translate(label));
|
||||
</script>
|
||||
|
||||
<label class="row"
|
||||
><span>{translatedLabel}</span>
|
||||
{@render children?.()}
|
||||
</label>
|
||||
@@ -0,0 +1,10 @@
|
||||
<script lang="ts">
|
||||
type Props = {
|
||||
children?: () => any;
|
||||
};
|
||||
const { children }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class="question-container">
|
||||
{@render children?.()}
|
||||
</div>
|
||||
@@ -0,0 +1,81 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from "svelte";
|
||||
import { translateIfAvailable as translate } from "@vrtmrz/livesync-commonlib/compat/common/i18n";
|
||||
|
||||
type Props = {
|
||||
title: string;
|
||||
value: string;
|
||||
selectedValue: string;
|
||||
noteOnSelected?: () => any;
|
||||
noteOnUnselected?: () => any;
|
||||
group?: string;
|
||||
children?: () => any;
|
||||
};
|
||||
const definedGroupContext = getContext<string>("radioGroup");
|
||||
|
||||
let {
|
||||
title,
|
||||
value = $bindable(),
|
||||
noteOnSelected,
|
||||
noteOnUnselected,
|
||||
selectedValue,
|
||||
group,
|
||||
children,
|
||||
}: Props = $props();
|
||||
const actualGroup = group ?? definedGroupContext;
|
||||
const translatedTitle = $derived.by(() => translate(title));
|
||||
</script>
|
||||
|
||||
<div class="option-container {value === selectedValue ? 'selected' : ''}">
|
||||
<label>
|
||||
<div class="choice-row">
|
||||
<input type="radio" bind:group={value} name={actualGroup} value={selectedValue} />
|
||||
<span class="choice-title">{translatedTitle}</span>
|
||||
</div>
|
||||
<div class="choice-notes">
|
||||
{#if value === selectedValue && noteOnSelected}
|
||||
{@render noteOnSelected()}
|
||||
{:else if value !== selectedValue && noteOnUnselected}
|
||||
{@render noteOnUnselected()}
|
||||
{/if}
|
||||
{@render children?.()}
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.option-container {
|
||||
border: 1px solid transparent;
|
||||
border-radius: 0.25lh;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
.option-container.selected {
|
||||
border-color: var(--interactive-accent);
|
||||
}
|
||||
.choice-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
/* margin-top: 1rem; */
|
||||
cursor: pointer;
|
||||
}
|
||||
.choice-row span.choice-title {
|
||||
width: auto;
|
||||
}
|
||||
.choice-row input[type="radio"] {
|
||||
/* width: 1.2rem;
|
||||
height: 1.2rem; */
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.choice-notes {
|
||||
margin-left: 2rem;
|
||||
margin-top: 0.25rem;
|
||||
color: var(--text-muted);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.option-container.selected .choice-notes {
|
||||
color: var(--text-normal);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { setContext } from "svelte";
|
||||
|
||||
type Props = {
|
||||
children?: () => any;
|
||||
};
|
||||
const { children }: Props = $props();
|
||||
const questionGroupID = Math.random().toString(36).substring(2, 15);
|
||||
setContext("radioGroup", questionGroupID);
|
||||
</script>
|
||||
|
||||
<div class="options-container">
|
||||
{@render children?.()}
|
||||
</div>
|
||||
@@ -0,0 +1,34 @@
|
||||
<script lang="ts">
|
||||
import { translateIfAvailable as translate } from "@vrtmrz/livesync-commonlib/compat/common/i18n";
|
||||
|
||||
type Props = {
|
||||
value: string;
|
||||
name?: string;
|
||||
placeholder?: string;
|
||||
disabled?: boolean;
|
||||
required?: boolean;
|
||||
};
|
||||
let {
|
||||
value = $bindable(),
|
||||
name = "password",
|
||||
placeholder = "Enter your password",
|
||||
disabled = false,
|
||||
required = false,
|
||||
}: Props = $props();
|
||||
let showPassword = $state(false);
|
||||
const type = $derived.by(() => (showPassword ? "text" : "password"));
|
||||
const translatedPlaceholder = $derived.by(() => translate(placeholder));
|
||||
</script>
|
||||
|
||||
<input
|
||||
{type}
|
||||
{name}
|
||||
placeholder={translatedPlaceholder}
|
||||
bind:value
|
||||
{disabled}
|
||||
{required}
|
||||
spellcheck="false"
|
||||
autocorrect="off"
|
||||
autocapitalize="off"
|
||||
/>
|
||||
<input type="checkbox" bind:checked={showPassword} />
|
||||
@@ -0,0 +1,26 @@
|
||||
<script lang="ts">
|
||||
import { setContext } from "svelte";
|
||||
|
||||
type Props = {
|
||||
children?: () => any;
|
||||
question?: () => any;
|
||||
};
|
||||
const { children, question }: Props = $props();
|
||||
const questionGroupID = Math.random().toString(36).substring(2, 15);
|
||||
setContext("radioGroup", questionGroupID);
|
||||
</script>
|
||||
|
||||
<div class="question-container">
|
||||
{#if question}<h3>{@render question?.()}</h3>{/if}
|
||||
<div class="question-content">
|
||||
{@render children?.()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.question-container {
|
||||
border-bottom: 2px solid var(--interactive-accent);
|
||||
margin-bottom: 0.5lh;
|
||||
padding-bottom: 0.5lh;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,12 @@
|
||||
<script lang="ts">
|
||||
export type Props = {
|
||||
children?: () => any;
|
||||
};
|
||||
const { children }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class="button-group">
|
||||
{#if children}
|
||||
{@render children()}
|
||||
{/if}
|
||||
</div>
|
||||
@@ -0,0 +1,59 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import type { GuestDialogProps } from "@/modules/services/LiveSyncUI/svelteDialog";
|
||||
import DialogHeader from "@/modules/services/LiveSyncUI/components/DialogHeader.svelte";
|
||||
import Instruction from "@/modules/services/LiveSyncUI/components/Instruction.svelte";
|
||||
import InputRow from "@/modules/services/LiveSyncUI/components/InputRow.svelte";
|
||||
import Decision from "@/modules/services/LiveSyncUI/components/Decision.svelte";
|
||||
import UserDecisions from "@/modules/services/LiveSyncUI/components/UserDecisions.svelte";
|
||||
import InfoNote from "@/modules/services/LiveSyncUI/components/InfoNote.svelte";
|
||||
const TYPE_OK = "ok";
|
||||
type ResultType = typeof TYPE_OK;
|
||||
type Options = {
|
||||
title?: string;
|
||||
dataToCopy: string;
|
||||
};
|
||||
type Props = GuestDialogProps<ResultType, Options>;
|
||||
const { setResult, getInitialData }: Props = $props();
|
||||
let dataToCopy = $state("");
|
||||
let title = $state<string | undefined>(undefined);
|
||||
let copied = $state(false);
|
||||
onMount(() => {
|
||||
if (getInitialData) {
|
||||
const initialData = getInitialData();
|
||||
if (initialData) {
|
||||
dataToCopy = initialData.dataToCopy;
|
||||
title = initialData.title;
|
||||
}
|
||||
}
|
||||
});
|
||||
function commit() {
|
||||
setResult(TYPE_OK);
|
||||
}
|
||||
async function copyToClipboard() {
|
||||
await navigator.clipboard.writeText(dataToCopy);
|
||||
copied = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<DialogHeader title="Your {title || 'Data'} is ready to be copied" />
|
||||
<Instruction>
|
||||
<InputRow label={title || "Data to Copy"}>
|
||||
<textarea readonly rows="4">{dataToCopy}</textarea>
|
||||
<button onclick={() => copyToClipboard()}
|
||||
>{#if !copied}📋{:else}✔️{/if}
|
||||
</button>
|
||||
</InputRow>
|
||||
</Instruction>
|
||||
<InfoNote visible={copied}>
|
||||
Your {title || "data"} has been copied to the clipboard.
|
||||
</InfoNote>
|
||||
<UserDecisions>
|
||||
<Decision title="OK" important={true} {commit} />
|
||||
</UserDecisions>
|
||||
|
||||
<style>
|
||||
textarea {
|
||||
resize: none;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,14 @@
|
||||
export type {
|
||||
HasSetResult,
|
||||
HasGetInitialData,
|
||||
ComponentHasResult,
|
||||
GuestDialogProps,
|
||||
DialogSvelteComponentBaseProps,
|
||||
DialogControlBase,
|
||||
} from "@vrtmrz/livesync-commonlib/compat/services/implements/base/SvelteDialog";
|
||||
export {
|
||||
CONTEXT_DIALOG_CONTROLS,
|
||||
setupDialogContext,
|
||||
getDialogContext,
|
||||
SvelteDialogManagerBase,
|
||||
} from "@vrtmrz/livesync-commonlib/compat/services/implements/base/SvelteDialog";
|
||||
Reference in new issue
Block a user