@weavy/uikit-react 12.1.0 → 13.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 (86) hide show
  1. package/changelog.md +36 -0
  2. package/dist/cjs/index.js +28 -6
  3. package/dist/cjs/index.js.map +1 -1
  4. package/dist/cjs/types/client/WeavyClient.d.ts +8 -1
  5. package/dist/cjs/types/components/Attachment.d.ts +2 -1
  6. package/dist/cjs/types/components/Chat.d.ts +1 -1
  7. package/dist/cjs/types/components/PdfViewer.d.ts +3 -1
  8. package/dist/cjs/types/components/Preview.d.ts +8 -10
  9. package/dist/cjs/types/contexts/PreviewContext.d.ts +2 -1
  10. package/dist/cjs/types/contexts/WeavyContext.d.ts +2 -3
  11. package/dist/cjs/types/types/Chat.d.ts +1 -1
  12. package/dist/cjs/types/types/types.d.ts +16 -5
  13. package/dist/cjs/types/ui/Spinner.d.ts +9 -0
  14. package/dist/cjs/types/utils/fileUtilities.d.ts +1 -4
  15. package/dist/css/weavy-chat.css +270 -94
  16. package/dist/css/weavy-messenger.css +274 -96
  17. package/dist/css/weavy.css +274 -96
  18. package/dist/esm/index.js +28 -6
  19. package/dist/esm/index.js.map +1 -1
  20. package/dist/esm/types/client/WeavyClient.d.ts +8 -1
  21. package/dist/esm/types/components/Attachment.d.ts +2 -1
  22. package/dist/esm/types/components/Chat.d.ts +1 -1
  23. package/dist/esm/types/components/PdfViewer.d.ts +3 -1
  24. package/dist/esm/types/components/Preview.d.ts +8 -10
  25. package/dist/esm/types/contexts/PreviewContext.d.ts +2 -1
  26. package/dist/esm/types/contexts/WeavyContext.d.ts +2 -3
  27. package/dist/esm/types/types/Chat.d.ts +1 -1
  28. package/dist/esm/types/types/types.d.ts +16 -5
  29. package/dist/esm/types/ui/Spinner.d.ts +9 -0
  30. package/dist/esm/types/utils/fileUtilities.d.ts +1 -4
  31. package/dist/index.d.ts +14 -7
  32. package/package.json +2 -2
  33. package/rollup.config.js +3 -1
  34. package/src/client/WeavyClient.ts +105 -24
  35. package/src/components/Attachment.tsx +8 -7
  36. package/src/components/Chat.tsx +3 -3
  37. package/src/components/Conversation.tsx +3 -3
  38. package/src/components/Message.tsx +1 -1
  39. package/src/components/Messages.tsx +1 -1
  40. package/src/components/PdfViewer.tsx +88 -83
  41. package/src/components/Preview.tsx +115 -54
  42. package/src/components/SearchUsers.tsx +2 -2
  43. package/src/contexts/PreviewContext.tsx +90 -16
  44. package/src/contexts/WeavyContext.tsx +7 -4
  45. package/src/hooks/useBadge.ts +2 -6
  46. package/src/hooks/useChat.ts +3 -14
  47. package/src/hooks/useConversation.ts +1 -7
  48. package/src/hooks/useConversations.ts +1 -7
  49. package/src/hooks/useFileUploader.ts +6 -8
  50. package/src/hooks/useMembers.ts +1 -7
  51. package/src/hooks/useMessages.ts +1 -7
  52. package/src/hooks/useMutateChat.ts +6 -11
  53. package/src/hooks/useMutateConversation.ts +7 -10
  54. package/src/hooks/useMutateConversationName.ts +10 -12
  55. package/src/hooks/useMutateDeleteReaction.ts +3 -8
  56. package/src/hooks/useMutateExternalBlobs.ts +6 -11
  57. package/src/hooks/useMutateMeeting.ts +6 -11
  58. package/src/hooks/useMutateMembers.ts +8 -13
  59. package/src/hooks/useMutateMessage.ts +10 -15
  60. package/src/hooks/useMutatePinned.ts +3 -8
  61. package/src/hooks/useMutateReaction.ts +6 -12
  62. package/src/hooks/useMutateRead.ts +1 -10
  63. package/src/hooks/useMutateRemoveMembers.ts +7 -12
  64. package/src/hooks/useMutateTyping.ts +6 -11
  65. package/src/hooks/useSearchUsers.ts +1 -6
  66. package/src/hooks/useUser.ts +3 -14
  67. package/src/scss/theme/_appbar.scss +4 -2
  68. package/src/scss/theme/_cm-editor.scss +1 -1
  69. package/src/scss/theme/_code-vscode-dark.scss +184 -0
  70. package/src/scss/theme/_code-vscode-light.scss +179 -0
  71. package/src/scss/theme/_code.scss +9 -112
  72. package/src/scss/theme/_files.scss +1 -1
  73. package/src/scss/theme/_message-editor.scss +1 -1
  74. package/src/scss/theme/_overlays.scss +2 -2
  75. package/src/scss/theme/_panels.scss +11 -7
  76. package/src/scss/theme/_preview-code.scss +5 -0
  77. package/src/scss/theme/_preview-embed.scss +3 -3
  78. package/src/scss/theme/_preview-image.scss +1 -1
  79. package/src/scss/theme/_preview-text.scss +1 -1
  80. package/src/scss/theme/_preview.scss +7 -2
  81. package/src/scss/weavy-chat.scss +1 -0
  82. package/src/scss/weavy-messenger.scss +1 -0
  83. package/src/types/Chat.ts +1 -1
  84. package/src/types/types.ts +16 -5
  85. package/src/ui/Spinner.tsx +18 -0
  86. package/src/utils/fileUtilities.ts +11 -125
@@ -1,23 +1,45 @@
1
- import React, { useEffect, useRef, useCallback, useState } from "react";
2
- import pdfjsLib from 'pdfjs-dist';
3
- import pdfjsViewer from 'pdfjs-dist/web/pdf_viewer';
1
+ import React, { useContext, useEffect, useRef, useCallback, useState } from "react";
2
+ import pdfjsLib, { PDFDocumentLoadingTask } from 'pdfjs-dist';
3
+ import { PDFViewer, EventBus, PDFFindController, PDFLinkService } from 'pdfjs-dist/web/pdf_viewer';
4
4
  import Icon from "../ui/Icon";
5
5
 
6
6
  type Props = {
7
- src: string
7
+ src: string,
8
+ pdfCMapsUrl: string,
9
+ pdfWorkerUrl: string
8
10
  }
9
11
 
10
- const PdfViewer = ({ src }: Props) => {
11
- //const viewerRef = useRef(null);
12
+ const PdfViewer = ({ src, pdfCMapsUrl, pdfWorkerUrl }: Props) => {
13
+
12
14
  const pageNumberRef = useRef<HTMLInputElement>(null);
13
15
  const totalPagesRef = useRef<HTMLSpanElement>(null);
14
16
  const zoomLevelRef = useRef<HTMLInputElement>(null);
15
17
 
16
- const [pdfEventBus, setPdfEventBus] = useState<pdfjsViewer.EventBus>();
17
-
18
- const [pdfViewer, setPdfViewer] = useState<pdfjsViewer.PDFViewer>();
19
- const [pdfLinkService, setPdfLinkService] = useState<pdfjsViewer.PDFLinkService>();
20
- const [pdfFindController, setPdfFindController] = useState<pdfjsViewer.PDFFindController>();
18
+ const [pdfEventBus, setPdfEventBus] = useState<EventBus>(() => {
19
+ //console.debug("new pdf event bus")
20
+ return new EventBus();
21
+ });
22
+
23
+ const [pdfLinkService, setPdfLinkService] = useState<PDFLinkService>(() => {
24
+ // (Optionally) enable hyperlinks within PDF files.
25
+ //console.debug("new pdf link service")
26
+
27
+ return new PDFLinkService({
28
+ eventBus: pdfEventBus!
29
+ })
30
+ });
31
+
32
+ const [pdfFindController, setPdfFindController] = useState<PDFFindController>(() => {
33
+ // (Optionally) enable find controller.
34
+ //console.debug("new pdf find controller")
35
+
36
+ return new PDFFindController({
37
+ eventBus: pdfEventBus!,
38
+ linkService: pdfLinkService!,
39
+ })
40
+ });
41
+
42
+ const [pdfViewer, setPdfViewer] = useState<PDFViewer | null>();
21
43
 
22
44
  const DEFAULT_SCALE_DELTA = 1.1;
23
45
  const MAX_SCALE = 3.0;
@@ -27,70 +49,57 @@ const PdfViewer = ({ src }: Props) => {
27
49
  const ENABLE_XFA = true;
28
50
 
29
51
  // Some PDFs need external cmaps.
30
- const CMAP_URL = "/cmaps/";
52
+ const CMAP_URL = pdfCMapsUrl || '';
31
53
  const CMAP_PACKED = true;
32
54
 
55
+ const WORKER_URL = pdfWorkerUrl || '';
56
+
33
57
  // Setting worker path to worker bundle.
34
- pdfjsLib.GlobalWorkerOptions.workerSrc = "/js/pdf.worker.min.js";
58
+ pdfjsLib.GlobalWorkerOptions.workerSrc = WORKER_URL;
35
59
 
60
+ // Save container in state after being available
61
+ const [viewerContainer, setViewerContainer] = useState<HTMLDivElement>();
36
62
  const viewerRef = useCallback((container: any) => {
37
- if (container !== null) {
63
+ setViewerContainer(container);
64
+ }, [])
38
65
 
39
- let linkService = pdfLinkService;
40
- let findController = pdfFindController;
41
- let viewer = pdfViewer;
42
- let eventBus = pdfEventBus;
43
-
44
- if (!pdfEventBus) {
45
- eventBus = new pdfjsViewer.EventBus();
46
- setPdfEventBus(eventBus);
47
- }
66
+ useEffect(() => {
67
+ let viewer = pdfViewer;
48
68
 
49
- // (Optionally) enable hyperlinks within PDF files.
50
- if (!pdfLinkService) {
51
- linkService = new pdfjsViewer.PDFLinkService({
52
- eventBus: eventBus!,
53
- })
54
- setPdfLinkService(linkService);
55
- }
69
+ if (viewerContainer) {
56
70
 
57
- // (Optionally) enable find controller.
58
- if (!pdfFindController) {
59
- findController = new pdfjsViewer.PDFFindController({
60
- eventBus: eventBus!,
61
- linkService: linkService!,
62
- })
63
- setPdfFindController(findController);
64
- }
71
+ if (!pdfViewer && pdfEventBus) {
72
+ // INIT PDF VIEWER
73
+ //console.debug("new pdf viewer")
65
74
 
66
- if (!pdfViewer && eventBus) {
67
75
  // @ts-ignore due to incorrect type def
68
- viewer = new pdfjsViewer.PDFViewer({
69
- container: container,
70
- eventBus: eventBus,
71
- linkService: linkService!,
72
- findController: findController,
76
+ viewer = new PDFViewer({
77
+ container: viewerContainer!,
78
+ eventBus: pdfEventBus,
79
+ linkService: pdfLinkService!,
80
+ findController: pdfFindController,
73
81
  //defaultZoomValue: 1.0,
74
82
  //scriptingManager: pdfScriptingManager,
75
83
  //enableScripting: true, // Only necessary in PDF.js version 2.10.377 and below.
76
84
  })
77
85
  //pdfViewer!.MAX_AUTO_SCALE = 1.0;
78
- linkService!.setViewer(viewer);
86
+
87
+ pdfLinkService!.setViewer(viewer);
79
88
  setPdfViewer(viewer);
80
89
 
81
- eventBus.on("scalechanging", function () {
82
- console.debug("scalechanging")
90
+ pdfEventBus.on("scalechanging", function () {
91
+ //console.debug("scalechanging")
83
92
  zoomLevelRef.current!.value = (Math.round(viewer!.currentScale * 100)).toFixed(0) + "%";
84
93
  });
85
94
 
86
- eventBus.on("pagechanging", function () {
87
- console.debug("pagechanging")
95
+ pdfEventBus.on("pagechanging", function () {
96
+ //console.debug("pagechanging")
88
97
  if (pageNumberRef.current) {
89
98
  pageNumberRef.current.value = viewer!.currentPageNumber.toFixed(0);
90
99
  }
91
100
  });
92
101
 
93
- eventBus.on("pagesinit", function () {
102
+ pdfEventBus.on("pagesinit", function () {
94
103
  // We can use pdfViewer now, e.g. let's change default scale.
95
104
  viewer!.currentScaleValue = "auto";
96
105
  pageNumberRef.current!.value = "1";
@@ -98,77 +107,73 @@ const PdfViewer = ({ src }: Props) => {
98
107
 
99
108
  // We can try searching for things.
100
109
  if (SEARCH_FOR) {
101
- if (findController && !("_onFind" in findController)) {
110
+ if (pdfFindController && !("_onFind" in pdfFindController)) {
102
111
  // @ts-ignore due to missing type def
103
- findController.executeCommand("find", { query: SEARCH_FOR });
112
+ pdfFindController.executeCommand("find", { query: SEARCH_FOR });
104
113
  } else {
105
- eventBus!.dispatch("find", { type: "", query: SEARCH_FOR });
114
+ pdfEventBus!.dispatch("find", { type: "", query: SEARCH_FOR });
106
115
  }
107
116
  }
108
117
  });
109
118
  }
110
119
 
111
- } else {
112
- console.warn("pdf viewer dismounted")
120
+ return () => {
121
+ if (viewer) {
122
+ //console.debug("pdf viewer dismount cleanup")
123
+ viewer.cleanup();
124
+ setPdfViewer(null);
125
+ }
126
+ }
113
127
  }
114
- }, []);
115
128
 
116
- useEffect(() => {
117
- if (src && pdfViewer && pdfLinkService) {
118
- let loadingTask: pdfjsLib.PDFDocumentLoadingTask;
119
- let loadedDoc: pdfjsLib.PDFDocumentProxy;
129
+ }, [viewerContainer]);
120
130
 
121
- loadingTask = pdfjsLib.getDocument({
131
+
132
+ useEffect(() => {
133
+ if (src && pdfViewer) {
134
+ let loadingTask: PDFDocumentLoadingTask | null = pdfjsLib.getDocument({
122
135
  url: src,
123
136
  enableXfa: ENABLE_XFA,
124
137
  cMapUrl: CMAP_URL,
125
138
  cMapPacked: CMAP_PACKED,
126
139
  });
127
140
 
128
- // Re-use the worker?
129
- //pdfjsLib.GlobalWorkerOptions.workerPort = loadingTask._worker.port
141
+ loadingTask.promise.then((doc) => {
142
+ //console.log("LOADED PDF", src);
130
143
 
131
- loadingTask.promise.then(function (doc) {
132
- loadedDoc = doc;
133
- //console.log("LOADED PDF");
134
144
  pdfViewer.setDocument(doc);
135
145
  pdfLinkService.setDocument(doc, null);
136
146
  });
137
147
 
138
148
  return () => {
139
149
  if (loadingTask) {
140
- console.log("resetting pdf viewer");
141
- if(loadingTask._transport){
142
- loadingTask._transport.destroy();
143
- }
150
+ loadingTask.destroy();
151
+ //console.debug("loadingtask cleanup", loadingTask);
144
152
 
145
- if (loadedDoc) {
146
- loadedDoc.destroy();
147
- }
148
-
149
- //loadingTask.destroy();
150
- pdfViewer.cleanup();
153
+ // @ts-ignore due to incorrect param type def
154
+ pdfViewer.setDocument(null);
155
+ pdfLinkService.setDocument(null, null);
151
156
  }
152
157
  }
153
158
  }
154
159
  }, [src, pdfViewer])
155
160
 
156
161
  function setScale(scale: number | string) {
157
- console.debug("setScale:", scale)
162
+ //console.debug("setScale:", scale)
158
163
  if (pdfViewer) {
159
164
  pdfViewer.currentScaleValue = typeof scale === "number" ? scale.toFixed(2) : scale;
160
165
  }
161
166
  }
162
167
 
163
168
  function setPage(pageNumber: number) {
164
- console.debug("setPage:", pageNumber)
169
+ //console.debug("setPage:", pageNumber)
165
170
  if (pdfViewer) {
166
171
  pdfViewer.currentPageNumber = pageNumber;
167
172
  }
168
173
  }
169
174
 
170
175
  function updatePage() {
171
- console.debug("updatePage");
176
+ //console.debug("updatePage");
172
177
  if (pdfViewer && pageNumberRef.current) {
173
178
  let pageNumber = parseInt(pageNumberRef.current.value);
174
179
 
@@ -186,7 +191,7 @@ const PdfViewer = ({ src }: Props) => {
186
191
  }
187
192
 
188
193
  function select(e: any) {
189
- console.debug("select");
194
+ //console.debug("select");
190
195
  e.target.setSelectionRange(0, e.target.value.length);
191
196
  }
192
197
 
@@ -215,7 +220,7 @@ const PdfViewer = ({ src }: Props) => {
215
220
  }
216
221
 
217
222
  function zoomOut() {
218
- console.debug("zoomOut");
223
+ //console.debug("zoomOut");
219
224
  if (pdfViewer) {
220
225
  let newScale = pdfViewer.currentScale;
221
226
  let steps = 1;
@@ -230,7 +235,7 @@ const PdfViewer = ({ src }: Props) => {
230
235
  }
231
236
 
232
237
  function updateZoom() {
233
- console.debug("updateZoom");
238
+ //console.debug("updateZoom");
234
239
  if (pdfViewer && zoomLevelRef.current) {
235
240
  let zoomValue = parseFloat(zoomLevelRef.current.value.replace("%", ""));
236
241
  if (isNaN(zoomValue)) {
@@ -268,4 +273,4 @@ const PdfViewer = ({ src }: Props) => {
268
273
  )
269
274
  }
270
275
 
271
- export default PdfViewer;
276
+ export default PdfViewer;
@@ -1,5 +1,6 @@
1
1
  import React, { useEffect, useState, useCallback } from "react";
2
2
  import Icon from "../ui/Icon";
3
+ import Spinner from "../ui/Spinner";
3
4
  import classNames from "classnames";
4
5
  import PdfViewer from "./PdfViewer";
5
6
  import { checkImageLoad, imageLoaded } from "./Image";
@@ -45,8 +46,8 @@ export const PreviewImage = ({ src, width, height }: ImageProps) => {
45
46
  <>
46
47
  {width && height ?
47
48
  <div className="wy-content-image wy-responsive-image" style={{ ["--width" as any]: width, ["--height" as any]: height }}>
48
- <img ref={imageRef} src={src} onLoad={imageLoaded} width={width} height={height} decoding="async" />
49
- {/* TODO: spinner here */}
49
+ <img className="wy-loading-transition" ref={imageRef} src={src} onLoad={imageLoaded} width={width} height={height} decoding="async" />
50
+ <Spinner.UI />
50
51
  </div>
51
52
  :
52
53
  <div className="wy-content-image wy-responsive-image wy-intrinsic-image">
@@ -58,12 +59,16 @@ export const PreviewImage = ({ src, width, height }: ImageProps) => {
58
59
  }
59
60
 
60
61
  type DocumentProps = {
61
- src: string
62
+ src: string,
63
+ client: any
62
64
  }
63
65
 
64
- export const PreviewDocument = ({ src }: DocumentProps) => {
66
+ export const PreviewDocument = ({ src, client }: DocumentProps) => {
67
+ let pdfWorkerUrl = (new URL("/js/preview.worker.js", client.url)).toString();
68
+ let pdfCMapsUrl = (new URL("/js/cmaps/", client.url)).toString();
69
+
65
70
  return (
66
- <PdfViewer src={src} />
71
+ <PdfViewer src={src} pdfWorkerUrl={pdfWorkerUrl} pdfCMapsUrl={pdfCMapsUrl} />
67
72
  );
68
73
  }
69
74
 
@@ -111,38 +116,56 @@ function codecError(event: any) {
111
116
  }
112
117
  }
113
118
 
114
- type VideoProps = {
119
+ type MediaProps = {
120
+ format: string,
115
121
  src: string,
116
122
  name: string,
117
123
  mediaType?: string
118
124
  }
119
125
 
120
- export const PreviewVideo = ({ src, name, mediaType }: VideoProps) => {
121
- /* TODO: loading, error handling and stopping */
126
+ export const PreviewMedia = ({ format, src, name, mediaType }: MediaProps) => {
122
127
 
123
- return (
128
+ const [mediaElement, setMediaElement] = useState<HTMLVideoElement|HTMLAudioElement>();
129
+ const mediaRef = useCallback((element: any) => {
130
+ if (element) {
131
+ element.classList.add("wy-loading");
132
+ setMediaElement(element);
133
+ }
134
+ }, [])
135
+
136
+ useEffect(() => {
137
+ if (mediaElement) {
138
+
139
+ mediaElement.addEventListener('error', mediaError, true); // needs capturing
140
+ mediaElement.addEventListener('loadedmetadata', mediaLoaded, true); // needs capturing
141
+ mediaElement.addEventListener('loadedmetadata', codecError, true); // needs capturing
142
+
143
+ return () => {
144
+ // cleanup
145
+ mediaElement.pause();
146
+ mediaElement.removeAttribute("autoplay");
147
+ mediaElement.setAttribute("preload", "none");
148
+
149
+ mediaElement.removeEventListener('error', mediaError, true); // needs capturing
150
+ mediaElement.removeEventListener('loadedmetadata', mediaLoaded, true); // needs capturing
151
+ mediaElement.removeEventListener('loadedmetadata', codecError, true); // needs capturing
152
+ }
153
+ }
154
+
155
+ }, [mediaElement])
156
+
157
+ return (format === "video" ?
124
158
  <>
125
- <video className="wy-content-video" controls crossOrigin="use-credentials" autoPlay>
159
+ <video ref={mediaRef} className="wy-content-video" controls crossOrigin="use-credentials" autoPlay>
126
160
  <source src={src} type={mediaType} />
127
161
  <PreviewIcon src={src} name={name} icon="file-video" download />
128
162
  </video>
129
- {/* TODO: spinner here */}
163
+ <Spinner.UI />
130
164
  </>
131
- );
132
- }
133
-
134
- type AudioProps = {
135
- src: string,
136
- name: string,
137
- mediaType?: string
138
- }
139
-
140
- export const PreviewAudio = ({ src, name, mediaType }: AudioProps) => {
141
- /* TODO: loading, error handling and stopping */
142
- return (
165
+ :
143
166
  <>
144
167
  <PreviewIcon src={src} name={name} icon="file-music" download>
145
- <audio className="wy-content-audio" controls crossOrigin="use-credentials" autoPlay>
168
+ <audio ref={mediaRef} className="wy-content-audio" controls crossOrigin="use-credentials" autoPlay>
146
169
  <source src={src} type={mediaType} />
147
170
  </audio>
148
171
  </PreviewIcon>
@@ -175,23 +198,24 @@ export const PreviewText = ({ src, html = false, code = false }: TextProps) => {
175
198
  <>
176
199
  {html ?
177
200
  code ?
178
- <div className="wy-content-code" dangerouslySetInnerHTML={{ __html: textContent }}></div>
201
+ <div className="wy-content-code wy-code" dangerouslySetInnerHTML={{ __html: textContent }}></div>
179
202
  :
180
- <div className="wy-content-text">
181
- <pre className="wy-document" dangerouslySetInnerHTML={{ __html: textContent }}></pre>
203
+ <div className="wy-document">
204
+ <div className="wy-content-html" dangerouslySetInnerHTML={{ __html: textContent }}></div>
182
205
  </div>
183
206
  :
184
207
  code ?
185
208
  <div className="wy-content-code">{textContent}</div>
186
209
  :
187
- <div className="wy-content-text">
188
- <pre className="wy-document">{textContent}</pre>
210
+ <div className="wy-document">
211
+ <pre className="wy-content-text">{textContent}</pre>
189
212
  </div>
190
213
  }
191
214
  </>
192
215
  );
193
216
  }
194
217
 
218
+
195
219
  type EmbedProps = {
196
220
  src: string,
197
221
  name: string,
@@ -200,13 +224,47 @@ type EmbedProps = {
200
224
  }
201
225
 
202
226
  export const PreviewEmbed = ({ src, name, icon, provider }: EmbedProps) => {
203
- /* TODO: add loading and error handling */
227
+ const [embedElement, setEmbedElement] = useState<HTMLObjectElement>();
228
+ const embedRef = useCallback((element: any) => {
229
+ if (element) {
230
+ element.classList.add("wy-loading");
231
+ setEmbedElement(element);
232
+ }
233
+ }, [])
234
+
235
+ useEffect(() => {
236
+ if (embedElement) {
237
+ let embedFallbackTimeout = setTimeout(function () {
238
+ console.log("fallback");
239
+ embedElement.classList.add("wy-fallback");
240
+ }, 2500)
241
+
242
+ let embedLoaded = (event: any) => {
243
+ var obj = event.target;
244
+ if (obj.tagName === 'OBJECT' && obj.classList.contains("wy-loading") && !obj.classList.contains("wy-loaded")) {
245
+ console.log("loaded");
246
+ obj.classList.add("wy-loaded");
247
+ clearTimeout(embedFallbackTimeout);
248
+ }
249
+ }
250
+
251
+ embedElement.addEventListener('load', embedLoaded, true); // needs capturing
252
+
253
+ return () => {
254
+ // cleanup
255
+ embedElement.removeEventListener('load', embedLoaded, true); // needs capturing
256
+ clearTimeout(embedFallbackTimeout);
257
+ }
258
+ }
259
+
260
+ }, [embedElement])
261
+
204
262
  return (
205
263
  <>
206
264
  {/* iframe needs to be object to not render error pages when content is blocked */}
207
- <object className="wy-content-iframe" data={src}></object>
265
+ <object ref={embedRef} className="wy-content-iframe" data={src}></object>
208
266
 
209
- {/* TODO: add spinner here */}
267
+ <Spinner.UI />
210
268
 
211
269
  <PreviewIcon src={src} name={name} icon={icon} provider={provider} className="wy-content-iframe-fallback" />
212
270
  </>
@@ -225,13 +283,16 @@ type IconProps = {
225
283
 
226
284
  export const PreviewIcon = ({ children, src, icon, name, provider, download = false, className }: IconProps) => {
227
285
  return (
228
- <div className={classNames("wy-content-media", className)}>
286
+ <div className={classNames("wy-content-icon", className)}>
229
287
  <div className="wy-content-icon">
230
288
  <Icon.UI name={icon} />
231
289
  </div>
232
290
  <div className="wy-content-name">
233
291
  {provider ?
234
- <a href={src} target="_blank" title={`Open in ${provider}`}>{name} <Icon.UI name="open-in-new" size={1} /></a>
292
+ <>
293
+ <span>No preview available. </span>
294
+ <a href={src} target="_blank" title={name}>{`Open in ${provider}?`}</a>
295
+ </>
235
296
  : download ?
236
297
  <a href={src} target="_top" download>{name}</a>
237
298
  :
@@ -244,7 +305,9 @@ export const PreviewIcon = ({ children, src, icon, name, provider, download = fa
244
305
  }
245
306
 
246
307
  type PreviewProps = {
308
+ client: any,
247
309
  src: string,
310
+ link?: string,
248
311
  format: PreviewFormatType,
249
312
  name: string,
250
313
  icon: string,
@@ -254,39 +317,37 @@ type PreviewProps = {
254
317
  provider?: string
255
318
  }
256
319
 
257
- export const Preview = ({ src, format, name, icon, width, height, mediaType, provider }: PreviewProps) => {
320
+ export const Preview = ({ client, src, link, format, name, icon, width, height, mediaType, provider }: PreviewProps) => {
258
321
  return (
259
322
  <>
260
323
  {format === "image" &&
261
- <PreviewImage src={src} width={width} height={height} />
262
- }
263
- {format === "document" &&
264
- <PreviewDocument src={src} />
324
+ <PreviewImage key={src} src={src} width={width} height={height} />
265
325
  }
266
- {format === "video" &&
267
- <PreviewVideo src={src} name={name} mediaType={mediaType} />
326
+ {format === "pdf" &&
327
+ /* Key not needed since component is reused and handles cleanup */
328
+ <PreviewDocument src={src} client={client} />
268
329
  }
269
- {format === "audio" &&
270
- <PreviewAudio src={src} name={name} mediaType={mediaType} />
330
+ {(format === "video" || format === "audio") &&
331
+ <PreviewMedia key={src} format={format} src={src} name={name} mediaType={mediaType} />
271
332
  }
272
333
  {format === "text" &&
273
- <PreviewText src={src} />
334
+ <PreviewText key={src} src={src} />
274
335
  }
275
336
  {format === "code" &&
276
- <PreviewText src={src} html code />
337
+ <PreviewText key={src} src={src} html code />
277
338
  }
278
- {format === "markup" &&
279
- <PreviewText src={src} html />
339
+ {format === "html" &&
340
+ <PreviewText key={src} src={src} html />
280
341
  }
281
342
  {format === "embed" &&
282
- <PreviewEmbed src={src} name={name} icon={icon} provider={provider} />
283
- }
284
- {format === "link" &&
285
- <PreviewIcon src={src} name={name} icon={icon} provider={provider} />
286
- }
287
- {format === "download" &&
288
- <PreviewIcon src={src} name={name} icon={icon} download />
343
+ <PreviewEmbed key={src} src={src} name={name} icon={icon} provider={provider} />
289
344
  }
345
+ {format === "none" && (
346
+ link ?
347
+ <PreviewIcon src={src} name={name} icon={icon} provider={provider} />
348
+ :
349
+ <PreviewIcon src={src} name={name} icon={icon} download />
350
+ )}
290
351
  </>
291
352
  )
292
353
  }
@@ -48,12 +48,12 @@ const SearchUsers = ({handleSubmit, buttonTitle}: SearchUsersProps) => {
48
48
  </div>
49
49
 
50
50
  <div className="wy-pane-group">
51
- {data && data.data.length === 0 &&
51
+ {data && (!data.data || data.data.length === 0) &&
52
52
  <div className="wy-search-no-result">Your search did not match any people.</div>
53
53
  }
54
54
  <table className="wy-search-result-table">
55
55
  <tbody>
56
- {data && data.data.length > 0 && data.data.map((user: MemberType) => {
56
+ {data && data.data && data.data.length > 0 && data.data.map((user: MemberType) => {
57
57
  return (
58
58
  <tr key={user.id} className="wy-search-result-table-checkbox">
59
59
  <td className="wy-search-result-table-icon">