Skip to content

Commit

Permalink
feat: hide columns in EDA (#1181)
Browse files Browse the repository at this point in the history
Closes #1171 

### Summary of Changes

- You can now hide columns by right clicking them
- Showing either by right click again or by clicking on new column count
in sidebar footer (row count there too now)
- Hidden columns excluded from visualizations that affect whole table
(heatmap)
- Still possible to select in other visualizations but marked red and
with "hidden" in case you still want to use that column
- Hiding/Showing cols marks whole table vis as "outdated"
- TabContent evaluates which vis types allow non numeric columns and
manages buildATab and drop downs based on that

Other fixes/refactors:
- Context menu on columns
- Profiling errors present evaluation
- Color css vars
- Sidebar width reactivity

---------

Co-authored-by: megalinter-bot <[email protected]>
Co-authored-by: Lars Reimann <[email protected]>
  • Loading branch information
3 people authored May 29, 2024
1 parent 846f404 commit 15ccaac
Show file tree
Hide file tree
Showing 21 changed files with 739 additions and 241 deletions.
5 changes: 4 additions & 1 deletion packages/safe-ds-eda/src/App.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,9 @@
flex-shrink: 0;
overflow: hidden;
position: relative;
background-color: var(--bg-dark);
background-color: var(--medium-light-color);
height: 100%;
z-index: 10;
}
.contentWrapper {
Expand All @@ -91,6 +93,7 @@
top: 0;
cursor: ew-resize;
background-color: transparent;
z-index: 20;
}
.hide {
Expand Down
35 changes: 33 additions & 2 deletions packages/safe-ds-eda/src/apis/extensionApi.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { get } from 'svelte/store';
import type { HistoryEntry } from '../../types/state';
import { table } from '../webviewState';

export const createInfoToast = function (message: string) {
window.injVscode.postMessage({ command: 'setInfo', value: message });
Expand All @@ -8,6 +10,35 @@ 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 executeRunnerExcludingHiddenColumns = function (
pastEntries: HistoryEntry[],
newEntry: HistoryEntry,
hiddenColumns: string[],
) {
window.injVscode.postMessage({
command: 'executeRunner',
value: { pastEntries, newEntry, hiddenColumns, type: 'excludingHiddenColumns' },
});
};

const executeRunnerDefault = function (pastEntries: HistoryEntry[], newEntry: HistoryEntry) {
window.injVscode.postMessage({
command: 'executeRunner',
value: { pastEntries, newEntry, type: 'default' },
});
};

export const executeRunner = function (state: HistoryEntry[], entry: HistoryEntry) {
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 {
executeRunnerDefault(state, entry);
}
};
45 changes: 38 additions & 7 deletions packages/safe-ds-eda/src/apis/historyApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -38,7 +38,7 @@ window.addEventListener('message', (event) => {
return;
}

deployResult(message);
deployResult(message, asyncQueue[0]);
asyncQueue.shift();
evaluateMessagesWaitingForTurn();
} else if (message.command === 'cancelRunnerExecution') {
Expand All @@ -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 {
Expand All @@ -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) {
Expand Down Expand Up @@ -129,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');
Expand Down Expand Up @@ -174,7 +178,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) {
Expand All @@ -198,6 +202,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');
}
};

Expand All @@ -209,7 +216,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) {
Expand All @@ -220,3 +227,27 @@ 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 === 'hideColumn' || entry.action === 'showColumn') {
tabs.update((state) => {
const newTabs = state.map((t) => {
if (
t.type !== 'empty' &&
t.columnNumber === 'none' &&
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 {
return t;
}
});

return newTabs;
});
}
};
146 changes: 146 additions & 0 deletions packages/safe-ds-eda/src/components/ColumnCounts.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
<script lang="ts">
import { addInternalToHistory } from '../apis/historyApi';
import { disableNonContextMenuEffects, restoreNonContextMenuEffects } from '../toggleNonContextMenuEffects';
import { preventClicks, table } from '../webviewState';
export let flexAsRow = true;
let contextMenuVisible = false;
let menuRef: HTMLElement;
const toggleMenu = function () {
if ($preventClicks) return;
contextMenuVisible = !contextMenuVisible;
if (contextMenuVisible) {
preventClicks.set(true);
disableNonContextMenuEffects();
document.addEventListener('click', handleClickOutside);
} else {
document.removeEventListener('click', handleClickOutside);
}
};
const handleClickOutside = function (event: MouseEvent) {
if (contextMenuVisible) {
if (menuRef && !menuRef.contains(event.target as Node)) {
preventClicks.set(false);
restoreNonContextMenuEffects();
contextMenuVisible = false;
document.removeEventListener('click', handleClickOutside);
}
}
};
const selectOption = function (callback: () => void) {
preventClicks.set(false);
restoreNonContextMenuEffects();
contextMenuVisible = false;
document.removeEventListener('click', handleClickOutside);
callback();
};
const showColumn = function (columnName: string) {
table.update(($table) => {
return {
...$table!,
columns: $table!.columns.map((column) => {
if (columnName === column.name) {
return {
...column,
hidden: false,
};
}
return column;
}),
};
});
addInternalToHistory({
action: 'showColumn',
alias: `Show column ${columnName}`,
type: 'internal',
columnName,
});
};
</script>

<div class="wrapper" bind:this={menuRef}>
<div class="text" class:textColumn={!flexAsRow} on:mouseup={toggleMenu} role="none">
<span>{$table?.columns.filter((col) => !col.hidden).length ?? 0}/{$table?.columns.length ?? 0}</span>
<span>Columns </span>
</div>
{#if contextMenuVisible && $table}
<div class="contextMenu">
{#each $table.columns.filter((col) => col.hidden) as column}
<button class="contextItem" on:click={() => selectOption(() => showColumn(column.name))}
>Show {column.name}</button
>
{/each}
{#if $table.columns.filter((col) => col.hidden).length === 0}
<button disabled>All visible</button>
{/if}
</div>
{/if}
</div>

<style>
.wrapper {
cursor: pointer;
position: relative;
z-index: 100;
}
.text {
display: flex;
flex-direction: row;
justify-content: center;
align-items: flex-start;
gap: 5px;
}
.textColumn {
flex-direction: column;
gap: 0px;
}
.text:hover * {
color: var(--dark-color);
}
.contextMenu {
position: absolute;
border: 2px solid var(--medium-light-color);
background-color: var(--lightest-color);
z-index: 100;
padding: 0;
color: var(--darkest-color);
display: flex;
flex-direction: column;
width: max-content;
left: 50%;
bottom: 100%;
min-width: 100px;
}
.contextMenu button {
padding: 5px 15px;
cursor: pointer;
background-color: var(--lightest-color);
color: var(--darkest-color);
text-align: left;
width: 100%;
}
.contextMenu button:hover {
background-color: var(--primary-color);
color: var(--light-color);
}
.contextMenu :disabled:hover {
background-color: var(--lightest-color);
color: var(--darkest-color);
cursor: default;
}
</style>
Loading

0 comments on commit 15ccaac

Please sign in to comment.