From 4186e1413a3d01b79ce7b194629df547b4196d56 Mon Sep 17 00:00:00 2001 From: Sam Chau Date: Sat, 17 Jan 2026 00:06:08 +1030 Subject: [PATCH] feat: add database configuration page with manifest and README support --- src/app.css | 249 ++++++++++++++++ src/lib/client/ui/form/KeyValueList.svelte | 213 ++++++++++++++ src/lib/client/ui/form/MarkdownInput.svelte | 34 +-- src/lib/client/ui/form/NumberInput.svelte | 16 +- src/routes/databases/[id]/+layout.svelte | 14 +- .../databases/[id]/config/+page.server.ts | 35 +++ src/routes/databases/[id]/config/+page.svelte | 275 ++++++++++++++++++ 7 files changed, 801 insertions(+), 35 deletions(-) create mode 100644 src/lib/client/ui/form/KeyValueList.svelte create mode 100644 src/routes/databases/[id]/config/+page.server.ts create mode 100644 src/routes/databases/[id]/config/+page.svelte diff --git a/src/app.css b/src/app.css index aa814b7..4786572 100644 --- a/src/app.css +++ b/src/app.css @@ -89,6 +89,255 @@ --color-accent-950: var(--accent-950); } +/* Prose styles for rendered markdown */ +.prose { + line-height: 1.75; + color: rgb(64 64 64); +} + +.dark .prose { + color: rgb(212 212 212); +} + +.prose > :first-child { + margin-top: 0; +} + +.prose > :last-child { + margin-bottom: 0; +} + +.prose h1 { + font-size: 1.875em; + font-weight: 700; + margin-top: 0; + margin-bottom: 0.875em; + color: rgb(23 23 23); + letter-spacing: -0.025em; +} + +.dark .prose h1 { + color: rgb(250 250 250); +} + +.prose h2 { + font-size: 1.375em; + font-weight: 600; + margin-top: 1.75em; + margin-bottom: 0.5em; + color: rgb(38 38 38); + letter-spacing: -0.015em; +} + +.dark .prose h2 { + color: rgb(245 245 245); +} + +.prose h3 { + font-size: 1.125em; + font-weight: 600; + margin-top: 1.5em; + margin-bottom: 0.5em; + color: rgb(64 64 64); +} + +.dark .prose h3 { + color: rgb(229 229 229); +} + +.prose h4 { + font-size: 1em; + font-weight: 600; + margin-top: 1.25em; + margin-bottom: 0.5em; + color: rgb(64 64 64); +} + +.dark .prose h4 { + color: rgb(229 229 229); +} + +.prose p { + margin-top: 1em; + margin-bottom: 1em; +} + +.prose ul, +.prose ol { + margin-top: 1em; + margin-bottom: 1em; + padding-left: 1.5em; +} + +.prose ul { + list-style-type: disc; +} + +.prose ol { + list-style-type: decimal; +} + +.prose li { + margin-top: 0.375em; + margin-bottom: 0.375em; +} + +.prose li > ul, +.prose li > ol { + margin-top: 0.375em; + margin-bottom: 0.375em; +} + +.prose code { + font-family: var(--font-mono); + font-size: 0.875em; + background-color: rgb(245 245 245); + color: rgb(64 64 64); + padding: 0.2em 0.4em; + border-radius: 0.375rem; + font-weight: 500; +} + +.dark .prose code { + background-color: transparent; + color: rgb(229 229 229); +} + +.prose pre { + background-color: rgb(250 250 250); + border: 1px solid rgb(229 229 229); + padding: 1rem; + border-radius: 0.5rem; + overflow-x: auto; + margin-top: 1.25em; + margin-bottom: 1.25em; +} + +.dark .prose pre { + background-color: rgb(23 23 23); + border-color: rgb(64 64 64); +} + +.prose pre code { + background-color: transparent; + padding: 0; + font-size: 0.875em; + font-weight: 400; + color: inherit; +} + +.prose a { + color: var(--color-accent-600); + text-decoration: none; + font-weight: 500; + transition: color 0.15s; +} + +.prose a:hover { + color: var(--color-accent-700); + text-decoration: underline; +} + +.dark .prose a { + color: var(--color-accent-400); +} + +.dark .prose a:hover { + color: var(--color-accent-300); +} + +.prose strong { + font-weight: 600; + color: rgb(38 38 38); +} + +.dark .prose strong { + color: rgb(245 245 245); +} + +.prose blockquote { + border-left: 3px solid var(--color-accent-400); + padding-left: 1em; + margin-top: 1.25em; + margin-bottom: 1.25em; + font-style: italic; + color: rgb(115 115 115); +} + +.dark .prose blockquote { + border-left-color: var(--color-accent-600); + color: rgb(163 163 163); +} + +.prose blockquote p { + margin-top: 0.5em; + margin-bottom: 0.5em; +} + +.prose hr { + border: none; + border-top: 1px solid rgb(229 229 229); + margin-top: 2em; + margin-bottom: 2em; +} + +.dark .prose hr { + border-top-color: rgb(64 64 64); +} + +.prose table { + width: 100%; + border-collapse: collapse; + margin-top: 1.25em; + margin-bottom: 1.25em; + font-size: 0.875em; +} + +.prose th, +.prose td { + border: 1px solid rgb(229 229 229); + padding: 0.625rem 0.875rem; + text-align: left; +} + +.dark .prose th, +.dark .prose td { + border-color: rgb(64 64 64); +} + +.prose th { + background-color: rgb(250 250 250); + font-weight: 600; + color: rgb(38 38 38); +} + +.dark .prose th { + background-color: rgb(38 38 38); + color: rgb(245 245 245); +} + +.prose img { + border-radius: 0.5rem; + margin-top: 1.25em; + margin-bottom: 1.25em; +} + +.prose-sm { + font-size: 0.875rem; +} + +.prose-sm h1 { + font-size: 1.5em; +} + +.prose-sm h2 { + font-size: 1.25em; +} + +.prose-sm h3 { + font-size: 1.125em; +} + @layer base { * { font-family: 'DM Sans', ui-sans-serif, system-ui, sans-serif; diff --git a/src/lib/client/ui/form/KeyValueList.svelte b/src/lib/client/ui/form/KeyValueList.svelte new file mode 100644 index 0000000..b8ff637 --- /dev/null +++ b/src/lib/client/ui/form/KeyValueList.svelte @@ -0,0 +1,213 @@ + + +
+ {#if label} + + {/if} + + {#if description} +

+ {description} +

+ {/if} + +
+ {#if entries.length > 0} + +
+ {keyLabel} + {valueLabel} + +
+ {/if} + + + {#each entries as entry, index (index)} + {@const isLocked = lockedFirst && index === 0} + {@const [vMajor, vMinor, vPatch] = parseVersion(entry.value)} +
+ updateKey(index, (e.target as HTMLInputElement).value)} + onfocus={(e) => { + if (isLocked) { + onLockedEditAttempt?.(); + (e.target as HTMLInputElement).blur(); + } + }} + class="rounded-lg border border-neutral-300 bg-white px-3 py-2 text-sm text-neutral-900 transition-colors focus:border-neutral-400 focus:outline-none dark:border-neutral-700 dark:bg-neutral-800 dark:text-neutral-100 dark:focus:border-neutral-500" + /> + {#if valueType === 'version'} +
+
+ updateValue(index, updateVersionPart(entry.value, 0, v))} + onMinBlocked={isLocked ? onLockedVersionMinBlocked : undefined} + /> +
+ . +
+ updateValue(index, updateVersionPart(entry.value, 1, v))} + /> +
+ . +
+ updateValue(index, updateVersionPart(entry.value, 2, v))} + /> +
+
+ {:else} + updateValue(index, (e.target as HTMLInputElement).value)} + class="rounded-lg border border-neutral-300 bg-white px-3 py-2 text-sm text-neutral-900 transition-colors focus:border-neutral-400 focus:outline-none dark:border-neutral-700 dark:bg-neutral-800 dark:text-neutral-100 dark:focus:border-neutral-500" + /> + {/if} + +
+ {/each} + + + +
+
diff --git a/src/lib/client/ui/form/MarkdownInput.svelte b/src/lib/client/ui/form/MarkdownInput.svelte index 194caaf..78f0074 100644 --- a/src/lib/client/ui/form/MarkdownInput.svelte +++ b/src/lib/client/ui/form/MarkdownInput.svelte @@ -1,5 +1,6 @@ + + + Config - {data.database.name} - Profilarr + + +
+ {#if manifest} +
+ +
+ +

+ Unique identifier for the database (lowercase, hyphens preferred) +

+ update('name', (e.target as HTMLInputElement).value)} + placeholder="my-database" + class="mt-1 block w-full rounded-lg border border-neutral-300 bg-white px-3 py-2 text-sm text-neutral-900 placeholder-neutral-400 transition-colors focus:border-neutral-400 focus:outline-none dark:border-neutral-700 dark:bg-neutral-800 dark:text-neutral-100 dark:placeholder-neutral-500 dark:focus:border-neutral-500" + /> +
+ + +
+ +

+ Short summary of what the database provides +

+ update('description', (e.target as HTMLInputElement).value)} + placeholder="My custom Arr configurations" + class="mt-1 block w-full rounded-lg border border-neutral-300 bg-white px-3 py-2 text-sm text-neutral-900 placeholder-neutral-400 transition-colors focus:border-neutral-400 focus:outline-none dark:border-neutral-700 dark:bg-neutral-800 dark:text-neutral-100 dark:placeholder-neutral-500 dark:focus:border-neutral-500" + /> +
+ + +
+ + Version * + +

+ Semantic version of the database (MAJOR.MINOR.PATCH) +

+
+
+ update('version', updateVersionPart(manifest.version, 0, v))} + onMinBlocked={() => alertStore.add('warning', 'Database version must be at least 1.0.0')} + /> +
+ . +
+ update('version', updateVersionPart(manifest.version, 1, v))} + /> +
+ . +
+ update('version', updateVersionPart(manifest.version, 2, v))} + /> +
+
+
+ + +
+ + Minimum Profilarr Version * + +

+ Minimum Profilarr version required to use this database +

+
+
+ updateProfilarr('minimum_version', updateVersionPart(manifest.profilarr.minimum_version, 0, v))} + onMinBlocked={() => alertStore.add('warning', 'Minimum Profilarr version must be at least 2.0.0')} + /> +
+ . +
+ updateProfilarr('minimum_version', updateVersionPart(manifest.profilarr.minimum_version, 1, v))} + /> +
+ . +
+ updateProfilarr('minimum_version', updateVersionPart(manifest.profilarr.minimum_version, 2, v))} + /> +
+
+
+ + +
+ + Arr Types + +

+ Which arr applications this database supports. Leave empty if all are supported. +

+
+ update('arr_types', tags)} + /> +
+
+ + +
+ Tags +

+ Descriptive keywords for discovery +

+
+ update('tags', tags)} + /> +
+
+ + +
+ +

+ SPDX license identifier (e.g., MIT, Apache-2.0) +

+ update('license', (e.target as HTMLInputElement).value || undefined)} + placeholder="MIT" + class="mt-1 block w-full rounded-lg border border-neutral-300 bg-white px-3 py-2 text-sm text-neutral-900 placeholder-neutral-400 transition-colors focus:border-neutral-400 focus:outline-none dark:border-neutral-700 dark:bg-neutral-800 dark:text-neutral-100 dark:placeholder-neutral-500 dark:focus:border-neutral-500" + /> +
+ + +
+ +

+ Git repository URL +

+ update('repository', (e.target as HTMLInputElement).value || undefined)} + placeholder="https://github.com/user/repo" + class="mt-1 block w-full rounded-lg border border-neutral-300 bg-white px-3 py-2 text-sm text-neutral-900 placeholder-neutral-400 transition-colors focus:border-neutral-400 focus:outline-none dark:border-neutral-700 dark:bg-neutral-800 dark:text-neutral-100 dark:placeholder-neutral-500 dark:focus:border-neutral-500" + /> +
+ + + update('dependencies', v)} + lockedFirst={{ key: 'https://github.com/Dictionarry-Hub/schema', value: '1.0.0', minMajor: 1 }} + onLockedEditAttempt={() => alertStore.add('warning', 'The schema package URL cannot be changed')} + onLockedDeleteAttempt={() => alertStore.add('warning', 'The schema dependency is required and cannot be removed')} + onLockedVersionMinBlocked={() => alertStore.add('warning', 'Schema version must be at least 1.0.0')} + addDisabled={true} + onAddBlocked={() => alertStore.add('info', 'Additional dependencies are not available yet. Coming in a future version.')} + /> + + +
+ README +

+ Documentation for your database +

+
+ +
+
+
+ {:else} +

No manifest found

+ {/if} +