Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Advanced Timeline Filtering #1535

Open
wants to merge 92 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 88 commits
Commits
Show all changes
92 commits
Select commit Hold shift + click to select a range
050c46b
WIP
AaronPlave Oct 21, 2024
55e7706
Fixes for spans
AaronPlave Oct 30, 2024
edd0037
Componentize editor section headers. Remove unused styles.
AaronPlave Oct 31, 2024
61b72eb
Refactoring, fixing, improving timeline editor
AaronPlave Oct 31, 2024
e5d558b
Add icon
AaronPlave Nov 5, 2024
6a9fbef
Add svelte dragging library
AaronPlave Nov 5, 2024
2595f71
Style refactor
AaronPlave Nov 5, 2024
fa0b8bf
Make menu items tab focusable
AaronPlave Nov 5, 2024
3e740e5
Style tweak
AaronPlave Nov 5, 2024
1b1f16e
WIP activity layer filtering
AaronPlave Nov 5, 2024
e24e16b
style refactor
AaronPlave Nov 5, 2024
d4cfdb2
Get default activity arguments during plan load
AaronPlave Nov 12, 2024
69c0f4e
Potentially remove unused input workaround
AaronPlave Nov 12, 2024
251e6b5
WIP filtering
AaronPlave Nov 12, 2024
ef8a7b4
Basic manual type selection list filtering
AaronPlave Nov 12, 2024
52cd277
add cssgrid to activity filter builder
AaronPlave Nov 12, 2024
3f0cae1
Bring back resource editing in old form for now
AaronPlave Nov 12, 2024
cab3815
Remove all layer fixes
AaronPlave Nov 12, 2024
23ac089
Style fix
AaronPlave Nov 12, 2024
650525f
Refactor
AaronPlave Nov 12, 2024
a15739c
Show an indication dot when an activity layer has active filters
AaronPlave Nov 12, 2024
3ed8826
Show resulting type and instance counts
AaronPlave Nov 12, 2024
3c0a6a8
Greater/less than support
AaronPlave Nov 12, 2024
b657b24
Remove log
AaronPlave Nov 12, 2024
1f2d615
use > and <
AaronPlave Nov 12, 2024
5896f10
Reactivity fix
AaronPlave Nov 12, 2024
91d5d54
Support boolean comparison
AaronPlave Nov 12, 2024
6ac5399
Input filtering
AaronPlave Nov 21, 2024
6c548ca
Bug fix
AaronPlave Nov 21, 2024
d7a961e
Support for min col and row css grid sizing
AaronPlave Nov 21, 2024
111d808
Add derived subsystem tags store. Fix subsystem tags filtering.
AaronPlave Nov 21, 2024
cd183c0
Tooltip tweaks
AaronPlave Nov 21, 2024
949689a
Fixes
AaronPlave Nov 21, 2024
c5805a3
Scheduling goal id filtering WIP support
AaronPlave Nov 21, 2024
18a4c90
Manual type selection menu positioning fix
AaronPlave Nov 21, 2024
4d24008
Filter parameter possibilities by resulting types if any. List all re…
AaronPlave Nov 22, 2024
839d2cb
Tweak SearchableDropdown to be more like a dropdown
AaronPlave Nov 22, 2024
8738671
Popper positioning workaround when inside css transformed parent. Use…
AaronPlave Nov 22, 2024
933a3a6
Fixes and refactoring
AaronPlave Nov 25, 2024
0a8c461
Fixes
AaronPlave Nov 26, 2024
1e1e5f2
Support within and not within
AaronPlave Nov 26, 2024
bfe0d7b
Apply subfilters to resulting types
AaronPlave Nov 30, 2024
dd3fa2e
Type fixes
AaronPlave Nov 30, 2024
2b5999c
Type fixes
AaronPlave Nov 30, 2024
ce3229c
Fixes and refactoring
AaronPlave Dec 2, 2024
31d4342
Bug fix for confirm modal when adding activities to timeline
AaronPlave Dec 3, 2024
4085ab6
Type fix
AaronPlave Dec 5, 2024
ac87fcd
Swap x-range icon
AaronPlave Dec 17, 2024
eba5450
Add left slot to MenuHeader
AaronPlave Dec 17, 2024
640ef76
Refactor SearchableDropdown to allow for multiple options if requested
AaronPlave Dec 17, 2024
13092c6
Style and functionality updates for all layers
AaronPlave Dec 17, 2024
96581d9
Bug fixes
AaronPlave Dec 17, 2024
7b954e1
Bug fixes
AaronPlave Dec 17, 2024
4d97f7b
Use resource layer name as override on SearchableDropdown selected op…
AaronPlave Dec 17, 2024
1e05be9
Show units for duration, int, and real parameters. Filter window drag…
AaronPlave Dec 17, 2024
ce208e7
Show all activities when no filters exist on layer. Bug fix.
AaronPlave Dec 17, 2024
5bd1530
Virtualization of SearchableDropdown. Fixes.
AaronPlave Dec 18, 2024
eeba7b1
View migration to v2
AaronPlave Dec 19, 2024
4f2ac4f
Prevent highlight on drag
AaronPlave Dec 19, 2024
b939ec3
Improve auto width sizing of SearchableDropdown
AaronPlave Dec 19, 2024
9ed8e65
Fixes
AaronPlave Dec 20, 2024
c46718c
Type fix
AaronPlave Dec 20, 2024
33c39bc
Type fixes
AaronPlave Dec 20, 2024
3ea7ca8
Type fix
AaronPlave Dec 20, 2024
85815f8
Tests
AaronPlave Dec 20, 2024
e3a1e39
Remove unused code
AaronPlave Dec 20, 2024
e196337
Type fixes
AaronPlave Dec 20, 2024
ab5576e
Tests. Rename field Tag -> Tags. Remove unused is_one_of and is_not_o…
AaronPlave Dec 20, 2024
781b78f
Fix
AaronPlave Dec 20, 2024
027b8b3
Test fix
AaronPlave Dec 20, 2024
f9787ed
Unit test fixes
AaronPlave Dec 23, 2024
702b9b6
Tweak
AaronPlave Dec 23, 2024
c7fd1f5
Aria labels
AaronPlave Dec 23, 2024
b0f93ff
Dispatch show event
AaronPlave Dec 23, 2024
10db2c1
Aria fixes
AaronPlave Dec 23, 2024
5fd5f49
Basic test for DynamicFilter
AaronPlave Dec 23, 2024
c609e0e
Remove log
AaronPlave Dec 23, 2024
8a44eb6
Remove .only
AaronPlave Dec 23, 2024
753248c
Fixes
AaronPlave Dec 30, 2024
fac7ae8
e2e tests and fixes
AaronPlave Dec 30, 2024
cb90b1e
Remove pause in e2e test
AaronPlave Dec 30, 2024
48813fd
Test fix
AaronPlave Dec 31, 2024
71af95f
Test fixes
AaronPlave Dec 31, 2024
e46e8bd
Fixes
AaronPlave Dec 31, 2024
5eb918e
Remove logs
AaronPlave Dec 31, 2024
b7fbd44
Add tooltip
AaronPlave Jan 2, 2025
a0fb1d8
Use Activity Layer instead of All Activities as default activity laye…
AaronPlave Jan 6, 2025
11e2d90
Change global filters to other filters
AaronPlave Jan 6, 2025
4658bcf
Turn off native autosuggest in various inputs. Add string input for t…
AaronPlave Jan 6, 2025
f8bea69
Fix within input values
AaronPlave Jan 6, 2025
dd79f3c
Filtering bug fixes and refactoring
AaronPlave Jan 6, 2025
c09c288
Resource layer tests
AaronPlave Jan 6, 2025
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
25 changes: 13 additions & 12 deletions e2e-tests/fixtures/Plan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ export class Plan {
}

async fillActivityPresetName(presetName: string) {
await this.panelActivityForm.getByRole('button', { name: 'Set Preset' }).click();
await this.panelActivityForm.getByRole('combobox', { name: 'None' }).click();
await this.panelActivityForm.locator('.dropdown-header').waitFor({ state: 'attached' });
await this.panelActivityForm.getByPlaceholder('Enter preset name').click();
await this.panelActivityForm.getByPlaceholder('Enter preset name').fill(presetName);
Expand All @@ -269,7 +269,7 @@ export class Plan {
}

async fillSimulationTemplateName(templateName: string) {
await this.panelSimulation.getByRole('button', { name: 'Set Template' }).click();
await this.panelSimulation.locator('div[name="Set Template"]').click();
await this.panelSimulation.locator('.dropdown-header').waitFor({ state: 'attached' });
await this.panelSimulation.getByPlaceholder('Enter template name').click();
await this.panelSimulation.getByPlaceholder('Enter template name').fill(templateName);
Expand Down Expand Up @@ -372,22 +372,23 @@ export class Plan {
}

async selectActivityAnchorByIndex(index: number) {
await this.panelActivityForm.getByRole('button', { name: 'Set Anchor' }).click();
const anchorCollapse = this.panelActivityForm.getByRole('group', { name: 'Anchor-collapse' });
await anchorCollapse.getByRole('combobox').click();

await this.panelActivityForm.getByRole('menuitem').nth(index).waitFor({ state: 'attached' });
const anchorMenuName = await this.panelActivityForm.getByRole('menuitem').nth(index)?.innerText();
await this.panelActivityForm.getByRole('menuitem').nth(index).click();
await this.panelActivityForm.getByRole('menuitem').nth(index).waitFor({ state: 'detached' });
await anchorCollapse.getByRole('menuitem').nth(index).waitFor({ state: 'attached' });
const anchorMenuName = await anchorCollapse.getByRole('menuitem').nth(index)?.innerText();
await anchorCollapse.getByRole('menuitem').nth(index).click();
await anchorCollapse.getByRole('menuitem').nth(index).waitFor({ state: 'detached' });

await this.page.waitForFunction(
anchorMenuName => document.querySelector('.anchor-form .selected-display-value')?.innerHTML === anchorMenuName,
anchorMenuName,
);
await expect(this.panelActivityForm.getByRole('textbox', { name: anchorMenuName })).toBeVisible();
await expect(anchorCollapse.getByRole('combobox', { name: anchorMenuName })).toBeVisible();
}

async selectActivityPresetByName(presetName: string) {
await this.panelActivityForm.getByRole('button', { name: 'Set Preset' }).click();
await this.panelActivityForm.locator('div[name="Set Preset"]').click();

await this.panelActivityForm.getByRole('menuitem', { name: presetName }).waitFor({ state: 'attached' });
await this.panelActivityForm.getByRole('menuitem', { name: presetName }).click();
Expand All @@ -413,11 +414,11 @@ export class Plan {
document.querySelector('.activity-preset-input-container .selected-display-value')?.innerHTML === presetName,
presetName,
);
await expect(this.panelActivityForm.getByRole('textbox', { name: presetName })).toBeVisible();
await expect(this.panelActivityForm.getByRole('combobox', { name: presetName })).toBeVisible();
}

async selectSimulationTemplateByName(templateName: string) {
await this.panelSimulation.getByRole('button', { name: 'Set Template' }).click();
await this.panelSimulation.locator('div[name="Set Template"]').click();

await this.panelSimulation.getByRole('menuitem', { name: templateName }).waitFor({ state: 'attached' });
await this.panelSimulation.getByRole('menuitem', { name: templateName }).click();
Expand All @@ -444,7 +445,7 @@ export class Plan {
templateName,
templateName,
);
await expect(this.panelSimulation.getByRole('textbox', { name: templateName })).toBeVisible();
await expect(this.panelSimulation.getByRole('combobox', { name: templateName })).toBeVisible();
}

async showConstraintsLayout() {
Expand Down
2 changes: 1 addition & 1 deletion e2e-tests/tests/plan-activities.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ test.describe.serial('Plan Activities', () => {
() => document.querySelector('.anchor-form .selected-display-value')?.innerHTML === 'To Plan',
);

await expect(plan.panelActivityForm.getByRole('textbox', { name: 'To Plan' })).toBeVisible();
await expect(plan.panelActivityForm.getByRole('combobox', { name: 'To Plan' })).toBeVisible();
});

test('Deleting multiple activity directives but only 1 has a remaining anchored dependent should prompt for just the one with a remaining dependent', async () => {
Expand Down
10 changes: 5 additions & 5 deletions e2e-tests/tests/plan-activity-presets.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ test.beforeAll(async ({ baseURL, browser }) => {

await plan.selectActivityPresetByName('None');

await expect(plan.panelActivityForm.getByRole('textbox', { name: 'None' })).toBeVisible();
await expect(plan.panelActivityForm.getByRole('combobox', { name: 'None' })).toBeVisible();
});

test.afterAll(async () => {
Expand All @@ -72,18 +72,18 @@ test.afterAll(async () => {
test.describe.serial('Plan Activity Presets', () => {
test(`Setting a preset to a directive should update the parameter values`, async () => {
await plan.selectActivityPresetByName('Preset 1');
await expect(page.getByRole('textbox', { name: 'Preset 1' })).toBeVisible();
await expect(page.getByRole('combobox', { name: 'Preset 1' })).toBeVisible();
});

test(`Removing an activity preset from a directive should reflect that it is no longer present`, async () => {
await plan.selectActivityPresetByName('None');
await expect(page.getByRole('textbox', { name: 'None' })).toBeVisible();
await expect(page.getByRole('combobox', { name: 'None' })).toBeVisible();
});

test('Deleting an activity preset should remove it from the list of presets', async () => {
await plan.selectActivityPresetByName('Preset 1');

await page.getByRole('button', { name: 'Set Preset' }).click();
await page.getByRole('combobox', { name: 'Preset 1' }).click();

await page.getByRole('button', { name: 'Delete preset' }).waitFor({ state: 'attached' });
await page.getByRole('button', { name: 'Delete preset' }).click();
Expand All @@ -96,6 +96,6 @@ test.describe.serial('Plan Activity Presets', () => {
() => document.querySelector('.activity-preset-input-container .selected-display-value')?.innerHTML === 'None',
);

await expect(page.getByRole('textbox', { name: 'None' })).toBeVisible();
await expect(page.getByRole('combobox', { name: 'None' })).toBeVisible();
});
});
10 changes: 5 additions & 5 deletions e2e-tests/tests/plan-simulation-templates.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ test.beforeAll(async ({ baseURL, browser }) => {

await plan.selectSimulationTemplateByName('None');

expect(page.getByRole('textbox', { name: 'None' })).toBeVisible();
expect(page.getByRole('combobox', { name: 'None' })).toBeVisible();
});

test.afterAll(async () => {
Expand All @@ -75,19 +75,19 @@ test.describe.serial('Plan Simulation Templates', async () => {
test(`Setting a simulation template to a simulation should update the parameter values`, async () => {
await plan.selectSimulationTemplateByName('Template 1');

expect(plan.panelSimulation.getByRole('textbox', { name: 'Template 1' })).toBeVisible();
expect(plan.panelSimulation.getByRole('combobox', { name: 'Template 1' })).toBeVisible();
});

test(`Removing an simulation template from a simulation should reflect that it is no longer present`, async () => {
await plan.selectSimulationTemplateByName('None');

expect(page.getByRole('textbox', { name: 'None' })).toBeVisible();
expect(page.getByRole('combobox', { name: 'None' })).toBeVisible();
});

test('Deleting an simulation template should remove it from the list of templates', async () => {
await plan.selectSimulationTemplateByName('Template 1');

await page.getByRole('button', { name: 'Set Template' }).click();
await plan.panelSimulation.locator('div[name="Set Template"]').click();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's no longer possible to get this by role?


await page.getByRole('button', { name: 'Delete Template' }).waitFor({ state: 'attached' });
await page.getByRole('button', { name: 'Delete Template' }).click();
Expand All @@ -98,6 +98,6 @@ test.describe.serial('Plan Simulation Templates', async () => {

await page.waitForFunction(() => document.querySelector('.selected-display-value')?.innerHTML === 'None');

expect(page.getByRole('textbox', { name: 'None' })).toBeVisible();
expect(page.getByRole('combobox', { name: 'None' })).toBeVisible();
});
});
126 changes: 110 additions & 16 deletions e2e-tests/tests/timeline-view-editing.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ test.describe.serial('Timeline View Editing', () => {
test('Add an activity to the parent plan', async () => {
await plan.showPanel(PanelNames.TIMELINE_ITEMS);
await plan.addActivity('PickBanana');
await plan.addActivity('PeelBanana');
});

test('Change the start time of the activity', async () => {
Expand Down Expand Up @@ -105,26 +106,121 @@ test.describe.serial('Timeline View Editing', () => {
// Look for back button indicating that the row editor is active
expect(page.locator('.section-back-button ').first()).toBeDefined();

const existingLayerCount = await page.locator('.timeline-layer').count();

// Give the row a name
await page.locator('input[name="name"]').first().fill(rowName);
await page.locator('input[name="name"]').first().blur();
});

test('Add an activity layer', async () => {
const activityLayerEditor = page.getByLabel('Activity Layer-editor');
const existingLayerCount = await activityLayerEditor.locator('.timeline-layer-editor').count();

// Add a layer
await page.getByRole('button', { name: 'New Layer' }).click();
const newLayerCount = await page.locator('.timeline-layer').count();
// Add an activity layer
await activityLayerEditor.getByRole('button', { name: 'New Activity Layer' }).click();
const newLayerCount = await activityLayerEditor.locator('.timeline-layer-editor').count();
expect(newLayerCount - existingLayerCount).toEqual(1);

// Expect an activity layer to be created by default
expect(await page.locator('select[name="chartType"]').last().inputValue()).toBe('activity');
// Expect the activity layer to include all activities
expect(await activityLayerEditor.locator('.timeline-layer-editor').first()).toHaveText('Activity Layer');
});

// Expect the filter list to open
await page.getByPlaceholder('Search').last().click();
await expect(page.locator('.menu-slot > .header')).toBeDefined();
test('Edit an activity layer', async () => {
const activityLayerEditor = page.getByLabel('Activity Layer-editor');

// Open the activity filter builder
await activityLayerEditor
.locator('.timeline-layer-editor')
.first()
.getByLabel('activity-filter-builder-trigger')
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a little hesitant about using the label as a way to select the button in the test. Anyway this could be getting the button by text or role?

.click();

// Expect that the modal is present
const modal = activityLayerEditor.getByLabel('activity-filter-builder');
expect(modal).toBeDefined();

// Expect that layer name is showing in the name input
expect(modal.locator('input[name="layer-name"]')).toHaveValue('Activity Layer');

// Expect that the resulting types list is not empty
const resultingTypesList = modal.locator('.resulting-types-list');
const allActivityTypesCount = await resultingTypesList.locator('.activity-type-result').count();
expect(allActivityTypesCount).toBeGreaterThan(0);

// Expect that manually selecting types cause the types to appear in the resulting types list
await modal.locator("input[name='manual-types-filter-input']").click();
expect(await modal.locator('.manual-types-menu').first()).toBeDefined();
await modal.getByRole('menuitem', { name: 'ChangeProducer' }).click();
await modal.getByRole('menuitem', { name: 'ControllableDurationActivity' }).click();
await page.keyboard.press('Escape');

expect(await resultingTypesList.getByText('ChangeProducer')).toBeDefined();
expect(await resultingTypesList.getByText('ControllableDurationActivity')).toBeDefined();

// Expect that dynamic types can be added
await modal.getByLabel('dynamic-types').getByRole('button', { name: 'Add Filter' }).click();
expect(await modal.getByLabel('dynamic-types').getByRole('listitem').count()).toBe(1);
await modal.getByLabel('dynamic-types').getByRole('listitem').locator("input[name='filter-value']").fill('banana');
expect(await resultingTypesList.locator('.activity-type-result').count()).toEqual(11);

// Expect that other filters can be added
await modal.getByLabel('other-filters').getByRole('button', { name: 'Add Filter' }).click();
expect(await modal.getByLabel('other-filters').getByRole('listitem').count()).toBe(1);
// Select parameter field
await modal.getByLabel('other-filters').locator("select[aria-label='field']").selectOption('Parameter');
// Select specific parameter
await modal.getByLabel('other-filters').getByText('Select Parameter').click();
await modal.getByLabel('other-filters').getByText('quantity (int)').click();
// Select operator
await modal.getByLabel('other-filters').locator("select[aria-label='operator']").selectOption('equals');
// Fill filter value input
await modal.getByLabel('other-filters').getByRole('listitem').locator("input[name='filter-value']").fill('10');
// Ensure that only one instance (PickBanana) is listed
expect(await modal.getByText('1 instance')).toBeDefined();

// Expect that type subfilters can be added
const activityResult = resultingTypesList.getByRole('listitem', { name: 'activity-type-result-PickBanana' });
await activityResult.getByRole('button', { name: 'Add Filter' }).click();
expect(await activityResult.getByRole('listitem').count()).toBe(1);
// Select name field
await activityResult.locator("select[aria-label='field']").selectOption('Name');
// Select operator
await activityResult.locator("select[aria-label='operator']").selectOption('includes');
// Fill filter value input
await activityResult.getByRole('listitem').locator("input[name='filter-value']").fill('foo');
// Ensure that only one instance (PickBanana) is listed
expect(await modal.getByText('0 instances')).toBeDefined();

// Expect that type subfilters can be removed
await activityResult.getByRole('button', { name: 'Remove filter' }).click();
expect(await modal.getByText('1 instance')).toBeDefined();

// Expect that other filters can be removed
await modal.getByLabel('other-filters').getByRole('button', { name: 'Remove filter' }).click();
expect(await modal.getByText('2 instances')).toBeDefined();

// Expect that dynamic types can be removed
await modal.getByLabel('dynamic-types').getByRole('button', { name: 'Remove filter' }).click();
expect(await resultingTypesList.locator('.activity-type-result').count()).toEqual(2);

// Expect that manual types can be cleared
await modal.locator("input[name='manual-types-filter-input']").click();
await modal.getByRole('menuitem', { name: 'ChangeProducer' }).click();
await page.keyboard.press('Escape');
await modal.getByRole('button', { name: 'Remove Types' }).click();
expect(await resultingTypesList.locator('.activity-type-result').count()).toEqual(allActivityTypesCount);

// Give the layer a new name
await modal.locator('input[name="layer-name"]').fill('Foo');

// Close the modal
await modal.getByRole('button', { name: 'close' }).click();

// Expect name to match given name
expect(await activityLayerEditor.locator('.timeline-layer-editor').first()).toHaveText('Foo');
});

// Add all activities
await page.locator('button', { hasText: /Select [0-9]* activit/ }).click();
test('Change activity layer settings', async () => {
const activityLayerEditor = await page.getByLabel('Activity Layer-editor');

// Expect to not see an activity tree group in this row
expect(await page.locator('.timeline-row-wrapper', { hasText: rowName }).locator('.activity-tree').count()).toBe(0);
Expand All @@ -141,9 +237,7 @@ test.describe.serial('Timeline View Editing', () => {
).toBe(1);

// Delete an activity layer
await page.getByRole('button', { name: 'Layer Settings' }).last().click();
await page.getByText('Delete Layer').click();
const finalLayerCount = await page.locator('.timeline-layer').count();
expect(finalLayerCount - newLayerCount).toEqual(-1);
await activityLayerEditor.locator('.timeline-layer-editor').first().getByRole('button', { name: 'Delete' }).click();
expect(await activityLayerEditor.locator('.timeline-layer-editor').count()).toBe(0);
});
});
34 changes: 34 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,11 @@
"@lezer/generator": "^1.7.0",
"@lezer/highlight": "^1.2.0",
"@lezer/lr": "^1.4.0",
"@neodrag/svelte": "^2.0.6",
"@playwright/test": "^1.44.0",
"@poppanator/sveltekit-svg": "^4.2.1",
"@sveltejs/vite-plugin-svelte": "^3.0.0",
"@tanstack/svelte-virtual": "^3.11.2",
"@testing-library/svelte": "^4.0.2",
"@types/cookie": "^0.6.0",
"@types/d3-array": "^3.0.5",
Expand Down
Loading
Loading