mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
Revert "CSV export"
This commit is contained in:
+1
-1
@@ -95,5 +95,5 @@
|
||||
"svelte-preprocess"
|
||||
]
|
||||
},
|
||||
"packageManager": "pnpm@10.18.3"
|
||||
"packageManager": "pnpm@10.20.0"
|
||||
}
|
||||
|
||||
@@ -153,7 +153,6 @@ export enum Click {
|
||||
DatabaseTableDelete = 'click_table_delete',
|
||||
DatabaseDatabaseDelete = 'click_database_delete',
|
||||
DatabaseImportCsv = 'click_database_import_csv',
|
||||
DatabaseExportCsv = 'click_database_export_csv',
|
||||
DomainCreateClick = 'click_domain_create',
|
||||
DomainDeleteClick = 'click_domain_delete',
|
||||
DomainRetryDomainVerificationClick = 'click_domain_retry_domain_verification',
|
||||
@@ -278,7 +277,6 @@ export enum Submit {
|
||||
DatabaseDelete = 'submit_database_delete',
|
||||
DatabaseUpdateName = 'submit_database_update_name',
|
||||
DatabaseImportCsv = 'submit_database_import_csv',
|
||||
DatabaseExportCsv = 'submit_database_export_csv',
|
||||
|
||||
ColumnCreate = 'submit_column_create',
|
||||
ColumnUpdate = 'submit_column_update',
|
||||
|
||||
@@ -1,281 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { page } from '$app/state';
|
||||
import { realtime, sdk } from '$lib/stores/sdk';
|
||||
import { getProjectId } from '$lib/helpers/project';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { Layout, Typography, Code } from '@appwrite.io/pink-svelte';
|
||||
import { type Models, type Payload } from '@appwrite.io/console';
|
||||
import { Modal } from '$lib/components';
|
||||
import { Query } from '@appwrite.io/console';
|
||||
|
||||
type ExportItem = {
|
||||
status: string;
|
||||
table?: string;
|
||||
bucketId?: string;
|
||||
bucketName?: string;
|
||||
fileName?: string;
|
||||
downloadUrl?: string;
|
||||
errors?: string[];
|
||||
};
|
||||
|
||||
type ExportItemsMap = Map<string, ExportItem>;
|
||||
|
||||
let exportItems = $state<ExportItemsMap>(new Map());
|
||||
|
||||
function downloadExportedFile(downloadUrl: string) {
|
||||
if (!downloadUrl) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.open(downloadUrl, '_blank');
|
||||
}
|
||||
|
||||
async function showErrorNotification(payload: Payload) {
|
||||
let errorMessage = 'Export failed. Please try again.';
|
||||
try {
|
||||
const parsed = JSON.parse(payload.errors[0]);
|
||||
errorMessage = parsed?.message || errorMessage;
|
||||
} catch {
|
||||
errorMessage = payload.errors[0] || errorMessage;
|
||||
}
|
||||
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: errorMessage,
|
||||
isHtml: true,
|
||||
timeout: 10000
|
||||
});
|
||||
}
|
||||
|
||||
async function updateOrAddItem(exportData: Payload | Models.Migration) {
|
||||
if (exportData.destination?.toLowerCase() !== 'csv') return;
|
||||
|
||||
const status = exportData.status;
|
||||
const current = exportItems.get(exportData.$id);
|
||||
let tableName = current?.table;
|
||||
|
||||
// Get bucket, filename, and download URL from migration options
|
||||
const options = ('options' in exportData ? exportData.options : {}) || {};
|
||||
const bucketId = options.bucketId || '';
|
||||
const fileName = options.filename || '';
|
||||
const downloadUrl = options.downloadUrl || '';
|
||||
let bucketName = current?.bucketName;
|
||||
|
||||
const existing = exportItems.get(exportData.$id);
|
||||
|
||||
const isDone = (s: string) => ['completed', 'failed'].includes(s);
|
||||
const isInProgress = (s: string) => ['pending', 'processing'].includes(s);
|
||||
|
||||
// Skip if we're trying to set an in-progress status on a completed migration
|
||||
const shouldSkip = existing && isDone(existing.status) && isInProgress(status);
|
||||
|
||||
const hasNewData =
|
||||
downloadUrl && (!existing?.downloadUrl || existing.downloadUrl !== downloadUrl);
|
||||
const shouldSkipDuplicate = existing?.status === status && !hasNewData;
|
||||
|
||||
if (shouldSkip || shouldSkipDuplicate) return;
|
||||
|
||||
exportItems.set(exportData.$id, {
|
||||
status,
|
||||
table: tableName ?? current?.table,
|
||||
bucketId: bucketId,
|
||||
bucketName: bucketName,
|
||||
fileName: fileName,
|
||||
downloadUrl: downloadUrl,
|
||||
errors: exportData.errors || []
|
||||
});
|
||||
|
||||
exportItems = new Map(exportItems);
|
||||
|
||||
switch (status) {
|
||||
case 'completed':
|
||||
if (downloadUrl) {
|
||||
downloadExportedFile(downloadUrl);
|
||||
}
|
||||
break;
|
||||
case 'failed':
|
||||
await showErrorNotification(exportData);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function clear() {
|
||||
exportItems = new Map();
|
||||
}
|
||||
|
||||
function graphSize(status: string): number {
|
||||
switch (status) {
|
||||
case 'pending':
|
||||
return 10;
|
||||
case 'processing':
|
||||
return 60;
|
||||
case 'completed':
|
||||
case 'failed':
|
||||
return 100;
|
||||
default:
|
||||
return 30;
|
||||
}
|
||||
}
|
||||
|
||||
function text(status: string, tableName = '') {
|
||||
const table = tableName ? `<b>${tableName}</b>` : '';
|
||||
switch (status) {
|
||||
case 'completed':
|
||||
return `Exporting ${table} completed`;
|
||||
case 'failed':
|
||||
return `Exporting ${table} failed`;
|
||||
case 'processing':
|
||||
return `Exporting ${table}`;
|
||||
default:
|
||||
return 'Preparing export...';
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
sdk.forProject(page.params.region, page.params.project)
|
||||
.migrations.list({
|
||||
queries: [
|
||||
Query.equal('destination', 'CSV'),
|
||||
Query.equal('status', ['pending', 'processing'])
|
||||
]
|
||||
})
|
||||
.then((migrations) => {
|
||||
migrations.migrations.forEach(updateOrAddItem);
|
||||
});
|
||||
|
||||
return realtime.forConsole(page.params.region, 'console', (response) => {
|
||||
if (!response.channels.includes(`projects.${getProjectId()}`)) return;
|
||||
if (response.events.includes('migrations.*')) {
|
||||
updateOrAddItem(response.payload as Payload);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
let isOpen = $state(true);
|
||||
let showCsvExportBox = $derived(exportItems.size > 0);
|
||||
let showErrorModal = $state(false);
|
||||
let selectedErrors = $state<string[]>([]);
|
||||
</script>
|
||||
|
||||
{#if showCsvExportBox}
|
||||
<Layout.Stack direction="column" gap="l" alignItems="flex-end">
|
||||
<section class="upload-box">
|
||||
<header class="upload-box-header">
|
||||
<h4 class="upload-box-title">
|
||||
<Typography.Text variant="m-500">
|
||||
Exporting rows ({exportItems.size})
|
||||
</Typography.Text>
|
||||
</h4>
|
||||
<button
|
||||
class="upload-box-button"
|
||||
class:is-open={isOpen}
|
||||
aria-label="toggle upload box"
|
||||
onclick={() => (isOpen = !isOpen)}>
|
||||
<span class="icon-cheveron-up" aria-hidden="true"></span>
|
||||
</button>
|
||||
<button class="upload-box-button" aria-label="close export box" onclick={clear}>
|
||||
<span class="icon-x" aria-hidden="true"></span>
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<div class="upload-box-content-list">
|
||||
{#each [...exportItems.entries()] as [key, value] (key)}
|
||||
<div class="upload-box-content" class:is-open={isOpen}>
|
||||
<ul class="upload-box-list">
|
||||
<li class="upload-box-item">
|
||||
<section class="progress-bar u-width-full-line">
|
||||
<div
|
||||
class="progress-bar-top-line u-flex u-gap-8 u-main-space-between">
|
||||
<Typography.Text>
|
||||
{@html text(value.status, value.table)}
|
||||
</Typography.Text>
|
||||
{#if value.status === 'failed' && value.errors && value.errors.length > 0}
|
||||
<button
|
||||
class="link"
|
||||
type="button"
|
||||
onclick={() => {
|
||||
selectedErrors = value.errors;
|
||||
showErrorModal = true;
|
||||
}}>
|
||||
more details
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
<div
|
||||
class="progress-bar-container"
|
||||
class:is-danger={value.status === 'failed'}
|
||||
style="--graph-size:{graphSize(value.status)}%">
|
||||
</div>
|
||||
</section>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</section>
|
||||
</Layout.Stack>
|
||||
{/if}
|
||||
|
||||
<Modal bind:show={showErrorModal} title="Export error details" hideFooter>
|
||||
{#if selectedErrors.length > 0}
|
||||
<Code
|
||||
code={JSON.stringify(
|
||||
selectedErrors.map((err) => {
|
||||
try {
|
||||
return JSON.parse(err);
|
||||
} catch {
|
||||
return err;
|
||||
}
|
||||
}),
|
||||
null,
|
||||
2
|
||||
)}
|
||||
lang="json"
|
||||
hideHeader />
|
||||
{/if}
|
||||
</Modal>
|
||||
|
||||
<style lang="scss">
|
||||
.upload-box {
|
||||
display: flex;
|
||||
max-height: 320px;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.upload-box-header {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.upload-box-title {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.upload-box-content-list {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.upload-box-content {
|
||||
width: 304px;
|
||||
}
|
||||
|
||||
.upload-box-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.progress-bar-container {
|
||||
height: 4px;
|
||||
|
||||
&::before {
|
||||
height: 4px;
|
||||
background-color: var(--bgcolor-neutral-invert);
|
||||
}
|
||||
|
||||
&.is-danger::before {
|
||||
height: 4px;
|
||||
background-color: var(--bgcolor-error);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -13,7 +13,6 @@ export { default as Copy } from './copy.svelte';
|
||||
export { default as CopyInput } from './copyInput.svelte';
|
||||
export { default as UploadBox } from './uploadBox.svelte';
|
||||
export { default as BackupRestoreBox } from './backupRestoreBox.svelte';
|
||||
export { default as CsvExportBox } from './csvExportBox.svelte';
|
||||
export { default as List } from './list.svelte';
|
||||
export { default as ListItem } from './listItem.svelte';
|
||||
export { default as Empty } from './empty.svelte';
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
indeterminate?: boolean;
|
||||
size?: 's' | 'm';
|
||||
description?: string;
|
||||
truncate?: boolean;
|
||||
}
|
||||
|
||||
export let id: string = '';
|
||||
@@ -23,7 +22,6 @@
|
||||
export let element: HTMLInputElement | undefined = undefined;
|
||||
export let size: $$Props['size'] = 's';
|
||||
export let description = '';
|
||||
export let truncate: boolean = false;
|
||||
let error: string;
|
||||
|
||||
const handleInvalid = (event: Event) => {
|
||||
@@ -52,7 +50,6 @@
|
||||
{label}
|
||||
{required}
|
||||
{description}
|
||||
{truncate}
|
||||
on:invalid={handleInvalid}
|
||||
on:click
|
||||
on:change />
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { BackupRestoreBox, MigrationBox, UploadBox, CsvExportBox } from '$lib/components';
|
||||
import { BackupRestoreBox, MigrationBox, UploadBox } from '$lib/components';
|
||||
import { realtime } from '$lib/stores/sdk';
|
||||
import { onMount } from 'svelte';
|
||||
import { project, stats } from './store';
|
||||
@@ -119,7 +119,6 @@
|
||||
<MigrationBox />
|
||||
<BackupRestoreBox />
|
||||
<CsvImportBox />
|
||||
<CsvExportBox />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
||||
+22
-71
@@ -2,8 +2,6 @@
|
||||
import { Filters, hasPageQueries, queries } from '$lib/components/filters';
|
||||
import ViewSelector from '$lib/components/viewSelector.svelte';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { goto } from '$app/navigation';
|
||||
import { resolve } from '$app/paths';
|
||||
import type { Column, ColumnType } from '$lib/helpers/types';
|
||||
import { Container } from '$lib/layout';
|
||||
import { preferences } from '$lib/stores/preferences';
|
||||
@@ -34,9 +32,7 @@
|
||||
IconChevronUp,
|
||||
IconPlus,
|
||||
IconViewBoards,
|
||||
IconRefresh,
|
||||
IconUpload,
|
||||
IconDownload
|
||||
IconRefresh
|
||||
} from '@appwrite.io/pink-icons-svelte';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import EmptySheet from './layout/emptySheet.svelte';
|
||||
@@ -135,20 +131,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
function getTableExportUrl() {
|
||||
const queryParam = page.url.searchParams.get('query');
|
||||
const url = resolve(
|
||||
'/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export',
|
||||
{
|
||||
region: page.params.region,
|
||||
project: page.params.project,
|
||||
database: page.params.database,
|
||||
table: page.params.table
|
||||
}
|
||||
);
|
||||
return queryParam ? `${url}?query=${encodeURIComponent(queryParam)}` : url;
|
||||
}
|
||||
|
||||
onDestroy(() => ($showCreateColumnSheet.show = false));
|
||||
</script>
|
||||
|
||||
@@ -190,10 +172,17 @@
|
||||
justifyContent="flex-end"
|
||||
style="padding-right: 40px;">
|
||||
<Layout.Stack
|
||||
gap="s"
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
justifyContent="flex-end"
|
||||
gap="s">
|
||||
justifyContent="flex-end">
|
||||
<Button
|
||||
secondary
|
||||
event={Click.DatabaseImportCsv}
|
||||
disabled={!(hasColumns && hasValidColumns) || disableButton}
|
||||
on:click={() => (showImportCSV = true)}>
|
||||
Import CSV
|
||||
</Button>
|
||||
{#if !$isSmallViewport}
|
||||
<Button
|
||||
secondary
|
||||
@@ -204,37 +193,19 @@
|
||||
Create row
|
||||
</Button>
|
||||
|
||||
<Tooltip placement="top">
|
||||
<Button
|
||||
icon
|
||||
<Button
|
||||
icon
|
||||
size="s"
|
||||
secondary
|
||||
class="small-button-dimensions"
|
||||
on:click={() => {
|
||||
$expandTabs = !$expandTabs;
|
||||
preferences.setKey('tableHeaderExpanded', $expandTabs);
|
||||
}}>
|
||||
<Icon
|
||||
size="s"
|
||||
secondary
|
||||
class="small-button-dimensions"
|
||||
disabled={!(hasColumns && hasValidColumns) || disableButton}
|
||||
on:click={() => (showImportCSV = true)}>
|
||||
<Icon icon={IconUpload} size="s" />
|
||||
</Button>
|
||||
|
||||
<svelte:fragment slot="tooltip">Import CSV</svelte:fragment>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip placement="top">
|
||||
<Button
|
||||
icon
|
||||
size="s"
|
||||
secondary
|
||||
class="small-button-dimensions"
|
||||
disabled={!(hasColumns && hasValidColumns && data.rows.total) ||
|
||||
disableButton}
|
||||
on:click={() => {
|
||||
trackEvent(Click.DatabaseExportCsv);
|
||||
goto(getTableExportUrl());
|
||||
}}>
|
||||
<Icon icon={IconDownload} size="s" />
|
||||
</Button>
|
||||
|
||||
<svelte:fragment slot="tooltip">Export CSV</svelte:fragment>
|
||||
</Tooltip>
|
||||
icon={!$expandTabs ? IconChevronDown : IconChevronUp} />
|
||||
</Button>
|
||||
|
||||
<Tooltip disabled={isRefreshing || !data.rows.total} placement="top">
|
||||
<Button
|
||||
@@ -258,25 +229,6 @@
|
||||
|
||||
<svelte:fragment slot="tooltip">Refresh</svelte:fragment>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip placement="top">
|
||||
<Button
|
||||
icon
|
||||
size="s"
|
||||
secondary
|
||||
class="small-button-dimensions"
|
||||
on:click={() => {
|
||||
$expandTabs = !$expandTabs;
|
||||
preferences.setKey('tableHeaderExpanded', $expandTabs);
|
||||
}}>
|
||||
<Icon
|
||||
icon={!$expandTabs ? IconChevronDown : IconChevronUp}
|
||||
size="s" />
|
||||
</Button>
|
||||
|
||||
<svelte:fragment slot="tooltip"
|
||||
>{!$expandTabs ? 'Expand' : 'Collapse'}</svelte:fragment>
|
||||
</Tooltip>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
@@ -431,7 +383,6 @@
|
||||
|
||||
:global(.rotating) {
|
||||
animation: rotate 1s linear infinite;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
|
||||
-248
@@ -1,248 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { resolve } from '$app/paths';
|
||||
import { page } from '$app/state';
|
||||
import { goto } from '$app/navigation';
|
||||
import { Wizard } from '$lib/layout';
|
||||
import { Fieldset, Layout, Icon, Divider, Tooltip } from '@appwrite.io/pink-svelte';
|
||||
import { Button, InputSelect, InputCheckbox, Form } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { IconInfo } from '@appwrite.io/pink-icons-svelte';
|
||||
import { table } from '../store';
|
||||
import { queries, type TagValue } from '$lib/components/filters/store';
|
||||
import { TagList } from '$lib/components/filters';
|
||||
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
|
||||
import { toLocalDateTimeISO } from '$lib/helpers/date';
|
||||
import { writable } from 'svelte/store';
|
||||
import { isSmallViewport } from '$lib/stores/viewport';
|
||||
|
||||
let showExitModal = $state(false);
|
||||
let formComponent: Form;
|
||||
let isSubmitting = writable(false);
|
||||
|
||||
let localQueries = $state<Map<TagValue, string>>(new Map());
|
||||
const localTags = $derived(Array.from(localQueries.keys()));
|
||||
|
||||
const timestamp = toLocalDateTimeISO(Date.now())
|
||||
.replace(/[:.]/g, '-')
|
||||
.split('T')
|
||||
.join('_')
|
||||
.slice(0, -4);
|
||||
const filename = `${$table.name}_${timestamp}.csv`;
|
||||
|
||||
let selectedColumns = $state<Record<string, boolean>>({});
|
||||
let showAllColumns = $state(false);
|
||||
|
||||
type DelimiterOption = 'Comma' | 'Semicolon' | 'Tab' | 'Pipe';
|
||||
const delimiterMap: Record<DelimiterOption, string> = {
|
||||
Comma: ',',
|
||||
Semicolon: ';',
|
||||
Tab: '\t',
|
||||
Pipe: '|'
|
||||
};
|
||||
|
||||
let delimiter = $state<DelimiterOption>('Comma');
|
||||
let includeHeader = $state(true);
|
||||
let exportWithFilters = $state(false);
|
||||
|
||||
const columnLimit = $derived($isSmallViewport ? 6 : 9);
|
||||
const visibleColumns = $derived(
|
||||
showAllColumns ? $table.columns : $table.columns.slice(0, columnLimit)
|
||||
);
|
||||
const hasMoreColumns = $derived($table.columns.length > columnLimit);
|
||||
const selectedColumnCount = $derived(Object.values(selectedColumns).filter(Boolean).length);
|
||||
|
||||
const tableUrl = $derived.by(() => {
|
||||
const queryParam = page.url.searchParams.get('query');
|
||||
const url = resolve(
|
||||
'/(console)/project-[region]-[project]/databases/database-[database]/table-[table]',
|
||||
{
|
||||
region: page.params.region,
|
||||
project: page.params.project,
|
||||
database: page.params.database,
|
||||
table: page.params.table
|
||||
}
|
||||
);
|
||||
return queryParam ? `${url}?query=${queryParam}` : url;
|
||||
});
|
||||
|
||||
function removeLocalFilter(tag: TagValue) {
|
||||
localQueries.delete(tag);
|
||||
localQueries = new Map(localQueries);
|
||||
}
|
||||
|
||||
function initializeColumns() {
|
||||
selectedColumns = Object.fromEntries($table.columns.map((col) => [col.key, true]));
|
||||
}
|
||||
|
||||
function selectAllColumns() {
|
||||
selectedColumns = Object.fromEntries($table.columns.map((col) => [col.key, true]));
|
||||
}
|
||||
|
||||
function deselectAllColumns() {
|
||||
selectedColumns = Object.fromEntries($table.columns.map((col) => [col.key, false]));
|
||||
}
|
||||
|
||||
async function handleExport() {
|
||||
const selectedCols = Object.entries(selectedColumns)
|
||||
.filter(([_, selected]) => selected)
|
||||
.map(([key]) => key);
|
||||
|
||||
if (selectedCols.length === 0) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: 'Please select at least one column to export'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.migrations.createCSVExport({
|
||||
resourceId: `${page.params.database}:${page.params.table}`,
|
||||
filename: filename,
|
||||
columns: selectedCols,
|
||||
queries: exportWithFilters ? Array.from(localQueries.values()) : [],
|
||||
delimiter: delimiterMap[delimiter],
|
||||
header: includeHeader,
|
||||
notify: true
|
||||
});
|
||||
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'CSV export has started'
|
||||
});
|
||||
|
||||
trackEvent(Submit.DatabaseExportCsv);
|
||||
|
||||
await goto(tableUrl);
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
|
||||
trackError(error, Submit.DatabaseExportCsv);
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
initializeColumns();
|
||||
localQueries = new Map($queries);
|
||||
});
|
||||
</script>
|
||||
|
||||
<Wizard title="Export CSV" columnSize="s" href={tableUrl} bind:showExitModal confirmExit column>
|
||||
<Form bind:this={formComponent} bind:isSubmitting onSubmit={handleExport}>
|
||||
<Layout.Stack gap="xxl">
|
||||
<Fieldset legend="Columns">
|
||||
<Layout.Stack gap="l">
|
||||
<Layout.Stack direction="row" gap="s" alignItems="center">
|
||||
<Button compact on:click={selectAllColumns}>Select all</Button>
|
||||
<span style:height="20px">
|
||||
<Divider vertical />
|
||||
</span>
|
||||
<Button compact on:click={deselectAllColumns}>Deselect all</Button>
|
||||
</Layout.Stack>
|
||||
|
||||
<Layout.Grid columns={3} columnsS={1} gap="l">
|
||||
{#each visibleColumns as column (column.key)}
|
||||
<div style="min-width: 0;">
|
||||
<InputCheckbox
|
||||
id={`column-${column.key}`}
|
||||
label={column.key}
|
||||
bind:checked={selectedColumns[column.key]}
|
||||
truncate />
|
||||
</div>
|
||||
{/each}
|
||||
</Layout.Grid>
|
||||
|
||||
{#if hasMoreColumns}
|
||||
<div style:margin-bottom="-0.5rem">
|
||||
<Button compact on:click={() => (showAllColumns = !showAllColumns)}>
|
||||
{showAllColumns ? 'Show less' : 'Show more'}
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</Fieldset>
|
||||
|
||||
<Fieldset legend="Export options">
|
||||
<Layout.Stack gap="l">
|
||||
<InputSelect
|
||||
id="delimiter"
|
||||
label="Delimiter"
|
||||
bind:value={delimiter}
|
||||
options={[
|
||||
{ value: 'Comma', label: 'Comma' },
|
||||
{ value: 'Semicolon', label: 'Semicolon' },
|
||||
{ value: 'Tab', label: 'Tab' },
|
||||
{ value: 'Pipe', label: 'Pipe' }
|
||||
]}>
|
||||
<Layout.Stack direction="row" gap="none" alignItems="center" slot="info">
|
||||
<Tooltip>
|
||||
<Icon size="s" icon={IconInfo} />
|
||||
<span slot="tooltip">
|
||||
Define how to separate values in the exported file.
|
||||
</span>
|
||||
</Tooltip>
|
||||
</Layout.Stack>
|
||||
</InputSelect>
|
||||
|
||||
<InputCheckbox
|
||||
id="includeHeader"
|
||||
label="Include header row"
|
||||
description="Column names will be added as the first row in the CSV"
|
||||
bind:checked={includeHeader} />
|
||||
|
||||
<Layout.Stack gap="m">
|
||||
<div class:disabled-checkbox={localTags.length === 0}>
|
||||
<InputCheckbox
|
||||
id="exportWithFilters"
|
||||
label="Export with filters"
|
||||
description="Export rows that match the current table filters"
|
||||
disabled={localTags.length === 0}
|
||||
bind:checked={exportWithFilters} />
|
||||
</div>
|
||||
|
||||
{#if localTags.length > 0}
|
||||
<Layout.Stack
|
||||
direction="row"
|
||||
gap="xs"
|
||||
alignItems="center"
|
||||
style="padding-left: 1.75rem;"
|
||||
wrap="wrap">
|
||||
<TagList
|
||||
tags={localTags}
|
||||
on:remove={(e) => {
|
||||
removeLocalFilter(e.detail);
|
||||
}} />
|
||||
</Layout.Stack>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
</Fieldset>
|
||||
</Layout.Stack>
|
||||
</Form>
|
||||
<svelte:fragment slot="footer">
|
||||
<Layout.Stack justifyContent="flex-end" direction="row">
|
||||
<Button fullWidthMobile secondary on:click={() => (showExitModal = true)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
fullWidthMobile
|
||||
on:click={() => formComponent.triggerSubmit()}
|
||||
disabled={$isSubmitting || selectedColumnCount === 0}>
|
||||
Export
|
||||
</Button>
|
||||
</Layout.Stack>
|
||||
</svelte:fragment>
|
||||
</Wizard>
|
||||
|
||||
<style>
|
||||
.disabled-checkbox :global(*) {
|
||||
cursor: unset;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user