Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions src/lib/components/buttons/DownloadDataButton.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,19 @@


let {
downloadData
downloadData,
disabled = false,
title = ''
}: {
downloadData: () => void|Promise<void>;
disabled?: boolean;
title?: string;
} = $props();

let isLoading = $state(false);

async function handleDownloadData(){
if (isLoading || disabled) return;
isLoading = true;
try {
await downloadData();
Expand All @@ -23,7 +28,7 @@

</script>

<Button onclick={handleDownloadData}>
<Button onclick={handleDownloadData} disabled={isLoading || disabled} {title}>
{#if isLoading}
<LoadingIcon class="animate-spin" />
Downloading...
Expand Down
8 changes: 6 additions & 2 deletions src/lib/components/buttons/VisualiseDataButton.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,14 @@
visualiseTable,
visualiseChart,
visualiseMap,
disabled = false,
title = ''
}: {
visualiseTable: () => void|Promise<void>;
visualiseChart: () => void|Promise<void>;
visualiseMap: () => void|Promise<void>;
disabled?: boolean;
title?: string;
} = $props();

let isLoading = $state(false);
Expand All @@ -34,8 +38,8 @@


<DropdownMenu.Root>
<DropdownMenu.Trigger disabled={isLoading}>
<Button disabled={isLoading}>
<DropdownMenu.Trigger disabled={isLoading || disabled}>
<Button disabled={isLoading || disabled} {title}>
{#if isLoading}
<LoadingIcon class="animate-spin" />
Executing...
Expand Down
23 changes: 21 additions & 2 deletions src/lib/components/query-builder/QueryActionBar.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,26 @@
import UrlIcon from '@lucide/svelte/icons/link-2';
import InfoIcon from '@lucide/svelte/icons/info';
import PencilIcon from '@lucide/svelte/icons/pencil';
import { settings } from '@/stores/settings';



let {
queryActions
}: { queryActions: QueryActions } = $props();

/**
* The reason that the active query must not run, or null.
*
* The switch is read here as well. `runBlockReason` reads a snapshot of the
* settings, so without this read the bar keeps its state after the user turns
* the safeguard off on the settings page.
*/
const blockReason = $derived.by(() => {
if (!$settings.requireQueryFilters) return null;
return queryActions.runBlockReason?.() ?? null;
});

let showingCacheInfoModal = $state(false);

function showCacheInfoModal(): void {
Expand All @@ -46,18 +59,24 @@

<div class="query-action-group">

<DownloadDataButton downloadData={queryActions.downloadData} />
<DownloadDataButton
downloadData={queryActions.downloadData}
disabled={!!blockReason}
title={blockReason ?? ''}
/>

{#if queryActions.editQuery}
<Button onclick={queryActions.editQuery} title="Edit query">
<PencilIcon />
Edit Query
</Button>
{:else}
<VisualiseDataButton
<VisualiseDataButton
visualiseTable={queryActions.visualiseTable}
visualiseChart={queryActions.visualiseChart}
visualiseMap={queryActions.visualiseMap}
disabled={!!blockReason}
title={blockReason ?? ''}
/>
{/if}

Expand Down
33 changes: 32 additions & 1 deletion src/lib/components/query-builder/QueryActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { addToast } from '@/stores/toasts';
import { saveQueryFrom } from '@/stores/saved-queries';
import { goto } from '$app/navigation';
import { resolve } from '$app/paths';
import { runBlockReason } from '@/query/query-guard';

export type ActionCallback = (() => void | Promise<void>) | undefined;

Expand All @@ -31,6 +32,14 @@ export type QueryActions = {
* See `buildShareLink`.
*/
getInstanceRef?: () => InstanceRef | null;
/**
* The reason that the active query must not run, or null. The action bar
* disables the run and download buttons with it, and names the reason.
*
* The value is a snapshot. A component that must react to a change of the
* safeguard switch reads `$settings` beside this call.
*/
runBlockReason?: () => string | null;
};

/**
Expand All @@ -55,6 +64,23 @@ export function getDefaultQueryActions(workspace: QueryWorkspace): QueryActions
return workspace.activeBlock?.instance ?? null;
}

/** The reason that the active query must not run. See {@link runBlockReason}. */
function activeRunBlockReason(): string | null {
return runBlockReason(QueryWorkspace.getQuery(workspace.activeBlock));
}

/**
* True when the safeguard stops this query. The caller must not talk to the
* node. A query with no filter reads a whole table.
*/
function isBlocked(query: CompiledQuery): boolean {
const reason = runBlockReason(query);
if (!reason) return false;

addToast({ message: reason, type: 'warning' });
return true;
}

/**
* The node of the active block, or null with a toast. Every action that talks
* to a node starts here. A missing node is a normal state: the block can come
Expand Down Expand Up @@ -88,6 +114,8 @@ export function getDefaultQueryActions(workspace: QueryWorkspace): QueryActions
return null;
}

if (isBlocked(query)) return null;

const instance = requireInstance();
if (!instance) return null;

Expand Down Expand Up @@ -133,6 +161,8 @@ export function getDefaultQueryActions(workspace: QueryWorkspace): QueryActions
return;
}

if (isBlocked(query)) return;

const instance = requireInstance();
if (!instance) return;

Expand Down Expand Up @@ -207,7 +237,8 @@ export function getDefaultQueryActions(workspace: QueryWorkspace): QueryActions
resetQuery,
saveQuery,
getInstance,
getInstanceRef
getInstanceRef,
runBlockReason: activeRunBlockReason
};
}

Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,12 @@
import { addToast } from '@/stores/toasts';
import type { QuerySelectionStatus } from '@/query/selection-status';
import type { QueryActions } from './QueryActions';
import { defaultOutputFormat, type QueryDraft } from '@/query/draft';
import { compileDraft, defaultOutputFormat, type QueryDraft } from '@/query/draft';
import MapPinnedIcon from '@lucide/svelte/icons/map-pinned';
import TriangleAlertIcon from '@lucide/svelte/icons/triangle-alert';
import XIcon from '@lucide/svelte/icons/x';
import { runBlockReason } from '@/query/query-guard';
import { settings } from '@/stores/settings';
import { describeSelection, type SpatialSelection } from '@/geo/spatial-selection';
import { hydrateDraftFromQuery } from '@/query/seed-hydration';

Expand Down Expand Up @@ -92,6 +95,27 @@
let lastEmittedDraftKey = $state('');
let lastReceivedDraftKey = $state(initialDraft ? JSON.stringify(initialDraft) : '');

/**
* The reason that this query must not run, or null. It compiles the draft,
* because a filter with no value compiles to nothing.
*
* The switch is read here as well. `runBlockReason` reads a snapshot of the
* settings, so without this read the warning stays after the user turns the
* safeguard off on the settings page.
*/
const filterWarning = $derived.by(() => {
if (!$settings.requireQueryFilters) return null;

return runBlockReason(
compileDraft({
tableName: table_name,
selectedFields,
outputFormat: selected_output_format,
spatialFilter
})
);
});

const firstVisibleItem = $derived(fields.find((item) => !(item.ref as {hidden: boolean})?.hidden));

const firstVisibleMatchingItem = $derived.by(() => {
Expand Down Expand Up @@ -406,6 +430,13 @@
</Dialog.Content>
</Dialog.Root>

{#if filterWarning}
<p class="filter-warning">
<TriangleAlertIcon size={16} />
{filterWarning}
</p>
{/if}

{#if spatialFilter}
<div class="area-filter">
<MapPinnedIcon size={16} />
Expand Down Expand Up @@ -435,6 +466,22 @@

<style lang="scss">

.filter-warning {
display: flex;
align-items: center;
gap: 0.5rem;
margin: 0.5rem 0 0;
padding: 0.5rem 0.75rem;
border: 1px solid var(--destructive);
border-radius: var(--radius, 0.5rem);
font-size: 0.85rem;
color: var(--destructive);

:global(svg) {
flex-shrink: 0;
}
}

.area-filter {
display: flex;
flex-direction: row;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ add new query blocks, duplicate blocks, close clocks, select active blocks
// import CircleDashedIcon from '@lucide/svelte/icons/circle-dashed';
import { QueryWorkspace } from './QueryWorkspace.svelte';
import type { StoredQuery } from '@/stores/stored-query';
import { runBlockReason } from '@/query/query-guard';
import { settings } from '@/stores/settings';

// All state lives in the workspace; this component only reads/acts on it.
let {
Expand All @@ -31,6 +33,20 @@ add new query blocks, duplicate blocks, close clocks, select active blocks
} = $props();

const COLUMN_PREVIEW_LIMIT = 3;

/**
* The reason that a block must not run, or null. The card shows a warning
* triangle with this text.
*
* The switch is read here as well. `runBlockReason` reads a snapshot of the
* settings, so without this read the cards keep the triangle after the user
* turns the safeguard off on the settings page.
*/
function blockReasonFor(block: StoredQuery): string | null {
if (!$settings.requireQueryFilters) return null;
return runBlockReason(QueryWorkspace.getQuery(block));
}

let editingBlockId: string | null = $state(null);
let editingName = $state('');
let isCommittingFromKeyboard: boolean = $state(false);
Expand Down Expand Up @@ -138,6 +154,7 @@ add new query blocks, duplicate blocks, close clocks, select active blocks
-->
{@const instance = workspace.instanceFor(block)}
{@const missingUrl = workspace.missingInstanceUrlFor(block)}
{@const blockReason = blockReasonFor(block)}
<div

class="query-block-wrapper"
Expand Down Expand Up @@ -256,7 +273,14 @@ add new query blocks, duplicate blocks, close clocks, select active blocks
<span class="query-stat" title="Amount of selected columns">
{status.columns} columns
</span>
<span class="query-stat" title="Amount of applied filters">
<span
class="query-stat filter-stat"
class:missing={blockReason}
title={blockReason ?? 'Amount of applied filters'}
>
{#if blockReason}
<TriangleAlertIcon size="0.75rem" />
{/if}
{status.filters}
{status.filters == 1 ? 'filter' : 'filters'}
Comment on lines +281 to 285
</span>
Expand Down Expand Up @@ -344,6 +368,17 @@ add new query blocks, duplicate blocks, close clocks, select active blocks
}
}

// The filter count of the block. The triangle sits on the text baseline.
.filter-stat {
display: inline-flex;
align-items: center;
gap: 0.25rem;

&.missing {
color: IndianRed;
}
}

// The node of the block. The dot and the icon sit on the text baseline.
.instance-stat {
display: inline-flex;
Expand Down
Loading