@firecms/core 3.3.0 → 3.4.0-canary.00f9be5

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 (220) hide show
  1. package/README.md +4 -0
  2. package/dist/app/useApp.d.ts +1 -0
  3. package/dist/components/EntityCollectionTable/column_utils.d.ts +4 -3
  4. package/dist/components/EntityCollectionTable/internal/CollectionTableToolbar.d.ts +6 -1
  5. package/dist/components/EntityCollectionTable/internal/popup_field/PopupFormField.d.ts +3 -1
  6. package/dist/components/EntityCollectionView/EntityCollectionBoardView.d.ts +3 -1
  7. package/dist/components/EntityCollectionView/EntityCollectionView.d.ts +8 -0
  8. package/dist/components/EntityCollectionView/EntityCollectionViewActions.d.ts +3 -1
  9. package/dist/components/EntityCollectionView/EntityCollectionViewStartActions.d.ts +3 -1
  10. package/dist/components/EntityCollectionView/FiltersDialog.d.ts +2 -1
  11. package/dist/components/EntityCollectionView/useBoardDataController.d.ts +3 -1
  12. package/dist/components/EntityPreview.d.ts +3 -1
  13. package/dist/components/ErrorBoundary.d.ts +1 -3
  14. package/dist/components/LicenseBanner.d.ts +23 -0
  15. package/dist/components/ReferenceTable/ReferenceSelectionTable.d.ts +10 -1
  16. package/dist/components/SelectableTable/filters/filter_text_input.d.ts +30 -0
  17. package/dist/components/SelectableTable/filters/filter_text_input.test.d.ts +1 -0
  18. package/dist/components/common/table_url_params.d.ts +24 -0
  19. package/dist/components/common/useDataSourceTableController.d.ts +9 -1
  20. package/dist/components/common/useTableSearchHelper.d.ts +9 -1
  21. package/dist/components/index.d.ts +1 -0
  22. package/dist/contexts/LicenseStatusContext.d.ts +16 -0
  23. package/dist/core/DefaultDrawer.d.ts +10 -3
  24. package/dist/core/EntityEditView.d.ts +9 -1
  25. package/dist/core/EntityEditViewFormActions.d.ts +1 -1
  26. package/dist/core/field_configs.d.ts +1 -1
  27. package/dist/core/index.d.ts +1 -0
  28. package/dist/core/plugin_translations.d.ts +38 -0
  29. package/dist/core/pro_plugins.d.ts +40 -0
  30. package/dist/de-BF5vcQPY.js +703 -0
  31. package/dist/de-BF5vcQPY.js.map +1 -0
  32. package/dist/editor/extensions/Image.d.ts +11 -0
  33. package/dist/editor/index.d.ts +2 -1
  34. package/dist/editor/lazy_editor.d.ts +6 -0
  35. package/dist/editor-BSo_aS1I.js +4563 -0
  36. package/dist/editor-BSo_aS1I.js.map +1 -0
  37. package/dist/form/EntityForm.d.ts +8 -1
  38. package/dist/form/EntityFormActions.d.ts +2 -0
  39. package/dist/form/field_bindings/GeopointFieldBinding.d.ts +5 -0
  40. package/dist/form/index.d.ts +1 -0
  41. package/dist/fr-DH9_wyFI.js +703 -0
  42. package/dist/fr-DH9_wyFI.js.map +1 -0
  43. package/dist/hi-BM8dm1Lu.js +703 -0
  44. package/dist/hi-BM8dm1Lu.js.map +1 -0
  45. package/dist/hooks/data/delete.d.ts +1 -1
  46. package/dist/hooks/data/save.d.ts +1 -1
  47. package/dist/hooks/data/useCollectionFetch.d.ts +7 -1
  48. package/dist/hooks/data/useEntityFetch.d.ts +9 -1
  49. package/dist/hooks/index.d.ts +2 -0
  50. package/dist/hooks/useLicenseStatus.d.ts +16 -0
  51. package/dist/hooks/useProjectLog.d.ts +11 -6
  52. package/dist/hooks/useTranslation.d.ts +1 -1
  53. package/dist/i18n/__tests__/FireCMSi18nProvider.test.d.ts +1 -0
  54. package/dist/i18n/__tests__/nested_provider_lifecycle.test.d.ts +1 -0
  55. package/dist/index.es.js +4516 -10668
  56. package/dist/index.es.js.map +1 -1
  57. package/dist/index.umd.js +9403 -6774
  58. package/dist/index.umd.js.map +1 -1
  59. package/dist/it-D3K1gfe4.js +703 -0
  60. package/dist/it-D3K1gfe4.js.map +1 -0
  61. package/dist/locales/pl.d.ts +2 -0
  62. package/dist/pl-CP1zeLzn.js +705 -0
  63. package/dist/pl-CP1zeLzn.js.map +1 -0
  64. package/dist/preview/index.d.ts +1 -0
  65. package/dist/preview/property_previews/GeopointPropertyPreview.d.ts +4 -0
  66. package/dist/pt-Cus7eKQR.js +705 -0
  67. package/dist/pt-Cus7eKQR.js.map +1 -0
  68. package/dist/types/analytics.d.ts +1 -1
  69. package/dist/types/collections.d.ts +31 -0
  70. package/dist/types/datasource.d.ts +67 -4
  71. package/dist/types/entities.d.ts +21 -1
  72. package/dist/types/entity_actions.d.ts +6 -0
  73. package/dist/types/entity_callbacks.d.ts +21 -0
  74. package/dist/types/firecms.d.ts +18 -1
  75. package/dist/types/firecms_context.d.ts +8 -0
  76. package/dist/types/index.d.ts +1 -0
  77. package/dist/types/license.d.ts +55 -0
  78. package/dist/types/navigation.d.ts +34 -5
  79. package/dist/types/plugins.d.ts +22 -1
  80. package/dist/types/properties.d.ts +17 -1
  81. package/dist/types/side_entity_controller.d.ts +7 -0
  82. package/dist/types/translations.d.ts +43 -2
  83. package/dist/util/__tests__/collections.test.d.ts +1 -0
  84. package/dist/util/__tests__/urls.test.d.ts +1 -0
  85. package/dist/util/collections.d.ts +1 -1
  86. package/dist/util/date_fns_locales.d.ts +18 -0
  87. package/dist/util/entities.d.ts +1 -0
  88. package/dist/util/entity_cache.d.ts +11 -0
  89. package/dist/util/geopoint.d.ts +22 -0
  90. package/dist/util/index.d.ts +3 -0
  91. package/dist/util/lazy_eager.d.ts +3 -3
  92. package/dist/util/navigation_blocking.d.ts +22 -0
  93. package/dist/util/navigation_from_path.d.ts +17 -0
  94. package/dist/util/navigation_utils.d.ts +107 -3
  95. package/dist/util/parent_references_from_path.d.ts +16 -0
  96. package/dist/util/paths.d.ts +28 -0
  97. package/dist/util/permissions.d.ts +10 -4
  98. package/dist/util/urls.d.ts +22 -0
  99. package/package.json +17 -11
  100. package/src/app/Scaffold.tsx +41 -44
  101. package/src/app/useApp.tsx +1 -0
  102. package/src/components/DeleteEntityDialog.tsx +2 -0
  103. package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +2 -2
  104. package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +3 -1
  105. package/src/components/EntityCollectionTable/column_utils.tsx +11 -19
  106. package/src/components/EntityCollectionTable/fields/TableReferenceField.tsx +1 -1
  107. package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +7 -0
  108. package/src/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +5 -1
  109. package/src/components/EntityCollectionView/EntityCollectionBoardView.tsx +12 -0
  110. package/src/components/EntityCollectionView/EntityCollectionView.tsx +66 -15
  111. package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +6 -2
  112. package/src/components/EntityCollectionView/EntityCollectionViewStartActions.tsx +8 -1
  113. package/src/components/EntityCollectionView/FiltersDialog.tsx +39 -28
  114. package/src/components/EntityCollectionView/useBoardDataController.tsx +17 -1
  115. package/src/components/EntityPreview.tsx +45 -41
  116. package/src/components/ErrorBoundary.tsx +12 -18
  117. package/src/components/LicenseBanner.tsx +207 -0
  118. package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +19 -2
  119. package/src/components/ReferenceWidget.tsx +1 -1
  120. package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +2 -2
  121. package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +33 -7
  122. package/src/components/SelectableTable/filters/filter_text_input.test.ts +184 -0
  123. package/src/components/SelectableTable/filters/filter_text_input.ts +71 -0
  124. package/src/components/VirtualTable/VirtualTable.tsx +19 -8
  125. package/src/components/common/default_entity_actions.tsx +22 -2
  126. package/src/components/common/table_url_params.ts +172 -0
  127. package/src/components/common/useDataSourceTableController.tsx +126 -163
  128. package/src/components/common/useTableSearchHelper.ts +26 -2
  129. package/src/components/index.tsx +1 -0
  130. package/src/contexts/LicenseStatusContext.tsx +50 -0
  131. package/src/contexts/SnackbarProvider.tsx +14 -1
  132. package/src/core/DefaultAppBar.tsx +28 -16
  133. package/src/core/DefaultDrawer.tsx +150 -64
  134. package/src/core/DrawerNavigationGroup.tsx +27 -29
  135. package/src/core/DrawerNavigationItem.tsx +10 -12
  136. package/src/core/EntityEditView.tsx +77 -35
  137. package/src/core/EntityEditViewFormActions.tsx +3 -2
  138. package/src/core/EntitySidePanel.tsx +8 -4
  139. package/src/core/FireCMS.tsx +73 -47
  140. package/src/core/SideDialogs.tsx +44 -1
  141. package/src/core/field_configs.tsx +15 -0
  142. package/src/core/index.tsx +1 -0
  143. package/src/core/plugin_translations.ts +87 -0
  144. package/src/core/pro_plugins.ts +79 -0
  145. package/src/editor/components/SlashCommandMenu.tsx +4 -1
  146. package/src/editor/extensions/Image.ts +17 -1
  147. package/src/editor/hooks/useProseMirror.ts +5 -0
  148. package/src/editor/index.ts +11 -1
  149. package/src/editor/lazy_editor.tsx +33 -0
  150. package/src/form/EntityForm.tsx +31 -10
  151. package/src/form/EntityFormActions.tsx +2 -0
  152. package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +1 -1
  153. package/src/form/field_bindings/GeopointFieldBinding.tsx +139 -0
  154. package/src/form/index.tsx +1 -0
  155. package/src/hooks/data/delete.ts +8 -0
  156. package/src/hooks/data/save.ts +6 -1
  157. package/src/hooks/data/useCollectionFetch.tsx +21 -1
  158. package/src/hooks/data/useEntityFetch.tsx +35 -4
  159. package/src/hooks/index.tsx +2 -0
  160. package/src/hooks/useBuildNavigationController.tsx +70 -15
  161. package/src/hooks/useFireCMSContext.tsx +7 -3
  162. package/src/hooks/useLicenseStatus.tsx +32 -0
  163. package/src/hooks/useProjectLog.tsx +27 -9
  164. package/src/hooks/useResolvedNavigationFrom.tsx +3 -1
  165. package/src/hooks/useTranslation.ts +3 -1
  166. package/src/hooks/useValidateAuthenticator.tsx +4 -0
  167. package/src/i18n/FireCMSi18nProvider.tsx +199 -23
  168. package/src/i18n/__tests__/FireCMSi18nProvider.test.tsx +91 -0
  169. package/src/i18n/__tests__/nested_provider_lifecycle.test.tsx +67 -0
  170. package/src/internal/useBuildDataSource.ts +37 -15
  171. package/src/internal/useBuildSideEntityController.tsx +20 -5
  172. package/src/locales/de.ts +26 -4
  173. package/src/locales/en.ts +30 -6
  174. package/src/locales/es.ts +26 -4
  175. package/src/locales/fr.ts +26 -4
  176. package/src/locales/hi.ts +26 -4
  177. package/src/locales/it.ts +26 -4
  178. package/src/locales/pl.ts +752 -0
  179. package/src/locales/pt.ts +26 -4
  180. package/src/preview/PropertyPreview.tsx +12 -0
  181. package/src/preview/components/DatePreview.tsx +2 -4
  182. package/src/preview/components/ReferencePreview.tsx +7 -3
  183. package/src/preview/components/StorageThumbnail.tsx +24 -2
  184. package/src/preview/index.ts +1 -0
  185. package/src/preview/property_previews/GeopointPropertyPreview.tsx +23 -0
  186. package/src/routes/FireCMSRoute.tsx +54 -27
  187. package/src/types/analytics.ts +3 -0
  188. package/src/types/collections.ts +34 -0
  189. package/src/types/datasource.ts +67 -4
  190. package/src/types/entities.ts +24 -1
  191. package/src/types/entity_actions.tsx +6 -0
  192. package/src/types/entity_callbacks.ts +24 -0
  193. package/src/types/firecms.tsx +19 -1
  194. package/src/types/firecms_context.tsx +9 -0
  195. package/src/types/index.ts +1 -0
  196. package/src/types/license.ts +63 -0
  197. package/src/types/navigation.ts +35 -5
  198. package/src/types/plugins.tsx +23 -1
  199. package/src/types/properties.ts +17 -0
  200. package/src/types/side_entity_controller.tsx +7 -0
  201. package/src/types/translations.ts +43 -2
  202. package/src/util/__tests__/collections.test.ts +32 -0
  203. package/src/util/__tests__/urls.test.ts +131 -0
  204. package/src/util/collections.ts +1 -1
  205. package/src/util/date_fns_locales.ts +168 -0
  206. package/src/util/entities.ts +16 -1
  207. package/src/util/entity_cache.ts +18 -0
  208. package/src/util/geopoint.ts +81 -0
  209. package/src/util/index.ts +3 -0
  210. package/src/util/lazy_eager.tsx +30 -8
  211. package/src/util/navigation_blocking.ts +45 -0
  212. package/src/util/navigation_from_path.ts +47 -7
  213. package/src/util/navigation_utils.ts +250 -5
  214. package/src/util/parent_references_from_path.ts +45 -4
  215. package/src/util/paths.ts +40 -3
  216. package/src/util/permissions.ts +22 -10
  217. package/src/util/previews.ts +8 -1
  218. package/src/util/resolutions.ts +8 -0
  219. package/src/util/urls.ts +52 -0
  220. package/src/util/useStorageUploadController.tsx +6 -1
package/src/locales/pt.ts CHANGED
@@ -70,8 +70,19 @@ export const pt: FireCMSTranslations = {
70
70
  select_from: "Selecionar de {{name}}",
71
71
  done: "Concluído",
72
72
  log_out: "Terminar sessão",
73
- license_needed: "Licença necessária",
74
- license_description: "Precisa de uma licença válida para usar o FireCMS PRO. Por favor contacte {{email}} para mais informações.",
73
+ license_trial_banner: "Avaliação do FireCMS PRO: faltam {{days}} dias em produção.",
74
+ license_trial_banner_one: "Avaliação do FireCMS PRO: falta {{days}} dia em produção.",
75
+ license_expired_banner: "A sua avaliação PRO terminou a {{date}}. As funcionalidades PRO, como o editor de esquemas, a importação/exportação e o histórico, estão em pausa; os seus dados e coleções continuam a funcionar.",
76
+ license_expired_banner_undated: "A sua avaliação PRO terminou. As funcionalidades PRO, como o editor de esquemas, a importação/exportação e o histórico, estão em pausa; os seus dados e coleções continuam a funcionar.",
77
+ license_invalid_project_banner: "Esta chave de licença não está associada ao projeto {{projectId}}. As funcionalidades PRO ficam em pausa até que esteja.",
78
+ license_invalid_project_banner_unnamed: "Esta chave de licença não está associada a este projeto. As funcionalidades PRO ficam em pausa até que esteja.",
79
+ license_over_quota_banner: "A licença deste projeto cobre {{licensed}} projetos em produção e este não é um deles. As funcionalidades PRO, como o editor de esquemas, a importação/exportação e o histórico, estão em pausa; os seus dados e coleções continuam a funcionar.",
80
+ license_over_quota_banner_one: "A licença deste projeto cobre {{licensed}} projeto em produção e é outro. As funcionalidades PRO, como o editor de esquemas, a importação/exportação e o histórico, estão em pausa; os seus dados e coleções continuam a funcionar.",
81
+ license_get_license: "Obter uma licença",
82
+ license_update_license: "Atualizar a sua licença",
83
+ license_add_project_to_license: "Adicioná-lo à sua licença",
84
+ pro_features_paused: "Funcionalidades PRO em pausa",
85
+ user_management_paused_description: "A gestão de utilizadores e funções faz parte do FireCMS PRO. Os seus utilizadores e funções atuais continuam a decidir quem pode iniciar sessão e o que pode fazer, e poderá voltar a editá-los assim que este projeto tiver uma licença.",
75
86
  column_cannot_be_edited: "Esta coluna não pode ser editada diretamente",
76
87
  close: "Fechar",
77
88
  unsaved_local_changes: "Alterações locais não guardadas",
@@ -151,8 +162,8 @@ export const pt: FireCMSTranslations = {
151
162
  // ─── Error states ─────────────────────────────────────────────
152
163
  error: "Erro",
153
164
  error_uploading_file: "Erro ao carregar ficheiro",
154
- error_deleting: "Erro ao eliminar",
155
- error_before_delete: "Erro antes de eliminar",
165
+ error_deleting: "Erro ao eliminar: {{message}}",
166
+ error_before_delete: "Erro antes de eliminar: {{message}}",
156
167
  error_updating_asset: "Erro ao atualizar recurso",
157
168
  error_deleting_asset: "Erro ao eliminar recurso",
158
169
  error_firestore_index: "É necessário um índice Firestore para esta consulta.",
@@ -394,6 +405,10 @@ export const pt: FireCMSTranslations = {
394
405
  download: "Descarregar",
395
406
  large_number_of_documents: "Esta coleção tem um grande número de documentos ({{count}}).",
396
407
  include_undefined_values: "Incluir valores indefinidos",
408
+ export_apply_filter_sort: "Exportar apenas os resultados que correspondem ao filtro/ordenação atual",
409
+ export_all_entities: "Todas as entidades",
410
+ export_selected_entities: "Entidades selecionadas ({{count}})",
411
+ export_error: "Não foi possível exportar os dados: {{message}}",
397
412
  submit: "Submeter",
398
413
 
399
414
  no_filterable_properties: "Não há propriedades filtráveis disponíveis",
@@ -443,6 +458,7 @@ export const pt: FireCMSTranslations = {
443
458
  roles_menu: "Funções",
444
459
  project_settings: "Definições do projeto",
445
460
  firestore_manager: "Gerenciador do Firestore",
461
+ datatalk: "DataTalk",
446
462
  manage_your_firestore_data: "Gerencie os seus dados do Firestore",
447
463
 
448
464
  // ─── FireCMS Cloud Login ──────────────────────────────────────
@@ -459,6 +475,7 @@ export const pt: FireCMSTranslations = {
459
475
  not_the_admin_panel: "não no painel de administração.",
460
476
  join_our_newsletter: "Subscreva a nossa newsletter. Sem spam, apenas atualizações importantes!",
461
477
  by_signing_in_you_agree_to_our: "Ao iniciar sessão, concorda com os nossos",
478
+ please_accept_the_terms_to_continue: "Aceite os termos e condições para continuar",
462
479
  terms_and_conditions: "Termos e Condições",
463
480
  and_our: "e a nossa",
464
481
  privacy_policy: "Política de privacidade",
@@ -501,6 +518,7 @@ export const pt: FireCMSTranslations = {
501
518
  licensed_projects_lowercase: "projetos licenciados",
502
519
  manage: "Gerir",
503
520
  update_payment_method: "Atualizar método de pagamento",
521
+ stripe_portal_error: "Não foi possível abrir o Stripe: {{message}}",
504
522
  your_firecms_cloud_projects: "Os seus projetos FireCMS Cloud",
505
523
  status_active: "Ativo",
506
524
  status_trialing: "Em avaliação",
@@ -595,6 +613,10 @@ export const pt: FireCMSTranslations = {
595
613
  order_property_not_found: "A propriedade \"{{property}}\" não existe ou não é uma propriedade numérica. Selecione uma propriedade válida ou limpe a seleção.",
596
614
  no_number_properties: "Nenhuma propriedade numérica encontrada. Adicione uma propriedade numérica para habilitar a ordenação.",
597
615
  order_property_description: "Selecione uma propriedade numérica para manter a ordem dos itens",
616
+ image_property: "Propriedade de imagem",
617
+ image_property_not_found: "A propriedade \"{{property}}\" não existe ou não é uma propriedade de imagem. Selecione uma propriedade válida ou limpe a seleção.",
618
+ no_image_properties: "Nenhuma propriedade de imagem encontrada. Adicione uma propriedade de armazenamento ou URL de imagem para usar como miniatura.",
619
+ image_property_description: "Selecione a propriedade usada como miniatura nas visualizações de Cartões e Kanban. Se não for definida, a primeira propriedade de imagem correspondente será usada automaticamente.",
598
620
  display_settings: "Configurações de exibição",
599
621
  default_row_size: "Tamanho de linha padrão",
600
622
  side_dialog_width: "Largura do diálogo lateral",
@@ -4,6 +4,7 @@ import equal from "react-fast-compare"
4
4
  import {
5
5
  CMSType,
6
6
  EntityReference,
7
+ GeoPoint,
7
8
  ResolvedArrayProperty,
8
9
  ResolvedMapProperty,
9
10
  ResolvedNumberProperty,
@@ -27,12 +28,14 @@ import { ArrayPropertyEnumPreview } from "./property_previews/ArrayPropertyEnumP
27
28
  import { ArrayOfStringsPreview } from "./property_previews/ArrayOfStringsPreview";
28
29
  import { ArrayOneOfPreview } from "./property_previews/ArrayOneOfPreview";
29
30
  import { MapPropertyPreview } from "./property_previews/MapPropertyPreview";
31
+ import { GeopointPropertyPreview } from "./property_previews/GeopointPropertyPreview";
30
32
  import { ReferencePreview } from "./components/ReferencePreview";
31
33
  import { DatePreview } from "./components/DatePreview";
32
34
  import { BooleanPreview } from "./components/BooleanPreview";
33
35
  import { NumberPropertyPreview } from "./property_previews/NumberPropertyPreview";
34
36
  import { ErrorView } from "../components";
35
37
  import { UserPreview } from "./components/UserPreview";
38
+ import { getGeoPointCoordinates } from "../util";
36
39
 
37
40
  /**
38
41
  * @group Preview components
@@ -202,6 +205,15 @@ export const PropertyPreview = React.memo(function PropertyPreview<T extends CMS
202
205
  } else {
203
206
  content = buildWrongValueType(propertyKey, property.dataType, value);
204
207
  }
208
+ } else if (property.dataType === "geopoint") {
209
+ const coordinates = getGeoPointCoordinates(value as GeoPoint);
210
+ if (coordinates) {
211
+ content = <GeopointPropertyPreview {...props}
212
+ property={property}
213
+ value={value as GeoPoint}/>;
214
+ } else {
215
+ content = buildWrongValueType(propertyKey, property.dataType, value);
216
+ }
205
217
  } else if (property.dataType === "reference") {
206
218
  if (typeof property.path === "string") {
207
219
  if (typeof value === "object" && "isEntityReference" in value && value.isEntityReference()) {
@@ -1,9 +1,8 @@
1
1
  import React from "react";
2
2
 
3
3
  import { format } from "date-fns";
4
- import * as locales from "date-fns/locale";
5
4
  import { useCustomizationController } from "../../hooks";
6
- import { defaultDateFormat } from "../../util";
5
+ import { defaultDateFormat, useDateFnsLocale } from "../../util";
7
6
 
8
7
  export interface DatePreviewProps {
9
8
  date: Date;
@@ -28,8 +27,7 @@ export function DatePreview({
28
27
  }: DatePreviewProps): React.ReactElement {
29
28
 
30
29
  const customizationController = useCustomizationController();
31
- // @ts-ignore
32
- const dateUtilsLocale = customizationController?.locale ? locales[customizationController?.locale] : undefined;
30
+ const dateUtilsLocale = useDateFnsLocale(customizationController?.locale);
33
31
 
34
32
  if (!date) {
35
33
  return <></>;
@@ -7,6 +7,7 @@ import { Skeleton } from "@firecms/ui";
7
7
  import { ErrorBoundary, ErrorView } from "../../components";
8
8
  import { EntityPreview, EntityPreviewContainer } from "../../components/EntityPreview";
9
9
  import { jsonStringifyReplacer } from "../../util/objects";
10
+ import { entityCacheKey } from "../../util/entity_cache";
10
11
 
11
12
  export type ReferencePreviewProps = {
12
13
  disabled?: boolean;
@@ -53,7 +54,7 @@ function ReferencePreviewInternal({
53
54
 
54
55
  const navigationController = useNavigationController();
55
56
 
56
- const collection = navigationController.getCollection(reference.path);
57
+ const collection = navigationController.getCollection(reference.path, false, reference.pathSegments);
57
58
  if (!collection) {
58
59
  if (customizationController.components?.missingReference) {
59
60
  return <customizationController.components.missingReference path={reference.path}/>;
@@ -99,16 +100,19 @@ function ReferencePreviewExisting<M extends Record<string, any> = any>({
99
100
  dataLoadingError
100
101
  } = useEntityFetch({
101
102
  path: reference.path,
103
+ // References carry the segments of the collection they point at, when the code that
104
+ // built them knew them (see getReferenceFrom).
105
+ pathSegments: reference.pathSegments,
102
106
  entityId: reference.id,
103
107
  collection,
104
108
  useCache: true
105
109
  });
106
110
 
107
111
  if (entity) {
108
- referencesCache.set(reference.pathWithId, entity);
112
+ referencesCache.set(entityCacheKey(reference.path, reference.id), entity);
109
113
  }
110
114
 
111
- const usedEntity = entity ?? referencesCache.get(reference.pathWithId);
115
+ const usedEntity = entity ?? referencesCache.get(entityCacheKey(reference.path, reference.id));
112
116
 
113
117
  let body: React.ReactNode;
114
118
 
@@ -6,6 +6,7 @@ import { useStorageSource } from "../../hooks";
6
6
  import { DownloadConfig, FileType } from "../../types";
7
7
  import { PreviewSize } from "../PropertyPreviewProps";
8
8
  import { ErrorView } from "../../components";
9
+ import { getPreviewTypeFromUrl, isAbsoluteHttpUrl } from "../../util/urls";
9
10
 
10
11
  type StorageThumbnailProps = {
11
12
  storagePathOrDownloadUrl: string;
@@ -41,10 +42,20 @@ export function StorageThumbnailInternal({
41
42
  const [error, setError] = React.useState<Error | undefined>(undefined);
42
43
  const storage = useStorageSource();
43
44
 
45
+ /**
46
+ * The saved value may already be a resolved download URL instead of a
47
+ * storage path: either because the property is configured with
48
+ * `storeUrl: true`, or because the value was written directly in the
49
+ * datasource pointing to an externally hosted file. Such values can be
50
+ * previewed as they are, and must not be handed over to the storage source,
51
+ * which can only resolve paths of its own bucket.
52
+ */
53
+ const valueIsUrl = storeUrl || isAbsoluteHttpUrl(storagePathOrDownloadUrl);
54
+
44
55
  const [downloadConfig, setDownloadConfig] = React.useState<DownloadConfig>(URL_CACHE[storagePathOrDownloadUrl]);
45
56
 
46
57
  useEffect(() => {
47
- if (!storagePathOrDownloadUrl)
58
+ if (!storagePathOrDownloadUrl || valueIsUrl)
48
59
  return;
49
60
  let unmounted = false;
50
61
  storage.getDownloadURL(storagePathOrDownloadUrl)
@@ -57,10 +68,21 @@ export function StorageThumbnailInternal({
57
68
  return () => {
58
69
  unmounted = true;
59
70
  };
60
- }, [storagePathOrDownloadUrl]);
71
+ }, [storagePathOrDownloadUrl, valueIsUrl]);
61
72
 
62
73
  if (!storagePathOrDownloadUrl) return null;
63
74
 
75
+ if (valueIsUrl) {
76
+ // There is no storage metadata for a value that is already a URL, so the
77
+ // preview type is inferred from its file extension when possible.
78
+ return <UrlComponentPreview previewType={getPreviewTypeFromUrl(storagePathOrDownloadUrl) ?? "file"}
79
+ url={storagePathOrDownloadUrl}
80
+ interactive={interactive}
81
+ size={size}
82
+ fill={fill}
83
+ hint={storagePathOrDownloadUrl} />;
84
+ }
85
+
64
86
  const filetype = downloadConfig?.metadata ? getFiletype(downloadConfig?.metadata.contentType) : undefined;
65
87
  const previewType = filetype?.startsWith("image")
66
88
  ? "image"
@@ -7,6 +7,7 @@ export * from "./property_previews/ArrayOfStringsPreview";
7
7
  export * from "./property_previews/ArrayPropertyEnumPreview";
8
8
  export * from "./property_previews/ArrayOfMapsPreview";
9
9
  export * from "./property_previews/NumberPropertyPreview";
10
+ export * from "./property_previews/GeopointPropertyPreview";
10
11
  export * from "./property_previews/StringPropertyPreview";
11
12
  export * from "./property_previews/ArrayOfStorageComponentsPreview";
12
13
  export * from "./property_previews/ArrayOfReferencesPreview";
@@ -0,0 +1,23 @@
1
+ import React from "react";
2
+
3
+ import { GeoPoint } from "../../types";
4
+ import { PropertyPreviewProps } from "../PropertyPreviewProps";
5
+ import { formatGeoPoint, getGeoPointCoordinates } from "../../util";
6
+
7
+ export function GeopointPropertyPreview({
8
+ value,
9
+ size
10
+ }: PropertyPreviewProps<GeoPoint>): React.ReactElement {
11
+
12
+ const coordinates = getGeoPointCoordinates(value);
13
+
14
+ if (!coordinates) {
15
+ return <span className={size === "small" ? "text-sm text-text-secondary dark:text-text-secondary-dark" : "text-text-secondary dark:text-text-secondary-dark"}>—</span>;
16
+ }
17
+
18
+ return (
19
+ <span className={size === "small" ? "text-sm font-mono" : "font-mono"}>
20
+ {formatGeoPoint(coordinates)}
21
+ </span>
22
+ );
23
+ }
@@ -10,6 +10,8 @@ import {
10
10
  } from "../util/navigation_from_path";
11
11
  import { useBreadcrumbsController } from "../hooks/useBreadcrumbsController";
12
12
  import { toArray } from "../util/arrays";
13
+ import { addInitialSlash, encodeEntityId } from "../util/navigation_utils";
14
+ import { shouldBlockEntityNavigation } from "../util/navigation_blocking";
13
15
  import { NotFoundPage } from "../components";
14
16
  import { lazyEager } from "../util/lazy_eager";
15
17
 
@@ -88,7 +90,7 @@ export function FireCMSRoute() {
88
90
  let collection: EntityCollection<any> | undefined;
89
91
  collection = navigation.getCollectionById(navigationEntries[0].id);
90
92
  if (!collection)
91
- collection = navigation.getCollection(navigationEntries[0].path);
93
+ collection = navigation.getCollection(navigationEntries[0].path, false, navigationEntries[0].pathSegments);
92
94
  if (!collection)
93
95
  return null;
94
96
  return <React.Suspense fallback={null}>
@@ -97,6 +99,7 @@ export function FireCMSRoute() {
97
99
  isSubCollection={false}
98
100
  parentCollectionIds={[]}
99
101
  fullPath={collection.path}
102
+ pathSegments={navigationEntries[0].pathSegments}
100
103
  fullIdPath={collection.id}
101
104
  updateUrl={true}
102
105
  {...collection}
@@ -111,7 +114,7 @@ export function FireCMSRoute() {
111
114
  const firstEntry = navigationEntries[0] as NavigationViewCollectionInternal<any>;
112
115
  collection = navigation.getCollectionById(firstEntry.id);
113
116
  if (!collection)
114
- collection = navigation.getCollection(firstEntry.path);
117
+ collection = navigation.getCollection(firstEntry.path, false, firstEntry.pathSegments);
115
118
  if (!collection)
116
119
  return null;
117
120
  return <React.Suspense fallback={null}>
@@ -121,6 +124,7 @@ export function FireCMSRoute() {
121
124
  isSubCollection={false}
122
125
  parentCollectionIds={[]}
123
126
  fullPath={collection.path}
127
+ pathSegments={firstEntry.pathSegments}
124
128
  updateUrl={true}
125
129
  {...collection}
126
130
  Actions={toArray(collection.Actions)} />
@@ -182,6 +186,8 @@ function EntityFullScreenRoute({
182
186
  const urlTab = getSelectedTabFromUrl(isNew, lastCustomView);
183
187
  const [selectedTab, setSelectedTab] = useState<string | undefined>(urlTab);
184
188
 
189
+ // `navigationPath` comes from the URL, where entity ids are still escaped (see
190
+ // encodeEntityId), so splitting it on "/" inside is correct and no segments are needed.
185
191
  const parentCollectionIds = navigation.getParentCollectionIds(navigationPath);
186
192
  useEffect(() => {
187
193
  if (urlTab !== selectedTab) {
@@ -189,27 +195,51 @@ function EntityFullScreenRoute({
189
195
  }
190
196
  }, [urlTab]);
191
197
 
192
- const basePath = !entityId || isNew
198
+ const lastCollectionEntry = navigationEntries.findLast((entry) => entry.type === "collection");
199
+
200
+ // The entity id reaches the URL escaped AND percent-encoded, so it cannot be located by
201
+ // searching `pathname` for the raw id — that misses for any id needing encoding (a "/",
202
+ // but equally a space or a non-ASCII character). Rebuild the URLs from the navigation
203
+ // entries instead, which already carry the escaped chain in `fullPath`.
204
+ const entityEntryIndex = lastEntityEntry ? navigationEntries.indexOf(lastEntityEntry) : -1;
205
+ const parentCollectionEntry = entityEntryIndex > 0
206
+ ? navigationEntries[entityEntryIndex - 1] as NavigationViewCollectionInternal<any>
207
+ : undefined;
208
+
209
+ const buildUrl = (escapedPath: string) => addInitialSlash(navigation.buildUrlCollectionPath(escapedPath));
210
+
211
+ const basePath = !entityId || isNew || !parentCollectionEntry
193
212
  ? pathname
194
- : pathname.substring(0, pathname.lastIndexOf(`/${entityId}`));
213
+ : buildUrl(parentCollectionEntry.fullPath);
214
+
215
+ const entityPath = lastEntityEntry ? buildUrl(lastEntityEntry.fullPath) : basePath;
195
216
 
196
- const entityPath = basePath + `/${entityId}`;
217
+ /**
218
+ * Build the URL of an entity in the current collection. `id` is raw, so it is escaped
219
+ * before being joined; `buildUrlCollectionPath` then percent-encodes the whole path.
220
+ */
221
+ const buildEntityUrl = (id: string, tab?: string) => {
222
+ const parentPath = parentCollectionEntry?.fullPath ?? lastCollectionEntry?.fullPath;
223
+ if (!parentPath) return pathname;
224
+ return buildUrl(`${parentPath}/${encodeEntityId(id)}${tab ? "/" + tab : ""}`);
225
+ };
197
226
 
198
227
  let blocker: Blocker | undefined = undefined;
199
228
  try {
200
229
  blocker = useBlocker(({
230
+ currentLocation,
201
231
  nextLocation
202
- }) => {
203
- if (nextLocation.pathname.startsWith(entityPath))
204
- return false;
205
- return blocked.current;
206
- });
232
+ }) => shouldBlockEntityNavigation({
233
+ currentLocation,
234
+ nextLocation,
235
+ entityPath,
236
+ basePath,
237
+ blocked: blocked.current
238
+ }));
207
239
  } catch (e) {
208
240
  // console.warn("Blocker not available, navigation will not be blocked");
209
241
  }
210
242
 
211
- const lastCollectionEntry = navigationEntries.findLast((entry) => entry.type === "collection");
212
-
213
243
  if (isNew && !lastCollectionEntry) {
214
244
  throw new Error("INTERNAL: No collection found in the navigation");
215
245
  }
@@ -219,14 +249,20 @@ function EntityFullScreenRoute({
219
249
  }
220
250
 
221
251
  const collection = isNew ? lastCollectionEntry!.collection : lastEntityEntry!.parentCollection;
222
- const fullIdPath = isNew ? lastCollectionEntry!.path : lastEntityEntry!.path;
223
- const collectionPath = navigation.resolveIdsFrom(fullIdPath);
252
+ // `fullIdPath` is used downstream to build URLs, so it carries the escaped chain;
253
+ // `collectionPath` addresses the datasource, so it is resolved from the raw one.
254
+ const fullIdPath = isNew ? lastCollectionEntry!.fullPath : (parentCollectionEntry?.fullPath ?? lastEntityEntry!.fullPath);
255
+ // The navigation entries know the real segment boundaries, so the path is resolved
256
+ // from them rather than re-parsed out of the flattened string.
257
+ const pathSegments = isNew ? lastCollectionEntry!.pathSegments : lastEntityEntry!.pathSegments;
258
+ const collectionPath = navigation.resolveIdsFrom(isNew ? lastCollectionEntry!.path : lastEntityEntry!.path, pathSegments);
224
259
  return <>
225
260
  <React.Suspense fallback={null}>
226
261
  <EntityEditView
227
262
  key={collection.id + "_" + (isNew ? "new" : (isCopy ? entityId + "_copy" : entityId))}
228
263
  entityId={isNew ? undefined : entityId}
229
264
  fullIdPath={fullIdPath}
265
+ pathSegments={pathSegments}
230
266
  collection={collection}
231
267
  layout={"full_screen"}
232
268
  path={collectionPath}
@@ -234,25 +270,16 @@ function EntityFullScreenRoute({
234
270
  selectedTab={selectedTab ?? undefined}
235
271
  onValuesModified={(modified) => blocked.current = modified}
236
272
  onSaved={(params) => {
237
- const newSelectedTab = params.selectedTab;
238
273
  const newEntityId = params.entityId;
239
- if (newSelectedTab) {
240
- navigate(`${basePath}/${newEntityId}/${newSelectedTab}`, { replace: true });
241
- } else {
242
- navigate(`${basePath}/${newEntityId}`, { replace: true });
243
- }
274
+ if (!newEntityId) return;
275
+ navigate(buildEntityUrl(newEntityId, params.selectedTab), { replace: true });
244
276
  }}
245
277
  onTabChange={(params) => {
246
278
  setSelectedTab(params.selectedTab);
247
- if (isNew) {
279
+ if (isNew || !entityId) {
248
280
  return;
249
281
  }
250
- const newSelectedTab = params.selectedTab;
251
- if (newSelectedTab) {
252
- navigate(`${basePath}/${entityId}/${newSelectedTab}`, { replace: true });
253
- } else {
254
- navigate(`${basePath}/${entityId}`, { replace: true });
255
- }
282
+ navigate(buildEntityUrl(entityId, params.selectedTab), { replace: true });
256
283
  }}
257
284
  parentCollectionIds={parentCollectionIds}
258
285
  />
@@ -44,5 +44,8 @@ export type CMSAnalyticsEvent =
44
44
 
45
45
  | "card_view_entity_click"
46
46
 
47
+ | "entity_history_opened"
48
+ | "entity_history_reverted"
49
+
47
50
  | "unmapped_event"
48
51
  ;
@@ -101,6 +101,14 @@ export interface EntityCollection<M extends Record<string, any> = any, USER exte
101
101
  */
102
102
  titleProperty?: keyof M;
103
103
 
104
+ /**
105
+ * Property used as the image thumbnail in Cards and Kanban view modes.
106
+ * Must reference a storage/image property (or an array of them), or a
107
+ * string property with `url: "image"`.
108
+ * If not specified, the first matching image property will be used.
109
+ */
110
+ imageProperty?: keyof M;
111
+
104
112
  /**
105
113
  * When editing an entity, you can choose to open the entity in a side dialog
106
114
  * or in a full screen dialog. Defaults to `full_screen`.
@@ -261,6 +269,22 @@ export interface EntityCollection<M extends Record<string, any> = any, USER exte
261
269
  */
262
270
  initialFilter?: FilterValues<Extract<keyof M, string>>; // setting FilterValues<M> can break defining collections by code
263
271
 
272
+ /**
273
+ * Array of property keys that are allowed for filtering. Allowed filters will be displayed in the collection view, table row headers
274
+ * and will be used to filter data.
275
+ *
276
+ * If not specified, all properties that are filterable will be allowed.
277
+ *
278
+ * If you specify this prop, the filters will be displayed in the collection view if they are filterable.
279
+ *
280
+ * If you set it as an empty array, no filters will be displayed.
281
+ *
282
+ * e.g. `allowedFilters: ["name", "category"]`
283
+ *
284
+ * e.g. `allowedFilters: []`
285
+ */
286
+ allowedFilters?: (keyof M)[];
287
+
264
288
  /**
265
289
  * Default sort applied to this collection.
266
290
  * When setting this prop, entities will have a default order
@@ -473,6 +497,14 @@ export interface CollectionActionsProps<M extends Record<string, any> = any, USE
473
497
  */
474
498
  path: string;
475
499
 
500
+ /**
501
+ * `path` split at its real segment boundaries, e.g. `["nodes", "node/42", "edges"]`.
502
+ *
503
+ * Optional, and never derived by splitting `path` — a parent entity id may itself
504
+ * contain "/". Absent means "not known here", not "no slashes".
505
+ */
506
+ pathSegments?: string[];
507
+
476
508
  /**
477
509
  * Path of the last collection, like `addresses`
478
510
  */
@@ -725,6 +757,8 @@ export type EntityTableController<M extends Record<string, any> = any> = {
725
757
  dataLoadingError?: Error;
726
758
  filterValues?: FilterValues<Extract<keyof M, string>>;
727
759
  setFilterValues?: (filterValues: FilterValues<Extract<keyof M, string>>) => void;
760
+ allowedFilters?: (keyof M)[];
761
+ forcedFilters?: (keyof M)[];
728
762
  sortBy?: [Extract<keyof M, string>, "asc" | "desc"];
729
763
  setSortBy?: (sortBy?: [Extract<keyof M, string>, "asc" | "desc"]) => void;
730
764
  searchString?: string;
@@ -8,6 +8,20 @@ import { FireCMSContext } from "./firecms_context";
8
8
  */
9
9
  export interface FetchEntityProps<M extends Record<string, any> = any> {
10
10
  path: string;
11
+ /**
12
+ * The path split at its real segment boundaries, e.g.
13
+ * `["nodes", "node/42", "edges"]`.
14
+ *
15
+ * `path` is a single flattened string, so a parent entity id containing "/" cannot be
16
+ * recovered from it — `"nodes/node/42/edges"` is indistinguishable from a three-level
17
+ * nesting. This is the unambiguous form: one array element per segment, entity ids
18
+ * kept whole however many slashes they contain.
19
+ *
20
+ * For any backend whose ids cannot contain "/" this is exactly `path.split("/")`, so
21
+ * delegates that do not care can keep using `path`. Delegates that do should prefer
22
+ * `pathSegments ?? path.split("/")`.
23
+ */
24
+ pathSegments?: string[];
11
25
  entityId: string;
12
26
  databaseId?: string;
13
27
  collection?: EntityCollection<M, any>
@@ -28,6 +42,20 @@ export type ListenEntityProps<M extends Record<string, any> = any> =
28
42
  */
29
43
  export interface FetchCollectionProps<M extends Record<string, any> = any> {
30
44
  path: string;
45
+ /**
46
+ * The path split at its real segment boundaries, e.g.
47
+ * `["nodes", "node/42", "edges"]`.
48
+ *
49
+ * `path` is a single flattened string, so a parent entity id containing "/" cannot be
50
+ * recovered from it — `"nodes/node/42/edges"` is indistinguishable from a three-level
51
+ * nesting. This is the unambiguous form: one array element per segment, entity ids
52
+ * kept whole however many slashes they contain.
53
+ *
54
+ * For any backend whose ids cannot contain "/" this is exactly `path.split("/")`, so
55
+ * delegates that do not care can keep using `path`. Delegates that do should prefer
56
+ * `pathSegments ?? path.split("/")`.
57
+ */
58
+ pathSegments?: string[];
31
59
  collection?: EntityCollection<M> | ResolvedEntityCollection<M>;
32
60
  filter?: FilterValues<Extract<keyof M, string>>,
33
61
  limit?: number;
@@ -52,6 +80,20 @@ export type ListenCollectionProps<M extends Record<string, any> = any> =
52
80
  */
53
81
  export interface SaveEntityProps<M extends Record<string, any> = any> {
54
82
  path: string;
83
+ /**
84
+ * The path split at its real segment boundaries, e.g.
85
+ * `["nodes", "node/42", "edges"]`.
86
+ *
87
+ * `path` is a single flattened string, so a parent entity id containing "/" cannot be
88
+ * recovered from it — `"nodes/node/42/edges"` is indistinguishable from a three-level
89
+ * nesting. This is the unambiguous form: one array element per segment, entity ids
90
+ * kept whole however many slashes they contain.
91
+ *
92
+ * For any backend whose ids cannot contain "/" this is exactly `path.split("/")`, so
93
+ * delegates that do not care can keep using `path`. Delegates that do should prefer
94
+ * `pathSegments ?? path.split("/")`.
95
+ */
96
+ pathSegments?: string[];
55
97
  values: Partial<EntityValues<M>>;
56
98
  entityId?: string; // can be empty for new entities
57
99
  previousValues?: Partial<EntityValues<M>>;
@@ -64,6 +106,11 @@ export interface SaveEntityProps<M extends Record<string, any> = any> {
64
106
  */
65
107
  export interface DeleteEntityProps<M extends Record<string, any> = any> {
66
108
  entity: Entity<M>;
109
+ /**
110
+ * `entity.path` split at its real segment boundaries, when the caller knows them.
111
+ * Never derived from `entity.path` — see `pathSegments` on FetchCollectionProps.
112
+ */
113
+ pathSegments?: string[];
67
114
  collection?: EntityCollection<M> | ResolvedEntityCollection<M>;
68
115
  }
69
116
 
@@ -203,13 +250,19 @@ export interface DataSource {
203
250
  name: string,
204
251
  value: any,
205
252
  entityId?: string,
206
- collection?: EntityCollection
253
+ collection?: EntityCollection,
254
+ /**
255
+ * Appended last, and optional, so existing implementations and callers keep
256
+ * working unchanged — a function declaring fewer parameters stays assignable.
257
+ * See `pathSegments` on FetchCollectionProps.
258
+ */
259
+ pathSegments?: string[]
207
260
  ): Promise<boolean>;
208
261
 
209
262
  /**
210
263
  * Generate an id for a new entity
211
264
  */
212
- generateEntityId(path: string, collection: EntityCollection): string;
265
+ generateEntityId(path: string, collection: EntityCollection, pathSegments?: string[]): string;
213
266
 
214
267
  /**
215
268
  * Count the number of entities in a collection
@@ -230,6 +283,10 @@ export interface DataSource {
230
283
  initTextSearch?: (props: {
231
284
  context: FireCMSContext,
232
285
  path: string,
286
+ /**
287
+ * `path` split at its real segment boundaries, when known. Never derived from `path`.
288
+ */
289
+ pathSegments?: string[],
233
290
  collection: EntityCollection,
234
291
  parentCollectionIds?: string[]
235
292
  }) => Promise<boolean>;
@@ -237,6 +294,8 @@ export interface DataSource {
237
294
  }
238
295
 
239
296
  export type FilterCombinationValidProps = {
297
+ /** `path` split at its real segment boundaries — see `pathSegments` on FetchCollectionProps. */
298
+ pathSegments?: string[];
240
299
  path: string;
241
300
  collection: EntityCollection<any>;
242
301
  filterValues: FilterValues<any>;
@@ -366,12 +425,12 @@ export interface DataSourceDelegate {
366
425
  * @param collection
367
426
  * @return `true` if there are no other fields besides the given entity
368
427
  */
369
- checkUniqueField(path: string, name: string, value: any, entityId?: string, collection?: EntityCollection): Promise<boolean>;
428
+ checkUniqueField(path: string, name: string, value: any, entityId?: string, collection?: EntityCollection, pathSegments?: string[]): Promise<boolean>;
370
429
 
371
430
  /**
372
431
  * Generate an id for a new entity
373
432
  */
374
- generateEntityId(path: string, collection?: EntityCollection): string;
433
+ generateEntityId(path: string, collection?: EntityCollection, pathSegments?: string[]): string;
375
434
 
376
435
  /**
377
436
  * Count the number of entities in a collection
@@ -398,6 +457,10 @@ export interface DataSourceDelegate {
398
457
  initTextSearch?: (props: {
399
458
  context: FireCMSContext,
400
459
  path: string,
460
+ /**
461
+ * `path` split at its real segment boundaries, when known. Never derived from `path`.
462
+ */
463
+ pathSegments?: string[],
401
464
  databaseId?: string,
402
465
  collection: EntityCollection,
403
466
  parentCollectionIds?: string[]