@bpmn-io/form-js-viewer 1.14.0 → 1.15.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.
package/dist/index.cjs CHANGED
@@ -927,7 +927,6 @@ function useDeepCompareMemoize(value) {
927
927
  * @enum { String }
928
928
  */
929
929
  const LOAD_STATES = {
930
- LOADING: 'loading',
931
930
  LOADED: 'loaded',
932
931
  ERROR: 'error'
933
932
  };
@@ -1138,6 +1137,9 @@ function _isElementScrollable(el) {
1138
1137
  return (overflowY === 'auto' || overflowY === 'scroll') && el.scrollHeight > el.clientHeight;
1139
1138
  }
1140
1139
 
1140
+ const EMPTY_OBJECT = {};
1141
+ const EMPTY_ARRAY$2 = [];
1142
+
1141
1143
  /**
1142
1144
  * Custom hook to scroll an element within a scrollable container.
1143
1145
  *
@@ -1151,8 +1153,8 @@ function _isElementScrollable(el) {
1151
1153
  * @param {Array} [flagRefs] - An array of refs that are used as flags to control when to scroll.
1152
1154
  */
1153
1155
  function useScrollIntoView(scrolledElementRef, deps, scrollOptions, flagRefs) {
1154
- const _scrollOptions = scrollOptions;
1155
- const _flagRefs = flagRefs;
1156
+ const _scrollOptions = scrollOptions || EMPTY_OBJECT;
1157
+ const _flagRefs = flagRefs || EMPTY_ARRAY$2;
1156
1158
  hooks.useEffect(() => {
1157
1159
  // return early if flags are not raised, or component is not mounted
1158
1160
  if (minDash.some(_flagRefs, ref => !ref.current) || !scrolledElementRef.current) {
@@ -5921,10 +5923,15 @@ var SvgDownload = function SvgDownload(props) {
5921
5923
 
5922
5924
  const type = 'documentPreview';
5923
5925
 
5926
+ /**
5927
+ * @typedef DocumentEndpointBuilder
5928
+ * @property {(document: DocumentMetadata) => string} buildUrl
5929
+ */
5930
+
5924
5931
  /**
5925
5932
  * @typedef DocumentMetadata
5926
5933
  * @property {string} documentId
5927
- * @property {string} contentHash
5934
+ * @property {string} endpoint
5928
5935
  * @property {Object} metadata
5929
5936
  * @property {string|undefined} [metadata.contentType]
5930
5937
  * @property {string} metadata.fileName
@@ -5933,7 +5940,6 @@ const type = 'documentPreview';
5933
5940
  * @property {string} id
5934
5941
  * @property {string} [title]
5935
5942
  * @property {string} [dataSource]
5936
- * @property {string} [endpointKey]
5937
5943
  * @property {number} [maxHeight]
5938
5944
  * @property {string} [label]
5939
5945
  *
@@ -5945,18 +5951,18 @@ const type = 'documentPreview';
5945
5951
  * @returns {import("preact").JSX.Element}
5946
5952
  */
5947
5953
  function DocumentPreview(props) {
5954
+ /** @type {DocumentEndpointBuilder | null} */
5955
+ const documentEndpointBuilder = useService('documentEndpointBuilder', false);
5948
5956
  const {
5949
5957
  field,
5950
5958
  domId
5951
5959
  } = props;
5952
5960
  const {
5953
5961
  dataSource,
5954
- endpointKey,
5955
5962
  maxHeight,
5956
5963
  label
5957
5964
  } = field;
5958
5965
  const errorMessageId = `${domId}-error-message`;
5959
- const endpoint = useExpressionEvaluation(endpointKey || '');
5960
5966
  const data = useValidDocumentData(dataSource || '');
5961
5967
  const evaluatedLabel = useSingleLineTemplateEvaluation(label, {
5962
5968
  debug: true
@@ -5969,18 +5975,19 @@ function DocumentPreview(props) {
5969
5975
  }), jsxRuntime.jsx("div", {
5970
5976
  class: `fjs-${type}-document-container`,
5971
5977
  id: domId,
5972
- children: isValidDocumentEndpoint(endpoint) ? data.map((document, index) => jsxRuntime.jsx(DocumentRenderer, {
5973
- documentMetadata: document,
5974
- endpoint: endpoint,
5975
- maxHeight: maxHeight,
5976
- domId: `${domId}-${index}`
5977
- }, document.documentId)) : null
5978
+ children: data.map((document, index) => {
5979
+ const finalEndpoint = tryCatch(() => documentEndpointBuilder?.buildUrl(document)) ?? document.endpoint;
5980
+ return isValidDocumentEndpoint(finalEndpoint) ? jsxRuntime.jsx(DocumentRenderer, {
5981
+ documentMetadata: document,
5982
+ endpoint: finalEndpoint,
5983
+ maxHeight: maxHeight,
5984
+ domId: `${domId}-${index}`
5985
+ }, document.documentId) : null;
5986
+ })
5978
5987
  }), jsxRuntime.jsx(Errors, {
5979
5988
  id: errorMessageId,
5980
5989
  errors: getErrors({
5981
- dataSource,
5982
- endpoint,
5983
- endpointKey
5990
+ dataSource
5984
5991
  })
5985
5992
  })]
5986
5993
  });
@@ -5992,43 +5999,27 @@ DocumentPreview.config = {
5992
5999
  name: 'Document preview',
5993
6000
  create: (options = {}) => ({
5994
6001
  label: 'Document preview',
5995
- endpointKey: DEFAULT_ENDPOINT_KEY,
5996
6002
  ...options
5997
6003
  })
5998
6004
  };
5999
6005
 
6000
6006
  // helpers /////////////////////////////
6001
6007
 
6002
- const DOCUMENT_ID_PLACEHOLDER = '{documentId}';
6003
- const DEFAULT_ENDPOINT_KEY = '=defaultDocumentsEndpointKey';
6004
-
6005
6008
  /**
6006
6009
  * @typedef GetErrorOptions
6007
6010
  * @property {string|undefined} dataSource
6008
- * @property {string|undefined} endpointKey
6009
- * @property {string|null} endpoint
6010
6011
  *
6011
6012
  * @param {GetErrorOptions} options
6012
6013
  * @returns {string[]}
6013
6014
  */
6014
6015
  function getErrors(options) {
6015
6016
  const {
6016
- dataSource,
6017
- endpointKey,
6018
- endpoint
6017
+ dataSource
6019
6018
  } = options;
6020
6019
  let errors = [];
6021
6020
  if (!minDash.isString(dataSource) || dataSource.length < 1) {
6022
6021
  errors.push('Document reference is not defined.');
6023
6022
  }
6024
- if (!minDash.isString(endpointKey) || endpointKey.length < 1) {
6025
- errors.push('Endpoint key is not defined.');
6026
- }
6027
- if (endpointKey !== DEFAULT_ENDPOINT_KEY && !URL.canParse(endpoint)) {
6028
- errors.push(`If you change the endpoint key from "${DEFAULT_ENDPOINT_KEY}", the document preview won't work with Camunda Tasklist and you must provide a valid URL.`);
6029
- } else if (endpointKey !== DEFAULT_ENDPOINT_KEY && !isValidDocumentEndpoint(endpoint)) {
6030
- errors.push('Endpoint must contain "{documentId}".');
6031
- }
6032
6023
  return errors;
6033
6024
  }
6034
6025
 
@@ -6038,7 +6029,7 @@ function getErrors(options) {
6038
6029
  * @returns boolean
6039
6030
  */
6040
6031
  function isValidDocumentEndpoint(endpoint) {
6041
- return typeof endpoint === 'string' && URL.canParse(endpoint) && endpoint.includes(DOCUMENT_ID_PLACEHOLDER);
6032
+ return typeof endpoint === 'string' && URL.canParse(endpoint);
6042
6033
  }
6043
6034
 
6044
6035
  /**
@@ -6061,6 +6052,61 @@ function useValidDocumentData(dataSource) {
6061
6052
  return data.filter(isValidDocument);
6062
6053
  }
6063
6054
 
6055
+ /**
6056
+ * @param {Object} props
6057
+ * @param {string} props.url
6058
+ * @param {string} props.fileName
6059
+ * @param {Function} props.onError
6060
+ * @param {string} props.errorMessageId
6061
+ * @returns {import("preact").JSX.Element}
6062
+ */
6063
+ function PdfRenderer(props) {
6064
+ const {
6065
+ url,
6066
+ onError,
6067
+ errorMessageId
6068
+ } = props;
6069
+ /** @type {ReturnType<typeof import("preact/hooks").useState<null | string>>} */
6070
+ const [pdfObjectUrl, setPdfObjectUrl] = hooks.useState(null);
6071
+ const [hasError, setHasError] = hooks.useState(false);
6072
+ hooks.useEffect(() => {
6073
+ /** @type {null | string} */
6074
+ let objectUrl = null;
6075
+ const fetchPdf = async () => {
6076
+ try {
6077
+ const response = await fetch(url);
6078
+ if (!response.ok) {
6079
+ setHasError(true);
6080
+ onError();
6081
+ return;
6082
+ }
6083
+ const blob = await response.blob();
6084
+ objectUrl = URL.createObjectURL(blob);
6085
+ setPdfObjectUrl(objectUrl);
6086
+ } catch {
6087
+ setHasError(true);
6088
+ onError();
6089
+ }
6090
+ };
6091
+ fetchPdf();
6092
+ return () => {
6093
+ if (objectUrl) {
6094
+ URL.revokeObjectURL(objectUrl);
6095
+ }
6096
+ };
6097
+ }, [url, onError]);
6098
+ return jsxRuntime.jsxs(jsxRuntime.Fragment, {
6099
+ children: [pdfObjectUrl !== null ? jsxRuntime.jsx("embed", {
6100
+ src: pdfObjectUrl,
6101
+ type: "application/pdf",
6102
+ class: `fjs-${type}-pdf-viewer`
6103
+ }) : null, hasError ? jsxRuntime.jsx(Errors, {
6104
+ id: errorMessageId,
6105
+ errors: ['Unable to download document']
6106
+ }) : null]
6107
+ });
6108
+ }
6109
+
6064
6110
  /**
6065
6111
  *
6066
6112
  * @param {Object} props
@@ -6084,11 +6130,6 @@ function DocumentRenderer(props) {
6084
6130
  const [hasError, setHasError] = hooks.useState(false);
6085
6131
  const ref = hooks.useRef(null);
6086
6132
  const isInViewport = useInViewport(ref);
6087
- const fullUrl = buildUrl({
6088
- baseUrl: endpoint,
6089
- documentId: documentMetadata.documentId,
6090
- contentHash: documentMetadata.contentHash
6091
- });
6092
6133
  const singleDocumentContainerClassName = `fjs-${type}-single-document-container`;
6093
6134
  const errorMessageId = `${domId}-error-message`;
6094
6135
  const errorMessage = 'Unable to download document';
@@ -6101,11 +6142,11 @@ function DocumentRenderer(props) {
6101
6142
  },
6102
6143
  "aria-describedby": hasError ? errorMessageId : undefined,
6103
6144
  children: [jsxRuntime.jsx("img", {
6104
- src: fullUrl,
6145
+ src: endpoint,
6105
6146
  alt: metadata.fileName,
6106
6147
  class: `fjs-${type}-image`
6107
6148
  }), jsxRuntime.jsx(DownloadButton, {
6108
- endpoint: fullUrl,
6149
+ endpoint: endpoint,
6109
6150
  fileName: metadata.fileName,
6110
6151
  onDownloadError: () => {
6111
6152
  setHasError(true);
@@ -6117,20 +6158,18 @@ function DocumentRenderer(props) {
6117
6158
  });
6118
6159
  }
6119
6160
  if (isContentTypePresent && metadata.contentType.toLowerCase() === 'application/pdf' && isInViewport) {
6120
- return jsxRuntime.jsxs("div", {
6161
+ return jsxRuntime.jsx("div", {
6121
6162
  class: singleDocumentContainerClassName,
6122
6163
  style: {
6123
6164
  maxHeight
6124
6165
  },
6125
6166
  "aria-describedby": hasError ? errorMessageId : undefined,
6126
- children: [jsxRuntime.jsx("embed", {
6127
- src: fullUrl,
6128
- type: "application/pdf",
6129
- class: `fjs-${type}-pdf-viewer`
6130
- }), hasError ? jsxRuntime.jsx(Errors, {
6131
- id: errorMessageId,
6132
- errors: [errorMessage]
6133
- }) : null]
6167
+ children: jsxRuntime.jsx(PdfRenderer, {
6168
+ url: endpoint,
6169
+ fileName: metadata.fileName,
6170
+ onError: () => setHasError(true),
6171
+ errorMessageId: errorMessageId
6172
+ })
6134
6173
  });
6135
6174
  }
6136
6175
  return jsxRuntime.jsxs("div", {
@@ -6146,7 +6185,7 @@ function DocumentRenderer(props) {
6146
6185
  errors: [errorMessage]
6147
6186
  }) : null]
6148
6187
  }), jsxRuntime.jsx(DownloadButton, {
6149
- endpoint: fullUrl,
6188
+ endpoint: endpoint,
6150
6189
  fileName: metadata.fileName,
6151
6190
  onDownloadError: () => {
6152
6191
  setHasError(true);
@@ -6226,27 +6265,18 @@ function useInViewport(ref) {
6226
6265
  }
6227
6266
 
6228
6267
  /**
6229
- * This solution should be a temporary fix, we should try to remove it via: https://github.com/bpmn-io/form-js/issues/1341
6230
- *
6231
- * @param {Object} options
6232
- * @param {string} options.baseUrl
6233
- * @param {string} options.documentId
6234
- * @param {string} [options.contentHash]
6235
- *
6236
- * @returns {string}
6268
+ * @template T
6269
+ * @param {() => T} fn - Function to execute
6270
+ * @returns {T | null}
6237
6271
  */
6238
- function buildUrl(options) {
6239
- const {
6240
- baseUrl,
6241
- documentId,
6242
- contentHash
6243
- } = options;
6244
- const finalUrl = new URL(baseUrl.replace(DOCUMENT_ID_PLACEHOLDER, documentId));
6245
- if (contentHash !== undefined) {
6246
- finalUrl.searchParams.set('contentHash', contentHash);
6272
+ const tryCatch = fn => {
6273
+ try {
6274
+ return fn();
6275
+ } catch (error) {
6276
+ console.error(error);
6277
+ return null;
6247
6278
  }
6248
- return decodeURI(finalUrl.toString());
6249
- }
6279
+ };
6250
6280
 
6251
6281
  /**
6252
6282
  * This file must not be changed or exchanged.
@@ -6405,7 +6435,7 @@ class FormFields {
6405
6435
  }
6406
6436
  }
6407
6437
 
6408
- const EXPRESSION_PROPERTIES = ['alt', 'appearance.prefixAdorner', 'appearance.suffixAdorner', 'conditional.hide', 'description', 'label', 'source', 'readonly', 'text', 'validate.min', 'validate.max', 'validate.minLength', 'validate.maxLength', 'valuesExpression', 'url', 'dataSource', 'columnsExpression', 'expression', 'multiple', 'accept', 'endpointKey', 'title'];
6438
+ const EXPRESSION_PROPERTIES = ['alt', 'appearance.prefixAdorner', 'appearance.suffixAdorner', 'conditional.hide', 'description', 'label', 'source', 'readonly', 'text', 'validate.min', 'validate.max', 'validate.minLength', 'validate.maxLength', 'valuesExpression', 'url', 'dataSource', 'columnsExpression', 'expression', 'multiple', 'accept', 'title'];
6409
6439
  const TEMPLATE_PROPERTIES = ['alt', 'appearance.prefixAdorner', 'appearance.suffixAdorner', 'description', 'label', 'source', 'text', 'content', 'url', 'title'];
6410
6440
 
6411
6441
  /**