mirror of
https://github.com/seanmorley15/AdventureLog.git
synced 2026-07-31 15:59:34 -04:00
Enhance AboutModal and AppVersionDisplay components
- Added functionality to copy the app version to clipboard with user feedback via toasts. - Improved layout and styling of the AboutModal for better user experience. - Updated AppVersionDisplay to support different display variants. - Added new translations for the "Copy version" feature in the localization files.
This commit is contained in:
@@ -2,13 +2,30 @@
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { copyrightYear, versionChangelog } from '$lib/config';
|
||||
import { copyrightYear, versionChangelog, appVersion } from '$lib/config';
|
||||
import { copyToClipboard } from '$lib/index';
|
||||
import { addToast } from '$lib/toasts';
|
||||
import AppVersionDisplay from '$lib/components/shared/AppVersionDisplay.svelte';
|
||||
|
||||
import CloseIcon from '~icons/mdi/close';
|
||||
import AccountIcon from '~icons/mdi/account-circle';
|
||||
import TagIcon from '~icons/mdi/tag-outline';
|
||||
import InformationIcon from '~icons/mdi/information-outline';
|
||||
import ScaleBalanceIcon from '~icons/mdi/scale-balance';
|
||||
import GitHubIcon from '~icons/mdi/github';
|
||||
import HeartIcon from '~icons/mdi/heart';
|
||||
import BookOpenIcon from '~icons/mdi/book-open-page-variant';
|
||||
import DiscordIcon from '~icons/mdi/discord';
|
||||
import OpenInNewIcon from '~icons/mdi/open-in-new';
|
||||
import ContentCopyIcon from '~icons/mdi/content-copy';
|
||||
import CheckIcon from '~icons/mdi/check';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
let modal: HTMLDialogElement;
|
||||
|
||||
let integrations: Record<string, boolean> | null = null;
|
||||
let versionCopied = false;
|
||||
let copyResetTimeout: ReturnType<typeof setTimeout> | undefined;
|
||||
|
||||
onMount(async () => {
|
||||
modal = document.getElementById('about_modal') as HTMLDialogElement;
|
||||
@@ -21,6 +38,10 @@
|
||||
} else {
|
||||
integrations = null;
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (copyResetTimeout) clearTimeout(copyResetTimeout);
|
||||
};
|
||||
});
|
||||
|
||||
function close() {
|
||||
@@ -32,215 +53,271 @@
|
||||
dispatch('close');
|
||||
}
|
||||
}
|
||||
|
||||
function handleBackdropClick(event: MouseEvent) {
|
||||
if (event.target === modal) {
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
async function copyVersion() {
|
||||
try {
|
||||
await copyToClipboard(appVersion);
|
||||
versionCopied = true;
|
||||
addToast('success', $t('adventures.copied_to_clipboard'));
|
||||
if (copyResetTimeout) clearTimeout(copyResetTimeout);
|
||||
copyResetTimeout = setTimeout(() => {
|
||||
versionCopied = false;
|
||||
}, 2000);
|
||||
} catch {
|
||||
addToast('error', $t('adventures.copy_failed'));
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<dialog id="about_modal" class="modal backdrop-blur-sm">
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
|
||||
<dialog id="about_modal" class="modal backdrop-blur-sm" on:click={handleBackdropClick}>
|
||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
|
||||
<div
|
||||
class="modal-box w-11/12 max-w-2xl bg-gradient-to-br from-base-100 via-base-100 to-base-200 border border-base-300 shadow-2xl"
|
||||
class="modal-box w-11/12 max-w-2xl p-0 bg-base-100 border border-base-300 shadow-2xl max-h-[90vh] flex flex-col"
|
||||
role="dialog"
|
||||
on:keydown={handleKeydown}
|
||||
tabindex="0"
|
||||
>
|
||||
<!-- Header -->
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="p-2 bg-primary/10 rounded-lg">
|
||||
<img src="/favicon.png" alt="AdventureLog" class="w-12 h-12" />
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-primary">
|
||||
{$t('about.about')} AdventureLog
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Hero -->
|
||||
<div
|
||||
class="relative bg-gradient-to-r from-primary via-primary/90 to-secondary/80 text-primary-content px-6 pt-6 pb-5 shrink-0"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-ghost btn-sm btn-square"
|
||||
class="btn btn-ghost btn-sm btn-circle absolute top-4 right-4 text-primary-content hover:bg-base-100/20"
|
||||
aria-label={$t('about.close')}
|
||||
title={$t('about.close')}
|
||||
on:click={close}
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
<CloseIcon class="w-5 h-5" />
|
||||
</button>
|
||||
|
||||
<div class="flex items-start gap-4 pr-10">
|
||||
<div
|
||||
class="h-14 w-14 rounded-2xl bg-base-100/20 backdrop-blur flex items-center justify-center shadow-inner shrink-0"
|
||||
>
|
||||
<img src="/favicon.png" alt="AdventureLog" class="w-9 h-9" />
|
||||
</div>
|
||||
<div class="min-w-0">
|
||||
<p class="text-xs uppercase tracking-widest font-semibold opacity-75">
|
||||
{$t('about.about')}
|
||||
</p>
|
||||
<h1 class="text-2xl font-bold leading-tight">AdventureLog</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="space-y-4">
|
||||
<!-- Version & Developer Info -->
|
||||
<div class="card bg-base-200/30 border border-base-300">
|
||||
<div class="card-body p-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<div class="text-sm text-base-content/60">{$t('about.version')}</div>
|
||||
<AppVersionDisplay />
|
||||
<a
|
||||
href={versionChangelog}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="text-sm link link-primary"
|
||||
>
|
||||
{$t('about.view_changelog')} →
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-sm text-base-content/60">{$t('about.developer')}</div>
|
||||
<a
|
||||
href="https://seanmorley.com"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="text-lg font-semibold link link-primary"
|
||||
>
|
||||
Sean Morley
|
||||
</a>
|
||||
<div class="text-sm text-base-content/60">{$t('about.message')}</div>
|
||||
</div>
|
||||
<!-- Body -->
|
||||
<div class="p-6 space-y-5 overflow-y-auto flex-1">
|
||||
<!-- Version -->
|
||||
<div class="rounded-2xl border border-base-300/70 bg-base-200/40 p-4 space-y-3">
|
||||
<div class="flex items-center gap-2 text-primary">
|
||||
<TagIcon class="w-5 h-5 shrink-0" />
|
||||
<h2 class="font-semibold text-sm">{$t('about.version')}</h2>
|
||||
</div>
|
||||
|
||||
<AppVersionDisplay />
|
||||
|
||||
<div
|
||||
class="flex items-center gap-2 rounded-xl border border-base-300/80 bg-base-100/80 px-3 py-2"
|
||||
>
|
||||
<code class="flex-1 min-w-0 truncate font-mono text-sm text-base-content/80"
|
||||
>{appVersion}</code
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-ghost btn-xs btn-square shrink-0 {versionCopied
|
||||
? 'text-success'
|
||||
: 'text-base-content/60'}"
|
||||
aria-label={$t('about.copy_version')}
|
||||
title={$t('about.copy_version')}
|
||||
on:click={copyVersion}
|
||||
>
|
||||
{#if versionCopied}
|
||||
<CheckIcon class="w-4 h-4" />
|
||||
{:else}
|
||||
<ContentCopyIcon class="w-4 h-4" />
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<a
|
||||
href={versionChangelog}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="inline-flex items-center gap-1 text-sm link link-primary font-medium"
|
||||
>
|
||||
{$t('about.view_changelog')}
|
||||
<OpenInNewIcon class="w-3.5 h-3.5" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Developer -->
|
||||
<div
|
||||
class="flex flex-col sm:flex-row sm:items-center gap-4 rounded-2xl border border-base-300/70 bg-base-200/40 p-4"
|
||||
>
|
||||
<div class="flex items-center gap-3 min-w-0">
|
||||
<div
|
||||
class="w-10 h-10 rounded-full bg-primary/10 flex items-center justify-center shrink-0"
|
||||
>
|
||||
<AccountIcon class="w-5 h-5 text-primary" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Map Services -->
|
||||
<div class="card bg-base-200/30 border border-base-300">
|
||||
<div class="card-body p-4">
|
||||
<h3 class="font-bold text-primary mb-3 flex items-center gap-2">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
{$t('about.attributions')}
|
||||
</h3>
|
||||
{#if integrations && integrations?.google_maps}
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-sm text-base-content/60">{$t('about.nominatim_1')}</span>
|
||||
<a
|
||||
href="https://developers.google.com/maps/terms"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="link link-primary font-semibold"
|
||||
>
|
||||
Google Maps Platform
|
||||
</a>
|
||||
</div>
|
||||
{:else if integrations && !integrations?.google_maps}
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-sm text-base-content/60">{$t('about.nominatim_1')}</span>
|
||||
<a
|
||||
href="https://operations.osmfoundation.org/policies/nominatim/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="link link-primary font-semibold"
|
||||
>
|
||||
OpenStreetMap Nominatim
|
||||
</a>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-sm text-base-content/60">{$t('about.generic_attributions')}</div>
|
||||
{/if}
|
||||
<p class="text-sm text-base-content/60">{$t('about.other_attributions')}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Liscense info -->
|
||||
<div class="card bg-base-200/30 border border-base-300">
|
||||
<div class="card-body p-4">
|
||||
<h3 class="font-bold text-primary mb-3 flex items-center gap-2">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
|
||||
/>
|
||||
</svg>
|
||||
{$t('about.license_info')}
|
||||
</h3>
|
||||
<p class="text-sm text-base-content/60 mb-2">
|
||||
© {copyrightYear}
|
||||
<div class="min-w-0">
|
||||
<p class="text-xs font-semibold uppercase tracking-wider text-base-content/50 mb-0.5">
|
||||
{$t('about.developer')}
|
||||
</p>
|
||||
<a
|
||||
href="https://seanmorley.com"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="link link-primary"
|
||||
class="font-semibold link link-primary truncate block"
|
||||
>
|
||||
Sean Morley
|
||||
</a>
|
||||
</p>
|
||||
<p class="text-sm text-base-content/60">
|
||||
{$t('about.license')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-sm text-base-content/65 m-0 sm:ml-auto sm:text-right sm:max-w-[14rem]">
|
||||
{$t('about.message')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<a
|
||||
href="https://github.com/seanmorley15/AdventureLog/blob/main/LICENSE"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="link link-primary mt-2"
|
||||
>
|
||||
{$t('about.view_license')} →
|
||||
</a>
|
||||
<!-- Info grid -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="rounded-2xl border border-base-300/70 bg-base-200/40 p-4 space-y-3">
|
||||
<div class="flex items-center gap-2 text-primary">
|
||||
<InformationIcon class="w-5 h-5 shrink-0" />
|
||||
<h2 class="font-semibold text-sm">{$t('about.attributions')}</h2>
|
||||
</div>
|
||||
<div class="space-y-2 text-sm text-base-content/70">
|
||||
{#if integrations?.google_maps}
|
||||
<p class="m-0">
|
||||
{$t('about.nominatim_1')}
|
||||
<a
|
||||
href="https://developers.google.com/maps/terms"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="link link-primary font-medium"
|
||||
>
|
||||
Google Maps Platform
|
||||
</a>
|
||||
</p>
|
||||
{:else if integrations && !integrations.google_maps}
|
||||
<p class="m-0">
|
||||
{$t('about.nominatim_1')}
|
||||
<a
|
||||
href="https://operations.osmfoundation.org/policies/nominatim/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="link link-primary font-medium"
|
||||
>
|
||||
OpenStreetMap Nominatim
|
||||
</a>
|
||||
</p>
|
||||
{:else}
|
||||
<p class="m-0">{$t('about.generic_attributions')}</p>
|
||||
{/if}
|
||||
<p class="m-0 text-xs text-base-content/55">{$t('about.other_attributions')}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-2xl border border-base-300/70 bg-base-200/40 p-4 space-y-3">
|
||||
<div class="flex items-center gap-2 text-primary">
|
||||
<ScaleBalanceIcon class="w-5 h-5 shrink-0" />
|
||||
<h2 class="font-semibold text-sm">{$t('about.license_info')}</h2>
|
||||
</div>
|
||||
<div class="space-y-2 text-sm text-base-content/70">
|
||||
<p class="m-0">
|
||||
© {copyrightYear}
|
||||
<a
|
||||
href="https://seanmorley.com"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="link link-primary"
|
||||
>
|
||||
Sean Morley
|
||||
</a>
|
||||
</p>
|
||||
<p class="m-0">{$t('about.license')}</p>
|
||||
<a
|
||||
href="https://github.com/seanmorley15/AdventureLog/blob/main/LICENSE"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="inline-flex items-center gap-1 link link-primary text-sm font-medium"
|
||||
>
|
||||
{$t('about.view_license')}
|
||||
<OpenInNewIcon class="w-3.5 h-3.5" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Links -->
|
||||
<div class="card bg-base-200/30 border border-base-300">
|
||||
<div class="card-body p-4">
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<a
|
||||
href="https://github.com/seanmorley15/AdventureLog"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="btn btn-outline btn-sm"
|
||||
>
|
||||
GitHub →
|
||||
</a>
|
||||
<a
|
||||
href="https://seanmorley.com/sponsor"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="btn btn-outline btn-sm"
|
||||
>
|
||||
{$t('about.sponsor')} →
|
||||
</a>
|
||||
<!-- documentation -->
|
||||
<a
|
||||
href="https://adventurelog.app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="btn btn-outline btn-sm"
|
||||
>
|
||||
{$t('navbar.documentation')} →
|
||||
</a>
|
||||
<!-- discord -->
|
||||
<a
|
||||
href="https://discord.gg/wRbQ9Egr8C"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="btn btn-outline btn-sm"
|
||||
>
|
||||
Discord →
|
||||
</a>
|
||||
</div>
|
||||
<!-- Community links -->
|
||||
<div class="space-y-3">
|
||||
<p class="text-xs font-semibold uppercase tracking-wider text-base-content/50 m-0">
|
||||
{$t('navbar.support')}
|
||||
</p>
|
||||
<div class="grid grid-cols-2 sm:grid-cols-4 gap-2">
|
||||
<a
|
||||
href="https://github.com/seanmorley15/AdventureLog"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="group flex flex-col items-center gap-2 rounded-xl border border-base-300/70 bg-base-200/30 p-3 text-center transition-colors hover:bg-base-200 hover:border-primary/30"
|
||||
>
|
||||
<GitHubIcon
|
||||
class="w-5 h-5 text-base-content/70 group-hover:text-primary transition-colors"
|
||||
/>
|
||||
<span class="text-xs font-medium">GitHub</span>
|
||||
</a>
|
||||
<a
|
||||
href="https://seanmorley.com/sponsor"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="group flex flex-col items-center gap-2 rounded-xl border border-base-300/70 bg-base-200/30 p-3 text-center transition-colors hover:bg-base-200 hover:border-primary/30"
|
||||
>
|
||||
<HeartIcon
|
||||
class="w-5 h-5 text-base-content/70 group-hover:text-primary transition-colors"
|
||||
/>
|
||||
<span class="text-xs font-medium">{$t('about.sponsor')}</span>
|
||||
</a>
|
||||
<a
|
||||
href="https://adventurelog.app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="group flex flex-col items-center gap-2 rounded-xl border border-base-300/70 bg-base-200/30 p-3 text-center transition-colors hover:bg-base-200 hover:border-primary/30"
|
||||
>
|
||||
<BookOpenIcon
|
||||
class="w-5 h-5 text-base-content/70 group-hover:text-primary transition-colors"
|
||||
/>
|
||||
<span class="text-xs font-medium">{$t('navbar.documentation')}</span>
|
||||
</a>
|
||||
<a
|
||||
href="https://discord.gg/wRbQ9Egr8C"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="group flex flex-col items-center gap-2 rounded-xl border border-base-300/70 bg-base-200/30 p-3 text-center transition-colors hover:bg-base-200 hover:border-primary/30"
|
||||
>
|
||||
<DiscordIcon
|
||||
class="w-5 h-5 text-base-content/70 group-hover:text-primary transition-colors"
|
||||
/>
|
||||
<span class="text-xs font-medium">Discord</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="flex items-center justify-between mt-6 pt-4 border-t border-base-300">
|
||||
<div class="text-sm text-base-content/60">
|
||||
{$t('about.thank_you')}
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center justify-between gap-4 px-6 py-4 border-t border-base-300 shrink-0 bg-base-100/80"
|
||||
>
|
||||
<p class="text-sm text-base-content/60 m-0">{$t('about.thank_you')}</p>
|
||||
<button class="btn btn-primary btn-sm" on:click={close}>
|
||||
{$t('about.close')}
|
||||
</button>
|
||||
|
||||
@@ -2,17 +2,21 @@
|
||||
import { appTitle, appVersion, appCodename } from '$lib/config';
|
||||
|
||||
export let size: 'sm' | 'md' = 'md';
|
||||
export let variant: 'default' | 'inverse' = 'default';
|
||||
|
||||
$: titleClass = variant === 'inverse' ? 'text-primary-content' : 'text-primary';
|
||||
$: versionClass = variant === 'inverse' ? 'text-primary-content/80' : 'text-base-content/70';
|
||||
$: codenameClass = variant === 'inverse' ? 'text-primary-content/90' : 'text-secondary';
|
||||
</script>
|
||||
|
||||
<p
|
||||
class="inline-flex flex-wrap items-baseline gap-x-1.5 gap-y-0.5 leading-snug m-0
|
||||
class="flex flex-wrap items-baseline gap-x-1.5 gap-y-0.5 leading-snug m-0 w-full
|
||||
{size === 'sm' ? 'text-sm' : 'text-lg'}"
|
||||
>
|
||||
<span class="font-bold text-primary">{appTitle}</span>
|
||||
<span class="font-semibold italic text-secondary">“{appCodename}”</span>
|
||||
<span class="text-base-content/35 font-light select-none" aria-hidden="true">·</span>
|
||||
<span class="font-bold {titleClass}">{appTitle}</span>
|
||||
<span
|
||||
class="font-mono text-base-content/60 tabular-nums tracking-tight
|
||||
class="font-mono tabular-nums tracking-tight {versionClass}
|
||||
{size === 'sm' ? 'text-xs' : 'text-sm'}">{appVersion}</span
|
||||
>
|
||||
<span class="font-semibold italic {codenameClass}">{appCodename}</span>
|
||||
</p>
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
"close": "Close",
|
||||
"thank_you": "Thank you for using AdventureLog!",
|
||||
"version": "Version",
|
||||
"copy_version": "Copy version",
|
||||
"view_changelog": "View Changelog",
|
||||
"developer": "Developer",
|
||||
"attributions": "Attributions",
|
||||
|
||||
Reference in New Issue
Block a user