Merge pull request #1717 from appwrite/add-date-tooltips

Add date tooltips
This commit is contained in:
Darshan
2025-03-18 15:54:31 +05:30
committed by GitHub
18 changed files with 135 additions and 62 deletions
+69
View File
@@ -0,0 +1,69 @@
<script lang="ts">
import { getUTCOffset, timeFromNow, toLocaleDateTime } from '$lib/helpers/date';
import { Badge, InteractiveText, Layout, Popover, Typography } from '@appwrite.io/pink-svelte';
export let time: string = '';
export let placement: any | string = 'bottom-end';
function timeDifference(dateString: string): string {
const SECONDS_IN_MINUTE = 60;
const SECONDS_IN_HOUR = SECONDS_IN_MINUTE * 60;
const SECONDS_IN_DAY = SECONDS_IN_HOUR * 24;
const SECONDS_IN_MONTH = SECONDS_IN_DAY * 30;
const now = new Date();
const pastDate = new Date(dateString);
if (isNaN(pastDate.getTime())) return 'Invalid time';
const diffInSeconds = Math.floor((now.getTime() - pastDate.getTime()) / 1000);
const timeParts = [
{ label: 'month', value: Math.floor(diffInSeconds / SECONDS_IN_MONTH) },
{ label: 'day', value: Math.floor(diffInSeconds / SECONDS_IN_DAY) % 30 },
{ label: 'hour', value: Math.floor(diffInSeconds / SECONDS_IN_HOUR) % 24 },
{ label: 'minute', value: Math.floor(diffInSeconds / SECONDS_IN_MINUTE) % 60 }
];
const formattedTime = timeParts
.filter((unit) => unit.value > 0)
.slice(0, 3)
.map((unit) => `${unit.value} ${unit.label}${unit.value > 1 ? 's' : ''}`)
.join(', ');
return formattedTime ? `${formattedTime} ago` : 'Just now';
}
$: timeToString = time ? timeDifference(time) : 'Invalid time';
</script>
<Popover let:toggle {placement} portal>
<button on:mouseenter={(e) => toggle(e)}>
<slot>{timeFromNow(time)}</slot>
</button>
<Layout.Stack gap="m" alignContent="flex-start" slot="tooltip">
<Typography.Text color="--fgcolor-neutral-tertiary" variant="m-400"
>{timeToString}</Typography.Text>
<Layout.Stack gap="xxs">
<Layout.Stack direction="row" justifyContent="space-between">
<Typography.Text variant="m-400">
<InteractiveText
variant="copy"
text={toLocaleDateTime(time, 'UTC')}
isVisible />
</Typography.Text>
<Badge variant="secondary" content="UTC" size="xs" />
</Layout.Stack>
<Layout.Stack direction="row" justifyContent="space-between">
<Typography.Text variant="m-400">
<InteractiveText variant="copy" text={toLocaleDateTime(time)} isVisible />
</Typography.Text>
<Badge variant="secondary" content="UTC{getUTCOffset()}" size="xs" />
</Layout.Stack>
</Layout.Stack>
</Layout.Stack>
</Popover>
+3 -4
View File
@@ -1,17 +1,16 @@
<script lang="ts">
import { Id, Trim } from '.';
import { Id } from '.';
import { Button } from '$lib/elements/forms';
import { sdk } from '$lib/stores/sdk';
import { ID, Query, Permission, Role } from '@appwrite.io/console';
import type { Models } from '@appwrite.io/console';
import { calculateSize } from '$lib/helpers/sizeConvertion';
import { toLocaleDate } from '$lib/helpers/date';
import InputSearch from '$lib/elements/forms/inputSearch.svelte';
import { writable } from 'svelte/store';
import { onMount } from 'svelte';
import { clickOnEnter } from '$lib/helpers/a11y';
import { base } from '$app/paths';
import { page } from '$app/stores';
import DualTimeView from './dualTimeView.svelte';
import {
Layout,
Typography,
@@ -307,7 +306,7 @@
{calculateSize(file.sizeOriginal)}
</Table.Cell>
<Table.Cell>
{toLocaleDate(file.$createdAt)}
<DualTimeView time={file.$createdAt} />
</Table.Cell>
</Table.Button>
{/each}
@@ -1,9 +1,10 @@
<script lang="ts">
import Link from '$lib/elements/link.svelte';
import { timeFromNow, toLocaleDateTime } from '$lib/helpers/date';
import { capitalize } from '$lib/helpers/string';
import type { Models } from '@appwrite.io/console';
import { Tooltip } from '@appwrite.io/pink-svelte';
import { timeFromNow, toLocaleDateTime } from '$lib/helpers/date';
import DualTimeView from '$lib/components/dualTimeView.svelte';
export let deployment: Models.Deployment;
</script>
@@ -19,11 +20,6 @@
by <Link href={deployment.providerCommitAuthorUrl} external
>{deployment.providerCommitAuthor}</Link>
{:else}
<Tooltip>
<span>
{capitalize(timeFromNow(deployment.$updatedAt))}
</span>
<span slot="tooltip">{toLocaleDateTime(deployment.$updatedAt)}</span>
</Tooltip>
<DualTimeView time={deployment.$updatedAt} />
{/if}
</p>
+13 -1
View File
@@ -31,7 +31,10 @@ export const toLocaleDate = (datetime: string) => {
return date.toLocaleDateString('en', options);
};
export const toLocaleDateTime = (datetime: string | number) => {
export const toLocaleDateTime = (
datetime: string | number,
timeZone: string | undefined = undefined
) => {
const date = new Date(datetime);
if (isNaN(date.getTime())) {
@@ -39,6 +42,7 @@ export const toLocaleDateTime = (datetime: string | number) => {
}
const options: Intl.DateTimeFormatOptions = {
timeZone,
year: 'numeric',
month: 'short',
day: 'numeric',
@@ -181,3 +185,11 @@ export function getTomorrow(date: Date) {
return tomorrow;
}
export function getUTCOffset(): string {
const offsetMinutes = -new Date().getTimezoneOffset();
const hours = Math.floor(offsetMinutes / 60);
const minutes = Math.abs(offsetMinutes % 60);
return `${hours >= 0 ? '+' : ''}${hours}${minutes ? `:${minutes.toString().padStart(2, '0')}` : ''}`;
}
@@ -11,6 +11,7 @@
import { oAuthProviders } from '$lib/stores/oauth-providers';
import { Card, Empty, Icon, Layout, Table } from '@appwrite.io/pink-svelte';
import { IconTrash } from '@appwrite.io/pink-icons-svelte';
import DualTimeView from '$lib/components/dualTimeView.svelte';
async function deleteIdentity(id: string) {
try {
@@ -70,7 +71,7 @@
{identity.providerEmail}
</Table.Cell>
<Table.Cell>
{toLocaleDateTime(identity.$createdAt)}
<DualTimeView time={identity.$createdAt} />
</Table.Cell>
<Table.Cell>
{toLocaleDateTime(identity.providerAccessTokenExpiry)}
@@ -3,7 +3,7 @@
import { Click, Submit, trackError, trackEvent } from '$lib/actions/analytics';
import { CardGrid } from '$lib/components';
import { BillingPlan, Dependencies } from '$lib/constants';
import { Button, Form, FormList, InputNumber, InputSwitch } from '$lib/elements/forms';
import { Button, Form, InputNumber, InputSwitch } from '$lib/elements/forms';
import { showUsageRatesModal, upgradeURL } from '$lib/stores/billing';
import { addNotification } from '$lib/stores/notifications';
import { organization, currentPlan } from '$lib/stores/organization';
@@ -12,11 +12,10 @@
} from '@appwrite.io/pink-svelte';
import { IconDotsHorizontal, IconPencil, IconTrash } from '@appwrite.io/pink-icons-svelte';
import { columns } from './store';
import { timeFromNow } from '$lib/helpers/date';
import DeleteRecordModal from './deleteRecordModal.svelte';
import { capitalize } from '$lib/helpers/string';
import EditRecordModal from './updateRecordModal.svelte';
import type { DnsRecord } from '$lib/sdk/domains';
import DualTimeView from '$lib/components/dualTimeView.svelte';
export let data;
@@ -78,9 +77,7 @@
</Table.Cell>
{:else if column.id === '$createdAt'}
<Table.Cell>
<Typography.Text>
{capitalize(timeFromNow(record.$createdAt))}
</Typography.Text>
<DualTimeView time={record.$createdAt} />
</Table.Cell>
{/if}
{/if}
@@ -3,13 +3,13 @@
import { EmptyCardCloud } from '$lib/components/billing';
import { BillingPlan } from '$lib/constants';
import { Button } from '$lib/elements/forms';
import { toLocaleDate } from '$lib/helpers/date';
import { formatCurrency } from '$lib/helpers/numbers';
import { plansInfo, tierToPlan } from '$lib/stores/billing';
import { newMemberModal, organization } from '$lib/stores/organization';
import type { Models } from '@appwrite.io/console';
import { IconInfo, IconPlus } from '@appwrite.io/pink-icons-svelte';
import { Icon, Layout, Table, Tooltip, Typography } from '@appwrite.io/pink-svelte';
import DualTimeView from '$lib/components/dualTimeView.svelte';
export let members: Models.MembershipList;
@@ -78,9 +78,7 @@
</Layout.Stack>
</Table.Cell>
<Table.Cell>
{member.joined
? toLocaleDate(member.joined)
: toLocaleDate(member.$createdAt)}
<DualTimeView time={member.joined ?? member.$createdAt} />
</Table.Cell>
</Table.Row>
{/each}
@@ -14,7 +14,6 @@
} from '$lib/components';
import Create from '../createTeam.svelte';
import { goto } from '$app/navigation';
import { toLocaleDateTime } from '$lib/helpers/date';
import { Container } from '$lib/layout';
import { base } from '$app/paths';
import type { Models } from '@appwrite.io/console';
@@ -23,6 +22,7 @@
import { canWriteTeams } from '$lib/stores/roles';
import { Icon, Layout, Table } from '@appwrite.io/pink-svelte';
import { IconPlus } from '@appwrite.io/pink-icons-svelte';
import DualTimeView from '$lib/components/dualTimeView.svelte';
export let data: PageData;
@@ -64,7 +64,7 @@
{team.total} members
</Table.Cell>
<Table.Cell>
{toLocaleDateTime(team.$createdAt)}
<DualTimeView time={team.$createdAt} />
</Table.Cell>
</Table.Link>
{/each}
@@ -27,6 +27,7 @@
import { LabelCard } from '$lib/components/index.js';
import { Dependencies } from '$lib/constants';
import { Tooltip } from '@appwrite.io/pink-svelte';
import DualTimeView from '$lib/components/dualTimeView.svelte';
export let data: PageData;
@@ -155,12 +156,9 @@
<TableRow>
<TableCellCheck id={backup.$id} bind:selectedIds={selectedBackups} />
<TableCell title={backup.$createdAt}>
<Tooltip>
<span>
{cleanBackupName(backup)}
</span>
<span slot="tooltip">{timeFromNow(backup.$createdAt)}</span>
</Tooltip>
<DualTimeView time={backup.$createdAt}>
{cleanBackupName(backup)}
</DualTimeView>
</TableCell>
<TableCell title="Backup Size">
{#if backup.status === 'completed'}
@@ -16,8 +16,8 @@
import RelationshipsModal from './relationshipsModal.svelte';
import { attributes, collection, columns } from './store';
import type { ColumnType } from '$lib/helpers/types';
import { toLocaleDateTime } from '$lib/helpers/date';
import { Tooltip, Table, Selector, Button, Link } from '@appwrite.io/pink-svelte';
import DualTimeView from '$lib/components/dualTimeView.svelte';
export let data: PageData;
@@ -237,10 +237,10 @@
{/if}
{/each}
<Table.Cell>
{toLocaleDateTime(document.$createdAt)}
<DualTimeView time={document.$createdAt} />
</Table.Cell>
<Table.Cell>
{toLocaleDateTime(document.$updatedAt)}
<DualTimeView time={document.$updatedAt} />
</Table.Cell>
</Table.Link>
{/each}
@@ -269,7 +269,11 @@
</div>
</FloatingActionBar>
<Modal title="Delete Documents" bind:show={showDelete} onSubmit={handleDelete} closable={!deleting}>
<Modal
title="Delete Documents"
bind:show={showDelete}
onSubmit={handleDelete}
dismissible={!deleting}>
<div>
<p class="text" data-private>
Are you sure you want to delete <b>{selectedDb.length}</b>
@@ -1,6 +1,6 @@
<script lang="ts">
import { Id } from '$lib/components';
import { timeFromNow, toLocaleDateTime } from '$lib/helpers/date';
import { toLocaleDateTime } from '$lib/helpers/date';
import type { Column } from '$lib/helpers/types';
import type { Models } from '@appwrite.io/console';
import { Badge, Layout, Status, Table, Tooltip, Typography } from '@appwrite.io/pink-svelte';
@@ -8,6 +8,7 @@
import { capitalize } from '$lib/helpers/string';
import { formatTimeDetailed } from '$lib/helpers/timeConversion';
import { logStatusConverter } from './store';
import DualTimeView from '$lib/components/dualTimeView.svelte';
export let columns: Column[];
export let logs: Models.ExecutionList;
@@ -40,7 +41,7 @@
{/key}
{:else if column.id === '$createdAt'}
<Table.Cell>
{capitalize(timeFromNow(log.$createdAt))}
<DualTimeView time={log.$createdAt} />
</Table.Cell>
{:else if column.id === 'requestPath'}
<Table.Cell>
@@ -62,7 +62,6 @@
import { base } from '$app/paths';
import { page } from '$app/stores';
import { Button } from '$lib/elements/forms';
import { toLocaleDate } from '$lib/helpers/date';
import type { PageData } from './$types';
import { canWritePlatforms } from '$lib/stores/roles';
import { setOverviewAction } from '../context';
@@ -84,6 +83,7 @@
IconReact
} from '@appwrite.io/pink-icons-svelte';
import type { ComponentType } from 'svelte';
import DualTimeView from '$lib/components/dualTimeView.svelte';
export let data: PageData;
@@ -141,7 +141,11 @@
</Layout.Stack>
</Table.Cell>
<Table.Cell>
{platform.$updatedAt ? toLocaleDate(platform.$updatedAt) : 'never'}
{#if platform.$updatedAt}
<DualTimeView time={platform.$updatedAt} />
{:else}
never
{/if}
</Table.Cell>
</Table.Link>
{/each}
@@ -4,7 +4,7 @@
import { Arrow, Avatar, AvatarGroup, CardGrid } from '$lib/components';
import { Dependencies } from '$lib/constants';
import { Button } from '$lib/elements/forms';
import { isSameDay, toLocaleDate } from '$lib/helpers/date';
import { isSameDay } from '$lib/helpers/date';
import { Container } from '$lib/layout';
import { sdk } from '$lib/stores/sdk';
import { GRACE_PERIOD_OVERRIDE, isSelfHosted } from '$lib/system';
@@ -28,6 +28,7 @@
} from '@appwrite.io/pink-icons-svelte';
import { Icon, Layout, Link, Status, Table } from '@appwrite.io/pink-svelte';
import { capitalize } from '$lib/helpers/string';
import DualTimeView from '$lib/components/dualTimeView.svelte';
import { Click, trackEvent } from '$lib/actions/analytics';
export let data;
@@ -163,9 +164,11 @@
<Table.Row>
{@const status = getStatus(entry.status)}
<Table.Cell>
{isSameDay(new Date(), new Date(entry.$createdAt))
? 'Today'
: toLocaleDate(entry.$createdAt)}
{#if isSameDay(new Date(), new Date(entry.$createdAt))}
Today
{:else}
<DualTimeView time={entry.$createdAt} />
{/if}
</Table.Cell>
<Table.Cell>{entry.source}</Table.Cell>
<Table.Cell>
@@ -6,7 +6,6 @@
import { sdk } from '$lib/stores/sdk';
import type { Models } from '@appwrite.io/console';
import GitDisconnectModal from './GitDisconnectModal.svelte';
import dayjs from 'dayjs';
import { isSelfHosted } from '$lib/system';
import { consoleVariables } from '$routes/(console)/store';
import {
@@ -25,6 +24,7 @@
IconPlus,
IconXCircle
} from '@appwrite.io/pink-icons-svelte';
import DualTimeView from '$lib/components/dualTimeView.svelte';
import { Click, trackEvent } from '$lib/actions/analytics';
import type { ComponentType } from 'svelte';
import { Link } from '$lib/elements';
@@ -115,7 +115,7 @@
</Layout.Stack>
</Table.Cell>
<Table.Cell>
{dayjs().to(installation.$updatedAt)}
<DualTimeView time={installation.$updatedAt} />
</Table.Cell>
<Table.Cell>
<Popover let:toggle padding="none" placement="bottom-end">
@@ -1,11 +1,10 @@
<script lang="ts">
import { Id } from '$lib/components';
import { timeFromNow } from '$lib/helpers/date';
import type { Column } from '$lib/helpers/types';
import type { Models } from '@appwrite.io/console';
import { Badge, Layout, Table, Typography } from '@appwrite.io/pink-svelte';
import Sheet from './sheet.svelte';
import { capitalize } from '$lib/helpers/string';
import DualTimeView from '$lib/components/dualTimeView.svelte';
export let columns: Column[];
export let logs: Models.ExecutionList;
@@ -38,7 +37,7 @@
{/key}
{:else if column.id === '$createdAt'}
<Table.Cell>
{capitalize(timeFromNow(log.$createdAt))}
<DualTimeView time={log.$createdAt} />
</Table.Cell>
{:else if column.id === 'responseStatusCode'}
<Table.Cell>
@@ -10,7 +10,6 @@
import { fileSearcher } from '$lib/commandCenter/searchers';
import { canWriteBuckets } from '$lib/stores/roles';
import { project } from '../../store';
import { showCreateFile } from './+page.svelte';
import { bucket } from './store';
import { IconKey, IconLockClosed, IconPlus, IconPuzzle } from '@appwrite.io/pink-icons-svelte';
@@ -1,9 +1,3 @@
<script lang="ts" context="module">
export const showCreateFile = () => {
wizard.start(Create);
};
</script>
<script lang="ts">
import { invalidate } from '$app/navigation';
import { base } from '$app/paths';
@@ -13,17 +7,16 @@
import { Dependencies } from '$lib/constants';
import { Pill } from '$lib/elements';
import { Button } from '$lib/elements/forms';
import { toLocaleDate } from '$lib/helpers/date';
import { calculateSize } from '$lib/helpers/sizeConvertion';
import { Container } from '$lib/layout';
import type { Models } from '@appwrite.io/console';
import { addNotification } from '$lib/stores/notifications';
import { uploader } from '$lib/stores/uploader';
import { wizard } from '$lib/stores/wizard';
import { sdk } from '$lib/stores/sdk.js';
import DeleteFile from './deleteFile.svelte';
import { Layout, Table, Icon, Popover, ActionMenu } from '@appwrite.io/pink-svelte';
import { onMount } from 'svelte';
import DualTimeView from '$lib/components/dualTimeView.svelte';
import {
IconDotsHorizontal,
IconPencil,
@@ -34,7 +27,6 @@
export let data;
let showDelete = false;
let showDropdown = [];
let selectedFile: Models.File = null;
const projectId = $page.params.project;
@@ -84,7 +76,8 @@
onMount(() => {
return uploader.subscribe(() => {
isUploading = $uploader.files.some(
(file) => !file.completed && file.progress < 100 && !file.failed
(file) =>
file.status !== 'success' && file.progress < 100 && file.status !== 'failed'
);
});
});
@@ -117,7 +110,7 @@
<Table.Header.Cell width="120px">Created</Table.Header.Cell>
<Table.Header.Cell width="40px" />
</svelte:fragment>
{#each data.files.files as file, index}
{#each data.files.files as file}
{#if file.chunksTotal / file.chunksUploaded !== 1}
<Table.Row>
<Table.Cell>
@@ -134,7 +127,7 @@
{calculateSize(file.sizeOriginal)}
</Table.Cell>
<Table.Cell>
{toLocaleDate(file.$createdAt)}
<DualTimeView time={file.$createdAt} />
</Table.Cell>
<Table.Cell>
<div class="u-flex u-main-center">
@@ -163,7 +156,7 @@
{calculateSize(file.sizeOriginal)}
</Table.Cell>
<Table.Cell>
{toLocaleDate(file.$createdAt)}
<DualTimeView time={file.$createdAt} />
</Table.Cell>
<Table.Cell>
<Popover let:toggle placement="bottom-start" padding="none">