-
Notifications
You must be signed in to change notification settings - Fork 52
[WC-2890] doc viewer: add docx support #1519
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
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
24f23b5
feat(docviewer): add docx support
gjulivan 1a9c3ef
chore: refactoring and initial styling
gjulivan 471a346
chore(docviewer): cleanup mammoth js
gjulivan d199f09
chore(doc-viewer): reintroduce mammoth
gjulivan 4d60bd0
chore(doc-viewer): include mammoth build internally
gjulivan 370ce65
fix: styling and icons
gjulivan e95a9a7
fix: update rollup config
gjulivan 11048b5
fix: remove local mammoth dependency
gjulivan File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Binary file not shown.
69 changes: 69 additions & 0 deletions
69
packages/pluggableWidgets/document-viewer-web/components/DocxViewer.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import { createElement, Fragment, useCallback, useEffect, useState } from "react"; | ||
import mammoth from "mammoth"; | ||
import { DocumentViewerContainerProps } from "../typings/DocumentViewerProps"; | ||
import { DocRendererElement } from "./documentRenderer"; | ||
|
||
const DocxViewer: DocRendererElement = (props: DocumentViewerContainerProps) => { | ||
const { file } = props; | ||
const [docxHtml, setDocxHtml] = useState<string | null>(null); | ||
|
||
const loadContent = useCallback(async (arrayBuffer: any) => { | ||
try { | ||
mammoth | ||
.convertToHtml( | ||
{ arrayBuffer: arrayBuffer }, | ||
{ | ||
includeDefaultStyleMap: true | ||
} | ||
) | ||
.then((result: any) => { | ||
if (result) { | ||
setDocxHtml(result.value); | ||
} | ||
}); | ||
} catch (error) {} | ||
}, []); | ||
|
||
useEffect(() => { | ||
const controller = new AbortController(); | ||
const { signal } = controller; | ||
if (file.status === "available" && file.value.uri) { | ||
fetch(file.value.uri, { method: "GET", signal }) | ||
.then(res => res.arrayBuffer()) | ||
.then(response => { | ||
loadContent(response); | ||
}); | ||
} | ||
|
||
return () => { | ||
controller.abort(); | ||
}; | ||
}, [file, file?.status, file?.value?.uri]); | ||
|
||
return ( | ||
<Fragment> | ||
<div className="widget-document-viewer-controls"> | ||
<div className="widget-document-viewer-controls-left">{file.value?.name}</div> | ||
</div> | ||
{docxHtml && ( | ||
<div className="widget-document-viewer-content" dangerouslySetInnerHTML={{ __html: docxHtml }}> | ||
{/* {docHtmlStr} */} | ||
</div> | ||
)} | ||
</Fragment> | ||
); | ||
}; | ||
|
||
DocxViewer.contentTypes = [ | ||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document", | ||
"application/msword", | ||
"application/vnd.ms-word", | ||
"application/vnd.ms-word.document.macroEnabled.12", | ||
"application/vnd.openxmlformats-officedocument.wordprocessingml.template", | ||
"application/vnd.ms-word.template.macroEnabled.12", | ||
"application/vnd.ms-word.document.12", | ||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document", | ||
"application/octet-stream" | ||
]; | ||
|
||
export default DocxViewer; |
14 changes: 14 additions & 0 deletions
14
packages/pluggableWidgets/document-viewer-web/components/ErrorViewer.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { createElement, Fragment } from "react"; | ||
import { DocRendererElement } from "./documentRenderer"; | ||
|
||
const ErrorViewer: DocRendererElement = () => { | ||
return ( | ||
<Fragment> | ||
<div className="widget-document-viewer-content">No document selected</div> | ||
</Fragment> | ||
); | ||
}; | ||
|
||
ErrorViewer.contentTypes = []; | ||
|
||
export default ErrorViewer; |
69 changes: 69 additions & 0 deletions
69
packages/pluggableWidgets/document-viewer-web/components/PDFViewer.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import { createElement, Fragment, useEffect, useState } from "react"; | ||
import { Document, Page, pdfjs } from "react-pdf"; | ||
import "react-pdf/dist/Page/AnnotationLayer.css"; | ||
import "react-pdf/dist/Page/TextLayer.css"; | ||
import { DocumentViewerContainerProps } from "../typings/DocumentViewerProps"; | ||
import { DocRendererElement } from "./documentRenderer"; | ||
pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/build/pdf.worker.min.mjs`; | ||
const options = { | ||
cMapUrl: `https://unpkg.com/pdfjs-dist@${pdfjs.version}/cmaps/`, | ||
standardFontDataUrl: `https://unpkg.com/pdfjs-dist@${pdfjs.version}/standard_fonts` | ||
}; | ||
|
||
const PDFViewer: DocRendererElement = (props: DocumentViewerContainerProps) => { | ||
const { file } = props; | ||
const [numberOfPages, setNumberOfPages] = useState<number>(1); | ||
const [currentPage, setCurrentPage] = useState<number>(1); | ||
const [pdfUrl, setPdfUrl] = useState<string | null>(null); | ||
|
||
if (!file.value?.uri) { | ||
return <div>No document selected</div>; | ||
} | ||
|
||
useEffect(() => { | ||
if (file.status === "available" && file.value.uri) { | ||
setPdfUrl(file.value.uri); | ||
} | ||
}, [file, file.status, file.value.uri]); | ||
|
||
function onDocumentLoadSuccess({ numPages }: { numPages: number }): void { | ||
setNumberOfPages(numPages); | ||
} | ||
|
||
return ( | ||
<Fragment> | ||
<div className="widget-document-viewer-controls"> | ||
<div className="widget-document-viewer-controls-left">{file.value?.name}</div> | ||
<div className="widget-document-viewer-controls-icons"> | ||
<div className="widget-document-viewer-pagination"> | ||
<button | ||
onClick={() => setCurrentPage(prev => Math.max(prev - 1, 1))} | ||
disabled={currentPage <= 1} | ||
className="icons icon-Left btn btn-icon-only" | ||
aria-label={"Go to previous page"} | ||
></button> | ||
<span> | ||
{currentPage} / {numberOfPages} | ||
</span> | ||
<button | ||
onClick={() => setCurrentPage(prev => Math.min(prev + 1, numberOfPages))} | ||
className="icons icon-Right btn btn-icon-only" | ||
aria-label={"Go to next page"} | ||
></button> | ||
</div> | ||
</div> | ||
</div> | ||
<div className="widget-document-viewer-content"> | ||
{pdfUrl && ( | ||
<Document file={pdfUrl} options={options} onLoadSuccess={onDocumentLoadSuccess}> | ||
<Page pageNumber={currentPage} /> | ||
</Document> | ||
)} | ||
</div> | ||
</Fragment> | ||
); | ||
}; | ||
|
||
PDFViewer.contentTypes = ["application/pdf", "application/x-pdf", "application/acrobat", "text/pdf"]; | ||
|
||
export default PDFViewer; |
6 changes: 6 additions & 0 deletions
6
packages/pluggableWidgets/document-viewer-web/components/documentRenderer.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import { FC } from "react"; | ||
import { DocumentViewerContainerProps } from "../typings/DocumentViewerProps"; | ||
|
||
export interface DocRendererElement extends FC<DocumentViewerContainerProps> { | ||
contentTypes: string[]; | ||
} |
4 changes: 4 additions & 0 deletions
4
packages/pluggableWidgets/document-viewer-web/components/index.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
import DocxViewer from "./DocxViewer"; | ||
import PDFViewer from "./PDFViewer"; | ||
|
||
export const DocumentRenderers = [DocxViewer, PDFViewer]; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
6 changes: 0 additions & 6 deletions
6
packages/pluggableWidgets/document-viewer-web/rollup.config.js
This file was deleted.
Oops, something went wrong.
48 changes: 48 additions & 0 deletions
48
packages/pluggableWidgets/document-viewer-web/rollup.config.mjs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import commonjs from "@rollup/plugin-commonjs"; | ||
import replace from "@rollup/plugin-replace"; | ||
|
||
export default args => { | ||
const result = args.configDefaultConfig; | ||
return result.map((config, _index) => { | ||
config.output.inlineDynamicImports = true; | ||
if (config.output.format !== "es") { | ||
return config; | ||
} | ||
return { | ||
...config, | ||
plugins: [ | ||
...config.plugins.map(plugin => { | ||
if (plugin && plugin.name === "commonjs") { | ||
// replace common js plugin that transforms | ||
// external requires to imports | ||
// this is needed in order to work with modern client | ||
return commonjs({ | ||
extensions: [".js", ".jsx", ".tsx", ".ts"], | ||
transformMixedEsModules: true, | ||
requireReturnsDefault: "auto", | ||
esmExternals: true | ||
}); | ||
} | ||
|
||
return plugin; | ||
}), | ||
// rollup config for pdfjs-dist copying from https://github.com/unjs/unpdf/blob/main/pdfjs.rollup.config.ts | ||
replace({ | ||
delimiters: ["", ""], | ||
preventAssignment: true, | ||
values: { | ||
// Disable the `window` check (for requestAnimationFrame). | ||
"typeof window": '"undefined"', | ||
// Imitate the Node.js environment for all serverless environments, unenv will | ||
// take care of the remaining Node.js polyfills. Keep support for browsers. | ||
"const isNodeJS = typeof": 'const isNodeJS = typeof document === "undefined" // typeof', | ||
// Force inlining the PDF.js worker. | ||
"await import(/*webpackIgnore: true*/this.workerSrc)": "__pdfjsWorker__", | ||
// Tree-shake client worker initialization logic. | ||
"!PDFWorkerUtil.isWorkerDisabled && !PDFWorker.#mainThreadWorkerMessageHandler": "false" | ||
} | ||
}) | ||
] | ||
}; | ||
}); | ||
}; |
49 changes: 49 additions & 0 deletions
49
packages/pluggableWidgets/document-viewer-web/src/DocumentViewer.editorConfig.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import { Properties } from "@mendix/pluggable-widgets-tools"; | ||
import { | ||
StructurePreviewProps, | ||
structurePreviewPalette | ||
} from "@mendix/widget-plugin-platform/preview/structure-preview-api"; | ||
import { DocumentViewerPreviewProps } from "typings/DocumentViewerProps"; | ||
|
||
export function getProperties(_values: DocumentViewerPreviewProps, defaultProperties: Properties): Properties { | ||
return defaultProperties; | ||
} | ||
|
||
export function getPreview(_values: DocumentViewerPreviewProps, isDarkMode: boolean): StructurePreviewProps { | ||
const palette = structurePreviewPalette[isDarkMode ? "dark" : "light"]; | ||
return { | ||
type: "Container", | ||
children: [ | ||
{ | ||
type: "RowLayout", | ||
grow: 2, | ||
columnSize: "grow", | ||
borders: true, | ||
borderWidth: 1, | ||
borderRadius: 2, | ||
backgroundColor: _values.readOnly ? palette.background.containerDisabled : palette.background.container, | ||
children: [ | ||
{ | ||
type: "Container", | ||
grow: 1, | ||
padding: 4, | ||
children: [ | ||
{ | ||
type: "Text", | ||
content: getCustomCaption(_values), | ||
fontColor: palette.text.data | ||
} | ||
] | ||
} | ||
], | ||
padding: 8 | ||
} | ||
], | ||
backgroundColor: palette.background.container, | ||
borderRadius: 8 | ||
}; | ||
} | ||
|
||
export function getCustomCaption(_values: DocumentViewerPreviewProps): string { | ||
return `[${_values.file ?? "No attribute selected"}]`; | ||
} |
12 changes: 12 additions & 0 deletions
12
packages/pluggableWidgets/document-viewer-web/src/DocumentViewer.editorPreview.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { createElement, ReactElement } from "react"; | ||
import { DocumentViewerPreviewProps } from "typings/DocumentViewerProps"; | ||
import "../ui/documentViewer.scss"; | ||
|
||
export const preview = (props: DocumentViewerPreviewProps): ReactElement => { | ||
const { file } = props; | ||
return ( | ||
<div className="widget-document-viewer"> | ||
<div className="widget-document-viewer-content">{file ? `[${file}]` : "[No attribute selected]"}</div> | ||
</div> | ||
); | ||
}; |
Binary file added
BIN
+1.69 KB
packages/pluggableWidgets/document-viewer-web/src/DocumentViewer.icon.dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+1.69 KB
packages/pluggableWidgets/document-viewer-web/src/DocumentViewer.icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+5.51 KB
packages/pluggableWidgets/document-viewer-web/src/DocumentViewer.tile.dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+5.48 KB
packages/pluggableWidgets/document-viewer-web/src/DocumentViewer.tile.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
52 changes: 11 additions & 41 deletions
52
packages/pluggableWidgets/document-viewer-web/src/DocumentViewer.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,49 +1,19 @@ | ||
import { createElement, ReactElement, useState } from "react"; | ||
import { createElement, ReactElement } from "react"; | ||
import { DocumentContext } from "../store"; | ||
import { DocumentViewerContainerProps } from "../typings/DocumentViewerProps"; | ||
import { Document, Page, pdfjs } from "react-pdf"; | ||
import "react-pdf/dist/Page/AnnotationLayer.css"; | ||
import "react-pdf/dist/Page/TextLayer.css"; | ||
|
||
pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/build/pdf.worker.min.mjs`; | ||
const options = { | ||
cMapUrl: `https://unpkg.com/pdfjs-dist@${pdfjs.version}/cmaps/`, | ||
standardFontDataUrl: `https://unpkg.com/pdfjs-dist@${pdfjs.version}/standard_fonts` | ||
}; | ||
import { useRendererSelector } from "../utils/useRendererSelector"; | ||
import "../ui/documentViewer.scss"; | ||
import "../ui/documentViewerIcons.scss"; | ||
import classNames from "classnames"; | ||
|
||
export default function DocumentViewer(props: DocumentViewerContainerProps): ReactElement { | ||
const [numberOfPages, setNumberOfPages] = useState<number>(1); | ||
const [currentPage, setCurrentPage] = useState<number>(1); | ||
const [pdfUrl, setPdfUrl] = useState<string | null>(null); | ||
|
||
// Load PDF when URL changes | ||
if (props.file.value?.uri && !pdfUrl) { | ||
setPdfUrl(props.file.value.uri); | ||
} | ||
|
||
if (!props.file.value?.uri) { | ||
return <div>No document selected</div>; | ||
} | ||
|
||
function onDocumentLoadSuccess({ numPages }: { numPages: number }): void { | ||
setNumberOfPages(numPages); | ||
} | ||
const { CurrentRenderer } = useRendererSelector(props); | ||
|
||
return ( | ||
<div className="widget-document-viewer"> | ||
<div className="widget-document-viewer-controls"> | ||
<button onClick={() => setCurrentPage(prev => Math.max(prev - 1, 1))} disabled={currentPage <= 1}> | ||
Previous | ||
</button> | ||
<span> | ||
Page {currentPage} of {numberOfPages} | ||
</span> | ||
<button onClick={() => setCurrentPage(prev => Math.min(prev + 1, numberOfPages))}>Next</button> | ||
</div> | ||
<div className="widget-document-viewer-content"> | ||
<Document file={pdfUrl} options={options} onLoadSuccess={onDocumentLoadSuccess}> | ||
<Page pageNumber={currentPage} /> | ||
</Document> | ||
<DocumentContext.Provider value={props}> | ||
<div className={classNames(props.class, "widget-document-viewer", "form-control")}> | ||
{CurrentRenderer && <CurrentRenderer {...props} />} | ||
</div> | ||
</div> | ||
</DocumentContext.Provider> | ||
); | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import { createContext } from "react"; | ||
import { DocumentViewerContainerProps } from "typings/DocumentViewerProps"; | ||
|
||
const DocumentContext = createContext<DocumentViewerContainerProps>({} as DocumentViewerContainerProps); | ||
|
||
export { DocumentContext }; |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.