diff --git a/src/lib/components/archiveProject.svelte b/src/lib/components/archiveProject.svelte index 3ebb938c1..c7896c991 100644 --- a/src/lib/components/archiveProject.svelte +++ b/src/lib/components/archiveProject.svelte @@ -1,6 +1,7 @@ -
+
- + {@render children?.()}
@@ -90,6 +112,12 @@ } } + &.paddingInlineEndDouble { + @media (min-width: 1024px) { + padding-inline-end: calc(2 * 2.75rem) !important; + } + } + &.databasesScreen { @media (min-width: 1440px) { min-width: 1070px; @@ -102,6 +130,12 @@ } } + &.databasesMainScreen { + @media (min-width: 1440px) { + max-width: 1200px; + } + } + @media (min-width: 360px) { margin-inline: 1rem; } diff --git a/src/lib/layout/cover.svelte b/src/lib/layout/cover.svelte index aa3f227ce..2018e2b6f 100644 --- a/src/lib/layout/cover.svelte +++ b/src/lib/layout/cover.svelte @@ -7,6 +7,7 @@ export let expanded: boolean = false; export let animate: boolean = false; export let collapsed: boolean = false; + export let databasesMainScreen: boolean = false; let isAnimating = false; let animationTimeout: ReturnType; @@ -43,6 +44,7 @@ class="cover-container" {style} class:expanded + class:databasesMainScreen class:collapsed={animate && collapsed} class:animating={isAnimating}> @@ -119,6 +121,12 @@ &.animating { transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1); } + + &.databasesMainScreen { + @media (min-width: 1440px) { + max-width: 1200px; + } + } } .expanded-slot { diff --git a/src/lib/layout/shell.svelte b/src/lib/layout/shell.svelte index 79220304b..bfc06264f 100644 --- a/src/lib/layout/shell.svelte +++ b/src/lib/layout/shell.svelte @@ -18,6 +18,7 @@ import { page } from '$app/stores'; import type { Models } from '@appwrite.io/console'; import { getSidebarState, isInDatabasesRoute, updateSidebarState } from '$lib/helpers/sidebar'; + import { isTabletViewport } from '$lib/stores/viewport'; export let showHeader = true; export let showFooter = true; @@ -100,8 +101,15 @@ * * This needs to be handled like this because * the setup around the sidebar is very tightly configured with 2 states sync. + * + * The sidebar is **always closed** on mobile and tablet devices! */ afterNavigate((navigation) => { + if ($isTabletViewport) { + state = 'closed'; + return; + } + const isEnteringDatabase = isInDatabasesRoute(navigation.to.route); const isLeavingDatabase = isInDatabasesRoute(navigation.from.route) && !isInDatabasesRoute(navigation.to.route); diff --git a/src/routes/(console)/bottomAlerts.ts b/src/routes/(console)/bottomAlerts.ts index c8529ee42..224957e62 100644 --- a/src/routes/(console)/bottomAlerts.ts +++ b/src/routes/(console)/bottomAlerts.ts @@ -3,8 +3,8 @@ import { isSameDay } from '$lib/helpers/date'; import { type BottomModalAlertItem, showBottomModalAlert } from '$lib/stores/bottom-alerts'; import TablesApiLight from '$lib/images/promos/tables-api-light.png'; import TablesApiDark from '$lib/images/promos/tables-api-dark.png'; -import TimestampOverridesDark from '$lib/images/promos/timestamp-overrides-dark.png'; -import TimestampOverridesLight from '$lib/images/promos/timestamp-overrides-light.png'; +import TimeHelperQueriesDark from '$lib/images/promos/time-helper-queries-dark.png'; +import TimeHelperQueriesLight from '$lib/images/promos/time-helper-queries-light.png'; import OptInRelationDark from '$lib/images/promos/opt-relation-dark.png'; import OptInRelationLight from '$lib/images/promos/opt-relation-light.png'; @@ -32,21 +32,20 @@ if (isCloud) { show: true }; - const timestampOverridesPromo: BottomModalAlertItem = { - id: 'modal:timestamp_overrides_announcement', + const timeHelperQueriesPromo: BottomModalAlertItem = { + id: 'modal:time_helper_queries_announcement', src: { - dark: TimestampOverridesDark, - light: TimestampOverridesLight + dark: TimeHelperQueriesDark, + light: TimeHelperQueriesLight }, - title: 'Announcing Timestamp Overrides', - message: - 'Move historical data into Appwrite without losing context or disrupting chronological accuracy.', + title: 'Announcing Time helper queries', + message: 'New before/after filters for simpler time-based queries.', plan: 'free', importance: 8, scope: 'project', cta: { text: 'Read announcement', - link: () => 'https://appwrite.io/blog/post/announcing-timestamp-overrides', + link: () => 'https://appwrite.io/blog/post/announcing-time-helper-queries', external: true, hideOnClick: true }, @@ -73,7 +72,7 @@ if (isCloud) { }, show: true }; - listOfPromotions.push(optInRelationPromo, tablesApiPromo, timestampOverridesPromo); + listOfPromotions.push(timeHelperQueriesPromo, optInRelationPromo, tablesApiPromo); } export function addBottomModalAlerts() { diff --git a/src/routes/(console)/organization-[organization]/+page.svelte b/src/routes/(console)/organization-[organization]/+page.svelte index b8e112d45..69a84cd8a 100644 --- a/src/routes/(console)/organization-[organization]/+page.svelte +++ b/src/routes/(console)/organization-[organization]/+page.svelte @@ -81,7 +81,7 @@ $: projectCreationDisabled = (isCloud && getServiceLimit('projects') <= data.allProjectsCount) || - ($readOnly && !GRACE_PERIOD_OVERRIDE) || + (isCloud && $readOnly && !GRACE_PERIOD_OVERRIDE) || !$canWriteProjects; $: $registerCommands([ @@ -106,12 +106,14 @@ function isSetToArchive(project: Models.Project): boolean { if (!isCloud) return false; if (!project || !project.$id) return false; - return project.status !== 'active'; + return project.status === 'archived'; } - $: projectsToArchive = data.projects.projects.filter((project) => project.status !== 'active'); + $: projectsToArchive = isCloud + ? data.projects.projects.filter((project) => project.status === 'archived') + : []; - $: activeProjects = data.projects.projects.filter((project) => project.status === 'active'); + $: activeProjects = data.projects.projects.filter((project) => project.status !== 'archived'); function clearSearch() { searchQuery?.clearInput(); } diff --git a/src/routes/(console)/organization-[organization]/billing/paymentHistory.svelte b/src/routes/(console)/organization-[organization]/billing/paymentHistory.svelte index 078990471..e6c787b0d 100644 --- a/src/routes/(console)/organization-[organization]/billing/paymentHistory.svelte +++ b/src/routes/(console)/organization-[organization]/billing/paymentHistory.svelte @@ -1,5 +1,6 @@ @@ -86,11 +95,11 @@ Due date Status Amount due - + {#if isLoadingInvoices} - {#each Array.from({ length: 2 }).keys() as index (index)} + {#each Array.from({ length: 5 }).keys() as index (index)} {#each columns as column} @@ -99,91 +108,95 @@ {/each} {/each} - {/if} - - {#each invoiceList?.invoices as invoice} - {@const status = invoice.status} - - - {toLocaleDate(invoice.dueAt)} - - - {@const isDanger = - status === 'overdue' || - status === 'failed' || - status === 'requires_authentication'} - {@const isSuccess = status === 'paid' || status === 'succeeded'} - {@const isWarning = status === 'pending'} - - - {#if invoice?.lastError} - - Details - - The scheduled payment has failed. - retryPayment(invoice)} - >Try again - - - - {/if} - - - - {formatCurrency(invoice.grossAmount)} - - - - - - - - View invoice - - - Download PDF - - {#if status === 'overdue' || status === 'failed' || status === 'abandoned'} - { - retryPayment(invoice); - trackEvent(`click_retry_payment`, { - from: 'button', - source: 'billing_invoice_menu' - }); - }}> - Retry payment - + {:else} + {#each invoiceList?.invoices as invoice (invoice.$id)} + {@const status = invoice.status} + + {toLocaleDate(invoice.dueAt)} + + {@const isDanger = + status === 'overdue' || + status === 'failed' || + status === 'requires_authentication'} + {@const isSuccess = status === 'paid' || status === 'succeeded'} + {@const isWarning = status === 'pending'} + + + {#if invoice?.lastError} + + Details + + The scheduled payment has failed. + retryPayment(invoice)} + >Try again + + + {/if} - - - - - {/each} + + + + {formatCurrency(invoice.grossAmount)} + + + + + + + + View invoice + + + Download PDF + + {#if status === 'overdue' || status === 'failed' || status === 'abandoned'} + { + retryPayment(invoice); + trackEvent(`click_retry_payment`, { + from: 'button', + source: 'billing_invoice_menu' + }); + }}> + Retry payment + + {/if} + + + + + {/each} + {/if} - {#if invoiceList.total > limit} + {#if invoiceList.total >= limit}

Total results: {invoiceList.total}

- + request()} />
{/if} {:else} diff --git a/src/routes/(console)/organization-[organization]/domains/recordsCard.svelte b/src/routes/(console)/organization-[organization]/domains/recordsCard.svelte index 9c16ed14c..6d3d8b41a 100644 --- a/src/routes/(console)/organization-[organization]/domains/recordsCard.svelte +++ b/src/routes/(console)/organization-[organization]/domains/recordsCard.svelte @@ -84,8 +84,8 @@ {/if} - Add the following nameservers on your DNS provider. Note that DNS changes may take - time to propagate fully. + Add the following nameservers on your DNS provider. Note that changes may take up to + 48 hours to propagate fully. diff --git a/src/routes/(console)/organization-[organization]/domains/retryDomainModal.svelte b/src/routes/(console)/organization-[organization]/domains/retryDomainModal.svelte index e6a4a67c6..de237930a 100644 --- a/src/routes/(console)/organization-[organization]/domains/retryDomainModal.svelte +++ b/src/routes/(console)/organization-[organization]/domains/retryDomainModal.svelte @@ -64,8 +64,8 @@ >{selectedDomain.domain} - Add the following nameservers on your DNS provider. Note that DNS changes may take time - to propagate fully. + Add the following nameservers on your DNS provider. Note that changes may take up to 48 + hours to propagate fully. diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/+page.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/+page.svelte index be123b600..0726e2165 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/+page.svelte @@ -18,7 +18,7 @@ const databaseId = page.params.database; - + diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/+page.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/+page.svelte index ef597992c..da868ba29 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/+page.svelte @@ -177,7 +177,7 @@ }); - +
{#if !isDisabled}
diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/header.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/header.svelte index 7b18c985c..2f68d4544 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/header.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/header.svelte @@ -38,7 +38,7 @@ ].filter((tab) => !tab.disabled); - + {$database.name} diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/settings/+page.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/settings/+page.svelte index 65e6c2b07..00c09144e 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/settings/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/settings/+page.svelte @@ -61,7 +61,7 @@ {#if $database} - + {$database.name} diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/subNavigation.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/subNavigation.svelte index 26ee8f530..1cc5f7972 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/subNavigation.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/subNavigation.svelte @@ -134,7 +134,7 @@
-{:else} +{:else if data?.database?.name}
diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+page.ts b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+page.ts index aa0de01a2..31d33e029 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+page.ts +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+page.ts @@ -30,6 +30,7 @@ export const load: PageLoad = async ({ params, depends, url, route, parent }) => view, query, currentSort, + parsedQueries, rows: await sdk.forProject(params.region, params.project).tablesDB.listRows({ databaseId: params.database, tableId: params.table, diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/cell/edit.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/cell/edit.svelte index e50ca76c6..4f1689ea3 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/cell/edit.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/cell/edit.svelte @@ -10,11 +10,13 @@ row = $bindable(null), onChange = null, onRevert = null, + noInlineEdit = false, openSideSheet = null, onRowStructureUpdate = null }: { row: Models.Row; column: Columns; + noInlineEdit?: boolean; openSideSheet?: () => void; onChange?: (row: Models.DefaultRow) => void; onRevert?: (row: Models.DefaultRow) => void; @@ -27,6 +29,11 @@ onMount(() => { original = structuredClone(row); + if (noInlineEdit) { + openSideSheet?.(); + return; + } + const trigger = wrapperEl.querySelector('button.input') as HTMLButtonElement; if (trigger) { trigger.click(); @@ -62,5 +69,5 @@ fromSpreadsheet label={undefined} bind:formValues={row} - on:click={openSideSheet} /> + on:click={() => openSideSheet?.()} />
diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/columns/types/integer.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/columns/types/integer.svelte index e8da1e99e..23e19cd3b 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/columns/types/integer.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/columns/types/integer.svelte @@ -15,7 +15,7 @@ label = undefined; } - $: if (limited) { + $: { column.min = isWithinSafeRange(column.min) ? column.min : Number.MIN_SAFE_INTEGER; column.max = isWithinSafeRange(column.max) ? column.max : Number.MAX_SAFE_INTEGER; } diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/store.ts b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/store.ts index 97e6694c5..76ffa95ca 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/store.ts +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/store.ts @@ -2,8 +2,12 @@ import { page } from '$app/state'; import type { Columns } from '../store'; import { type Models, Query } from '@appwrite.io/console'; -export function isRelationshipToMany(column: Models.ColumnRelationship) { - if (!column) return false; +export function isRelationshipToMany(col: Columns) { + if (!col) return false; + if (!isRelationship(col)) return false; + + const column = col as Models.ColumnRelationship; + if (!column?.relationType) return false; if (column?.side === 'child') { return !['oneToOne', 'oneToMany'].includes(column?.relationType); diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/spreadsheet.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/spreadsheet.svelte index ee2e82dd9..5db4b40e6 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/spreadsheet.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/spreadsheet.svelte @@ -87,6 +87,7 @@ import { hash } from '$lib/helpers/string'; import { formatNumberWithCommas } from '$lib/helpers/numbers'; import { chunks } from '$lib/helpers/array'; + import { mapToQueryParams } from '$lib/components/filters/store'; export let data: PageData; export let showRowCreateSheet: { @@ -308,26 +309,31 @@ async function sort(query: string | null) { $spreadsheetLoading = true; - const url = new URL(page.url); + const parsedQueries = data.parsedQueries; - if (query === null) { + if (parsedQueries.size > 0) { + for (const [tagValue, queryString] of parsedQueries.entries()) { + if (queryString.includes('orderAsc') || queryString.includes('orderDesc')) { + parsedQueries.delete(tagValue); + } + } + } + + if (query !== null) { + const { attribute, method } = JSON.parse(query); + const tagValue = { + tag: `${attribute} ${method}`, + value: attribute + }; + + parsedQueries.set(tagValue, query); + } + + if (parsedQueries.size === 0) { url.searchParams.delete('query'); } else { - // compatible with `load` func! - const { attribute, method } = JSON.parse(query); - url.searchParams.set( - 'query', - JSON.stringify([ - [ - { - tag: `${attribute} ${method}`, - value: attribute - }, - query - ] - ]) - ); + url.searchParams.set('query', mapToQueryParams(parsedQueries)); } // save > navigate > restore! @@ -459,6 +465,12 @@ } } + function openSideSheetForRelationsToMany(tableId: string, rows: string | Models.Row[]) { + $databaseRelatedRowSheetOptions.tableId = tableId; + $databaseRelatedRowSheetOptions.rows = rows; + $databaseRelatedRowSheetOptions.show = true; + } + async function onSelectSheetOption( action: HeaderCellAction | RowCellAction, columnId: string, @@ -605,6 +617,9 @@ return false; } + const parsedQueries = data.parsedQueries; + const filterQueries = parsedQueries.size ? data.parsedQueries.values() : []; + $paginatedRowsLoading = true; const loadedRows = await sdk .forProject(page.params.region, page.params.project) @@ -615,6 +630,7 @@ getCorrectOrderQuery(), Query.limit(SPREADSHEET_PAGE_LIMIT), Query.offset(pageToOffset(pageNumber, SPREADSHEET_PAGE_LIMIT)), + ...filterQueries /* filter queries */, ...buildWildcardColumnsQuery($table) ] }); @@ -885,18 +901,10 @@ {/if} {:else} {@const itemsNum = row[columnId]?.length} - { - $databaseRelatedRowSheetOptions.show = true; - $databaseRelatedRowSheetOptions.rows = - row[columnId]; - $databaseRelatedRowSheetOptions.tableId = columnId; - }}> - Items - + Items {/if} {:else} {@const value = row[columnId]} @@ -946,11 +954,20 @@ {row} column={rowColumn} onRowStructureUpdate={updateRowContents} + noInlineEdit={isRelationshipToMany(rowColumn)} onChange={(row) => paginatedRows.update(index, row)} onRevert={(row) => paginatedRows.update(index, row)} openSideSheet={() => { close(); /* closes the editor */ - onSelectSheetOption('update', null, 'row', row); + + if (isRelationshipToMany(rowColumn)) { + openSideSheetForRelationsToMany( + columnId, + row[columnId] + ); + } else { + onSelectSheetOption('update', null, 'row', row); + } }} /> diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/usage/[[period]]/+page.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/usage/[[period]]/+page.svelte index 8ae2d9073..84c6ff2b7 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/usage/[[period]]/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/usage/[[period]]/+page.svelte @@ -1,14 +1,8 @@ - - - - - - + + diff --git a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/domains/recordsCard.svelte b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/domains/recordsCard.svelte index 2ba91a968..07a2da2c4 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/domains/recordsCard.svelte +++ b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/domains/recordsCard.svelte @@ -25,8 +25,8 @@ - Add the following nameservers on your DNS provider. Note that DNS changes may - take time to propagate fully. + Add the following nameservers on your DNS provider. Note that changes may take + up to 48 hours to propagate fully.