From 8a36f5c4f28b6a1e7f9ba898ceea28da067d2604 Mon Sep 17 00:00:00 2001 From: Jonas B Date: Tue, 14 May 2024 18:52:05 +0200 Subject: [PATCH 01/32] feat: clean up state and separate it --- packages/safe-ds-eda/src/App.svelte | 6 +- packages/safe-ds-eda/src/apis/historyApi.ts | 109 ++++++-------- .../safe-ds-eda/src/components/Sidebar.svelte | 10 +- .../src/components/TableView.svelte | 136 +++++++++--------- .../src/components/tabs/TabContent.svelte | 4 +- packages/safe-ds-eda/src/webviewState.ts | 59 ++++---- packages/safe-ds-eda/types/messaging.ts | 10 +- packages/safe-ds-eda/types/state.ts | 23 ++- .../src/extension/eda/apis/runnerApi.ts | 74 ++++------ .../src/extension/eda/edaPanel.ts | 43 ++---- 10 files changed, 203 insertions(+), 271 deletions(-) diff --git a/packages/safe-ds-eda/src/App.svelte b/packages/safe-ds-eda/src/App.svelte index b436865b1..9fb008372 100644 --- a/packages/safe-ds-eda/src/App.svelte +++ b/packages/safe-ds-eda/src/App.svelte @@ -2,7 +2,7 @@ import TableView from './components/TableView.svelte'; import Sidebar from './components/Sidebar.svelte'; import { throttle } from 'lodash'; - import { currentTabIndex, currentState } from './webviewState'; + import { currentTabIndex, tabs } from './webviewState'; import TabContent from './components/tabs/TabContent.svelte'; let sidebarWidth = 307; // Initial width of the sidebar in pixels @@ -47,8 +47,8 @@
- {#if $currentState.tabs} - {#each $currentState.tabs as tab, index} + {#if $tabs.length > 0} + {#each $tabs as tab, index}
diff --git a/packages/safe-ds-eda/src/apis/historyApi.ts b/packages/safe-ds-eda/src/apis/historyApi.ts index de723ebd5..be67dff01 100644 --- a/packages/safe-ds-eda/src/apis/historyApi.ts +++ b/packages/safe-ds-eda/src/apis/historyApi.ts @@ -10,7 +10,7 @@ import type { Tab, TabHistoryEntry, } from '../../types/state'; -import { cancelTabIdsWaiting, currentState, currentTabIndex } from '../webviewState'; +import { cancelTabIdsWaiting, tabs, history, currentTabIndex } from '../webviewState'; import { executeRunner } from './extensionApi'; // Wait for results to return from the server @@ -47,40 +47,34 @@ window.addEventListener('message', (event) => { }); export const addInternalToHistory = function (entry: InternalHistoryEntry): void { - currentState.update((state) => { + history.update((state) => { const entryWithId: HistoryEntry = { ...entry, id: getAndIncrementEntryId(), }; - const newHistory = [...state.history, entryWithId]; - return { - ...state, - history: newHistory, - }; + const newHistory = [...state, entryWithId]; + return newHistory; }); }; export const executeExternalHistoryEntry = function (entry: ExternalHistoryEntry): void { - currentState.update((state) => { + history.update((state) => { const entryWithId: HistoryEntry = { ...entry, id: getAndIncrementEntryId(), }; - const newHistory = [...state.history, entryWithId]; + const newHistory = [...state, entryWithId]; asyncQueue.push(entryWithId); - executeRunner(state.history, entryWithId); // Is this good in here? Otherwise risk of empty array idk + executeRunner(state, entryWithId); - return { - ...state, - history: newHistory, - }; + return newHistory; }); }; export const addAndDeployTabHistoryEntry = function (entry: TabHistoryEntry & { id: number }, tab: Tab): void { // Search if already exists and is up to date - const existingTab = get(currentState).tabs?.find( + const existingTab = get(tabs).find( (et) => et.type !== 'empty' && et.type === tab.type && @@ -90,20 +84,18 @@ export const addAndDeployTabHistoryEntry = function (entry: TabHistoryEntry & { !et.isInGeneration, ); if (existingTab) { - currentTabIndex.set(get(currentState).tabs!.indexOf(existingTab)); + currentTabIndex.set(get(tabs).indexOf(existingTab)); return; } - currentState.update((state) => { - const newHistory = [...state.history, entry]; - - return { - ...state, - history: newHistory, - tabs: (state.tabs ?? []).concat([tab]), - }; + history.update((state) => { + return [...state, entry]; + }); + tabs.update((state) => { + const newTabs = (state ?? []).concat(tab); + return newTabs; }); - currentTabIndex.set(get(currentState).tabs!.indexOf(tab)); + currentTabIndex.set(get(tabs).indexOf(tab)); }; export const addEmptyTabHistoryEntry = function (): void { @@ -119,16 +111,14 @@ export const addEmptyTabHistoryEntry = function (): void { isInGeneration: true, }; - currentState.update((state) => { - const newHistory = [...state.history, entry]; - - return { - ...state, - history: newHistory, - tabs: (state.tabs ?? []).concat([tab]), - }; + history.update((state) => { + return [...state, entry]; + }); + tabs.update((state) => { + const newTabs = (state ?? []).concat(tab); + return newTabs; }); - currentTabIndex.set(get(currentState).tabs!.indexOf(tab)); + currentTabIndex.set(get(tabs).indexOf(tab)); }; export const cancelExecuteExternalHistoryEntry = function (entry: HistoryEntry): void { @@ -139,9 +129,7 @@ export const cancelExecuteExternalHistoryEntry = function (entry: HistoryEntry): cancelTabIdsWaiting.update((ids) => { return ids.concat([entry.existingTabId!]); }); - const tab: RealTab = get(currentState).tabs!.find( - (t) => t.type !== 'empty' && t.id === entry.existingTabId, - )! as RealTab; + const tab: RealTab = get(tabs).find((t) => t.type !== 'empty' && t.id === entry.existingTabId)! as RealTab; unsetTabAsGenerating(tab); } } else { @@ -150,8 +138,8 @@ export const cancelExecuteExternalHistoryEntry = function (entry: HistoryEntry): }; export const setTabAsGenerating = function (tab: RealTab): void { - currentState.update((state) => { - const newTabs = state.tabs?.map((t) => { + tabs.update((state) => { + const newTabs = state.map((t) => { if (t === tab) { return { ...t, @@ -162,16 +150,13 @@ export const setTabAsGenerating = function (tab: RealTab): void { } }); - return { - ...state, - tabs: newTabs, - }; + return newTabs; }); }; export const unsetTabAsGenerating = function (tab: RealTab): void { - currentState.update((state) => { - const newTabs = state.tabs?.map((t) => { + tabs.update((state) => { + const newTabs = state.map((t) => { if (t === tab) { return { ...t, @@ -193,34 +178,26 @@ const deployResult = function (result: RunnerExecutionResultMessage) { const resultContent = result.value; if (resultContent.type === 'tab') { if (resultContent.content.id) { - const existingTab = get(currentState).tabs?.find((et) => et.id === resultContent.content.id); + const existingTab = get(tabs).find((et) => et.id === resultContent.content.id); if (existingTab) { - const tabIndex = get(currentState).tabs!.indexOf(existingTab); - currentState.update((state) => { - return { - ...state, - tabs: state.tabs?.map((t) => { - if (t.id === resultContent.content.id) { - return resultContent.content; - } else { - return t; - } - }), - }; - }); + const tabIndex = get(tabs).indexOf(existingTab); + tabs.update((state) => + state.map((t) => { + if (t.id === resultContent.content.id) { + return resultContent.content; + } else { + return t; + } + }), + ); currentTabIndex.set(tabIndex); return; } } const tab = resultContent.content; tab.id = crypto.randomUUID(); - currentState.update((state) => { - return { - ...state, - tabs: (state.tabs ?? []).concat(tab), - }; - }); - currentTabIndex.set(get(currentState).tabs!.indexOf(tab)); + tabs.update((state) => state.concat(tab)); + currentTabIndex.set(get(tabs).indexOf(tab)); } }; diff --git a/packages/safe-ds-eda/src/components/Sidebar.svelte b/packages/safe-ds-eda/src/components/Sidebar.svelte index d5f15e1a2..c02278704 100644 --- a/packages/safe-ds-eda/src/components/Sidebar.svelte +++ b/packages/safe-ds-eda/src/components/Sidebar.svelte @@ -1,5 +1,5 @@
- {#if !$currentState.table} + {#if !$table} Loading ... {:else}
@@ -686,18 +680,18 @@ class="borderColumn borderColumnHeader" on:mousemove={(event) => throttledHandleReorderDragOver(event, 0)}># - {#each $currentState.table.columns as column, index} + {#each $table.columns as column, index} handleColumnInteractionStart(event, index)} on:mousemove={(event) => throttledHandleReorderDragOver(event, index)} - >{column[1].name} + >{column.name}
- throttledHandleReorderDragOver(event, $currentState.table?.columns.length ?? 0)}># throttledHandleReorderDragOver(event, $table?.columns.length ?? 0)} + ># @@ -732,22 +726,22 @@ class="borderColumn borderRight profiling" on:mousemove={(event) => throttledHandleReorderDragOver(event, 0)} > - {#each $currentState.table.columns as column, index} + {#each $table.columns as column, index} throttledHandleReorderDragOver(event, index)} >
- {#if !column[1].profiling} + {#if !column.profiling}
Loading ...
{:else} {/if} @@ -756,8 +750,7 @@ {/each} - throttledHandleReorderDragOver(event, $currentState.table?.columns.length ?? 0)} + on:mousemove={(event) => throttledHandleReorderDragOver(event, $table?.columns.length ?? 0)} > @@ -784,8 +777,8 @@
- {#each $currentState.table.columns as _column, index} - {#if index !== $currentState.table.columns.length - 1} + {#each $table.columns as _column, index} + {#if index !== $table.columns.length - 1} - throttledHandleReorderDragOver(event, $currentState.table?.columns.length ?? 0)} + on:mousemove={(event) => throttledHandleReorderDragOver(event, $table?.columns.length ?? 0)} > @@ -812,23 +804,23 @@ class:selectedColumn={selectedRowIndexes.includes(visibleStart + i)} >{visibleStart + i} - {#each $currentState.table.columns as column, index} + {#each $table.columns as column, index} throttledHandleReorderDragOver(event, index)} class:selectedColumn={selectedColumnIndexes.includes(index) || selectedRowIndexes.includes(visibleStart + i)} - >{column[1].values[visibleStart + i] !== null && - column[1].values[visibleStart + i] !== undefined - ? column[1].values[visibleStart + i] + >{column.values[visibleStart + i] !== null && + column.values[visibleStart + i] !== undefined + ? column.values[visibleStart + i] : ''} {/each} - throttledHandleReorderDragOver(event, $currentState.table?.columns.length ?? 0)} + throttledHandleReorderDragOver(event, $table?.columns.length ?? 0)} on:click={(event) => handleRowClick(event, visibleStart + i)} class:selectedColumn={selectedRowIndexes.includes(visibleStart + i)} >{visibleStart + i} column[1].name) || []; + const columnNames = $table?.columns.map((column) => column.name) || []; const possibleTableNames = ['Histogram', 'Boxplot', 'Heatmap', 'Lineplot', 'Scatterplot']; let isLoadingGeneratedTab = false; diff --git a/packages/safe-ds-eda/src/webviewState.ts b/packages/safe-ds-eda/src/webviewState.ts index 24fbf0fc7..3ccf194fe 100644 --- a/packages/safe-ds-eda/src/webviewState.ts +++ b/packages/safe-ds-eda/src/webviewState.ts @@ -1,47 +1,48 @@ import type { FromExtensionMessage } from '../types/messaging'; -import type { State } from '../types/state'; +import type { HistoryEntry, Tab, Table } from '../types/state'; import { get, writable } from 'svelte/store'; -let currentTabIndex = writable(undefined); +const tabs = writable([]); -let preventClicks = writable(false); +const currentTabIndex = writable(undefined); -let cancelTabIdsWaiting = writable([]); +const preventClicks = writable(false); + +const cancelTabIdsWaiting = writable([]); // Define the stores, current state to default in case the extension never calls setWebviewState( Shouldn't happen) -const currentState = writable({ tableIdentifier: undefined, history: [], defaultState: true, tabs: [] }); +const table = writable(); + +const history = writable([]); window.addEventListener('message', (event) => { const message = event.data as FromExtensionMessage; // eslint-disable-next-line no-console console.log(Date.now() + ': ' + message.command + ' called'); switch (message.command) { - case 'setWebviewState': - // This should be fired immediately whenever the panel is created or made visible again - currentState.set(message.value); + case 'setInitialTable': + if (!get(table)) { + table.set(message.value); + } else { + throw new Error('setInitialTable called more than once'); + } break; case 'setProfiling': - if (get(currentState) && get(currentState).table) { - currentState.update((state) => { + if (get(table)) { + table.update((currentTable) => { return { - ...state, - table: { - ...state.table!, - columns: state.table!.columns.map((column) => { - const profiling = message.value.find((p) => p.columnName === column[1].name); - if (profiling) { - return [ - column[0], - { - ...column[1], - profiling: profiling.profiling, - }, - ]; - } else { - return column; - } - }), - }, + ...currentTable!, + columns: currentTable!.columns.map((column) => { + const profiling = message.value.find((p) => p.columnName === column.name); + if (profiling) { + return { + ...column, + profiling: profiling.profiling, + }; + } else { + return column; + } + }), }; }); } @@ -49,4 +50,4 @@ window.addEventListener('message', (event) => { } }); -export { currentState, currentTabIndex, preventClicks, cancelTabIdsWaiting }; +export { history, tabs, table, currentTabIndex, preventClicks, cancelTabIdsWaiting }; diff --git a/packages/safe-ds-eda/types/messaging.ts b/packages/safe-ds-eda/types/messaging.ts index 7a009310b..1ca6a3550 100644 --- a/packages/safe-ds-eda/types/messaging.ts +++ b/packages/safe-ds-eda/types/messaging.ts @@ -32,15 +32,15 @@ export type ToExtensionMessage = | ToExtensionExecuteRunnerMessage; // From extension -type FromExtensionCommand = 'setWebviewState' | 'setProfiling' | 'runnerExecutionResult' | 'cancelRunnerExecution'; +type FromExtensionCommand = 'setInitialTable' | 'setProfiling' | 'runnerExecutionResult' | 'cancelRunnerExecution'; interface FromExtensionCommandMessage { command: FromExtensionCommand; value: any; } -interface FromExtensionSetStateMessage extends FromExtensionCommandMessage { - command: 'setWebviewState'; - value: defaultTypes.State; +interface FromExtensionSetInitialTableMessage extends FromExtensionCommandMessage { + command: 'setInitialTable'; + value: defaultTypes.Table; } interface FromExtensionSetProfilingMessage extends FromExtensionCommandMessage { @@ -79,7 +79,7 @@ export interface CancelRunnerExecutionMessage extends FromExtensionCommandMessag } export type FromExtensionMessage = - | FromExtensionSetStateMessage + | FromExtensionSetInitialTableMessage | FromExtensionSetProfilingMessage | RunnerExecutionResultMessage | CancelRunnerExecutionMessage; diff --git a/packages/safe-ds-eda/types/state.ts b/packages/safe-ds-eda/types/state.ts index c61cc4f6b..87c813926 100644 --- a/packages/safe-ds-eda/types/state.ts +++ b/packages/safe-ds-eda/types/state.ts @@ -1,12 +1,3 @@ -export interface State { - tableIdentifier?: string; - table?: Table; - tabs: Tab[]; - defaultState?: boolean; - history: HistoryEntry[]; - settings?: UserSettings; -} - type InternalAction = 'reorderColumns' | 'resizeColumn' | 'hideColumn' | 'highlightColumn' | 'emptyTab'; type ExternalManipulatingAction = 'filterColumn' | 'sortColumn' | TableFilterTypes; type ExternalVisualizingAction = TabType | 'refreshTab'; @@ -197,11 +188,12 @@ export type PlotTab = OneColumnTab | TwoColumnTab | NoColumnTab; // ------------------ Types for the Table ------------------ export interface Table { - columns: [number, Column][]; + tableIdentifier?: string; + name: string; + columns: Column[]; visibleRows?: number; totalRows: number; - name: string; - appliedFilters: TableFilter[]; + appliedFilters: TableFilter; } // ------------ Types for the Profiling ----------- @@ -314,13 +306,14 @@ type TableFilterTypes = | 'hideDuplicateRows' | 'hideRowsWithOutliers'; -export interface TableFilter extends FilterBase { - type: TableFilterTypes; -} +export type TableFilter = { + [key in TableFilterTypes]?: boolean; +}; // ------------ Types for the Settings ----------- export interface UserSettings { profiling: ProfilingSettings; + darkMode: boolean; } interface ProfilingSettingsBase { diff --git a/packages/safe-ds-vscode/src/extension/eda/apis/runnerApi.ts b/packages/safe-ds-vscode/src/extension/eda/apis/runnerApi.ts index 93d0d2f72..85382f178 100644 --- a/packages/safe-ds-vscode/src/extension/eda/apis/runnerApi.ts +++ b/packages/safe-ds-vscode/src/extension/eda/apis/runnerApi.ts @@ -313,7 +313,6 @@ export class RunnerApi { appliedFilters: [] as Table['appliedFilters'], }; - let i = 0; let currentMax = 0; for (const [columnName, columnValues] of Object.entries(pythonTableColumns)) { if (!Array.isArray(columnValues)) { @@ -336,7 +335,7 @@ export class RunnerApi { appliedSort: null, coloredHighLow: false, }; - table.columns.push([i++, column]); + table.columns.push(column); } table.totalRows = currentMax; table.visibleRows = currentMax; @@ -368,31 +367,27 @@ export class RunnerApi { // Generate SDS code to get missing value ratio for each column for (const column of columns) { - const newMvPlaceholderName = this.genPlaceholderName(column[1].name + '_mv'); + const newMvPlaceholderName = this.genPlaceholderName(column.name + '_mv'); placeholderNames.push(newMvPlaceholderName); - columnNameToPlaceholderMVNameMap.set(column[1].name, newMvPlaceholderName); - sdsStrings += this.sdsStringForMissingValueRatioByColumnName( - column[1].name, - table.name, - newMvPlaceholderName, - ); + columnNameToPlaceholderMVNameMap.set(column.name, newMvPlaceholderName); + sdsStrings += this.sdsStringForMissingValueRatioByColumnName(column.name, table.name, newMvPlaceholderName); // Find unique values // TODO reevaluate when image stuck problem fixed let uniqueValues = new Set(); - for (let j = 0; j < column[1].values.length; j++) { - uniqueValues.add(column[1].values[j]); + for (let j = 0; j < column.values.length; j++) { + uniqueValues.add(column.values[j]); } - uniqueValuesMap.set(column[1].name, uniqueValues); + uniqueValuesMap.set(column.name, uniqueValues); // Different histogram conditions for numerical and categorical columns - if (column[1].type !== 'numerical') { + if (column.type !== 'numerical') { if (uniqueValues.size <= 3 || uniqueValues.size > 10) { // Must match conidtions below that choose to display histogram for categorical columns continue; // This historam only generated if between 4-10 categorigal uniques or numerical type } } else { - if (uniqueValues.size > column[1].values.length * 0.9) { + if (uniqueValues.size > column.values.length * 0.9) { // Must match conidtions below that choose to display histogram for numerical columns // If 90% of values are unique, it's not a good idea to display histogram continue; @@ -400,14 +395,10 @@ export class RunnerApi { } // Histogram for numerical columns or categorical columns with 4-10 unique values - const newHistogramPlaceholderName = this.genPlaceholderName(column[1].name + '_hist'); + const newHistogramPlaceholderName = this.genPlaceholderName(column.name + '_hist'); placeholderNames.push(newHistogramPlaceholderName); - columnNameToPlaceholderHistogramNameMap.set(column[1].name, newHistogramPlaceholderName); - sdsStrings += this.sdsStringForHistogramByColumnName( - column[1].name, - table.name, - newHistogramPlaceholderName, - ); + columnNameToPlaceholderHistogramNameMap.set(column.name, newHistogramPlaceholderName); + sdsStrings += this.sdsStringForHistogramByColumnName(column.name, table.name, newHistogramPlaceholderName); } // Execute with generated SDS code @@ -439,7 +430,7 @@ export class RunnerApi { for (const column of columns) { // Base info for the top of the profiling const missingValuesRatio = - missingValueRatioMap.get(columnNameToPlaceholderMVNameMap.get(column[1].name)!)! * 100; + missingValueRatioMap.get(columnNameToPlaceholderMVNameMap.get(column.name)!)! * 100; const validRatio: ProfilingDetailStatistical = { type: 'numerical', @@ -455,19 +446,16 @@ export class RunnerApi { interpretation: missingValuesRatio > 0 ? 'error' : 'default', }; - const uniqueValues = uniqueValuesMap.get(column[1].name)!.size; + const uniqueValues = uniqueValuesMap.get(column.name)!.size; // If not numerical, add proper profilings according to idness results - if (column[1].type !== 'numerical') { + if (column.type !== 'numerical') { if (uniqueValues <= 3) { // Can display each separate percentages of unique values // Find all unique values and count them const uniqueValueCounts = new Map(); - for (let i = 0; i < column[1].values.length; i++) { - if (column[1].values[i] !== undefined && column[1].values[i] !== null) - uniqueValueCounts.set( - column[1].values[i], - (uniqueValueCounts.get(column[1].values[i]) || 0) + 1, - ); + for (let i = 0; i < column.values.length; i++) { + if (column.values[i] !== undefined && column.values[i] !== null) + uniqueValueCounts.set(column.values[i], (uniqueValueCounts.get(column.values[i]) || 0) + 1); } let uniqueProfilings: ProfilingDetailStatistical[] = []; @@ -475,13 +463,13 @@ export class RunnerApi { uniqueProfilings.push({ type: 'numerical', name: key, - value: ((value / column[1].values.length) * 100).toFixed(2) + '%', + value: ((value / column.values.length) * 100).toFixed(2) + '%', interpretation: 'category', }); } profiling.push({ - columnName: column[1].name, + columnName: column.name, profiling: { validRatio, missingRatio, @@ -493,10 +481,10 @@ export class RunnerApi { }); } else if (uniqueValues <= 10) { // Display histogram for 4-10 unique values, has to match the condition above where histogram is generated - const histogram = histogramMap.get(columnNameToPlaceholderHistogramNameMap.get(column[1].name)!)!; + const histogram = histogramMap.get(columnNameToPlaceholderHistogramNameMap.get(column.name)!)!; profiling.push({ - columnName: column[1].name, + columnName: column.name, profiling: { validRatio, missingRatio, @@ -509,7 +497,7 @@ export class RunnerApi { } else { // Display only the number of unique values vs total valid values profiling.push({ - columnName: column[1].name, + columnName: column.name, profiling: { validRatio, missingRatio, @@ -524,10 +512,10 @@ export class RunnerApi { type: 'text', value: Math.round( - column[1].values.length * + column.values.length * (1 - (missingValueRatioMap.get( - columnNameToPlaceholderMVNameMap.get(column[1].name)!, + columnNameToPlaceholderMVNameMap.get(column.name)!, ) || 0)), ) + ' Total Valids', interpretation: 'default', @@ -537,9 +525,9 @@ export class RunnerApi { }); } } else { - if (uniqueValues > column[1].values.length * 0.9) { + if (uniqueValues > column.values.length * 0.9) { profiling.push({ - columnName: column[1].name, + columnName: column.name, profiling: { validRatio, missingRatio, @@ -554,10 +542,10 @@ export class RunnerApi { type: 'text', value: Math.round( - column[1].values.length * + column.values.length * (1 - (missingValueRatioMap.get( - columnNameToPlaceholderMVNameMap.get(column[1].name)!, + columnNameToPlaceholderMVNameMap.get(column.name)!, ) || 0)), ) + ' Total Valids', interpretation: 'default', @@ -566,10 +554,10 @@ export class RunnerApi { }, }); } else { - const histogram = histogramMap.get(columnNameToPlaceholderHistogramNameMap.get(column[1].name)!)!; + const histogram = histogramMap.get(columnNameToPlaceholderHistogramNameMap.get(column.name)!)!; profiling.push({ - columnName: column[1].name, + columnName: column.name, profiling: { validRatio, missingRatio, diff --git a/packages/safe-ds-vscode/src/extension/eda/edaPanel.ts b/packages/safe-ds-vscode/src/extension/eda/edaPanel.ts index bb7ee5ecd..b744cae19 100644 --- a/packages/safe-ds-vscode/src/extension/eda/edaPanel.ts +++ b/packages/safe-ds-vscode/src/extension/eda/edaPanel.ts @@ -1,7 +1,7 @@ import * as vscode from 'vscode'; import { ToExtensionMessage } from '@safe-ds/eda/types/messaging.js'; import * as webviewApi from './apis/webviewApi.ts'; -import { State } from '@safe-ds/eda/types/state.ts'; +import { Table } from '@safe-ds/eda/types/state.ts'; import { SafeDsServices, ast } from '@safe-ds/lang'; import { RunnerApi } from './apis/runnerApi.ts'; import { safeDsLogger } from '../helpers/logging.js'; @@ -201,28 +201,18 @@ export class EDAPanel { dark: vscode.Uri.joinPath(edaPanel.extensionUri, 'img', 'binoculars-solid.png'), }; await edaPanel.waitForUpdateHtmlDone(10000); - const stateInfo = await edaPanel.constructCurrentState(); + const table = await edaPanel.getBaseTable(); webviewApi.postMessage(edaPanel!.panel.webview, { - command: 'setWebviewState', - value: stateInfo.state, + command: 'setInitialTable', + value: table, }); - // TODO: if from existing state, show disclaimer that updated data is loading and execute pipeline + history + profiling and send - - if ( - !stateInfo.fromExisting || - !stateInfo.state.table || - !stateInfo.state.table!.columns.find((c) => c[1].profiling) - ) { - const profiling = await EDAPanel.panelsMap - .get(tableIdentifier)! - .runnerApi.getProfiling(stateInfo.state.table!); - - webviewApi.postMessage(edaPanel!.panel.webview, { - command: 'setProfiling', - value: profiling, - }); - } + const profiling = await EDAPanel.panelsMap.get(tableIdentifier)!.runnerApi.getProfiling(table); + + webviewApi.postMessage(edaPanel!.panel.webview, { + command: 'setProfiling', + value: profiling, + }); } } //#endregion @@ -255,7 +245,7 @@ export class EDAPanel { //#endregion //#region State handling - private async constructCurrentState(): Promise<{ state: State; fromExisting: boolean }> { + private async getBaseTable(): Promise
{ const panel = EDAPanel.panelsMap.get(this.tableIdentifier); if (!panel) { throw new Error('Panel not found.'); @@ -264,16 +254,7 @@ export class EDAPanel { if (!table) { throw new Error('Timeout waiting for placeholder value'); } else { - return { - state: { - tableIdentifier: panel.tableIdentifier, - history: [], - defaultState: false, - table, - tabs: [], - }, - fromExisting: false, - }; + return table; } } } From 4d8bf106c0aacf73df2ef74b43807d50ae398ce6 Mon Sep 17 00:00:00 2001 From: Jonas B Date: Tue, 14 May 2024 20:30:23 +0200 Subject: [PATCH 02/32] feat: hidden columns messaging logic for vis vs. manip --- packages/safe-ds-eda/src/apis/extensionApi.ts | 43 +++++++++++++++++-- packages/safe-ds-eda/types/messaging.ts | 18 ++++++-- .../src/extension/eda/apis/runnerApi.ts | 33 +++++++++++--- .../src/extension/eda/edaPanel.ts | 1 + 4 files changed, 82 insertions(+), 13 deletions(-) diff --git a/packages/safe-ds-eda/src/apis/extensionApi.ts b/packages/safe-ds-eda/src/apis/extensionApi.ts index 883dbe7da..93371f485 100644 --- a/packages/safe-ds-eda/src/apis/extensionApi.ts +++ b/packages/safe-ds-eda/src/apis/extensionApi.ts @@ -1,4 +1,10 @@ -import type { HistoryEntry } from '../../types/state'; +import { get } from 'svelte/store'; +import type { + ExternalManipulatingHistoryEntry, + ExternalVisualizingHistoryEntry, + HistoryEntry, +} from '../../types/state'; +import { table } from '../webviewState'; export const createInfoToast = function (message: string) { window.injVscode.postMessage({ command: 'setInfo', value: message }); @@ -8,6 +14,37 @@ export const createErrorToast = function (message: string) { window.injVscode.postMessage({ command: 'setError', value: message }); }; -export const executeRunner = function (pastEntries: HistoryEntry[], newEntry: HistoryEntry) { - window.injVscode.postMessage({ command: 'executeRunner', value: { pastEntries, newEntry } }); +const executeRunnerVisualizing = function ( + pastEntries: HistoryEntry[], + newEntry: ExternalVisualizingHistoryEntry & { id: number }, + hiddenColumns: string[], +) { + window.injVscode.postMessage({ + command: 'executeRunner', + value: { pastEntries, newEntry, hiddenColumns, type: 'visualizing' }, + }); +}; + +const executeRunnerManipulating = function ( + pastEntries: HistoryEntry[], + newEntry: ExternalManipulatingHistoryEntry & { id: number }, +) { + window.injVscode.postMessage({ + command: 'executeRunner', + value: { pastEntries, newEntry, type: 'manipulating' }, + }); +}; + +export const executeRunner = function (state: HistoryEntry[], entry: HistoryEntry) { + if (entry.type === 'external-visualizing') { + executeRunnerVisualizing( + state, + entry, + get(table) + ?.columns.filter((column) => column.hidden) + .map((column) => column.name) ?? [], + ); + } else if (entry.type === 'external-manipulating') { + executeRunnerManipulating(state, entry); + } }; diff --git a/packages/safe-ds-eda/types/messaging.ts b/packages/safe-ds-eda/types/messaging.ts index 1ca6a3550..3b839077d 100644 --- a/packages/safe-ds-eda/types/messaging.ts +++ b/packages/safe-ds-eda/types/messaging.ts @@ -18,18 +18,30 @@ interface ToExtensionSetErrorMessage extends ToExtensionCommandMessage { value: string; } -interface ToExtensionExecuteRunnerMessage extends ToExtensionCommandMessage { +interface ToExtensionExecuteManipulatingRunnerMessage extends ToExtensionCommandMessage { command: 'executeRunner'; value: { + type: 'manipulating'; pastEntries: defaultTypes.HistoryEntry[]; - newEntry: defaultTypes.HistoryEntry; + newEntry: defaultTypes.ExternalManipulatingHistoryEntry & { id: number }; + }; +} + +interface ToExtensionExecuteVisualizingRunnerMessage extends ToExtensionCommandMessage { + command: 'executeRunner'; + value: { + type: 'visualizing'; + pastEntries: defaultTypes.HistoryEntry[]; + newEntry: defaultTypes.ExternalVisualizingHistoryEntry & { id: number }; + hiddenColumns: string[]; }; } export type ToExtensionMessage = | ToExtensionSetInfoMessage | ToExtensionSetErrorMessage - | ToExtensionExecuteRunnerMessage; + | ToExtensionExecuteVisualizingRunnerMessage + | ToExtensionExecuteManipulatingRunnerMessage; // From extension type FromExtensionCommand = 'setInitialTable' | 'setProfiling' | 'runnerExecutionResult' | 'cancelRunnerExecution'; diff --git a/packages/safe-ds-vscode/src/extension/eda/apis/runnerApi.ts b/packages/safe-ds-vscode/src/extension/eda/apis/runnerApi.ts index 85382f178..99c7582de 100644 --- a/packages/safe-ds-vscode/src/extension/eda/apis/runnerApi.ts +++ b/packages/safe-ds-vscode/src/extension/eda/apis/runnerApi.ts @@ -120,7 +120,10 @@ export class RunnerApi { //#endregion //#region SDS code generation - private sdsStringForHistoryEntry(historyEntry: ExternalHistoryEntry): { + private sdsStringForHistoryEntry( + historyEntry: ExternalHistoryEntry, + overrideTablePlaceholder?: string, + ): { sdsString: string; placeholderNames: string[]; } { @@ -130,7 +133,7 @@ export class RunnerApi { return { sdsString: this.sdsStringForHistogramByColumnName( historyEntry.columnName, - this.tablePlaceholder, + overrideTablePlaceholder ?? this.tablePlaceholder, newPlaceholderName, ), placeholderNames: [newPlaceholderName], @@ -139,7 +142,7 @@ export class RunnerApi { return { sdsString: this.sdsStringForBoxplotByColumnName( historyEntry.columnName, - this.tablePlaceholder, + overrideTablePlaceholder ?? this.tablePlaceholder, newPlaceholderName, ), placeholderNames: [newPlaceholderName], @@ -149,7 +152,7 @@ export class RunnerApi { sdsString: this.sdsStringForLinePlotByColumnNames( historyEntry.xAxisColumnName, historyEntry.yAxisColumnName, - this.tablePlaceholder, + overrideTablePlaceholder ?? this.tablePlaceholder, newPlaceholderName, ), placeholderNames: [newPlaceholderName], @@ -159,14 +162,17 @@ export class RunnerApi { sdsString: this.sdsStringForScatterPlotByColumnNames( historyEntry.xAxisColumnName, historyEntry.yAxisColumnName, - this.tablePlaceholder, + overrideTablePlaceholder ?? this.tablePlaceholder, newPlaceholderName, ), placeholderNames: [newPlaceholderName], }; case 'heatmap': return { - sdsString: this.sdsStringForCorrelationHeatmap(this.tablePlaceholder, newPlaceholderName), + sdsString: this.sdsStringForCorrelationHeatmap( + overrideTablePlaceholder ?? this.tablePlaceholder, + newPlaceholderName, + ), placeholderNames: [newPlaceholderName], }; default: @@ -263,6 +269,10 @@ export class RunnerApi { private sdsStringForCorrelationHeatmap(tablePlaceholder: string, newPlaceholderName: string) { return 'val ' + newPlaceholderName + ' = ' + tablePlaceholder + '.plotCorrelationHeatmap(); \n'; } + + private sdsStringForRemoveColumns(columnNames: string[], tablePlaceholder: string, newPlaceholderName: string) { + return 'val ' + newPlaceholderName + ' = ' + tablePlaceholder + '.removeColumns(' + columnNames + '); \n'; + } //#endregion //#region Placeholder handling @@ -579,6 +589,7 @@ export class RunnerApi { public async executeHistoryAndReturnNewResult( pastEntries: HistoryEntry[], newEntry: HistoryEntry, + hiddenColumns?: string[], ): Promise { let sdsLines = ''; let placeholderNames: string[] = []; @@ -596,7 +607,15 @@ export class RunnerApi { if (newEntry.type === 'external-visualizing') { if (newEntry.action === 'infoPanel' || newEntry.action === 'refreshTab') throw new Error('Not implemented'); - const sdsStringObj = this.sdsStringForHistoryEntry(newEntry); + let overriddenTablePlaceholder; + if (hiddenColumns && hiddenColumns.length > 0) { + overriddenTablePlaceholder = this.genPlaceholderName('hiddenColsOverride'); + sdsLines += + this.sdsStringForRemoveColumns(hiddenColumns, this.tablePlaceholder, overriddenTablePlaceholder) + + '\n'; + } + + const sdsStringObj = this.sdsStringForHistoryEntry(newEntry, overriddenTablePlaceholder); sdsLines += sdsStringObj.sdsString + '\n'; placeholderNames = sdsStringObj.placeholderNames; diff --git a/packages/safe-ds-vscode/src/extension/eda/edaPanel.ts b/packages/safe-ds-vscode/src/extension/eda/edaPanel.ts index b744cae19..9aeba966a 100644 --- a/packages/safe-ds-vscode/src/extension/eda/edaPanel.ts +++ b/packages/safe-ds-vscode/src/extension/eda/edaPanel.ts @@ -91,6 +91,7 @@ export class EDAPanel { const resultPromise = this.runnerApi.executeHistoryAndReturnNewResult( data.value.pastEntries, data.value.newEntry, + data.value.type === 'visualizing' ? data.value.hiddenColumns : undefined, ); // Check if execution takes longer than 1s to show progress indicator From 79f0aa0baf0832d6304c9853cc1f62bd7c86da60 Mon Sep 17 00:00:00 2001 From: Jonas B Date: Tue, 14 May 2024 22:42:30 +0200 Subject: [PATCH 03/32] feat: hide columns functionality --- .../src/components/TableView.svelte | 211 ++++++++++++------ packages/safe-ds-eda/types/state.ts | 4 +- .../src/extension/eda/apis/runnerApi.ts | 22 +- 3 files changed, 166 insertions(+), 71 deletions(-) diff --git a/packages/safe-ds-eda/src/components/TableView.svelte b/packages/safe-ds-eda/src/components/TableView.svelte index 12fed9210..1ec67fe41 100644 --- a/packages/safe-ds-eda/src/components/TableView.svelte +++ b/packages/safe-ds-eda/src/components/TableView.svelte @@ -18,7 +18,7 @@ import { derived, writable, get } from 'svelte/store'; import ColumnFilters from './column-filters/ColumnFilters.svelte'; import { imageWidthToHeightRatio } from '../../consts.config'; - import { executeExternalHistoryEntry } from '../apis/historyApi'; + import { addInternalToHistory, executeExternalHistoryEntry } from '../apis/historyApi'; import { disableNonContextMenuEffects, restoreNonContextMenuEffects } from '../toggleNonContextMenuEffects'; export let sidebarWidth: number; @@ -488,6 +488,34 @@ }; //#endregion // Right clicks + //#region Column hiding + const toggleHideColumn = function (columnIndex: number): void { + let visible = true; + table.update(($table) => { + return { + ...$table!, + columns: $table!.columns.map((column, index) => { + if (index === columnIndex) { + visible = !column.hidden; + return { + ...column, + hidden: !column.hidden, + }; + } + return column; + }), + }; + }); + + addInternalToHistory({ + action: visible ? 'showColumn' : 'hideColumn', + alias: `${visible ? 'Show' : 'Hide'} column ${$table!.columns[columnIndex].name}`, + type: 'internal', + columnName: $table!.columns[columnIndex].name, + }); + }; + //#endregion + //#region Plotting const generateTwoColumnTab = function (type: TwoColumnTabTypes) { if (selectedColumnIndexes.length !== 2) { @@ -681,35 +709,46 @@ on:mousemove={(event) => throttledHandleReorderDragOver(event, 0)}># {#each $table.columns as column, index} - + + + {:else} + + {/if} {/each} + {#if !column.hidden} + + {:else} + {/if} {/each} {#each $table.columns as column, index} - + {#if !column.hidden} + + {:else} + {/if} {/each}
handleColumnInteractionStart(event, index)} - on:mousemove={(event) => throttledHandleReorderDragOver(event, index)} - >{column.name} -
handleFilterContextMenu(event, index)} - > - -
-
-
- + {#if !column.hidden} +
handleColumnInteractionStart(event, index)} + on:mousemove={(event) => throttledHandleReorderDragOver(event, index)} + >{column.name} +
handleFilterContextMenu(event, index)} + > +
-
- +
+
+ +
+
+ +
-
- -
handleColumnInteractionStart(event, index)} + on:mousemove={(event) => throttledHandleReorderDragOver(event, index)} + >... + throttledHandleReorderDragOver(event, 0)} > {#each $table.columns as column, index} - throttledHandleReorderDragOver(event, index)} - > -
- {#if !column.profiling} -
Loading ...
- {:else} - - {/if} -
-
throttledHandleReorderDragOver(event, index)} + > +
+ {#if !column.profiling} +
Loading ...
+ {:else} + + {/if} +
+
throttledHandleReorderDragOver(event, index)} + > + {visibleStart + i} throttledHandleReorderDragOver(event, index)} - class:selectedColumn={selectedColumnIndexes.includes(index) || - selectedRowIndexes.includes(visibleStart + i)} - >{column.values[visibleStart + i] !== null && - column.values[visibleStart + i] !== undefined - ? column.values[visibleStart + i] - : ''} throttledHandleReorderDragOver(event, index)} + class:selectedColumn={selectedColumnIndexes.includes(index) || + selectedRowIndexes.includes(visibleStart + i)} + >{column.values[visibleStart + i] !== null && + column.values[visibleStart + i] !== undefined + ? column.values[visibleStart + i] + : ''} throttledHandleReorderDragOver(event, index)} + > + {#if showingColumnHeaderRightClickMenu}
- {#if selectedColumnIndexes.includes(rightClickedColumnIndex)} + {#if $table?.columns[rightClickedColumnIndex].hidden ?? false} + + {:else} + + {/if} + {#if selectedColumnIndexes.length === 0 && !$table?.columns[rightClickedColumnIndex].hidden} + + + {:else if selectedColumnIndexes.includes(rightClickedColumnIndex) && !$table?.columns[rightClickedColumnIndex].hidden} + {#if !$table?.columns[rightClickedColumnIndex].hidden} + + {/if} {/if}
{/if} @@ -1175,4 +1252,10 @@ .profilingBanner:hover { cursor: pointer; } + + .hiddenColumnHeader { + background-color: var(--bg-dark); + width: 10px; + padding: 0px; + } diff --git a/packages/safe-ds-eda/types/state.ts b/packages/safe-ds-eda/types/state.ts index 87c813926..97ed9a877 100644 --- a/packages/safe-ds-eda/types/state.ts +++ b/packages/safe-ds-eda/types/state.ts @@ -1,4 +1,4 @@ -type InternalAction = 'reorderColumns' | 'resizeColumn' | 'hideColumn' | 'highlightColumn' | 'emptyTab'; +type InternalAction = 'reorderColumns' | 'resizeColumn' | 'hideColumn' | 'showColumn' | 'highlightColumn' | 'emptyTab'; type ExternalManipulatingAction = 'filterColumn' | 'sortColumn' | TableFilterTypes; type ExternalVisualizingAction = TabType | 'refreshTab'; type Action = InternalAction | ExternalManipulatingAction | ExternalVisualizingAction; @@ -33,7 +33,7 @@ export interface InternalColumnWithValueHistoryEntry extends InternalHistoryEntr } export interface InternalColumnHistoryEntry extends InternalHistoryEntryBase { - action: 'hideColumn' | 'highlightColumn'; + action: 'hideColumn' | 'highlightColumn' | 'showColumn'; columnName: string; } diff --git a/packages/safe-ds-vscode/src/extension/eda/apis/runnerApi.ts b/packages/safe-ds-vscode/src/extension/eda/apis/runnerApi.ts index 99c7582de..a5e4dc9d8 100644 --- a/packages/safe-ds-vscode/src/extension/eda/apis/runnerApi.ts +++ b/packages/safe-ds-vscode/src/extension/eda/apis/runnerApi.ts @@ -271,7 +271,15 @@ export class RunnerApi { } private sdsStringForRemoveColumns(columnNames: string[], tablePlaceholder: string, newPlaceholderName: string) { - return 'val ' + newPlaceholderName + ' = ' + tablePlaceholder + '.removeColumns(' + columnNames + '); \n'; + return ( + 'val ' + + newPlaceholderName + + ' = ' + + tablePlaceholder + + '.removeColumns(["' + + columnNames.join('","') + + '"]); \n' + ); } //#endregion @@ -610,13 +618,15 @@ export class RunnerApi { let overriddenTablePlaceholder; if (hiddenColumns && hiddenColumns.length > 0) { overriddenTablePlaceholder = this.genPlaceholderName('hiddenColsOverride'); - sdsLines += - this.sdsStringForRemoveColumns(hiddenColumns, this.tablePlaceholder, overriddenTablePlaceholder) + - '\n'; + sdsLines += this.sdsStringForRemoveColumns( + hiddenColumns, + this.tablePlaceholder, + overriddenTablePlaceholder, + ); } const sdsStringObj = this.sdsStringForHistoryEntry(newEntry, overriddenTablePlaceholder); - sdsLines += sdsStringObj.sdsString + '\n'; + sdsLines += sdsStringObj.sdsString; placeholderNames = sdsStringObj.placeholderNames; safeDsLogger.debug(`Running new entry ${newEntry.id} with action ${newEntry.action}`); @@ -628,6 +638,8 @@ export class RunnerApi { const pipelineExecutionId = crypto.randomUUID(); try { + // eslint-disable-next-line no-console + console.log(sdsLines); await this.addToAndExecutePipeline(pipelineExecutionId, sdsLines, placeholderNames); } catch (e) { throw e; From e98549c4f7febf9c43f8563c5e7b8c61af5ea81c Mon Sep 17 00:00:00 2001 From: Jonas B Date: Tue, 14 May 2024 23:34:20 +0200 Subject: [PATCH 04/32] feat: making other tabs outdated on relevant changes in history --- packages/safe-ds-eda/src/apis/historyApi.ts | 97 ++++++++++++++++++- .../src/components/TableView.svelte | 2 +- .../src/components/tabs/SidebarTab.svelte | 4 +- .../src/components/tabs/TabContent.svelte | 13 ++- packages/safe-ds-eda/types/state.ts | 9 +- .../src/extension/eda/apis/runnerApi.ts | 8 +- 6 files changed, 112 insertions(+), 21 deletions(-) diff --git a/packages/safe-ds-eda/src/apis/historyApi.ts b/packages/safe-ds-eda/src/apis/historyApi.ts index be67dff01..71ae3b697 100644 --- a/packages/safe-ds-eda/src/apis/historyApi.ts +++ b/packages/safe-ds-eda/src/apis/historyApi.ts @@ -10,7 +10,7 @@ import type { Tab, TabHistoryEntry, } from '../../types/state'; -import { cancelTabIdsWaiting, tabs, history, currentTabIndex } from '../webviewState'; +import { cancelTabIdsWaiting, tabs, history, currentTabIndex, table } from '../webviewState'; import { executeRunner } from './extensionApi'; // Wait for results to return from the server @@ -38,7 +38,7 @@ window.addEventListener('message', (event) => { return; } - deployResult(message); + deployResult(message, asyncQueue[0]); asyncQueue.shift(); evaluateMessagesWaitingForTurn(); } else if (message.command === 'cancelRunnerExecution') { @@ -55,6 +55,8 @@ export const addInternalToHistory = function (entry: InternalHistoryEntry): void const newHistory = [...state, entryWithId]; return newHistory; }); + + updateTabOutdated(entry); }; export const executeExternalHistoryEntry = function (entry: ExternalHistoryEntry): void { @@ -80,7 +82,7 @@ export const addAndDeployTabHistoryEntry = function (entry: TabHistoryEntry & { et.type === tab.type && et.tabComment === tab.tabComment && tab.type && - !et.content.outdated && + !et.outdated && !et.isInGeneration, ); if (existingTab) { @@ -174,7 +176,7 @@ export const unsetTabAsGenerating = function (tab: RealTab): void { }); }; -const deployResult = function (result: RunnerExecutionResultMessage) { +const deployResult = function (result: RunnerExecutionResultMessage, historyEntry: ExternalHistoryEntry) { const resultContent = result.value; if (resultContent.type === 'tab') { if (resultContent.content.id) { @@ -198,6 +200,9 @@ const deployResult = function (result: RunnerExecutionResultMessage) { tab.id = crypto.randomUUID(); tabs.update((state) => state.concat(tab)); currentTabIndex.set(get(tabs).indexOf(tab)); + } else if (resultContent.type === 'table') { + updateTabOutdated(historyEntry); + throw new Error('Not implemented'); } }; @@ -209,7 +214,7 @@ const evaluateMessagesWaitingForTurn = function () { if (asyncQueue[0].id === entry.value.historyId) { // eslint-disable-next-line no-console console.log(`Deploying message from waiting queue: ${entry}`); - deployResult(entry); + deployResult(entry, asyncQueue[0]); asyncQueue.shift(); firstItemQueueChanged = true; } else if (asyncQueue.findIndex((queueEntry) => queueEntry.id === entry.value.historyId) !== -1) { @@ -220,3 +225,85 @@ const evaluateMessagesWaitingForTurn = function () { messagesWaitingForTurn = newMessagesWaitingForTurn; if (firstItemQueueChanged) evaluateMessagesWaitingForTurn(); // Only if first element was deployed we have to scan again, as this is only deployment condition }; + +const updateTabOutdated = function (entry: ExternalHistoryEntry | InternalHistoryEntry): void { + if (entry.action === 'showColumn') { + tabs.update((state) => { + const newTabs = state.map((t) => { + if (t.type !== 'empty') { + if ( + t.columnNumber === 'none' && + t.outdated && + get(table)?.columns.filter((c) => c.hidden && c.type === 'numerical').length === 0 + ) { + // UPDATE the if in case there are none column tabs that do not depend on numerical columns + return { + ...t, + outdated: false, + }; + } else if (t.columnNumber === 'one' && t.outdated && t.content.columnName === entry.columnName) { + return { + ...t, + outdated: false, + }; + } else if ( + t.columnNumber === 'two' && + t.outdated && + !get(table)?.columns.find((c) => c.name === t.content.yAxisColumnName)?.hidden && + !get(table)?.columns.find((c) => c.name === t.content.xAxisColumnName)?.hidden + ) { + return { + ...t, + outdated: false, + }; + } else { + return t; + } + } else { + return t; + } + }); + + return newTabs; + }); + } else if (entry.action === 'hideColumn') { + tabs.update((state) => { + const newTabs = state.map((t) => { + if (t.type !== 'empty') { + if ( + t.columnNumber === 'none' && + !t.outdated && + get(table)?.columns.find((c) => c.name === entry.columnName)?.type === 'numerical' + ) { + // UPDATE the if in case there are none column tabs that do not depend on numerical columns + return { + ...t, + outdated: true, + }; + } else if (t.columnNumber === 'one' && !t.outdated && t.content.columnName === entry.columnName) { + return { + ...t, + outdated: true, + }; + } else if ( + t.columnNumber === 'two' && + !t.outdated && + (t.content.xAxisColumnName === entry.columnName || + t.content.yAxisColumnName === entry.columnName) + ) { + return { + ...t, + outdated: true, + }; + } else { + return t; + } + } else { + return t; + } + }); + + return newTabs; + }); + } +}; diff --git a/packages/safe-ds-eda/src/components/TableView.svelte b/packages/safe-ds-eda/src/components/TableView.svelte index 1ec67fe41..42cdb7658 100644 --- a/packages/safe-ds-eda/src/components/TableView.svelte +++ b/packages/safe-ds-eda/src/components/TableView.svelte @@ -496,7 +496,7 @@ ...$table!, columns: $table!.columns.map((column, index) => { if (index === columnIndex) { - visible = !column.hidden; + visible = column.hidden; return { ...column, hidden: !column.hidden, diff --git a/packages/safe-ds-eda/src/components/tabs/SidebarTab.svelte b/packages/safe-ds-eda/src/components/tabs/SidebarTab.svelte index 7b306dac5..31043a7f2 100644 --- a/packages/safe-ds-eda/src/components/tabs/SidebarTab.svelte +++ b/packages/safe-ds-eda/src/components/tabs/SidebarTab.svelte @@ -32,7 +32,9 @@ {#if width > 300 || tabObject.isInGeneration || (tabObject.tabComment === '' && width > 109)} - {#if tabObject.isInGeneration} + {#if tabObject.type !== 'empty' && tabObject.outdated} + Outdated + {:else if tabObject.isInGeneration} Generating... {:else if tabObject.type === 'histogram'} Histogram diff --git a/packages/safe-ds-eda/src/components/tabs/TabContent.svelte b/packages/safe-ds-eda/src/components/tabs/TabContent.svelte index 2d1b43637..1967f6cf7 100644 --- a/packages/safe-ds-eda/src/components/tabs/TabContent.svelte +++ b/packages/safe-ds-eda/src/components/tabs/TabContent.svelte @@ -11,7 +11,10 @@ export let tab: Tab | EmptyTab; export let sidebarWidth: number; - const columnNames = $table?.columns.map((column) => column.name) || []; + const columnNames = derived( + table, + ($table) => $table?.columns.filter((column) => !column.hidden).map((column) => column.name) || [], + ); const possibleTableNames = ['Histogram', 'Boxplot', 'Heatmap', 'Lineplot', 'Scatterplot']; let isLoadingGeneratedTab = false; @@ -251,7 +254,7 @@ {changesDisabled} /> {#if tab.type !== 'empty' && tab.content.outdated} + >{#if tab.type !== 'empty' && tab.outdated} Outdated! {/if} @@ -268,7 +271,7 @@ $tabInfo.xAxisColumnName ?? 'Select'} onSelect={newXAxisSelected} - possibleOptions={columnNames} + possibleOptions={$columnNames} fontSize="1.1em" height="30px" width="140px" @@ -283,7 +286,7 @@ $tabInfo.xAxisColumnName ?? 'Select'} onSelect={newXAxisSelected} - possibleOptions={columnNames} + possibleOptions={$columnNames} fontSize="1.1em" height="30px" width="140px" @@ -315,7 +318,7 @@ Date: Wed, 15 May 2024 00:24:00 +0200 Subject: [PATCH 05/32] fix: improv oudated logic & display --- packages/safe-ds-eda/src/apis/historyApi.ts | 57 +------------------ .../src/components/tabs/SidebarTab.svelte | 6 +- .../src/components/tabs/TabContent.svelte | 38 +++++++++++-- .../tabs/content/ImageContent.svelte | 2 +- .../utilities/DropDownButton.svelte | 13 +++-- 5 files changed, 47 insertions(+), 69 deletions(-) diff --git a/packages/safe-ds-eda/src/apis/historyApi.ts b/packages/safe-ds-eda/src/apis/historyApi.ts index 71ae3b697..ea729cb8b 100644 --- a/packages/safe-ds-eda/src/apis/historyApi.ts +++ b/packages/safe-ds-eda/src/apis/historyApi.ts @@ -227,52 +227,12 @@ const evaluateMessagesWaitingForTurn = function () { }; const updateTabOutdated = function (entry: ExternalHistoryEntry | InternalHistoryEntry): void { - if (entry.action === 'showColumn') { + if (entry.action === 'hideColumn' || entry.action === 'showColumn') { tabs.update((state) => { const newTabs = state.map((t) => { if (t.type !== 'empty') { if ( t.columnNumber === 'none' && - t.outdated && - get(table)?.columns.filter((c) => c.hidden && c.type === 'numerical').length === 0 - ) { - // UPDATE the if in case there are none column tabs that do not depend on numerical columns - return { - ...t, - outdated: false, - }; - } else if (t.columnNumber === 'one' && t.outdated && t.content.columnName === entry.columnName) { - return { - ...t, - outdated: false, - }; - } else if ( - t.columnNumber === 'two' && - t.outdated && - !get(table)?.columns.find((c) => c.name === t.content.yAxisColumnName)?.hidden && - !get(table)?.columns.find((c) => c.name === t.content.xAxisColumnName)?.hidden - ) { - return { - ...t, - outdated: false, - }; - } else { - return t; - } - } else { - return t; - } - }); - - return newTabs; - }); - } else if (entry.action === 'hideColumn') { - tabs.update((state) => { - const newTabs = state.map((t) => { - if (t.type !== 'empty') { - if ( - t.columnNumber === 'none' && - !t.outdated && get(table)?.columns.find((c) => c.name === entry.columnName)?.type === 'numerical' ) { // UPDATE the if in case there are none column tabs that do not depend on numerical columns @@ -280,21 +240,6 @@ const updateTabOutdated = function (entry: ExternalHistoryEntry | InternalHistor ...t, outdated: true, }; - } else if (t.columnNumber === 'one' && !t.outdated && t.content.columnName === entry.columnName) { - return { - ...t, - outdated: true, - }; - } else if ( - t.columnNumber === 'two' && - !t.outdated && - (t.content.xAxisColumnName === entry.columnName || - t.content.yAxisColumnName === entry.columnName) - ) { - return { - ...t, - outdated: true, - }; } else { return t; } diff --git a/packages/safe-ds-eda/src/components/tabs/SidebarTab.svelte b/packages/safe-ds-eda/src/components/tabs/SidebarTab.svelte index 31043a7f2..6be9f3723 100644 --- a/packages/safe-ds-eda/src/components/tabs/SidebarTab.svelte +++ b/packages/safe-ds-eda/src/components/tabs/SidebarTab.svelte @@ -32,10 +32,10 @@ {#if width > 300 || tabObject.isInGeneration || (tabObject.tabComment === '' && width > 109)} - {#if tabObject.type !== 'empty' && tabObject.outdated} - Outdated - {:else if tabObject.isInGeneration} + {#if tabObject.isInGeneration} Generating... + {:else if tabObject.outdated} + Outdated {:else if tabObject.type === 'histogram'} Histogram {:else if tabObject.type === 'boxPlot'} diff --git a/packages/safe-ds-eda/src/components/tabs/TabContent.svelte b/packages/safe-ds-eda/src/components/tabs/TabContent.svelte index 1967f6cf7..b512d86dc 100644 --- a/packages/safe-ds-eda/src/components/tabs/TabContent.svelte +++ b/packages/safe-ds-eda/src/components/tabs/TabContent.svelte @@ -193,6 +193,7 @@ if (Object.keys(buildATab).length === 0) return undefined; if ($buildATab.columnNumber === 'one') { if (!$buildATab.xAxisColumnName) return undefined; + if ($table?.columns.find((column) => column.name === $buildATab.xAxisColumnName)?.hidden) return undefined; return { existingTabId: tab.id, action: $buildATab.type, @@ -203,6 +204,13 @@ }; } else if ($buildATab.columnNumber === 'two') { if (!$buildATab.xAxisColumnName || !$buildATab.yAxisColumnName) return undefined; + if ( + $table?.columns.find( + (column) => + column.name === $buildATab.xAxisColumnName || column.name === $buildATab.yAxisColumnName, + )?.hidden + ) + return undefined; return { existingTabId: tab.id, action: $buildATab.type, @@ -254,7 +262,7 @@ {changesDisabled} /> {#if tab.type !== 'empty' && tab.outdated} + >{#if tab.type !== 'empty' && tab.outdated && !$isInBuildingState} Outdated! {/if} @@ -271,10 +279,20 @@ $tabInfo.xAxisColumnName ?? 'Select'} onSelect={newXAxisSelected} - possibleOptions={$columnNames} + possibleOptions={$tabInfo.type === 'boxPlot' + ? $table?.columns + .filter((column) => !column.hidden && column.type === 'numerical') + .map((column) => column.name) || [] + : $columnNames} fontSize="1.1em" height="30px" width="140px" + error={tab.type !== 'empty' && + $table?.columns.find( + (column) => + column.name === + ($tabInfo.content?.columnName ?? $tabInfo.xAxisColumnName), + )?.hidden} {changesDisabled} /> @@ -290,6 +308,12 @@ fontSize="1.1em" height="30px" width="140px" + error={tab.type !== 'empty' && + $table?.columns.find( + (column) => + column.name === + ($tabInfo.content?.xAxisColumnName ?? $tabInfo.xAxisColumnName), + )?.hidden} {changesDisabled} /> @@ -322,6 +346,11 @@ fontSize="1.1em" height="30px" width="140px" + error={tab.type !== 'empty' && + $table?.columns.find( + (column) => + column.name === ($tabInfo.content?.yAxisColumnName ?? $tabInfo.yAxisColumnName), + )?.hidden} {changesDisabled} /> @@ -404,8 +433,7 @@ } .loading { - min-width: 540px; - max-width: 800px; + min-width: 600px; margin: 0 auto; background-color: var(--bg-medium); position: absolute; @@ -421,7 +449,7 @@ .content { position: relative; z-index: 0; - min-width: 540px; + min-width: 600px; } .generateButton { diff --git a/packages/safe-ds-eda/src/components/tabs/content/ImageContent.svelte b/packages/safe-ds-eda/src/components/tabs/content/ImageContent.svelte index 290729e72..dc7d6bee0 100644 --- a/packages/safe-ds-eda/src/components/tabs/content/ImageContent.svelte +++ b/packages/safe-ds-eda/src/components/tabs/content/ImageContent.svelte @@ -12,7 +12,7 @@ .wrapper { height: 100%; width: 100%; - min-width: 500px; + min-width: 600px; max-width: 800px; margin: 0 auto; } diff --git a/packages/safe-ds-eda/src/components/utilities/DropDownButton.svelte b/packages/safe-ds-eda/src/components/utilities/DropDownButton.svelte index b7e1d6725..c6f9b3670 100644 --- a/packages/safe-ds-eda/src/components/utilities/DropDownButton.svelte +++ b/packages/safe-ds-eda/src/components/utilities/DropDownButton.svelte @@ -4,10 +4,11 @@ export let selectedOption: string; export let possibleOptions: string[]; - export let fontSize: string = '1.5em'; - export let height: string = '50px'; - export let width: string = '180px'; + export let fontSize: string = '1.4em'; + export let height: string = '45px'; + export let width: string = '160px'; export let changesDisabled: boolean = false; + export let error: boolean = false; export let onSelect: (selected: string) => void; // Function prop to notify parent of changes let isDropdownOpen = false; @@ -52,7 +53,7 @@ class:disabledButton={changesDisabled} on:click={toggleDropdown} > -
+
{selectedOption}
@@ -166,4 +167,8 @@ .dropdownItem:hover { background-color: #e0e0e0; } + + .error { + color: var(--error-color) !important; + } From 2254cf1709b30e83d81c86a0935f910128e48bf6 Mon Sep 17 00:00:00 2001 From: Jonas B Date: Wed, 15 May 2024 21:49:01 +0200 Subject: [PATCH 06/32] fix: better hidden col handling for not 'none' col count tabs --- packages/safe-ds-eda/src/apis/extensionApi.ts | 28 ++++----- packages/safe-ds-eda/src/apis/historyApi.ts | 6 +- .../src/components/tabs/TabContent.svelte | 61 ++++++++----------- .../utilities/DropDownButton.svelte | 51 ++++++++++++---- packages/safe-ds-eda/types/messaging.ts | 16 ++--- .../src/extension/eda/apis/runnerApi.ts | 2 - .../src/extension/eda/edaPanel.ts | 2 +- 7 files changed, 89 insertions(+), 77 deletions(-) diff --git a/packages/safe-ds-eda/src/apis/extensionApi.ts b/packages/safe-ds-eda/src/apis/extensionApi.ts index 93371f485..fdb5a0b05 100644 --- a/packages/safe-ds-eda/src/apis/extensionApi.ts +++ b/packages/safe-ds-eda/src/apis/extensionApi.ts @@ -1,9 +1,5 @@ import { get } from 'svelte/store'; -import type { - ExternalManipulatingHistoryEntry, - ExternalVisualizingHistoryEntry, - HistoryEntry, -} from '../../types/state'; +import type { HistoryEntry } from '../../types/state'; import { table } from '../webviewState'; export const createInfoToast = function (message: string) { @@ -14,37 +10,35 @@ export const createErrorToast = function (message: string) { window.injVscode.postMessage({ command: 'setError', value: message }); }; -const executeRunnerVisualizing = function ( +const executeRunnerExcludingHiddenColumns = function ( pastEntries: HistoryEntry[], - newEntry: ExternalVisualizingHistoryEntry & { id: number }, + newEntry: HistoryEntry, hiddenColumns: string[], ) { window.injVscode.postMessage({ command: 'executeRunner', - value: { pastEntries, newEntry, hiddenColumns, type: 'visualizing' }, + value: { pastEntries, newEntry, hiddenColumns, type: 'excludingHiddenColumns' }, }); }; -const executeRunnerManipulating = function ( - pastEntries: HistoryEntry[], - newEntry: ExternalManipulatingHistoryEntry & { id: number }, -) { +const executeRunnerDefault = function (pastEntries: HistoryEntry[], newEntry: HistoryEntry) { window.injVscode.postMessage({ command: 'executeRunner', - value: { pastEntries, newEntry, type: 'manipulating' }, + value: { pastEntries, newEntry, type: 'default' }, }); }; export const executeRunner = function (state: HistoryEntry[], entry: HistoryEntry) { - if (entry.type === 'external-visualizing') { - executeRunnerVisualizing( + if (entry.type === 'external-visualizing' && entry.columnNumber === 'none') { + // This means a tab where you do not select columns => don't include hidden columns in visualization + executeRunnerExcludingHiddenColumns( state, entry, get(table) ?.columns.filter((column) => column.hidden) .map((column) => column.name) ?? [], ); - } else if (entry.type === 'external-manipulating') { - executeRunnerManipulating(state, entry); + } else { + executeRunnerDefault(state, entry); } }; diff --git a/packages/safe-ds-eda/src/apis/historyApi.ts b/packages/safe-ds-eda/src/apis/historyApi.ts index ea729cb8b..76a087f4b 100644 --- a/packages/safe-ds-eda/src/apis/historyApi.ts +++ b/packages/safe-ds-eda/src/apis/historyApi.ts @@ -131,8 +131,10 @@ export const cancelExecuteExternalHistoryEntry = function (entry: HistoryEntry): cancelTabIdsWaiting.update((ids) => { return ids.concat([entry.existingTabId!]); }); - const tab: RealTab = get(tabs).find((t) => t.type !== 'empty' && t.id === entry.existingTabId)! as RealTab; - unsetTabAsGenerating(tab); + const tab: Tab = get(tabs).find((t) => t.id === entry.existingTabId)! as Tab; + if (tab.type !== 'empty') { + unsetTabAsGenerating(tab); + } } } else { throw new Error('Entry already fully executed'); diff --git a/packages/safe-ds-eda/src/components/tabs/TabContent.svelte b/packages/safe-ds-eda/src/components/tabs/TabContent.svelte index b512d86dc..91ec98b94 100644 --- a/packages/safe-ds-eda/src/components/tabs/TabContent.svelte +++ b/packages/safe-ds-eda/src/components/tabs/TabContent.svelte @@ -13,7 +13,27 @@ const columnNames = derived( table, - ($table) => $table?.columns.filter((column) => !column.hidden).map((column) => column.name) || [], + ($table) => + $table?.columns.map((column) => { + return { + name: column.name, + color: column.hidden ? 'var(--error-color)' : undefined, + comment: column.hidden ? 'hidden' : undefined, + }; + }) || [], + ); + const numericalColumnNames = derived( + table, + ($table) => + $table?.columns + .filter((column) => column.type === 'numerical') + .map((column) => { + return { + name: column.name, + color: column.hidden ? 'var(--error-color)' : undefined, + comment: column.hidden ? 'hidden' : undefined, + }; + }) || [], ); const possibleTableNames = ['Histogram', 'Boxplot', 'Heatmap', 'Lineplot', 'Scatterplot']; @@ -193,7 +213,6 @@ if (Object.keys(buildATab).length === 0) return undefined; if ($buildATab.columnNumber === 'one') { if (!$buildATab.xAxisColumnName) return undefined; - if ($table?.columns.find((column) => column.name === $buildATab.xAxisColumnName)?.hidden) return undefined; return { existingTabId: tab.id, action: $buildATab.type, @@ -204,13 +223,6 @@ }; } else if ($buildATab.columnNumber === 'two') { if (!$buildATab.xAxisColumnName || !$buildATab.yAxisColumnName) return undefined; - if ( - $table?.columns.find( - (column) => - column.name === $buildATab.xAxisColumnName || column.name === $buildATab.yAxisColumnName, - )?.hidden - ) - return undefined; return { existingTabId: tab.id, action: $buildATab.type, @@ -258,7 +270,7 @@ ({ name }))} {changesDisabled} /> !column.hidden && column.type === 'numerical') - .map((column) => column.name) || [] - : $columnNames} + possibleOptions={$tabInfo.type === 'boxPlot' ? $numericalColumnNames : $columnNames} fontSize="1.1em" - height="30px" + height="40px" width="140px" - error={tab.type !== 'empty' && - $table?.columns.find( - (column) => - column.name === - ($tabInfo.content?.columnName ?? $tabInfo.xAxisColumnName), - )?.hidden} {changesDisabled} />
@@ -306,14 +308,8 @@ onSelect={newXAxisSelected} possibleOptions={$columnNames} fontSize="1.1em" - height="30px" + height="40px" width="140px" - error={tab.type !== 'empty' && - $table?.columns.find( - (column) => - column.name === - ($tabInfo.content?.xAxisColumnName ?? $tabInfo.xAxisColumnName), - )?.hidden} {changesDisabled} />
@@ -344,13 +340,8 @@ onSelect={newYAxisSelected} possibleOptions={$columnNames} fontSize="1.1em" - height="30px" + height="40px" width="140px" - error={tab.type !== 'empty' && - $table?.columns.find( - (column) => - column.name === ($tabInfo.content?.yAxisColumnName ?? $tabInfo.yAxisColumnName), - )?.hidden} {changesDisabled} /> diff --git a/packages/safe-ds-eda/src/components/utilities/DropDownButton.svelte b/packages/safe-ds-eda/src/components/utilities/DropDownButton.svelte index c6f9b3670..8137591e7 100644 --- a/packages/safe-ds-eda/src/components/utilities/DropDownButton.svelte +++ b/packages/safe-ds-eda/src/components/utilities/DropDownButton.svelte @@ -3,12 +3,11 @@ import { preventClicks } from '../../webviewState'; export let selectedOption: string; - export let possibleOptions: string[]; + export let possibleOptions: { name: string; color?: string; comment?: string }[]; export let fontSize: string = '1.4em'; export let height: string = '45px'; export let width: string = '160px'; export let changesDisabled: boolean = false; - export let error: boolean = false; export let onSelect: (selected: string) => void; // Function prop to notify parent of changes let isDropdownOpen = false; @@ -44,7 +43,7 @@ bind:this={dropdownRef} class="wrapperDropdownButton" class:disabledWrapper={changesDisabled} - style="font-size: {fontSize}; width: {width}; height: {height};" + style="font-size: {fontSize}; width: {width}; height: {height}; min-height: 40px;" >
-
- {selectedOption} +
o.name === selectedOption)?.color}> + {selectedOption} + {#if possibleOptions.find((o) => o.name === selectedOption)?.comment} + {possibleOptions.find((o) => o.name === selectedOption)?.comment} + {/if}
@@ -62,10 +64,20 @@
{#if isDropdownOpen} -
{/if} From 573aee7f5fd5ee6271ebee6e63e9cacee688e890 Mon Sep 17 00:00:00 2001 From: Jonas B Date: Sun, 26 May 2024 20:37:06 +0200 Subject: [PATCH 10/32] fix: buildATab NonNumerical based changes --- .../src/components/tabs/TabContent.svelte | 44 ++++++++++++++++--- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/packages/safe-ds-eda/src/components/tabs/TabContent.svelte b/packages/safe-ds-eda/src/components/tabs/TabContent.svelte index d7b97d98b..4c384c113 100644 --- a/packages/safe-ds-eda/src/components/tabs/TabContent.svelte +++ b/packages/safe-ds-eda/src/components/tabs/TabContent.svelte @@ -11,6 +11,10 @@ export let tab: Tab | EmptyTab; export let sidebarWidth: number; + const allowsNonNumericalColumns = function (type: string) { + return type === 'histogram'; + }; + const columnNames = derived( table, ($table) => @@ -140,7 +144,7 @@ throw new Error('Invalid tab type'); } - if (copyOverColumns && tab.type !== 'empty') { + if (copyOverColumns && tab.type !== 'empty' && $buildATab.columnNumber !== 'none') { if (tab.columnNumber === 'one') { buildATab.update((buildingTab) => { buildingTab.xAxisColumnName = tab.content.columnName; @@ -154,6 +158,33 @@ }); } } + + // Check if columns now require numerical column only and if so check if selected column(s) are numerical + if (!allowsNonNumericalColumns($buildATab.type)) { + buildATab.update((buildingTab) => { + if ( + buildingTab.xAxisColumnName && + !$numericalColumnNames.find((column) => column.name === buildingTab.xAxisColumnName) + ) { + buildingTab.xAxisColumnName = undefined; + } + if ( + buildingTab.yAxisColumnName && + !$numericalColumnNames.find((column) => column.name === buildingTab.yAxisColumnName) + ) { + buildingTab.yAxisColumnName = undefined; + } + return buildingTab; + }); + } + + // Set yAxisColumnName to undefined if it is not required + if ($buildATab.columnNumber === 'one') { + buildATab.update((buildingTab) => { + buildingTab.yAxisColumnName = undefined; + return buildingTab; + }); + } }; const newXAxisSelected = function (selected: string) { @@ -292,7 +323,7 @@ $tabInfo.xAxisColumnName ?? 'Select'} onSelect={newXAxisSelected} - possibleOptions={$tabInfo.type === 'histogram' + possibleOptions={allowsNonNumericalColumns($tabInfo.type) ? $columnNames : $numericalColumnNames} fontSize="1.1em" @@ -309,7 +340,7 @@ $tabInfo.xAxisColumnName ?? 'Select'} onSelect={newXAxisSelected} - possibleOptions={$tabInfo.type === 'histogram' + possibleOptions={allowsNonNumericalColumns($tabInfo.type) ? $columnNames : $numericalColumnNames} fontSize="1.1em" @@ -343,7 +374,9 @@ Date: Mon, 27 May 2024 13:45:54 +0200 Subject: [PATCH 11/32] feat: footer basics for col count --- packages/safe-ds-eda/src/App.svelte | 1 + .../safe-ds-eda/src/components/Sidebar.svelte | 47 +++++++++++++++++-- 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/packages/safe-ds-eda/src/App.svelte b/packages/safe-ds-eda/src/App.svelte index 9fb008372..2e44f608c 100644 --- a/packages/safe-ds-eda/src/App.svelte +++ b/packages/safe-ds-eda/src/App.svelte @@ -71,6 +71,7 @@ overflow: hidden; position: relative; background-color: var(--bg-dark); + height: 100%; } .contentWrapper { diff --git a/packages/safe-ds-eda/src/components/Sidebar.svelte b/packages/safe-ds-eda/src/components/Sidebar.svelte index c02278704..99518bcc5 100644 --- a/packages/safe-ds-eda/src/components/Sidebar.svelte +++ b/packages/safe-ds-eda/src/components/Sidebar.svelte @@ -22,9 +22,8 @@ {$table?.name ?? 'Loading ...'} - {/if} - {#if width > 70} - {$table?.visibleRows ?? 0}/{$table?.totalRows ?? 0} Rows{/if} + + {/if}
{#if width > 50}
@@ -58,14 +57,55 @@
{/if} + diff --git a/packages/safe-ds-eda/src/components/Sidebar.svelte b/packages/safe-ds-eda/src/components/Sidebar.svelte index 99518bcc5..8d6cd91bc 100644 --- a/packages/safe-ds-eda/src/components/Sidebar.svelte +++ b/packages/safe-ds-eda/src/components/Sidebar.svelte @@ -6,6 +6,7 @@ import TableIcon from '../icons/Table.svelte'; import SidebarTab from './tabs/SidebarTab.svelte'; import NewTabButton from './NewTabButton.svelte'; + import ColumnCounts from './ColumnCounts.svelte'; export let width: number; @@ -63,8 +64,7 @@ Rows
- {$table?.columns.filter((col) => !col.hidden).length ?? 0}/{$table?.columns.length ?? 0} - Columns + = 300} />
From 5049fd7897aba5f9b46abda1914c5d80f9a10771 Mon Sep 17 00:00:00 2001 From: Jonas B Date: Mon, 27 May 2024 14:41:16 +0200 Subject: [PATCH 16/32] refactor: rename color vars --- packages/safe-ds-eda/src/App.svelte | 2 +- .../src/components/ColumnCounts.svelte | 18 ++--- .../safe-ds-eda/src/components/Sidebar.svelte | 16 ++--- .../src/components/TableView.svelte | 66 ++++++++++--------- .../column-filters/ColumnFilters.svelte | 2 +- .../components/profiling/ProfilingInfo.svelte | 6 +- .../src/components/tabs/SidebarTab.svelte | 6 +- .../src/components/tabs/TabContent.svelte | 10 +-- .../utilities/DropDownButton.svelte | 16 ++--- packages/safe-ds-eda/src/icons/Caret.svelte | 2 +- packages/safe-ds-eda/src/icons/Plus.svelte | 2 +- packages/safe-ds-eda/src/icons/Swap.svelte | 2 +- packages/safe-ds-eda/src/icons/Zoom.svelte | 4 +- packages/safe-ds-vscode/media/styles.css | 21 +++--- 14 files changed, 88 insertions(+), 85 deletions(-) diff --git a/packages/safe-ds-eda/src/App.svelte b/packages/safe-ds-eda/src/App.svelte index 2e44f608c..8d6b4dd88 100644 --- a/packages/safe-ds-eda/src/App.svelte +++ b/packages/safe-ds-eda/src/App.svelte @@ -70,7 +70,7 @@ flex-shrink: 0; overflow: hidden; position: relative; - background-color: var(--bg-dark); + background-color: var(--medium-light-color); height: 100%; } diff --git a/packages/safe-ds-eda/src/components/ColumnCounts.svelte b/packages/safe-ds-eda/src/components/ColumnCounts.svelte index 01c03048c..4b14ab47b 100644 --- a/packages/safe-ds-eda/src/components/ColumnCounts.svelte +++ b/packages/safe-ds-eda/src/components/ColumnCounts.svelte @@ -107,16 +107,16 @@ } .text:hover * { - color: var(--font-light); + color: var(--dark-color); } .contextMenu { position: absolute; - border: 2px solid var(--bg-dark); - background-color: var(--bg-bright); + border: 2px solid var(--medium-light-color); + background-color: var(--lightest-color); z-index: 1000; padding: 0; - color: var(--font-dark); + color: var(--darkest-color); display: flex; flex-direction: column; width: max-content; @@ -128,20 +128,20 @@ .contextMenu button { padding: 5px 15px; cursor: pointer; - background-color: var(--bg-bright); - color: var(--font-dark); + background-color: var(--lightest-color); + color: var(--darkest-color); text-align: left; width: 100%; } .contextMenu button:hover { background-color: var(--primary-color); - color: var(--font-bright); + color: var(--light-color); } .contextMenu :disabled:hover { - background-color: var(--bg-bright); - color: var(--font-dark); + background-color: var(--lightest-color); + color: var(--darkest-color); cursor: default; } diff --git a/packages/safe-ds-eda/src/components/Sidebar.svelte b/packages/safe-ds-eda/src/components/Sidebar.svelte index 8d6cd91bc..852810337 100644 --- a/packages/safe-ds-eda/src/components/Sidebar.svelte +++ b/packages/safe-ds-eda/src/components/Sidebar.svelte @@ -71,8 +71,8 @@ diff --git a/packages/safe-ds-eda/src/components/profiling/ProfilingInfo.svelte b/packages/safe-ds-eda/src/components/profiling/ProfilingInfo.svelte index 4c69cd4b2..d36cca251 100644 --- a/packages/safe-ds-eda/src/components/profiling/ProfilingInfo.svelte +++ b/packages/safe-ds-eda/src/components/profiling/ProfilingInfo.svelte @@ -139,15 +139,15 @@ } .important { - color: var(--font-dark); + color: var(--darkest-color); } .default { - color: var(--font-light); + color: var(--dark-color); } .category { - color: var(--font-light); + color: var(--dark-color); } .zoomIconWrapper { diff --git a/packages/safe-ds-eda/src/components/tabs/SidebarTab.svelte b/packages/safe-ds-eda/src/components/tabs/SidebarTab.svelte index b25da6ce7..0756cee60 100644 --- a/packages/safe-ds-eda/src/components/tabs/SidebarTab.svelte +++ b/packages/safe-ds-eda/src/components/tabs/SidebarTab.svelte @@ -65,13 +65,13 @@ height: 100%; padding: 0px 20px 0px 20px; margin-bottom: 10px; - background-color: var(--bg-medium); + background-color: var(--light-color); font-size: 1.1rem; cursor: pointer; } .activeWrapper { - background-color: var(--bg-bright); + background-color: var(--lightest-color); font-size: 1.2rem; } @@ -90,6 +90,6 @@ .comment { margin-left: auto; font-size: 0.9rem; - color: var(--font-light); + color: var(--dark-color); } diff --git a/packages/safe-ds-eda/src/components/tabs/TabContent.svelte b/packages/safe-ds-eda/src/components/tabs/TabContent.svelte index 4c384c113..6e01822d5 100644 --- a/packages/safe-ds-eda/src/components/tabs/TabContent.svelte +++ b/packages/safe-ds-eda/src/components/tabs/TabContent.svelte @@ -417,7 +417,7 @@ height: 100%; padding: 4vw 50px; overflow-x: scroll; - background-color: var(--bg-bright); + background-color: var(--lightest-color); display: grid; grid-template-columns: 1fr auto 1fr; } @@ -459,13 +459,13 @@ .axisName { font-size: 1.2em; - color: var(--font-light); + color: var(--dark-color); } .loading { min-width: 600px; margin: 0 auto; - background-color: var(--bg-medium); + background-color: var(--light-color); position: absolute; top: 0; left: 0; @@ -485,7 +485,7 @@ .generateButton { padding: 10px 20px; background-color: var(--primary-color); - color: var(--font-bright); + color: var(--light-color); border: none; border-radius: 5px; cursor: pointer; @@ -494,7 +494,7 @@ .generateButton:hover { background-color: var(--primary-color-desaturated); - color: var(--font-light); + color: var(--dark-color); } .columnSwitchButton { diff --git a/packages/safe-ds-eda/src/components/utilities/DropDownButton.svelte b/packages/safe-ds-eda/src/components/utilities/DropDownButton.svelte index 8137591e7..d25ed7077 100644 --- a/packages/safe-ds-eda/src/components/utilities/DropDownButton.svelte +++ b/packages/safe-ds-eda/src/components/utilities/DropDownButton.svelte @@ -59,7 +59,7 @@ {/if}
- +
@@ -100,7 +100,7 @@ align-items: center; padding: 0px 0px 0px 15px; border-radius: 5px; - background-color: var(--bg-dark); + background-color: var(--medium-light-color); height: 100%; width: 100%; cursor: pointer; @@ -113,7 +113,7 @@ } .dropdownButton .buttonText { - color: var(--font-dark); + color: var(--darkest-color); font-size: inherit; font-family: sans-serif; text-overflow: ellipsis; @@ -136,7 +136,7 @@ height: 100%; display: inline-block; border-radius: 5px 0px 0px 5px; - background-color: var(--bg-most-dark); + background-color: var(--medium-color); transform: rotate(180deg); display: flex; align-items: center; @@ -159,12 +159,12 @@ } .disabledButton:hover { - background-color: var(--bg-dark) !important; + background-color: var(--medium-light-color) !important; } .dropdownMenu { position: absolute; /* Positioning the dropdown near the button */ - background-color: var(--bg-dark); + background-color: var(--medium-light-color); border-radius: 0px 0px 5px 5px; list-style: none; padding: 0; @@ -175,7 +175,7 @@ .dropdownItem { padding: 10px 15px; - color: var(--font-dark); + color: var(--darkest-color); cursor: pointer; font-size: 16px; font-family: sans-serif; @@ -192,7 +192,7 @@ .itemComment { font-size: 0.7em; - color: var(--font-light); + color: var(--dark-color); } .dropdownItem:hover { diff --git a/packages/safe-ds-eda/src/icons/Caret.svelte b/packages/safe-ds-eda/src/icons/Caret.svelte index 4046bb43f..5d6599d0d 100644 --- a/packages/safe-ds-eda/src/icons/Caret.svelte +++ b/packages/safe-ds-eda/src/icons/Caret.svelte @@ -1,5 +1,5 @@ diff --git a/packages/safe-ds-eda/src/icons/Plus.svelte b/packages/safe-ds-eda/src/icons/Plus.svelte index c13ab58ff..5f50a54de 100644 --- a/packages/safe-ds-eda/src/icons/Plus.svelte +++ b/packages/safe-ds-eda/src/icons/Plus.svelte @@ -2,7 +2,7 @@ -
+
60 + ? 'inline-flex' + : 'none'} + >
@@ -743,9 +748,10 @@ bind:this={headerElements[index]} class:reorderHighlightedLeft={isReorderDragging && dragCurrentIndex === index} class="hiddenColumnHeader" - on:mousedown={(event) => handleColumnInteractionStart(event, index)} + on:mousedown={(event) => + event.button === 2 ? handleColumnRightClick(event, index) : null} on:mousemove={(event) => throttledHandleReorderDragOver(event, index)} - >... + > {/if} {/each} @@ -1241,5 +1247,6 @@ background-color: var(--medium-light-color); width: 10px; padding: 0px; + cursor: default; } From 01679ccf8a5824d1758496cb2890779eb9785084 Mon Sep 17 00:00:00 2001 From: Jonas B Date: Mon, 27 May 2024 16:03:27 +0200 Subject: [PATCH 19/32] fix: hiddenCol reorder drag over & resize min width --- .../src/components/TableView.svelte | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/packages/safe-ds-eda/src/components/TableView.svelte b/packages/safe-ds-eda/src/components/TableView.svelte index b70d63097..86c156159 100644 --- a/packages/safe-ds-eda/src/components/TableView.svelte +++ b/packages/safe-ds-eda/src/components/TableView.svelte @@ -151,6 +151,7 @@ const doResizeDrag = function (event: MouseEvent): void { if (isResizeDragging && targetColumn) { const currentWidth = startWidth + event.clientX - startX; + if (currentWidth < 20) return; // Minimum width requestAnimationFrame(() => { targetColumn.style.width = `${currentWidth}px`; savedColumnWidths.update((map) => { @@ -746,7 +747,8 @@ {:else}
event.button === 2 ? handleColumnRightClick(event, index) : null} @@ -1144,7 +1146,23 @@ } .reorderHighlightedLeft { - background: linear-gradient(to left, #036ed1 0%, #036ed1 calc(100% - 4px), white calc(100% - 4px), white 100%); + background: linear-gradient( + to left, + var(--primary-color) 0%, + var(--primary-color) calc(100% - 4px), + var(--lightest-color) calc(100% - 4px), + var(--lightest-color) 100% + ); + } + + .reorderHighlightedLeftHiddenColumn { + background: linear-gradient( + to left, + var(--medium-light-color) 0%, + var(--medium-light-color) calc(100% - 4px), + var(--dark-color) calc(100% - 2px), + var(--dark-color) 100% + ); } .dragging { @@ -1245,7 +1263,7 @@ .hiddenColumnHeader { background-color: var(--medium-light-color); - width: 10px; + width: 15px; padding: 0px; cursor: default; } From 67d65de31b9fd4b01456e0986b069c14b75d7dd1 Mon Sep 17 00:00:00 2001 From: Jonas B Date: Mon, 27 May 2024 16:04:28 +0200 Subject: [PATCH 20/32] refactor --- packages/safe-ds-eda/src/components/ColumnCounts.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/safe-ds-eda/src/components/ColumnCounts.svelte b/packages/safe-ds-eda/src/components/ColumnCounts.svelte index e8cd08631..6536bd4fe 100644 --- a/packages/safe-ds-eda/src/components/ColumnCounts.svelte +++ b/packages/safe-ds-eda/src/components/ColumnCounts.svelte @@ -79,7 +79,7 @@ > {/each} {#if $table.columns.filter((col) => col.hidden).length === 0} - + {/if} {/if} From 73844a7e245425d8ac20f8c7e75f921a3925380e Mon Sep 17 00:00:00 2001 From: Jonas B Date: Mon, 27 May 2024 16:16:52 +0200 Subject: [PATCH 21/32] fix: bug --- .../safe-ds-eda/src/components/profiling/ProfilingInfo.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/safe-ds-eda/src/components/profiling/ProfilingInfo.svelte b/packages/safe-ds-eda/src/components/profiling/ProfilingInfo.svelte index d36cca251..8d56e633e 100644 --- a/packages/safe-ds-eda/src/components/profiling/ProfilingInfo.svelte +++ b/packages/safe-ds-eda/src/components/profiling/ProfilingInfo.svelte @@ -26,12 +26,12 @@ content: { columnName, encodedImage: profilingItem.value, - outdated: false, }, id: crypto.randomUUID(), imageTab: true, columnNumber: 'one', isInGeneration: false, + outdated: false, }, ); }; From 4b448f7a569c8ff68e437a9cb437bd105cbae1b0 Mon Sep 17 00:00:00 2001 From: megalinter-bot <129584137+megalinter-bot@users.noreply.github.com> Date: Mon, 27 May 2024 14:19:02 +0000 Subject: [PATCH 22/32] style: apply automated linter fixes --- packages/safe-ds-eda/src/components/ColumnCounts.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/safe-ds-eda/src/components/ColumnCounts.svelte b/packages/safe-ds-eda/src/components/ColumnCounts.svelte index 6536bd4fe..a6e9d3875 100644 --- a/packages/safe-ds-eda/src/components/ColumnCounts.svelte +++ b/packages/safe-ds-eda/src/components/ColumnCounts.svelte @@ -61,7 +61,7 @@ action: 'showColumn', alias: `Show column ${columnName}`, type: 'internal', - columnName: columnName, + columnName, }); }; From 46aaf2c60499fc42d7ceccd219102210e024be05 Mon Sep 17 00:00:00 2001 From: Jonas B Date: Tue, 28 May 2024 10:39:18 +0200 Subject: [PATCH 23/32] feat: sort hover effect --- .../src/components/TableView.svelte | 13 ++++++++++-- packages/safe-ds-eda/src/icons/Caret.svelte | 20 ++++++++++++++++++- packages/safe-ds-eda/src/icons/Filter.svelte | 2 +- packages/safe-ds-vscode/media/styles.css | 3 ++- 4 files changed, 33 insertions(+), 5 deletions(-) diff --git a/packages/safe-ds-eda/src/components/TableView.svelte b/packages/safe-ds-eda/src/components/TableView.svelte index 86c156159..14aac3e4f 100644 --- a/packages/safe-ds-eda/src/components/TableView.svelte +++ b/packages/safe-ds-eda/src/components/TableView.svelte @@ -555,6 +555,9 @@ }; //#endregion // Plotting + //#region Sorting + //#endregion // Sorting + //#region Profiling --- let fullHeadBackground: HTMLElement; let profilingInfo: HTMLElement; @@ -735,10 +738,16 @@ : 'none'} >
- +
- +