react-iframe-bridge 1.0.0 → 2.0.0

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 (143) hide show
  1. package/README.md +0 -7
  2. package/{lib-cjs/components/ErrorPage.d.ts → lib/components/error_page.d.ts} +1 -0
  3. package/lib/components/error_page.d.ts.map +1 -0
  4. package/lib/components/error_page.js +15 -0
  5. package/lib/components/error_page.js.map +1 -0
  6. package/lib/components/home/{Home.d.ts → home.d.ts} +1 -0
  7. package/lib/components/home/home.d.ts.map +1 -0
  8. package/lib/components/home/home.js +44 -0
  9. package/lib/components/home/home.js.map +1 -0
  10. package/{lib-cjs/components/home/HomeContext.d.ts → lib/components/home/home_context.d.ts} +2 -1
  11. package/lib/components/home/home_context.d.ts.map +1 -0
  12. package/lib/components/home/{HomeContext.js → home_context.js} +4 -3
  13. package/lib/components/home/home_context.js.map +1 -0
  14. package/{lib-cjs/components/home/HomeHeader.d.ts → lib/components/home/home_header.d.ts} +1 -0
  15. package/lib/components/home/home_header.d.ts.map +1 -0
  16. package/lib/components/home/home_header.js +16 -0
  17. package/lib/components/home/home_header.js.map +1 -0
  18. package/lib/components/home/{HomeIframe.d.ts → home_iframe.d.ts} +1 -0
  19. package/lib/components/home/home_iframe.d.ts.map +1 -0
  20. package/lib/components/home/{HomeIframe.js → home_iframe.js} +8 -2
  21. package/lib/components/home/home_iframe.js.map +1 -0
  22. package/{lib-cjs/components/home/HomeNoSample.d.ts → lib/components/home/home_no_sample.d.ts} +1 -0
  23. package/lib/components/home/home_no_sample.d.ts.map +1 -0
  24. package/lib/components/home/{HomeNoSample.js → home_no_sample.js} +3 -2
  25. package/lib/components/home/home_no_sample.js.map +1 -0
  26. package/{lib-cjs/components/home/HomeSamples.d.ts → lib/components/home/home_samples.d.ts} +1 -0
  27. package/lib/components/home/home_samples.d.ts.map +1 -0
  28. package/lib/components/home/home_samples.js +29 -0
  29. package/lib/components/home/home_samples.js.map +1 -0
  30. package/{lib-cjs/components/home/HomeSelector.d.ts → lib/components/home/home_selector.d.ts} +1 -0
  31. package/lib/components/home/home_selector.d.ts.map +1 -0
  32. package/lib/components/home/home_selector.js +15 -0
  33. package/lib/components/home/home_selector.js.map +1 -0
  34. package/{lib-cjs/components/Input.d.ts → lib/components/input.d.ts} +3 -2
  35. package/lib/components/input.d.ts.map +1 -0
  36. package/lib/components/input.js +14 -0
  37. package/lib/components/input.js.map +1 -0
  38. package/{lib-cjs/components/LoadingFull.d.ts → lib/components/loading_full.d.ts} +1 -0
  39. package/lib/components/loading_full.d.ts.map +1 -0
  40. package/lib/components/loading_full.js +12 -0
  41. package/lib/components/loading_full.js.map +1 -0
  42. package/lib/components/{Spinner.d.ts → spinner.d.ts} +3 -1
  43. package/lib/components/spinner.d.ts.map +1 -0
  44. package/lib/components/spinner.js +18 -0
  45. package/lib/components/spinner.js.map +1 -0
  46. package/lib/contexts/{iframeBridge.d.ts → iframe_bridge.d.ts} +2 -1
  47. package/lib/contexts/iframe_bridge.d.ts.map +1 -0
  48. package/lib/contexts/{iframeBridge.js → iframe_bridge.js} +5 -6
  49. package/lib/contexts/iframe_bridge.js.map +1 -0
  50. package/lib/contexts/roc.d.ts +1 -0
  51. package/lib/contexts/roc.d.ts.map +1 -0
  52. package/lib/contexts/roc.js +1 -0
  53. package/lib/contexts/roc.js.map +1 -0
  54. package/{lib-cjs/hooks/localStorage.d.ts → lib/hooks/local_storage.d.ts} +1 -0
  55. package/lib/hooks/local_storage.d.ts.map +1 -0
  56. package/lib/hooks/{localStorage.js → local_storage.js} +2 -1
  57. package/lib/hooks/local_storage.js.map +1 -0
  58. package/{lib-cjs/hooks/useRocQuery.d.ts → lib/hooks/use_roc_query.d.ts} +1 -0
  59. package/lib/hooks/use_roc_query.d.ts.map +1 -0
  60. package/lib/hooks/{useRocQuery.js → use_roc_query.js} +2 -1
  61. package/lib/hooks/use_roc_query.js.map +1 -0
  62. package/lib/index.d.ts +4 -3
  63. package/lib/index.d.ts.map +1 -0
  64. package/lib/index.js +4 -3
  65. package/lib/index.js.map +1 -0
  66. package/lib/types/db.d.ts +5 -0
  67. package/lib/types/db.d.ts.map +1 -0
  68. package/lib/types/db.js +1 -0
  69. package/lib/types/db.js.map +1 -0
  70. package/lib/types/util.d.ts +1 -0
  71. package/lib/types/util.d.ts.map +1 -0
  72. package/lib/types/util.js +1 -0
  73. package/lib/types/util.js.map +1 -0
  74. package/{lib-cjs/utils/localStorage.d.ts → lib/utils/local_storage.d.ts} +1 -0
  75. package/lib/utils/local_storage.d.ts.map +1 -0
  76. package/lib/utils/{localStorage.js → local_storage.js} +1 -0
  77. package/lib/utils/local_storage.js.map +1 -0
  78. package/package.json +7 -7
  79. package/src/components/error_page.tsx +39 -0
  80. package/src/components/home/home.tsx +98 -0
  81. package/src/components/home/home_context.tsx +101 -0
  82. package/src/components/home/home_header.tsx +29 -0
  83. package/src/components/home/home_iframe.tsx +73 -0
  84. package/src/components/home/home_no_sample.tsx +16 -0
  85. package/src/components/home/home_samples.tsx +62 -0
  86. package/src/components/home/home_selector.tsx +23 -0
  87. package/src/components/input.tsx +31 -0
  88. package/src/components/loading_full.tsx +19 -0
  89. package/src/components/spinner.tsx +44 -0
  90. package/src/contexts/iframe_bridge.tsx +234 -0
  91. package/src/contexts/roc.tsx +23 -0
  92. package/src/hooks/local_storage.ts +45 -0
  93. package/src/hooks/use_roc_query.ts +75 -0
  94. package/src/index.ts +7 -0
  95. package/src/types/db.ts +81 -0
  96. package/src/types/util.ts +3 -0
  97. package/src/utils/local_storage.ts +35 -0
  98. package/lib/components/ErrorPage.d.ts +0 -8
  99. package/lib/components/ErrorPage.js +0 -4
  100. package/lib/components/Input.d.ts +0 -10
  101. package/lib/components/Input.js +0 -5
  102. package/lib/components/LoadingFull.d.ts +0 -1
  103. package/lib/components/LoadingFull.js +0 -5
  104. package/lib/components/Spinner.js +0 -5
  105. package/lib/components/home/Home.js +0 -22
  106. package/lib/components/home/HomeContext.d.ts +0 -20
  107. package/lib/components/home/HomeHeader.d.ts +0 -1
  108. package/lib/components/home/HomeHeader.js +0 -10
  109. package/lib/components/home/HomeNoSample.d.ts +0 -1
  110. package/lib/components/home/HomeSamples.d.ts +0 -1
  111. package/lib/components/home/HomeSamples.js +0 -23
  112. package/lib/components/home/HomeSelector.d.ts +0 -5
  113. package/lib/components/home/HomeSelector.js +0 -5
  114. package/lib/hooks/localStorage.d.ts +0 -13
  115. package/lib/hooks/useRocQuery.d.ts +0 -13
  116. package/lib/utils/localStorage.d.ts +0 -3
  117. package/lib-cjs/components/ErrorPage.js +0 -7
  118. package/lib-cjs/components/Input.js +0 -11
  119. package/lib-cjs/components/LoadingFull.js +0 -11
  120. package/lib-cjs/components/Spinner.d.ts +0 -4
  121. package/lib-cjs/components/Spinner.js +0 -11
  122. package/lib-cjs/components/home/Home.d.ts +0 -26
  123. package/lib-cjs/components/home/Home.js +0 -28
  124. package/lib-cjs/components/home/HomeContext.js +0 -54
  125. package/lib-cjs/components/home/HomeHeader.js +0 -16
  126. package/lib-cjs/components/home/HomeIframe.d.ts +0 -5
  127. package/lib-cjs/components/home/HomeIframe.js +0 -37
  128. package/lib-cjs/components/home/HomeNoSample.js +0 -14
  129. package/lib-cjs/components/home/HomeSamples.js +0 -29
  130. package/lib-cjs/components/home/HomeSelector.js +0 -11
  131. package/lib-cjs/contexts/iframeBridge.d.ts +0 -33
  132. package/lib-cjs/contexts/iframeBridge.js +0 -130
  133. package/lib-cjs/contexts/roc.d.ts +0 -8
  134. package/lib-cjs/contexts/roc.js +0 -20
  135. package/lib-cjs/hooks/localStorage.js +0 -38
  136. package/lib-cjs/hooks/useRocQuery.js +0 -45
  137. package/lib-cjs/index.d.ts +0 -3
  138. package/lib-cjs/index.js +0 -12
  139. package/lib-cjs/types/db.d.ts +0 -78
  140. package/lib-cjs/types/db.js +0 -2
  141. package/lib-cjs/types/util.d.ts +0 -6
  142. package/lib-cjs/types/util.js +0 -2
  143. package/lib-cjs/utils/localStorage.js +0 -34
package/lib/types/db.d.ts CHANGED
@@ -39,6 +39,10 @@ export interface SampleEntry {
39
39
  }
40
40
  export interface SampleEntryContent {
41
41
  general: {
42
+ title?: string;
43
+ name?: Array<{
44
+ value: string;
45
+ }>;
42
46
  mf: string;
43
47
  mw: number;
44
48
  em: number;
@@ -76,3 +80,4 @@ export interface SampleEntrySpectraNmr {
76
80
  filename: string;
77
81
  };
78
82
  }
83
+ //# sourceMappingURL=db.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/types/db.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,aAAa,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAC7C,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,OAAO,CAAC;IACf,GAAG,EAAE,aAAa,CAAC;IACnB,KAAK,EAAE,QAAQ,CAAC;IAChB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,EAAE,kBAAkB,CAAC;CAC9B;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE;QACP,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,IAAI,CAAC,EAAE,KAAK,CAAC;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QAChC,EAAE,EAAE,MAAM,CAAC;QACX,EAAE,EAAE,MAAM,CAAC;QACX,EAAE,EAAE,MAAM,CAAC;QACX,OAAO,EAAE,MAAM,CAAC;QAChB,GAAG,EAAE;YACH,KAAK,EAAE,MAAM,CAAC;YACd,WAAW,EAAE,MAAM,CAAC;YACpB,KAAK,EAAE,MAAM,EAAE,CAAC;SACjB,CAAC;KACH,CAAC;IACF,UAAU,EAAE;QACV,GAAG,EAAE,KAAK,CAAC;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;KAC/B,CAAC;IACF,OAAO,EAAE;QACP,GAAG,EAAE,qBAAqB,EAAE,CAAC;KAC9B,CAAC;CACH;AAED,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,KAAK,EAAE;QACL,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;CACH"}
package/lib/types/db.js CHANGED
@@ -1 +1,2 @@
1
1
  export {};
2
+ //# sourceMappingURL=db.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.js","sourceRoot":"","sources":["../../src/types/db.ts"],"names":[],"mappings":""}
@@ -4,3 +4,4 @@ export type ActionType<Action, Payload = void> = Payload extends void ? {
4
4
  type: Action;
5
5
  payload: Payload;
6
6
  };
7
+ //# sourceMappingURL=util.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../src/types/util.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,IAAI,OAAO,SAAS,IAAI,GACjE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC"}
package/lib/types/util.js CHANGED
@@ -1 +1,2 @@
1
1
  export {};
2
+ //# sourceMappingURL=util.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"util.js","sourceRoot":"","sources":["../../src/types/util.ts"],"names":[],"mappings":""}
@@ -1,3 +1,4 @@
1
1
  declare let getItem: (key: string) => unknown | null;
2
2
  declare let setItem: (key: string, value: unknown) => void;
3
3
  export { getItem, setItem };
4
+ //# sourceMappingURL=local_storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local_storage.d.ts","sourceRoot":"","sources":["../../src/utils/local_storage.ts"],"names":[],"mappings":"AAcA,QAAA,IAAI,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,GAAG,IAAI,CAAC;AAC7C,QAAA,IAAI,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;AAmBnD,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC"}
@@ -30,3 +30,4 @@ else {
30
30
  };
31
31
  }
32
32
  export { getItem, setItem };
33
+ //# sourceMappingURL=local_storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local_storage.js","sourceRoot":"","sources":["../../src/utils/local_storage.ts"],"names":[],"mappings":"AAAA,mEAAmE;AAEnE;;;;GAIG;AACH,SAAS,SAAS,CAAC,GAAW;IAC5B,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;IAC1D,CAAC;IACD,OAAO,uBAAuB,GAAG,EAAE,CAAC;AACtC,CAAC;AAED,IAAI,OAAwC,CAAC;AAC7C,IAAI,OAA8C,CAAC;AAEnD,IAAI,OAAO,YAAY,KAAK,WAAW,EAAE,CAAC;IACxC,OAAO,GAAG,SAAS,OAAO,CAAC,GAAW;QACpC,MAAM,KAAK,GAAG,YAAY,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;QACnD,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QACxB,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC3B,CAAC,CAAC;IACF,OAAO,GAAG,CAAC,GAAW,EAAE,KAAc,EAAE,EAAE,CACxC,YAAY,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;AAChE,CAAC;KAAM,CAAC;IACN,6DAA6D;IAC7D,OAAO,GAAG,CAAC,GAAW,EAAE,EAAE,CAAC,IAAI,CAAC;IAChC,6DAA6D;IAC7D,OAAO,GAAG,CAAC,GAAW,EAAE,KAAc,EAAQ,EAAE;QAC9C,OAAO;IACT,CAAC,CAAC;AACJ,CAAC;AAED,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC"}
package/package.json CHANGED
@@ -1,16 +1,16 @@
1
1
  {
2
2
  "name": "react-iframe-bridge",
3
- "version": "1.0.0",
3
+ "version": "2.0.0",
4
4
  "description": "React hooks and components to work with iframe-bridge.",
5
- "main": "lib-cjs/index.js",
6
- "module": "lib/index.js",
5
+ "type": "module",
6
+ "exports": "./lib/index.js",
7
7
  "files": [
8
8
  "lib",
9
- "lib-cjs"
9
+ "src"
10
10
  ],
11
11
  "scripts": {
12
12
  "check-types": "tsc --noEmit",
13
- "compile": "tsc && tsc -p tsconfig.cjs.json",
13
+ "compile": "tsc",
14
14
  "eslint": "eslint .",
15
15
  "eslint-fix": "npm run eslint -- --fix",
16
16
  "prepack": "npm run compile",
@@ -28,12 +28,12 @@
28
28
  },
29
29
  "homepage": "https://github.com/zakodium-oss/react-iframe-bridge#readme",
30
30
  "dependencies": {
31
- "clsx": "^2.1.1",
32
- "iframe-bridge": "^2.0.0",
31
+ "iframe-bridge": "^3.0.1",
33
32
  "immer": "^10.1.1",
34
33
  "rest-on-couch-client": "^5.3.1"
35
34
  },
36
35
  "devDependencies": {
36
+ "@types/node": "^22.15.21",
37
37
  "@types/react": "^18.3.1",
38
38
  "@types/react-dom": "^18.3.0",
39
39
  "eslint": "^9.27.0",
@@ -0,0 +1,39 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ interface ErrorPageProps {
4
+ title: string;
5
+ subtitle: string;
6
+ children?: ReactNode;
7
+ }
8
+
9
+ export default function ErrorPage(props: ErrorPageProps) {
10
+ return (
11
+ <div style={{ maxWidth: '56rem', margin: 'auto' }}>
12
+ <div
13
+ style={{
14
+ display: 'flex',
15
+ justifyContent: 'space-between',
16
+ paddingInline: '0.5rem',
17
+ paddingTop: '1rem',
18
+ }}
19
+ >
20
+ <div style={{ minWidth: 0 }}>
21
+ <h1
22
+ style={{
23
+ fontSize: '3rem',
24
+ fontWeight: 'bold',
25
+ marginTop: '4rem',
26
+ color: '#1c398e',
27
+ }}
28
+ >
29
+ {props.title}
30
+ </h1>
31
+ <h2 style={{ marginTop: '4rem', fontSize: '1.125rem' }}>
32
+ {props.subtitle}
33
+ </h2>
34
+ <div style={{ marginTop: '1rem' }}>{props.children}</div>
35
+ </div>
36
+ </div>
37
+ </div>
38
+ );
39
+ }
@@ -0,0 +1,98 @@
1
+ import { useEffect } from 'react';
2
+
3
+ import { HomeContextProvider, useHomeDispatchContext } from './home_context.js';
4
+ import HomeHeader from './home_header.js';
5
+ import HomeIframe from './home_iframe.js';
6
+ import HomeNoSample from './home_no_sample.js';
7
+ import HomeSamples from './home_samples.js';
8
+
9
+ export interface HomeProps {
10
+ /**
11
+ * URL of the rest-on-couch instance.
12
+ * @default 'http://localhost:3000/api/fake-roc'
13
+ */
14
+ rocUrl?: string;
15
+ /**
16
+ * Name of the rest-on-couch database.
17
+ * @default 'eln'
18
+ */
19
+ database?: string;
20
+ /**
21
+ * Base url loaded by the iframe
22
+ */
23
+ baseUrl?: string;
24
+ /**
25
+ * Default path the iframe should load, if no path is found in local storage
26
+ */
27
+ defaultPath?: string;
28
+ /**
29
+ * Opt out of selecting a sample / selecting no sample before loading the iframe.
30
+ * The sample selection UI will be hidden and the iframe will automatically load without a selected sample.
31
+ */
32
+ noSampleSelection?: boolean;
33
+ }
34
+
35
+ export function Home(props: HomeProps) {
36
+ const { baseUrl, noSampleSelection, rocUrl, database, defaultPath } = props;
37
+ return (
38
+ <HomeContextProvider
39
+ rocUrl={rocUrl}
40
+ database={database}
41
+ defaultPath={defaultPath}
42
+ >
43
+ <HomeInternal noSampleSelection={noSampleSelection} baseUrl={baseUrl} />
44
+ </HomeContextProvider>
45
+ );
46
+ }
47
+
48
+ function HomeInternal(props: Pick<HomeProps, 'baseUrl' | 'noSampleSelection'>) {
49
+ const homeDispatch = useHomeDispatchContext();
50
+ useEffect(() => {
51
+ if (props.noSampleSelection) {
52
+ homeDispatch({
53
+ type: 'OPEN_NO_SAMPLE',
54
+ });
55
+ }
56
+ }, [props.noSampleSelection, homeDispatch]);
57
+ return (
58
+ <div
59
+ style={{
60
+ display: 'flex',
61
+ flexDirection: 'column',
62
+ width: '100vw',
63
+ height: '100vh',
64
+ }}
65
+ >
66
+ <HomeHeader />
67
+ <div
68
+ style={{
69
+ marginTop: '0.5rem',
70
+ display: 'flex',
71
+ minHeight: 0,
72
+ flex: 1,
73
+ flexDirection: 'row',
74
+ borderTop: '1px solid #d1d5dc',
75
+ }}
76
+ >
77
+ {!props.noSampleSelection && (
78
+ <div
79
+ style={{
80
+ display: 'flex',
81
+ width: '12rem',
82
+ flexDirection: 'column',
83
+ gap: '0.75rem',
84
+ overflow: 'auto',
85
+ borderRight: '1px solid #d1d5dc',
86
+ paddingInline: '0.5rem',
87
+ paddingTop: '1rem',
88
+ }}
89
+ >
90
+ <HomeNoSample />
91
+ <HomeSamples />
92
+ </div>
93
+ )}
94
+ <HomeIframe baseUrl={props.baseUrl} />
95
+ </div>
96
+ </div>
97
+ );
98
+ }
@@ -0,0 +1,101 @@
1
+ import { produce } from 'immer';
2
+ import type { Dispatch, ReactNode, Reducer } from 'react';
3
+ import { createContext, useContext, useReducer } from 'react';
4
+
5
+ import { RocProvider } from '../../contexts/roc.js';
6
+ import { useSaveToLocalStorage } from '../../hooks/local_storage.js';
7
+ import type { ActionType } from '../../types/util.js';
8
+ import { getItem } from '../../utils/local_storage.js';
9
+
10
+ interface HomeContextType {
11
+ rocUrl: string;
12
+ database: string;
13
+ iframePath: string;
14
+ iframeMode: 'closed' | 'sample' | 'no-sample';
15
+ selectedSample: string | null;
16
+ }
17
+
18
+ function getInitialHomeContext(
19
+ config: Pick<
20
+ HomeContextProviderProps,
21
+ 'rocUrl' | 'database' | 'defaultPath'
22
+ > = {},
23
+ ): HomeContextType {
24
+ const {
25
+ rocUrl = 'http://localhost:3000/api/fake-roc',
26
+ database = 'eln',
27
+ defaultPath = '/dev/base-page',
28
+ } = config;
29
+ return {
30
+ rocUrl,
31
+ database,
32
+ iframePath: (getItem('dev-home-iframe-path') as string) || defaultPath,
33
+ iframeMode: 'closed',
34
+ selectedSample: null,
35
+ };
36
+ }
37
+
38
+ type HomeContextAction =
39
+ | ActionType<'SELECT_SAMPLE', string>
40
+ | ActionType<'OPEN_NO_SAMPLE'>
41
+ | ActionType<'SET_IFRAME_PAGE', string>;
42
+
43
+ const homeReducer: Reducer<HomeContextType, HomeContextAction> = produce(
44
+ (state: HomeContextType, action: HomeContextAction) => {
45
+ switch (action.type) {
46
+ case 'OPEN_NO_SAMPLE':
47
+ state.iframeMode = 'no-sample';
48
+ state.selectedSample = null;
49
+ break;
50
+ case 'SELECT_SAMPLE':
51
+ state.iframeMode = 'sample';
52
+ state.selectedSample = action.payload;
53
+ break;
54
+ case 'SET_IFRAME_PAGE':
55
+ state.iframePath = action.payload;
56
+ break;
57
+ default:
58
+ throw new Error('unreachable');
59
+ }
60
+ },
61
+ );
62
+
63
+ const homeContext = createContext(getInitialHomeContext());
64
+ const homeDispatchContext = createContext<Dispatch<HomeContextAction>>(() => {
65
+ // noop
66
+ });
67
+
68
+ interface HomeContextProviderProps {
69
+ children: ReactNode;
70
+ rocUrl?: string;
71
+ database?: string;
72
+ defaultPath?: string;
73
+ }
74
+
75
+ export function HomeContextProvider(props: HomeContextProviderProps) {
76
+ const { children, ...initial } = props;
77
+ const [homeState, dispatch] = useReducer(
78
+ homeReducer,
79
+ initial,
80
+ getInitialHomeContext,
81
+ );
82
+ useSaveToLocalStorage('dev-home-iframe-path', homeState.iframePath);
83
+
84
+ return (
85
+ <homeContext.Provider value={homeState}>
86
+ <homeDispatchContext.Provider value={dispatch}>
87
+ <RocProvider url={homeState.rocUrl} database={homeState.database}>
88
+ {children}
89
+ </RocProvider>
90
+ </homeDispatchContext.Provider>
91
+ </homeContext.Provider>
92
+ );
93
+ }
94
+
95
+ export function useHomeContext() {
96
+ return useContext(homeContext);
97
+ }
98
+
99
+ export function useHomeDispatchContext() {
100
+ return useContext(homeDispatchContext);
101
+ }
@@ -0,0 +1,29 @@
1
+ import Input from '../input.js';
2
+
3
+ import { useHomeContext, useHomeDispatchContext } from './home_context.js';
4
+
5
+ export default function HomeHeader() {
6
+ const { rocUrl, database, iframePath } = useHomeContext();
7
+ const dispatch = useHomeDispatchContext();
8
+ return (
9
+ <header
10
+ style={{
11
+ display: 'flex',
12
+ flexDirection: 'row',
13
+ gap: '1rem',
14
+ padding: '0.5rem',
15
+ }}
16
+ >
17
+ <Input name="rocUrl" style={{ flex: 1 }} value={rocUrl} readOnly />
18
+ <Input name="database" value={database} readOnly />
19
+ <Input
20
+ name="iframe-page"
21
+ value={iframePath}
22
+ style={{ flex: 1 }}
23
+ onChange={(event) => {
24
+ dispatch({ type: 'SET_IFRAME_PAGE', payload: event.target.value });
25
+ }}
26
+ />
27
+ </header>
28
+ );
29
+ }
@@ -0,0 +1,73 @@
1
+ // https://github.com/import-js/eslint-plugin-import/issues/1810
2
+
3
+ import { postMessage, registerHandler } from 'iframe-bridge/main';
4
+ import { useEffect, useState } from 'react';
5
+
6
+ import { useHomeContext } from './home_context.js';
7
+
8
+ interface AdminMessage {
9
+ type: 'admin.connect';
10
+ windowID: number;
11
+ }
12
+
13
+ interface HomeIframeProps {
14
+ baseUrl?: string;
15
+ }
16
+
17
+ export default function HomeIframe(props: HomeIframeProps) {
18
+ const { baseUrl } = props;
19
+ const { database, iframePath, rocUrl, selectedSample, iframeMode } =
20
+ useHomeContext();
21
+
22
+ const [windowId, setWindowId] = useState<number>();
23
+
24
+ useEffect(() => {
25
+ registerHandler('admin', (message: AdminMessage) => {
26
+ switch (message.type) {
27
+ case 'admin.connect': {
28
+ setWindowId(message.windowID);
29
+ break;
30
+ }
31
+ default:
32
+ throw new Error('unreachable');
33
+ }
34
+ });
35
+ }, []);
36
+
37
+ useEffect(() => {
38
+ if (windowId === undefined) return;
39
+ postMessage(
40
+ 'tab.data',
41
+ {
42
+ couchDB: {
43
+ url: rocUrl,
44
+ database,
45
+ },
46
+ uuid: selectedSample,
47
+ },
48
+ windowId,
49
+ );
50
+ }, [windowId, database, rocUrl, selectedSample]);
51
+
52
+ return (
53
+ <div
54
+ style={{
55
+ display: 'flex',
56
+ flex: 1,
57
+ alignItems: 'center',
58
+ justifyContent: 'center',
59
+ }}
60
+ >
61
+ {iframeMode !== 'closed' ? (
62
+ <iframe
63
+ key={selectedSample}
64
+ allowFullScreen
65
+ src={`${baseUrl || ''}${iframePath}`}
66
+ style={{ width: '100%', height: '100%' }}
67
+ />
68
+ ) : (
69
+ <div>Please select something</div>
70
+ )}
71
+ </div>
72
+ );
73
+ }
@@ -0,0 +1,16 @@
1
+ import { useHomeContext, useHomeDispatchContext } from './home_context.js';
2
+ import HomeSelector from './home_selector.js';
3
+
4
+ export default function HomeNoSample() {
5
+ const { iframeMode } = useHomeContext();
6
+ const dispatch = useHomeDispatchContext();
7
+ return (
8
+ <div>
9
+ <HomeSelector
10
+ onClick={() => dispatch({ type: 'OPEN_NO_SAMPLE' })}
11
+ selected={iframeMode === 'no-sample'}
12
+ text="No sample"
13
+ />
14
+ </div>
15
+ );
16
+ }
@@ -0,0 +1,62 @@
1
+ import type { RocQueryResult } from '../../hooks/use_roc_query.js';
2
+ import { useRocQuery } from '../../hooks/use_roc_query.js';
3
+ import type { TocEntry } from '../../types/db.js';
4
+ import Spinner from '../spinner.js';
5
+
6
+ import { useHomeContext, useHomeDispatchContext } from './home_context.js';
7
+ import HomeSelector from './home_selector.js';
8
+
9
+ export default function HomeSamples() {
10
+ const { loading, error, result } = useRocQuery<TocEntry>('sample_toc');
11
+ if (error) {
12
+ throw error;
13
+ }
14
+ return (
15
+ <>
16
+ <h1
17
+ style={{
18
+ marginBottom: '1rem',
19
+ textAlign: 'center',
20
+ fontSize: '1.125rem',
21
+ fontWeight: 'bold',
22
+ }}
23
+ >
24
+ Sample TOC
25
+ </h1>
26
+ <div style={{ flex: 1 }}>
27
+ {loading || !result ? <Loading /> : <SampleToc samples={result} />}
28
+ </div>
29
+ </>
30
+ );
31
+ }
32
+
33
+ function SampleToc(props: { samples: Array<RocQueryResult<TocEntry>> }) {
34
+ const { selectedSample } = useHomeContext();
35
+ const dispatch = useHomeDispatchContext();
36
+ function selectSample(id: string) {
37
+ dispatch({ type: 'SELECT_SAMPLE', payload: id });
38
+ }
39
+
40
+ return (
41
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
42
+ {props.samples.map((sample) => (
43
+ <HomeSelector
44
+ key={sample.id}
45
+ selected={sample.id === selectedSample}
46
+ text={sample.value.reference}
47
+ onClick={() => selectSample(sample.id)}
48
+ />
49
+ ))}
50
+ </div>
51
+ );
52
+ }
53
+
54
+ function Loading() {
55
+ return (
56
+ <div
57
+ style={{ marginTop: '2rem', display: 'flex', justifyContent: 'center' }}
58
+ >
59
+ <Spinner style={{ width: '2rem', height: '2rem', color: '#6a7282' }} />
60
+ </div>
61
+ );
62
+ }
@@ -0,0 +1,23 @@
1
+ import type { CSSProperties } from 'react';
2
+
3
+ export default function HomeSelector(props: {
4
+ selected: boolean;
5
+ onClick: () => void;
6
+ text: string;
7
+ }) {
8
+ const style: CSSProperties = {
9
+ padding: '0.25rem',
10
+ border: '1px solid #99a1af',
11
+ borderRadius: '0.25rem',
12
+ cursor: 'pointer',
13
+ };
14
+ if (props.selected) {
15
+ style.backgroundColor = '#f3f4f6';
16
+ style.boxShadow = 'inset 0 2px 4px 0 rgb(0 0 0 / 0.05)';
17
+ }
18
+ return (
19
+ <div style={style} onClick={props.onClick}>
20
+ {props.text}
21
+ </div>
22
+ );
23
+ }
@@ -0,0 +1,31 @@
1
+ import type { ChangeEvent, CSSProperties } from 'react';
2
+
3
+ interface InputProps {
4
+ name: string;
5
+ style?: CSSProperties;
6
+ value: string;
7
+ readOnly?: boolean;
8
+ onChange?: (event: ChangeEvent<HTMLInputElement>) => void;
9
+ }
10
+
11
+ export default function Input(props: InputProps) {
12
+ return (
13
+ <input
14
+ name={props.name}
15
+ type="text"
16
+ style={{
17
+ appearance: 'none',
18
+ border: '1px solid #4a5565',
19
+ backgroundColor: 'white',
20
+ paddingInline: '0.75rem',
21
+ paddingBlock: '0.5rem',
22
+ fontSize: '1rem',
23
+ lineHeight: 1,
24
+ ...props.style,
25
+ }}
26
+ value={props.value}
27
+ readOnly={props.readOnly}
28
+ onChange={props.onChange}
29
+ />
30
+ );
31
+ }
@@ -0,0 +1,19 @@
1
+ import Spinner from './spinner.js';
2
+
3
+ export default function LoadingFull() {
4
+ return (
5
+ <div
6
+ style={{
7
+ display: 'flex',
8
+ width: '100%',
9
+ height: '100%',
10
+ alignItems: 'center',
11
+ justifyContent: 'center',
12
+ }}
13
+ >
14
+ <Spinner
15
+ style={{ width: '2.5rem', height: '2.5rem', color: '#6a7282' }}
16
+ />
17
+ </div>
18
+ );
19
+ }
@@ -0,0 +1,44 @@
1
+ import type { CSSProperties } from 'react';
2
+ import { useEffect, useRef } from 'react';
3
+
4
+ export interface SpinnerProps {
5
+ style?: CSSProperties;
6
+ }
7
+
8
+ export default function Spinner(props: SpinnerProps) {
9
+ // Trick to spin the element without defining keyframes.
10
+ const style: CSSProperties = {
11
+ transform: 'rotate(0deg)',
12
+ transition: 'transform 60s linear',
13
+ ...props.style,
14
+ };
15
+ const ref = useRef<SVGSVGElement>(null);
16
+ useEffect(() => {
17
+ if (!ref.current) return;
18
+ ref.current.style.transform = 'rotate(36000deg)';
19
+ }, []);
20
+
21
+ return (
22
+ <svg
23
+ ref={ref}
24
+ style={style}
25
+ xmlns="http://www.w3.org/2000/svg"
26
+ fill="none"
27
+ viewBox="0 0 24 24"
28
+ >
29
+ <circle
30
+ opacity="25%"
31
+ cx="12"
32
+ cy="12"
33
+ r="10"
34
+ stroke="currentColor"
35
+ strokeWidth="4"
36
+ />
37
+ <path
38
+ opacity="75%"
39
+ fill="currentColor"
40
+ d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
41
+ />
42
+ </svg>
43
+ );
44
+ }