@genspectrum/dashboard-components 0.6.13 → 0.6.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/README.md +29 -0
  2. package/custom-elements.json +23 -17
  3. package/dist/dashboard-components.js +8237 -37898
  4. package/dist/dashboard-components.js.map +1 -1
  5. package/dist/genspectrum-components.d.ts +26 -8
  6. package/dist/style.css +3 -3
  7. package/package.json +9 -8
  8. package/src/index.ts +8 -0
  9. package/src/lapisApi/lapisApi.ts +15 -7
  10. package/src/operator/FetchAggregatedOperator.ts +2 -7
  11. package/src/preact/components/color-scale-selector-dropdown.stories.tsx +1 -1
  12. package/src/preact/components/error-boundary.stories.tsx +21 -4
  13. package/src/preact/components/error-display.stories.tsx +20 -2
  14. package/src/preact/components/error-display.tsx +64 -10
  15. package/src/preact/components/info.stories.tsx +5 -5
  16. package/src/preact/components/info.tsx +2 -4
  17. package/src/preact/components/percent-intput.tsx +7 -2
  18. package/src/preact/components/proportion-selector.tsx +12 -2
  19. package/src/preact/dateRangeSelector/date-range-selector.stories.tsx +2 -3
  20. package/src/preact/dateRangeSelector/date-range-selector.tsx +4 -4
  21. package/src/preact/lineageFilter/lineage-filter.stories.tsx +2 -3
  22. package/src/preact/lineageFilter/lineage-filter.tsx +2 -2
  23. package/src/preact/locationFilter/fetchAutocompletionList.ts +1 -14
  24. package/src/preact/locationFilter/location-filter.stories.tsx +2 -3
  25. package/src/preact/locationFilter/location-filter.tsx +2 -2
  26. package/src/preact/mutationFilter/mutation-filter.stories.tsx +2 -3
  27. package/src/preact/mutationsOverTime/__mockData__/aggregated_2024_01.json +13 -0
  28. package/src/preact/mutationsOverTime/__mockData__/aggregated_2024_02.json +13 -0
  29. package/src/preact/mutationsOverTime/__mockData__/aggregated_2024_03.json +13 -0
  30. package/src/preact/mutationsOverTime/__mockData__/aggregated_2024_04.json +13 -0
  31. package/src/preact/mutationsOverTime/__mockData__/aggregated_2024_05.json +13 -0
  32. package/src/preact/mutationsOverTime/__mockData__/aggregated_2024_06.json +13 -0
  33. package/src/preact/mutationsOverTime/__mockData__/aggregated_2024_07.json +13 -0
  34. package/src/preact/mutationsOverTime/__mockData__/aggregated_20_01_2024.json +13 -0
  35. package/src/preact/mutationsOverTime/__mockData__/aggregated_21_01_2024.json +13 -0
  36. package/src/preact/mutationsOverTime/__mockData__/aggregated_22_01_2024.json +13 -0
  37. package/src/preact/mutationsOverTime/__mockData__/aggregated_23_01_2024.json +13 -0
  38. package/src/preact/mutationsOverTime/__mockData__/aggregated_24_01_2024.json +13 -0
  39. package/src/preact/mutationsOverTime/__mockData__/aggregated_25_01_2024.json +13 -0
  40. package/src/preact/mutationsOverTime/__mockData__/aggregated_26_01_2024.json +13 -0
  41. package/src/preact/mutationsOverTime/__mockData__/aggregated_tooManyMutations_total.json +13 -0
  42. package/src/preact/mutationsOverTime/__mockData__/aggregated_week3_2024.json +13 -0
  43. package/src/preact/mutationsOverTime/__mockData__/aggregated_week4_2024.json +13 -0
  44. package/src/preact/mutationsOverTime/__mockData__/aggregated_week5_2024.json +13 -0
  45. package/src/preact/mutationsOverTime/__mockData__/aggregated_week6_2024.json +13 -0
  46. package/src/preact/mutationsOverTime/getFilteredMutationsOverTime.spec.ts +56 -8
  47. package/src/preact/mutationsOverTime/mutations-over-time-grid.tsx +4 -2
  48. package/src/preact/mutationsOverTime/mutations-over-time.stories.tsx +135 -0
  49. package/src/preact/prevalenceOverTime/prevalence-over-time.tsx +3 -3
  50. package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.tsx +1 -1
  51. package/src/preact/shared/floating-ui/hooks.ts +1 -1
  52. package/src/preact/textInput/text-input.stories.tsx +2 -3
  53. package/src/preact/textInput/text-input.tsx +2 -2
  54. package/src/query/queryMutationsOverTime.spec.ts +210 -64
  55. package/src/query/queryMutationsOverTime.ts +10 -2
  56. package/src/web-components/app.stories.ts +0 -2
  57. package/src/web-components/errorHandling.mdx +8 -0
  58. package/src/web-components/input/gs-date-range-selector.stories.ts +2 -3
  59. package/src/web-components/input/gs-date-range-selector.tsx +24 -4
  60. package/src/web-components/input/gs-lineage-filter.stories.ts +2 -3
  61. package/src/web-components/input/gs-lineage-filter.tsx +15 -1
  62. package/src/web-components/input/gs-location-filter.stories.ts +2 -3
  63. package/src/web-components/input/gs-location-filter.tsx +13 -1
  64. package/src/web-components/input/gs-mutation-filter.stories.ts +2 -3
  65. package/src/web-components/input/gs-mutation-filter.tsx +1 -0
  66. package/src/web-components/input/gs-text-input.stories.ts +2 -3
  67. package/src/web-components/input/gs-text-input.tsx +13 -1
  68. package/src/web-components/visualization/gs-aggregate.tsx +17 -1
  69. package/src/web-components/visualization/gs-mutation-comparison.tsx +9 -0
  70. package/src/web-components/visualization/gs-mutations-over-time.stories.ts +271 -0
  71. package/src/web-components/visualization/gs-mutations-over-time.tsx +7 -0
  72. package/src/web-components/visualization/gs-mutations.tsx +11 -5
  73. package/src/web-components/visualization/gs-number-sequences-over-time.tsx +15 -0
  74. package/src/web-components/visualization/gs-prevalence-over-time.stories.ts +8 -9
  75. package/src/web-components/visualization/gs-prevalence-over-time.tsx +26 -8
  76. package/src/web-components/visualization/gs-relative-growth-advantage.tsx +43 -5
  77. package/standalone-bundle/dashboard-components.js +30920 -0
  78. package/standalone-bundle/dashboard-components.js.map +1 -0
@@ -178,6 +178,12 @@ export declare class DateRangeSelectorComponent extends PreactLitAdapter {
178
178
  render(): JSX_2.Element;
179
179
  }
180
180
 
181
+ declare class ErrorEvent_2 extends Event {
182
+ readonly error: Error;
183
+ constructor(error: Error);
184
+ }
185
+ export { ErrorEvent_2 as ErrorEvent }
186
+
181
187
  declare type LapisFilter = Record<string, string | number | null | boolean>;
182
188
 
183
189
  /**
@@ -948,11 +954,23 @@ export declare class TextInputComponent extends PreactLitAdapter {
948
954
  render(): JSX_2.Element;
949
955
  }
950
956
 
957
+ export declare class UserFacingError extends Error {
958
+ readonly headline: string;
959
+ constructor(headline: string, message: string);
960
+ }
961
+
951
962
  declare type View = 'table';
952
963
 
953
964
  export { }
954
965
 
955
966
 
967
+ declare global {
968
+ interface HTMLElementEventMap {
969
+ 'gs-error': ErrorEvent;
970
+ }
971
+ }
972
+
973
+
956
974
  declare global {
957
975
  interface HTMLElementTagNameMap {
958
976
  'gs-app': App;
@@ -990,21 +1008,21 @@ declare global {
990
1008
 
991
1009
  declare global {
992
1010
  interface HTMLElementTagNameMap {
993
- 'gs-number-sequences-over-time': NumberSequencesOverTimeComponent;
1011
+ 'gs-aggregate-component': AggregateComponent;
994
1012
  }
995
1013
  }
996
1014
 
997
1015
 
998
1016
  declare global {
999
1017
  interface HTMLElementTagNameMap {
1000
- 'gs-mutations-over-time-component': MutationsOverTimeComponent;
1018
+ 'gs-number-sequences-over-time': NumberSequencesOverTimeComponent;
1001
1019
  }
1002
1020
  }
1003
1021
 
1004
1022
 
1005
1023
  declare global {
1006
1024
  interface HTMLElementTagNameMap {
1007
- 'gs-aggregate-component': AggregateComponent;
1025
+ 'gs-mutations-over-time-component': MutationsOverTimeComponent;
1008
1026
  }
1009
1027
  }
1010
1028
 
@@ -1041,21 +1059,21 @@ declare global {
1041
1059
 
1042
1060
  declare global {
1043
1061
  interface HTMLElementTagNameMap {
1044
- 'gs-mutation-filter': MutationFilterComponent;
1062
+ 'gs-lineage-filter': LineageFilterComponent;
1045
1063
  }
1046
1064
  interface HTMLElementEventMap {
1047
- 'gs-mutation-filter-changed': CustomEvent<SelectedMutationFilterStrings>;
1048
- 'gs-mutation-filter-on-blur': CustomEvent<SelectedMutationFilterStrings>;
1065
+ 'gs-lineage-filter-changed': CustomEvent<Record<string, string>>;
1049
1066
  }
1050
1067
  }
1051
1068
 
1052
1069
 
1053
1070
  declare global {
1054
1071
  interface HTMLElementTagNameMap {
1055
- 'gs-lineage-filter': LineageFilterComponent;
1072
+ 'gs-mutation-filter': MutationFilterComponent;
1056
1073
  }
1057
1074
  interface HTMLElementEventMap {
1058
- 'gs-lineage-filter-changed': CustomEvent<Record<string, string>>;
1075
+ 'gs-mutation-filter-changed': CustomEvent<SelectedMutationFilterStrings>;
1076
+ 'gs-mutation-filter-on-blur': CustomEvent<SelectedMutationFilterStrings>;
1059
1077
  }
1060
1078
  }
1061
1079
 
package/dist/style.css CHANGED
@@ -376,7 +376,7 @@ input[type="range"] {
376
376
  background-color: #C6C6C6;
377
377
  pointer-events: none;
378
378
  }/*
379
- ! tailwindcss v3.4.7 | MIT License | https://tailwindcss.com
379
+ ! tailwindcss v3.4.10 | MIT License | https://tailwindcss.com
380
380
  *//*
381
381
  1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
382
382
  2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
@@ -3372,9 +3372,9 @@ input.tab:checked + .tab-content,
3372
3372
  --tw-text-opacity: 1;
3373
3373
  color: rgb(30 64 175 / var(--tw-text-opacity));
3374
3374
  }
3375
- .hover\:text-gray-300:hover {
3375
+ .hover\:text-gray-400:hover {
3376
3376
  --tw-text-opacity: 1;
3377
- color: rgb(209 213 219 / var(--tw-text-opacity));
3377
+ color: rgb(156 163 175 / var(--tw-text-opacity));
3378
3378
  }
3379
3379
  .hover\:text-gray-700:hover {
3380
3380
  --tw-text-opacity: 1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@genspectrum/dashboard-components",
3
- "version": "0.6.13",
3
+ "version": "0.6.15",
4
4
  "description": "GenSpectrum web components for building dashboards",
5
5
  "type": "module",
6
6
  "license": "AGPL-3.0-only",
@@ -16,18 +16,19 @@
16
16
  "require": "./dist/dashboard-components.js",
17
17
  "types": "./dist/genspectrum-components.d.ts"
18
18
  },
19
- "./custom-elements.json": "./custom-,elements.json",
19
+ "./custom-elements.json": "./custom-elements.json",
20
20
  "./package.json": "./package.json",
21
21
  "./style.css": "./dist/style.css"
22
22
  },
23
23
  "files": [
24
24
  "dist",
25
+ "standalone-bundle",
25
26
  "src",
26
27
  "custom-elements.json",
27
28
  "package.json"
28
29
  ],
29
30
  "scripts": {
30
- "build": "vite --config vite.release.config.ts build && npm run generate-manifest",
31
+ "build": "vite --config vite.release.config.ts build && npm run generate-manifest && vite --config vite.release-standalone.config.ts build",
31
32
  "build-and-pack": "npm run build && npm pack | xargs -I {} cp {} genspectrum-dashboard-components.tgz",
32
33
  "test": "vitest",
33
34
  "lint": "npm run lint:lit-analyzer && npm run lint:eslint",
@@ -67,9 +68,6 @@
67
68
  "dayjs": "^1.11.10",
68
69
  "flatpickr": "^4.6.13",
69
70
  "gridjs": "^6.2.0",
70
- "@iconify-json/mdi": "^1.1.67",
71
- "@iconify-json/mdi-light": "^1.1.10",
72
- "@iconify/tailwind": "^1.1.1",
73
71
  "lit": "^3.1.3",
74
72
  "object-hash": "^3.0.0",
75
73
  "preact": "^10.20.1",
@@ -77,6 +75,9 @@
77
75
  },
78
76
  "devDependencies": {
79
77
  "@custom-elements-manifest/analyzer": "^0.10.2",
78
+ "@iconify-json/mdi": "^1.1.67",
79
+ "@iconify-json/mdi-light": "^1.1.10",
80
+ "@iconify/tailwind": "^1.1.2",
80
81
  "@playwright/test": "^1.43.1",
81
82
  "@storybook/addon-actions": "^8.0.9",
82
83
  "@storybook/addon-essentials": "^8.0.9",
@@ -92,8 +93,8 @@
92
93
  "@storybook/web-components-vite": "^8.0.9",
93
94
  "@types/node": "^22.0.0",
94
95
  "@types/object-hash": "^3.0.6",
95
- "@typescript-eslint/eslint-plugin": "^7.14.1",
96
- "@typescript-eslint/parser": "^7.14.1",
96
+ "@typescript-eslint/eslint-plugin": "^8.2.0",
97
+ "@typescript-eslint/parser": "^8.2.0",
97
98
  "autoprefixer": "^10.4.19",
98
99
  "daisyui": "^4.10.2",
99
100
  "depcheck": "^1.4.7",
package/src/index.ts CHANGED
@@ -1 +1,9 @@
1
1
  export * from './web-components';
2
+
3
+ export { type ErrorEvent, UserFacingError } from './preact/components/error-display';
4
+
5
+ declare global {
6
+ interface HTMLElementEventMap {
7
+ 'gs-error': ErrorEvent;
8
+ }
9
+ }
@@ -15,6 +15,7 @@ export class UnknownLapisError extends Error {
15
15
  constructor(
16
16
  message: string,
17
17
  public readonly status: number,
18
+ public readonly requestedData: string,
18
19
  ) {
19
20
  super(message);
20
21
  this.name = 'UnknownLapisError';
@@ -26,6 +27,7 @@ export class LapisError extends Error {
26
27
  message: string,
27
28
  public readonly status: number,
28
29
  public readonly problemDetail: ProblemDetail,
30
+ public readonly requestedData: string,
29
31
  ) {
30
32
  super(message);
31
33
  this.name = 'LapisError';
@@ -42,7 +44,7 @@ export async function fetchAggregated(lapisUrl: string, body: LapisBaseRequest,
42
44
  signal,
43
45
  });
44
46
 
45
- await handleErrors(response);
47
+ await handleErrors(response, 'aggregated data');
46
48
 
47
49
  return aggregatedResponse.parse(await response.json());
48
50
  }
@@ -62,7 +64,7 @@ export async function fetchInsertions(
62
64
  signal,
63
65
  });
64
66
 
65
- await handleErrors(response);
67
+ await handleErrors(response, `${sequenceType} insertions`);
66
68
 
67
69
  return insertionsResponse.parse(await response.json());
68
70
  }
@@ -82,7 +84,7 @@ export async function fetchSubstitutionsOrDeletions(
82
84
  signal,
83
85
  });
84
86
 
85
- await handleErrors(response);
87
+ await handleErrors(response, `${sequenceType} mutations`);
86
88
 
87
89
  return mutationsResponse.parse(await response.json());
88
90
  }
@@ -96,11 +98,11 @@ export async function fetchReferenceGenome(lapisUrl: string, signal?: AbortSigna
96
98
  signal,
97
99
  });
98
100
 
99
- await handleErrors(response);
101
+ await handleErrors(response, 'the reference genomes');
100
102
  return referenceGenomeResponse.parse(await response.json());
101
103
  }
102
104
 
103
- const handleErrors = async (response: Response) => {
105
+ const handleErrors = async (response: Response, requestedData: string) => {
104
106
  if (!response.ok) {
105
107
  if (response.status >= 400 && response.status < 500) {
106
108
  const json = await response.json();
@@ -111,6 +113,7 @@ const handleErrors = async (response: Response) => {
111
113
  response.statusText + lapisErrorResult.data.error.detail,
112
114
  response.status,
113
115
  lapisErrorResult.data.error,
116
+ requestedData,
114
117
  );
115
118
  }
116
119
 
@@ -120,12 +123,17 @@ const handleErrors = async (response: Response) => {
120
123
  response.statusText + problemDetailResult.data.detail,
121
124
  response.status,
122
125
  problemDetailResult.data,
126
+ requestedData,
123
127
  );
124
128
  }
125
129
 
126
- throw new UnknownLapisError(`${response.statusText}: ${JSON.stringify(json)}`, response.status);
130
+ throw new UnknownLapisError(
131
+ `${response.statusText}: ${JSON.stringify(json)}`,
132
+ response.status,
133
+ requestedData,
134
+ );
127
135
  }
128
- throw new UnknownLapisError(`${response.statusText}: ${response.status}`, response.status);
136
+ throw new UnknownLapisError(`${response.statusText}: ${response.status}`, response.status, requestedData);
129
137
  }
130
138
  };
131
139
 
@@ -5,16 +5,11 @@ import { type AggregatedItem } from '../lapisApi/lapisTypes';
5
5
  import { type LapisFilter } from '../types';
6
6
 
7
7
  export class FetchAggregatedOperator<Fields extends Record<string, unknown>>
8
- implements
9
- Operator<
10
- Fields & {
11
- count: number;
12
- }
13
- >
8
+ implements Operator<Fields & { count: number }>
14
9
  {
15
10
  constructor(
16
11
  private filter: LapisFilter,
17
- private fields: string[],
12
+ private fields: string[] = [],
18
13
  ) {}
19
14
 
20
15
  async evaluate(lapisUrl: string, signal?: AbortSignal): Promise<Dataset<Fields & { count: number }>> {
@@ -14,7 +14,7 @@ const meta: Meta<ColorScaleSelectorDropdownProps> = {
14
14
 
15
15
  export default meta;
16
16
 
17
- const WrapperWithState: FunctionComponent<{}> = () => {
17
+ const WrapperWithState: FunctionComponent = () => {
18
18
  const [colorScale, setColorScale] = useState<ColorScale>({ min: 0, max: 1, color: 'indigo' });
19
19
 
20
20
  return <ColorScaleSelectorDropdown colorScale={colorScale} setColorScale={setColorScale} />;
@@ -2,11 +2,14 @@ import { type Meta, type StoryObj } from '@storybook/preact';
2
2
  import { expect, waitFor, within } from '@storybook/test';
3
3
 
4
4
  import { ErrorBoundary } from './error-boundary';
5
+ import { UserFacingError } from './error-display';
5
6
 
6
7
  const meta: Meta = {
7
8
  title: 'Component/Error boundary',
8
9
  component: ErrorBoundary,
9
- parameters: { fetchMock: {} },
10
+ parameters: {
11
+ fetchMock: {},
12
+ },
10
13
  argTypes: {
11
14
  size: { control: 'object' },
12
15
  defaultSize: { control: 'object' },
@@ -34,7 +37,7 @@ export const ErrorBoundaryWithoutErrorStory: StoryObj = {
34
37
  export const ErrorBoundaryWithErrorStory: StoryObj = {
35
38
  render: (args) => (
36
39
  <ErrorBoundary size={args.size}>
37
- <ContentThatThrowsError />
40
+ <ContentThatThrowsError error={() => new Error('Some error')} />
38
41
  </ErrorBoundary>
39
42
  ),
40
43
  play: async ({ canvasElement }) => {
@@ -45,6 +48,20 @@ export const ErrorBoundaryWithErrorStory: StoryObj = {
45
48
  },
46
49
  };
47
50
 
48
- const ContentThatThrowsError = () => {
49
- throw new Error('Some error');
51
+ export const ErrorBoundaryWithUserFacingErrorStory: StoryObj = {
52
+ render: (args) => (
53
+ <ErrorBoundary size={args.size}>
54
+ <ContentThatThrowsError error={() => new UserFacingError('Error Headline', 'Some error')} />
55
+ </ErrorBoundary>
56
+ ),
57
+ play: async ({ canvasElement }) => {
58
+ const canvas = within(canvasElement);
59
+ const content = canvas.queryByText('Some content.', { exact: false });
60
+ await waitFor(() => expect(content).not.toBeInTheDocument());
61
+ await waitFor(() => expect(canvas.getByText('Error - Error Headline')).toBeInTheDocument());
62
+ },
63
+ };
64
+
65
+ const ContentThatThrowsError = (props: { error: () => Error }) => {
66
+ throw props.error();
50
67
  };
@@ -1,5 +1,5 @@
1
1
  import { type Meta, type StoryObj } from '@storybook/preact';
2
- import { expect, userEvent, waitFor, within } from '@storybook/test';
2
+ import { expect, fn, userEvent, waitFor, within } from '@storybook/test';
3
3
 
4
4
  import { ErrorDisplay, UserFacingError } from './error-display';
5
5
  import { ResizeContainer } from './resize-container';
@@ -12,7 +12,7 @@ const meta: Meta = {
12
12
 
13
13
  export default meta;
14
14
 
15
- export const ErrorStory: StoryObj = {
15
+ export const GenericErrorStory: StoryObj = {
16
16
  render: () => (
17
17
  <ResizeContainer size={{ height: '600px', width: '100%' }}>
18
18
  <ErrorDisplay error={new Error('some message')} />
@@ -48,3 +48,21 @@ export const UserFacingErrorStory: StoryObj = {
48
48
  });
49
49
  },
50
50
  };
51
+
52
+ export const FiresEvent: StoryObj = {
53
+ render: () => (
54
+ <ResizeContainer size={{ height: '600px', width: '100%' }}>
55
+ <ErrorDisplay error={new UserFacingError('Error Title', 'some message')} />
56
+ </ResizeContainer>
57
+ ),
58
+
59
+ play: async ({ canvasElement }) => {
60
+ const listenerMock = fn();
61
+ canvasElement.addEventListener('gs-error', listenerMock);
62
+
63
+ await waitFor(() => {
64
+ expect(listenerMock.mock.calls.at(-1)[0].error.name).toStrictEqual('UserFacingError');
65
+ expect(listenerMock.mock.calls.at(-1)[0].error.message).toStrictEqual('some message');
66
+ });
67
+ },
68
+ };
@@ -1,5 +1,18 @@
1
1
  import { type FunctionComponent } from 'preact';
2
- import { useRef } from 'preact/hooks';
2
+ import { useEffect, useRef } from 'preact/hooks';
3
+
4
+ import { LapisError, UnknownLapisError } from '../../lapisApi/lapisApi';
5
+
6
+ export const GS_ERROR_EVENT_TYPE = 'gs-error';
7
+
8
+ export class ErrorEvent extends Event {
9
+ constructor(public readonly error: Error) {
10
+ super(GS_ERROR_EVENT_TYPE, {
11
+ bubbles: true,
12
+ composed: true,
13
+ });
14
+ }
15
+ }
3
16
 
4
17
  export class UserFacingError extends Error {
5
18
  constructor(
@@ -14,20 +27,27 @@ export class UserFacingError extends Error {
14
27
  export const ErrorDisplay: FunctionComponent<{ error: Error }> = ({ error }) => {
15
28
  console.error(error);
16
29
 
30
+ const containerRef = useRef<HTMLInputElement>(null);
17
31
  const ref = useRef<HTMLDialogElement>(null);
18
32
 
33
+ useEffect(() => {
34
+ containerRef.current?.dispatchEvent(new ErrorEvent(error));
35
+ });
36
+
37
+ const { headline, details } = getDisplayedErrorMessage(error);
38
+
19
39
  return (
20
- <div className='h-full w-full rounded-md border-2 border-gray-100 p-2 flex items-center justify-center flex-col'>
21
- <div className='text-red-700 font-bold'>Error</div>
40
+ <div
41
+ ref={containerRef}
42
+ className='h-full w-full rounded-md border-2 border-gray-100 p-2 flex items-center justify-center flex-col'
43
+ >
44
+ <div className='text-red-700 font-bold'>{headline}</div>
22
45
  <div>
23
46
  Oops! Something went wrong.
24
- {error instanceof UserFacingError && (
47
+ {details !== undefined && (
25
48
  <>
26
49
  {' '}
27
- <button
28
- className='text-sm text-gray-600 hover:text-gray-300'
29
- onClick={() => ref.current?.showModal()}
30
- >
50
+ <button className='underline hover:text-gray-400' onClick={() => ref.current?.showModal()}>
31
51
  Show details.
32
52
  </button>
33
53
  <dialog ref={ref} class='modal'>
@@ -37,8 +57,8 @@ export const ErrorDisplay: FunctionComponent<{ error: Error }> = ({ error }) =>
37
57
 
38
58
  </button>
39
59
  </form>
40
- <h1 class='text-lg'>{error.headline}</h1>
41
- <p class='py-4'>{error.message}</p>
60
+ <h1 class='text-lg'>{details.headline}</h1>
61
+ <p class='py-4'>{details.message}</p>
42
62
  </div>
43
63
  <form method='dialog' class='modal-backdrop'>
44
64
  <button>close</button>
@@ -50,3 +70,37 @@ export const ErrorDisplay: FunctionComponent<{ error: Error }> = ({ error }) =>
50
70
  </div>
51
71
  );
52
72
  };
73
+
74
+ function getDisplayedErrorMessage(error: Error) {
75
+ if (error instanceof UserFacingError) {
76
+ return {
77
+ headline: `Error - ${error.headline}`,
78
+ details: {
79
+ headline: error.headline,
80
+ message: error.message,
81
+ },
82
+ };
83
+ }
84
+
85
+ if (error instanceof LapisError) {
86
+ return {
87
+ headline: `Error - Failed fetching ${error.requestedData} from LAPIS`,
88
+ details: {
89
+ headline: `LAPIS request failed: ${error.requestedData} - ${error.problemDetail.status} ${error.problemDetail.title}`,
90
+ message: error.problemDetail.detail ?? error.message,
91
+ },
92
+ };
93
+ }
94
+
95
+ if (error instanceof UnknownLapisError) {
96
+ return {
97
+ headline: `Error - Failed fetching ${error.requestedData} from LAPIS`,
98
+ details: {
99
+ headline: `LAPIS request failed: An unexpected error occurred while fetching ${error.requestedData}`,
100
+ message: error.message,
101
+ },
102
+ };
103
+ }
104
+
105
+ return { headline: 'Error', details: undefined };
106
+ }
@@ -1,9 +1,9 @@
1
1
  import { type Meta, type StoryObj } from '@storybook/preact';
2
2
  import { expect, userEvent, waitFor, within } from '@storybook/test';
3
3
 
4
- import Info, { type InfoProps } from './info';
4
+ import Info from './info';
5
5
 
6
- const meta: Meta<InfoProps> = {
6
+ const meta: Meta = {
7
7
  title: 'Component/Info',
8
8
  component: Info,
9
9
  parameters: { fetchMock: {} },
@@ -13,7 +13,7 @@ export default meta;
13
13
 
14
14
  const tooltipText = 'This is a tooltip which shows some information.';
15
15
 
16
- export const InfoStory: StoryObj<InfoProps> = {
16
+ export const InfoStory: StoryObj = {
17
17
  render: (args) => (
18
18
  <div class='flex justify-center px-4 py-16'>
19
19
  <Info {...args}>{tooltipText}</Info>
@@ -21,7 +21,7 @@ export const InfoStory: StoryObj<InfoProps> = {
21
21
  ),
22
22
  };
23
23
 
24
- export const OpenInfo: StoryObj<InfoProps> = {
24
+ export const OpenInfo: StoryObj = {
25
25
  ...InfoStory,
26
26
  play: async ({ canvasElement }) => {
27
27
  const canvas = within(canvasElement);
@@ -30,7 +30,7 @@ export const OpenInfo: StoryObj<InfoProps> = {
30
30
  },
31
31
  };
32
32
 
33
- export const ShowsAndClosesInfoOnClick: StoryObj<InfoProps> = {
33
+ export const ShowsAndClosesInfoOnClick: StoryObj = {
34
34
  ...InfoStory,
35
35
  play: async ({ canvasElement, step }) => {
36
36
  const canvas = within(canvasElement);
@@ -1,9 +1,7 @@
1
1
  import { type FunctionComponent } from 'preact';
2
2
  import { useRef } from 'preact/hooks';
3
3
 
4
- export interface InfoProps {}
5
-
6
- const Info: FunctionComponent<InfoProps> = ({ children }) => {
4
+ const Info: FunctionComponent = ({ children }) => {
7
5
  const dialogRef = useRef<HTMLDialogElement>(null);
8
6
 
9
7
  const toggleHelp = () => {
@@ -124,7 +122,7 @@ function generateFullExampleCode(componentCode: string, componentName: string) {
124
122
  const storyBookPath = `/docs/visualization-${componentName}--docs`;
125
123
  return `<html>
126
124
  <head>
127
- <script type="module" src="https://unpkg.com/@genspectrum/dashboard-components@latest/dist/dashboard-components.js"></script>
125
+ <script type="module" src="https://unpkg.com/@genspectrum/dashboard-components@latest/standalone-bundle/dashboard-components.js"></script>
128
126
  <link rel="stylesheet" href="https://unpkg.com/@genspectrum/dashboard-components@latest/dist/style.css" />
129
127
  </head>
130
128
 
@@ -4,13 +4,18 @@ import { useEffect, useState } from 'preact/hooks';
4
4
  export type PercentInputProps = {
5
5
  percentage: number;
6
6
  setPercentage: (percentage: number) => void;
7
+ indicateError?: boolean;
7
8
  };
8
9
 
9
10
  const percentageInRange = (percentage: number) => {
10
11
  return percentage <= 100 && percentage >= 0;
11
12
  };
12
13
 
13
- export const PercentInput: FunctionComponent<PercentInputProps> = ({ percentage, setPercentage }) => {
14
+ export const PercentInput: FunctionComponent<PercentInputProps> = ({
15
+ percentage,
16
+ setPercentage,
17
+ indicateError = false,
18
+ }) => {
14
19
  const [internalPercentage, setInternalPercentage] = useState(percentage);
15
20
 
16
21
  useEffect(() => {
@@ -33,7 +38,7 @@ export const PercentInput: FunctionComponent<PercentInputProps> = ({ percentage,
33
38
  setInternalPercentage(value);
34
39
  };
35
40
 
36
- const isError = !percentageInRange(internalPercentage);
41
+ const isError = indicateError || !percentageInRange(internalPercentage);
37
42
  return (
38
43
  <label className={`input input-bordered flex items-center gap-2 w-32 ${isError ? 'input-error' : ''}`}>
39
44
  <input
@@ -57,12 +57,22 @@ export const ProportionSelector: FunctionComponent<ProportionSelectorProps> = ({
57
57
  setInternalMaxProportion(newMaxProportion);
58
58
  };
59
59
 
60
+ const indicateError = internalMinProportion > internalMaxProportion;
61
+
60
62
  return (
61
63
  <div class='flex flex-col w-64 mb-2'>
62
64
  <div class='flex items-center '>
63
- <PercentInput percentage={internalMinProportion * 100} setPercentage={updateMinPercentage} />
65
+ <PercentInput
66
+ percentage={internalMinProportion * 100}
67
+ setPercentage={updateMinPercentage}
68
+ indicateError={indicateError}
69
+ />
64
70
  <div class='m-2'>-</div>
65
- <PercentInput percentage={internalMaxProportion * 100} setPercentage={updateMaxPercentage} />
71
+ <PercentInput
72
+ percentage={internalMaxProportion * 100}
73
+ setPercentage={updateMaxPercentage}
74
+ indicateError={indicateError}
75
+ />
66
76
  </div>
67
77
  <div class='my-1'>
68
78
  <MinMaxRangeSlider
@@ -1,4 +1,3 @@
1
- import { withActions } from '@storybook/addon-actions/decorator';
2
1
  import { type Meta, type StoryObj } from '@storybook/preact';
3
2
  import { expect, waitFor, within } from '@storybook/test';
4
3
  import dayjs from 'dayjs/esm';
@@ -13,6 +12,7 @@ import {
13
12
  PRESET_VALUE_LAST_6_MONTHS,
14
13
  PRESET_VALUE_LAST_MONTH,
15
14
  } from './selectableOptions';
15
+ import { previewHandles } from '../../../.storybook/preview';
16
16
  import { LAPIS_URL } from '../../constants';
17
17
  import { LapisUrlContext } from '../LapisUrlContext';
18
18
 
@@ -23,7 +23,7 @@ const meta: Meta<DateRangeSelectorProps<'CustomDateRange'>> = {
23
23
  component: DateRangeSelector,
24
24
  parameters: {
25
25
  actions: {
26
- handles: ['gs-date-range-changed'],
26
+ handles: ['gs-date-range-changed', ...previewHandles],
27
27
  },
28
28
  fetchMock: {},
29
29
  },
@@ -68,7 +68,6 @@ const meta: Meta<DateRangeSelectorProps<'CustomDateRange'>> = {
68
68
  initialDateFrom: '',
69
69
  initialDateTo: '',
70
70
  },
71
- decorators: [withActions],
72
71
  };
73
72
 
74
73
  export default meta;
@@ -20,10 +20,10 @@ export interface DateRangeSelectorProps<CustomLabel extends string> extends Date
20
20
 
21
21
  export interface DateRangeSelectorPropsInner<CustomLabel extends string> {
22
22
  customSelectOptions: CustomSelectOption<CustomLabel>[];
23
- earliestDate?: string;
24
- initialValue?: PresetOptionValues | CustomLabel;
25
- initialDateFrom?: string;
26
- initialDateTo?: string;
23
+ earliestDate: string;
24
+ initialValue: PresetOptionValues | CustomLabel;
25
+ initialDateFrom: string;
26
+ initialDateTo: string;
27
27
  dateColumn: string;
28
28
  }
29
29
 
@@ -1,7 +1,7 @@
1
- import { withActions } from '@storybook/addon-actions/decorator';
2
1
  import { type Meta, type StoryObj } from '@storybook/preact';
3
2
 
4
3
  import { LineageFilter, type LineageFilterProps } from './lineage-filter';
4
+ import { previewHandles } from '../../../.storybook/preview';
5
5
  import { AGGREGATED_ENDPOINT, LAPIS_URL } from '../../constants';
6
6
  import aggregatedData from '../../preact/lineageFilter/__mockData__/aggregated.json';
7
7
  import { LapisUrlContext } from '../LapisUrlContext';
@@ -11,7 +11,7 @@ const meta: Meta = {
11
11
  component: LineageFilter,
12
12
  parameters: {
13
13
  actions: {
14
- handles: ['gs-lineage-filter-changed'],
14
+ handles: ['gs-lineage-filter-changed', ...previewHandles],
15
15
  },
16
16
  fetchMock: {
17
17
  mocks: [
@@ -31,7 +31,6 @@ const meta: Meta = {
31
31
  ],
32
32
  },
33
33
  },
34
- decorators: [withActions],
35
34
  };
36
35
 
37
36
  export default meta;