@maxal_studio/kratosjs-react 1.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.
- package/README.md +44 -0
- package/dist/FieldRenderer.d.ts +13 -0
- package/dist/FieldRenderer.js +62 -0
- package/dist/FormRenderer.d.ts +7 -0
- package/dist/FormRenderer.js +78 -0
- package/dist/TableRenderer.d.ts +2 -0
- package/dist/TableRenderer.js +1 -0
- package/dist/api/actionsApi.d.ts +23 -0
- package/dist/api/actionsApi.js +46 -0
- package/dist/api/authenticatedFetch.d.ts +8 -0
- package/dist/api/authenticatedFetch.js +31 -0
- package/dist/api/exportApi.d.ts +18 -0
- package/dist/api/exportApi.js +50 -0
- package/dist/api/http.d.ts +24 -0
- package/dist/api/http.js +52 -0
- package/dist/api/resourceApi.d.ts +37 -0
- package/dist/api/resourceApi.js +52 -0
- package/dist/api/tableApi.d.ts +83 -0
- package/dist/api/tableApi.js +51 -0
- package/dist/api/urls.d.ts +19 -0
- package/dist/api/urls.js +46 -0
- package/dist/app.d.ts +101 -0
- package/dist/app.js +89 -0
- package/dist/auth/AuthContext.d.ts +22 -0
- package/dist/auth/AuthContext.js +147 -0
- package/dist/auth/LoginPage.d.ts +10 -0
- package/dist/auth/LoginPage.js +179 -0
- package/dist/auth/ProtectedRoute.d.ts +12 -0
- package/dist/auth/ProtectedRoute.js +22 -0
- package/dist/auth/authApiClient.d.ts +24 -0
- package/dist/auth/authApiClient.js +95 -0
- package/dist/auth/types.d.ts +103 -0
- package/dist/auth/types.js +1 -0
- package/dist/components/ActionFormModal.d.ts +22 -0
- package/dist/components/ActionFormModal.js +8 -0
- package/dist/components/AdminPanel.d.ts +11 -0
- package/dist/components/AdminPanel.js +194 -0
- package/dist/components/Checkbox.d.ts +10 -0
- package/dist/components/Checkbox.js +8 -0
- package/dist/components/CheckboxField.d.ts +7 -0
- package/dist/components/CheckboxField.js +26 -0
- package/dist/components/ColorPickerField.d.ts +7 -0
- package/dist/components/ColorPickerField.js +26 -0
- package/dist/components/DateTimePickerField.d.ts +7 -0
- package/dist/components/DateTimePickerField.js +64 -0
- package/dist/components/FileUploadField.d.ts +9 -0
- package/dist/components/FileUploadField.js +478 -0
- package/dist/components/GlobalSearch.d.ts +22 -0
- package/dist/components/GlobalSearch.js +181 -0
- package/dist/components/GroupField.d.ts +7 -0
- package/dist/components/GroupField.js +23 -0
- package/dist/components/HiddenField.d.ts +3 -0
- package/dist/components/HiddenField.js +10 -0
- package/dist/components/ModalBreadcrumb.d.ts +5 -0
- package/dist/components/ModalBreadcrumb.js +33 -0
- package/dist/components/ModalDrawer.d.ts +15 -0
- package/dist/components/ModalDrawer.js +40 -0
- package/dist/components/RadioField.d.ts +7 -0
- package/dist/components/RadioField.js +26 -0
- package/dist/components/RepeaterField.d.ts +3 -0
- package/dist/components/RepeaterField.js +191 -0
- package/dist/components/ResourceModalRenderer.d.ts +10 -0
- package/dist/components/ResourceModalRenderer.js +80 -0
- package/dist/components/RichEditorField.d.ts +3 -0
- package/dist/components/RichEditorField.js +655 -0
- package/dist/components/SectionField.d.ts +9 -0
- package/dist/components/SectionField.js +111 -0
- package/dist/components/SelectField.d.ts +8 -0
- package/dist/components/SelectField.js +523 -0
- package/dist/components/TabsField.d.ts +10 -0
- package/dist/components/TabsField.js +214 -0
- package/dist/components/TagsInputField.d.ts +7 -0
- package/dist/components/TagsInputField.js +172 -0
- package/dist/components/TextInputField.d.ts +7 -0
- package/dist/components/TextInputField.js +44 -0
- package/dist/components/TextareaField.d.ts +7 -0
- package/dist/components/TextareaField.js +31 -0
- package/dist/components/ToggleField.d.ts +7 -0
- package/dist/components/ToggleField.js +57 -0
- package/dist/components/ViewModal.d.ts +25 -0
- package/dist/components/ViewModal.js +159 -0
- package/dist/components/blocks/BlockRenderer.d.ts +7 -0
- package/dist/components/blocks/BlockRenderer.js +36 -0
- package/dist/components/blocks/FormBlockRenderer.d.ts +6 -0
- package/dist/components/blocks/FormBlockRenderer.js +110 -0
- package/dist/components/blocks/TableBlockRenderer.d.ts +6 -0
- package/dist/components/blocks/TableBlockRenderer.js +12 -0
- package/dist/components/blocks/TabsBlockRenderer.d.ts +7 -0
- package/dist/components/blocks/TabsBlockRenderer.js +11 -0
- package/dist/components/blocks/WidgetBlockRenderer.d.ts +6 -0
- package/dist/components/blocks/WidgetBlockRenderer.js +11 -0
- package/dist/components/columns/CheckboxColumnComponent.d.ts +6 -0
- package/dist/components/columns/CheckboxColumnComponent.js +21 -0
- package/dist/components/columns/ColorColumnComponent.d.ts +3 -0
- package/dist/components/columns/ColorColumnComponent.js +11 -0
- package/dist/components/columns/DeeplinkWrapper.d.ts +15 -0
- package/dist/components/columns/DeeplinkWrapper.js +85 -0
- package/dist/components/columns/IconColumnComponent.d.ts +3 -0
- package/dist/components/columns/IconColumnComponent.js +52 -0
- package/dist/components/columns/ImageColumnComponent.d.ts +3 -0
- package/dist/components/columns/ImageColumnComponent.js +98 -0
- package/dist/components/columns/MediaColumnComponent.d.ts +3 -0
- package/dist/components/columns/MediaColumnComponent.js +160 -0
- package/dist/components/columns/SelectColumnComponent.d.ts +6 -0
- package/dist/components/columns/SelectColumnComponent.js +26 -0
- package/dist/components/columns/TagsColumnComponent.d.ts +3 -0
- package/dist/components/columns/TagsColumnComponent.js +18 -0
- package/dist/components/columns/TextColumnComponent.d.ts +11 -0
- package/dist/components/columns/TextColumnComponent.js +107 -0
- package/dist/components/columns/TextInputColumnComponent.d.ts +6 -0
- package/dist/components/columns/TextInputColumnComponent.js +18 -0
- package/dist/components/columns/ToggleColumnComponent.d.ts +6 -0
- package/dist/components/columns/ToggleColumnComponent.js +25 -0
- package/dist/components/columns/VideoColumnComponent.d.ts +3 -0
- package/dist/components/columns/VideoColumnComponent.js +125 -0
- package/dist/components/columns/ViewColumnComponent.d.ts +3 -0
- package/dist/components/columns/ViewColumnComponent.js +7 -0
- package/dist/components/errors/ErrorBoundary.d.ts +23 -0
- package/dist/components/errors/ErrorBoundary.js +33 -0
- package/dist/components/filters/CustomFilterComponent.d.ts +10 -0
- package/dist/components/filters/CustomFilterComponent.js +33 -0
- package/dist/components/filters/DateFilterComponent.d.ts +15 -0
- package/dist/components/filters/DateFilterComponent.js +132 -0
- package/dist/components/filters/QueryBuilderFilterComponent.d.ts +11 -0
- package/dist/components/filters/QueryBuilderFilterComponent.js +200 -0
- package/dist/components/layout/Header.d.ts +10 -0
- package/dist/components/layout/Header.js +70 -0
- package/dist/components/layout/PanelBrandMark.d.ts +8 -0
- package/dist/components/layout/PanelBrandMark.js +28 -0
- package/dist/components/layout/Sidebar.d.ts +35 -0
- package/dist/components/layout/Sidebar.js +125 -0
- package/dist/components/modals/RelationCreateModal.d.ts +19 -0
- package/dist/components/modals/RelationCreateModal.js +57 -0
- package/dist/components/modals/ResourceFormModal.d.ts +37 -0
- package/dist/components/modals/ResourceFormModal.js +44 -0
- package/dist/components/modals/useResourceForm.d.ts +40 -0
- package/dist/components/modals/useResourceForm.js +138 -0
- package/dist/components/modals/view/RecordActions.d.ts +17 -0
- package/dist/components/modals/view/RecordActions.js +16 -0
- package/dist/components/modals/view/RecordDetails.d.ts +13 -0
- package/dist/components/modals/view/RecordDetails.js +29 -0
- package/dist/components/modals/view/RelationPanel.d.ts +18 -0
- package/dist/components/modals/view/RelationPanel.js +16 -0
- package/dist/components/modals/view/RelationTabs.d.ts +32 -0
- package/dist/components/modals/view/RelationTabs.js +42 -0
- package/dist/components/modals/view/useRecordView.d.ts +18 -0
- package/dist/components/modals/view/useRecordView.js +114 -0
- package/dist/components/pages/PageRenderer.d.ts +6 -0
- package/dist/components/pages/PageRenderer.js +107 -0
- package/dist/components/table/ColumnTogglePopup.d.ts +11 -0
- package/dist/components/table/ColumnTogglePopup.js +16 -0
- package/dist/components/table/GridCard.d.ts +21 -0
- package/dist/components/table/GridCard.js +30 -0
- package/dist/components/table/GridView.d.ts +23 -0
- package/dist/components/table/GridView.js +49 -0
- package/dist/components/table/LayoutToggle.d.ts +7 -0
- package/dist/components/table/LayoutToggle.js +9 -0
- package/dist/components/table/TableActionsDropdown.d.ts +13 -0
- package/dist/components/table/TableActionsDropdown.js +46 -0
- package/dist/components/table/TableBulkActions.d.ts +11 -0
- package/dist/components/table/TableBulkActions.js +21 -0
- package/dist/components/table/TableHeader.d.ts +14 -0
- package/dist/components/table/TableHeader.js +23 -0
- package/dist/components/table/TablePagination.d.ts +13 -0
- package/dist/components/table/TablePagination.js +55 -0
- package/dist/components/table/TableRow.d.ts +21 -0
- package/dist/components/table/TableRow.js +32 -0
- package/dist/components/table/TableSearchBar.d.ts +11 -0
- package/dist/components/table/TableSearchBar.js +12 -0
- package/dist/components/table/TableTabs.d.ts +14 -0
- package/dist/components/table/TableTabs.js +8 -0
- package/dist/components/ui/Badge.d.ts +6 -0
- package/dist/components/ui/Badge.js +12 -0
- package/dist/components/ui/Button.d.ts +22 -0
- package/dist/components/ui/Button.js +22 -0
- package/dist/components/ui/Card.d.ts +7 -0
- package/dist/components/ui/Card.js +5 -0
- package/dist/components/ui/ConfirmDialog.d.ts +19 -0
- package/dist/components/ui/ConfirmDialog.js +45 -0
- package/dist/components/ui/EmptyState.d.ts +9 -0
- package/dist/components/ui/EmptyState.js +6 -0
- package/dist/components/ui/ErrorAlert.d.ts +7 -0
- package/dist/components/ui/ErrorAlert.js +9 -0
- package/dist/components/ui/Input.d.ts +11 -0
- package/dist/components/ui/Input.js +10 -0
- package/dist/components/ui/Label.d.ts +5 -0
- package/dist/components/ui/Label.js +5 -0
- package/dist/components/ui/PillButton.d.ts +14 -0
- package/dist/components/ui/PillButton.js +19 -0
- package/dist/components/ui/Select.d.ts +7 -0
- package/dist/components/ui/Select.js +7 -0
- package/dist/components/ui/Spinner.d.ts +8 -0
- package/dist/components/ui/Spinner.js +14 -0
- package/dist/components/ui/Toast.d.ts +21 -0
- package/dist/components/ui/Toast.js +47 -0
- package/dist/components/ui/index.d.ts +24 -0
- package/dist/components/ui/index.js +12 -0
- package/dist/components/utils/HintDisplay.d.ts +11 -0
- package/dist/components/utils/HintDisplay.js +12 -0
- package/dist/components/utils/Icon.d.ts +22 -0
- package/dist/components/utils/Icon.js +22 -0
- package/dist/components/utils/MediaPreviewModal.d.ts +14 -0
- package/dist/components/utils/MediaPreviewModal.js +32 -0
- package/dist/components/utils/ViewFieldWrapper.d.ts +11 -0
- package/dist/components/utils/ViewFieldWrapper.js +9 -0
- package/dist/components/utils/layoutHelpers.d.ts +19 -0
- package/dist/components/utils/layoutHelpers.js +257 -0
- package/dist/components/widgets/ChartWidget.d.ts +16 -0
- package/dist/components/widgets/ChartWidget.js +192 -0
- package/dist/components/widgets/StatsWidget.d.ts +16 -0
- package/dist/components/widgets/StatsWidget.js +39 -0
- package/dist/components/widgets/WidgetRenderer.d.ts +10 -0
- package/dist/components/widgets/WidgetRenderer.js +50 -0
- package/dist/components/widgets/WidgetShell.d.ts +9 -0
- package/dist/components/widgets/WidgetShell.js +7 -0
- package/dist/contexts/AuthChallengeRegistryContext.d.ts +15 -0
- package/dist/contexts/AuthChallengeRegistryContext.js +15 -0
- package/dist/contexts/BlockRegistryContext.d.ts +18 -0
- package/dist/contexts/BlockRegistryContext.js +8 -0
- package/dist/contexts/ColumnRegistryContext.d.ts +8 -0
- package/dist/contexts/ColumnRegistryContext.js +30 -0
- package/dist/contexts/FieldRegistryContext.d.ts +13 -0
- package/dist/contexts/FieldRegistryContext.js +46 -0
- package/dist/contexts/PanelMetadataContext.d.ts +26 -0
- package/dist/contexts/PanelMetadataContext.js +26 -0
- package/dist/contexts/PanelProviders.d.ts +27 -0
- package/dist/contexts/PanelProviders.js +24 -0
- package/dist/contexts/ResourceModalContext.d.ts +26 -0
- package/dist/contexts/ResourceModalContext.js +76 -0
- package/dist/contexts/SlotRegistryContext.d.ts +19 -0
- package/dist/contexts/SlotRegistryContext.js +24 -0
- package/dist/contexts/TableRefreshContext.d.ts +10 -0
- package/dist/contexts/TableRefreshContext.js +30 -0
- package/dist/contexts/WidgetRegistryContext.d.ts +17 -0
- package/dist/contexts/WidgetRegistryContext.js +14 -0
- package/dist/contexts/createRegistryContext.d.ts +19 -0
- package/dist/contexts/createRegistryContext.js +20 -0
- package/dist/hooks/useAfterStateUpdated.d.ts +6 -0
- package/dist/hooks/useAfterStateUpdated.js +62 -0
- package/dist/hooks/useValidation.d.ts +26 -0
- package/dist/hooks/useValidation.js +76 -0
- package/dist/i18n/I18nProvider.d.ts +27 -0
- package/dist/i18n/I18nProvider.js +101 -0
- package/dist/i18n/LocaleSwitcher.d.ts +10 -0
- package/dist/i18n/LocaleSwitcher.js +30 -0
- package/dist/i18n/activeLocale.d.ts +11 -0
- package/dist/i18n/activeLocale.js +34 -0
- package/dist/i18n/buildClientI18n.d.ts +28 -0
- package/dist/i18n/buildClientI18n.js +67 -0
- package/dist/i18n/index.d.ts +11 -0
- package/dist/i18n/index.js +9 -0
- package/dist/i18n/locales/core/en.d.ts +225 -0
- package/dist/i18n/locales/core/en.js +252 -0
- package/dist/i18n/locales/core/index.d.ts +2 -0
- package/dist/i18n/locales/core/index.js +4 -0
- package/dist/i18n/locales/core/sq.d.ts +253 -0
- package/dist/i18n/locales/core/sq.js +255 -0
- package/dist/i18n/useFormatter.d.ts +18 -0
- package/dist/i18n/useFormatter.js +37 -0
- package/dist/i18n/useLocale.d.ts +11 -0
- package/dist/i18n/useLocale.js +11 -0
- package/dist/i18n/useTranslation.d.ts +12 -0
- package/dist/i18n/useTranslation.js +12 -0
- package/dist/index.d.ts +106 -0
- package/dist/index.js +101 -0
- package/dist/pages/ResourceListPage.d.ts +8 -0
- package/dist/pages/ResourceListPage.js +139 -0
- package/dist/plugin.d.ts +79 -0
- package/dist/plugin.js +34 -0
- package/dist/runtime/conditions.d.ts +35 -0
- package/dist/runtime/conditions.js +97 -0
- package/dist/runtime/formTraversal.d.ts +25 -0
- package/dist/runtime/formTraversal.js +37 -0
- package/dist/runtime/serializedFunctions.d.ts +41 -0
- package/dist/runtime/serializedFunctions.js +264 -0
- package/dist/slots/Slot.d.ts +24 -0
- package/dist/slots/Slot.js +29 -0
- package/dist/slots/SlotCluster.d.ts +22 -0
- package/dist/slots/SlotCluster.js +49 -0
- package/dist/slots/index.d.ts +7 -0
- package/dist/slots/index.js +4 -0
- package/dist/slots/mergeSlots.d.ts +18 -0
- package/dist/slots/mergeSlots.js +35 -0
- package/dist/slots/types.d.ts +87 -0
- package/dist/slots/types.js +30 -0
- package/dist/styles.css +1 -0
- package/dist/table/TableContext.d.ts +36 -0
- package/dist/table/TableContext.js +13 -0
- package/dist/table/TableRenderer.d.ts +29 -0
- package/dist/table/TableRenderer.js +159 -0
- package/dist/table/components/FiltersPanel.d.ts +11 -0
- package/dist/table/components/FiltersPanel.js +52 -0
- package/dist/table/components/TableToolbar.d.ts +28 -0
- package/dist/table/components/TableToolbar.js +27 -0
- package/dist/table/components/TableToolbarButton.d.ts +6 -0
- package/dist/table/components/TableToolbarButton.js +9 -0
- package/dist/table/components/TableView.d.ts +12 -0
- package/dist/table/components/TableView.js +21 -0
- package/dist/table/defaultRowActions.d.ts +21 -0
- package/dist/table/defaultRowActions.js +37 -0
- package/dist/table/hooks/useColumnVisibility.d.ts +13 -0
- package/dist/table/hooks/useColumnVisibility.js +59 -0
- package/dist/table/hooks/useEditableRows.d.ts +22 -0
- package/dist/table/hooks/useEditableRows.js +63 -0
- package/dist/table/hooks/useTableActions.d.ts +54 -0
- package/dist/table/hooks/useTableActions.js +313 -0
- package/dist/table/hooks/useTableData.d.ts +28 -0
- package/dist/table/hooks/useTableData.js +63 -0
- package/dist/table/hooks/useTableLayout.d.ts +12 -0
- package/dist/table/hooks/useTableLayout.js +31 -0
- package/dist/table/hooks/useTableQuery.d.ts +29 -0
- package/dist/table/hooks/useTableQuery.js +135 -0
- package/dist/types/index.d.ts +224 -0
- package/dist/types/index.js +6 -0
- package/dist/utils/classNames.d.ts +7 -0
- package/dist/utils/classNames.js +9 -0
- package/dist/utils/columnMediaDimensions.d.ts +13 -0
- package/dist/utils/columnMediaDimensions.js +29 -0
- package/dist/utils/columnVisibilityStorage.d.ts +22 -0
- package/dist/utils/columnVisibilityStorage.js +56 -0
- package/dist/utils/fieldErrors.d.ts +13 -0
- package/dist/utils/fieldErrors.js +25 -0
- package/dist/utils/formatValue.d.ts +28 -0
- package/dist/utils/formatValue.js +109 -0
- package/dist/utils/layoutStorage.d.ts +23 -0
- package/dist/utils/layoutStorage.js +53 -0
- package/dist/utils/redirectHandler.d.ts +7 -0
- package/dist/utils/redirectHandler.js +25 -0
- package/dist/utils/tableFormatters.d.ts +14 -0
- package/dist/utils/tableFormatters.js +93 -0
- package/dist/utils/widgetVisibilityStorage.d.ts +11 -0
- package/dist/utils/widgetVisibilityStorage.js +39 -0
- package/package.json +101 -0
- package/src/FieldRenderer.test.tsx +44 -0
- package/src/FieldRenderer.tsx +104 -0
- package/src/FormRenderer.containers.test.tsx +121 -0
- package/src/FormRenderer.test.tsx +174 -0
- package/src/FormRenderer.tsx +140 -0
- package/src/TableRenderer.tsx +2 -0
- package/src/api/actionsApi.ts +76 -0
- package/src/api/authenticatedFetch.ts +40 -0
- package/src/api/exportApi.ts +66 -0
- package/src/api/http.test.ts +58 -0
- package/src/api/http.ts +68 -0
- package/src/api/resourceApi.ts +88 -0
- package/src/api/tableApi.test.ts +108 -0
- package/src/api/tableApi.ts +107 -0
- package/src/api/urls.ts +50 -0
- package/src/app.test.tsx +67 -0
- package/src/app.tsx +181 -0
- package/src/auth/AuthContext.tsx +188 -0
- package/src/auth/LoginPage.tsx +380 -0
- package/src/auth/ProtectedRoute.tsx +39 -0
- package/src/auth/authApiClient.ts +109 -0
- package/src/auth/authFlow.test.tsx +168 -0
- package/src/auth/types.ts +104 -0
- package/src/components/ActionFormModal.tsx +45 -0
- package/src/components/AdminPanel.tsx +368 -0
- package/src/components/Checkbox.tsx +59 -0
- package/src/components/CheckboxField.tsx +88 -0
- package/src/components/ColorPickerField.tsx +93 -0
- package/src/components/DateTimePickerField.tsx +112 -0
- package/src/components/FileUploadField.tsx +841 -0
- package/src/components/GlobalSearch.tsx +436 -0
- package/src/components/GroupField.tsx +85 -0
- package/src/components/HiddenField.tsx +14 -0
- package/src/components/ModalBreadcrumb.tsx +74 -0
- package/src/components/ModalDrawer.tsx +137 -0
- package/src/components/RadioField.tsx +80 -0
- package/src/components/RepeaterField.tsx +546 -0
- package/src/components/ResourceModalRenderer.tsx +144 -0
- package/src/components/RichEditorField.tsx +942 -0
- package/src/components/SectionField.tsx +242 -0
- package/src/components/SelectField.tsx +843 -0
- package/src/components/TabsField.test.tsx +151 -0
- package/src/components/TabsField.tsx +386 -0
- package/src/components/TagsInputField.tsx +411 -0
- package/src/components/TextInputField.tsx +91 -0
- package/src/components/TextareaField.tsx +110 -0
- package/src/components/ToggleField.tsx +126 -0
- package/src/components/ViewModal.tsx +353 -0
- package/src/components/blocks/BlockRenderer.tsx +56 -0
- package/src/components/blocks/FormBlockRenderer.tsx +160 -0
- package/src/components/blocks/TableBlockRenderer.tsx +33 -0
- package/src/components/blocks/TabsBlockRenderer.tsx +49 -0
- package/src/components/blocks/WidgetBlockRenderer.tsx +19 -0
- package/src/components/columns/CheckboxColumnComponent.tsx +38 -0
- package/src/components/columns/ColorColumnComponent.tsx +23 -0
- package/src/components/columns/CustomColumn.test.tsx +55 -0
- package/src/components/columns/DeeplinkWrapper.tsx +103 -0
- package/src/components/columns/IconColumnComponent.tsx +55 -0
- package/src/components/columns/ImageColumnComponent.tsx +220 -0
- package/src/components/columns/MediaColumnComponent.tsx +294 -0
- package/src/components/columns/SelectColumnComponent.tsx +49 -0
- package/src/components/columns/TagsColumnComponent.tsx +46 -0
- package/src/components/columns/TextColumnComponent.tsx +191 -0
- package/src/components/columns/TextInputColumnComponent.tsx +35 -0
- package/src/components/columns/ToggleColumnComponent.tsx +56 -0
- package/src/components/columns/VideoColumnComponent.tsx +236 -0
- package/src/components/columns/ViewColumnComponent.tsx +9 -0
- package/src/components/errors/ErrorBoundary.tsx +58 -0
- package/src/components/filters/CustomFilterComponent.tsx +130 -0
- package/src/components/filters/DateFilterComponent.tsx +272 -0
- package/src/components/filters/QueryBuilderFilterComponent.tsx +502 -0
- package/src/components/layout/Header.tsx +212 -0
- package/src/components/layout/PanelBrandMark.tsx +61 -0
- package/src/components/layout/Sidebar.tsx +283 -0
- package/src/components/modals/RelationCreateModal.tsx +107 -0
- package/src/components/modals/ResourceFormModal.test.tsx +119 -0
- package/src/components/modals/ResourceFormModal.tsx +128 -0
- package/src/components/modals/useResourceForm.ts +207 -0
- package/src/components/modals/view/RecordActions.tsx +69 -0
- package/src/components/modals/view/RecordDetails.tsx +60 -0
- package/src/components/modals/view/RelationPanel.tsx +76 -0
- package/src/components/modals/view/RelationTabs.tsx +145 -0
- package/src/components/modals/view/useRecordView.ts +134 -0
- package/src/components/pages/PageRenderer.tsx +173 -0
- package/src/components/table/ColumnTogglePopup.tsx +85 -0
- package/src/components/table/GridCard.tsx +155 -0
- package/src/components/table/GridView.tsx +138 -0
- package/src/components/table/LayoutToggle.tsx +24 -0
- package/src/components/table/TableActionsDropdown.tsx +114 -0
- package/src/components/table/TableBulkActions.tsx +65 -0
- package/src/components/table/TableHeader.tsx +96 -0
- package/src/components/table/TablePagination.tsx +169 -0
- package/src/components/table/TableRow.tsx +155 -0
- package/src/components/table/TableSearchBar.tsx +66 -0
- package/src/components/table/TableTabs.tsx +49 -0
- package/src/components/ui/Badge.tsx +30 -0
- package/src/components/ui/Button.test.tsx +78 -0
- package/src/components/ui/Button.tsx +102 -0
- package/src/components/ui/Card.tsx +23 -0
- package/src/components/ui/ConfirmDialog.tsx +112 -0
- package/src/components/ui/EmptyState.tsx +24 -0
- package/src/components/ui/ErrorAlert.tsx +37 -0
- package/src/components/ui/Input.tsx +48 -0
- package/src/components/ui/Label.tsx +15 -0
- package/src/components/ui/PillButton.tsx +72 -0
- package/src/components/ui/Select.tsx +33 -0
- package/src/components/ui/Spinner.tsx +39 -0
- package/src/components/ui/Toast.tsx +105 -0
- package/src/components/ui/index.ts +24 -0
- package/src/components/utils/HintDisplay.tsx +26 -0
- package/src/components/utils/Icon.tsx +36 -0
- package/src/components/utils/MediaPreviewModal.tsx +114 -0
- package/src/components/utils/ViewFieldWrapper.tsx +23 -0
- package/src/components/utils/layoutHelpers.ts +267 -0
- package/src/components/widgets/ChartWidget.tsx +247 -0
- package/src/components/widgets/StatsWidget.tsx +72 -0
- package/src/components/widgets/WidgetRenderer.tsx +108 -0
- package/src/components/widgets/WidgetShell.tsx +37 -0
- package/src/contexts/AuthChallengeRegistryContext.tsx +29 -0
- package/src/contexts/BlockRegistryContext.tsx +28 -0
- package/src/contexts/ColumnRegistryContext.tsx +38 -0
- package/src/contexts/FieldRegistryContext.tsx +56 -0
- package/src/contexts/PanelMetadataContext.tsx +60 -0
- package/src/contexts/PanelProviders.tsx +85 -0
- package/src/contexts/ResourceModalContext.tsx +137 -0
- package/src/contexts/SlotRegistryContext.tsx +35 -0
- package/src/contexts/TableRefreshContext.tsx +44 -0
- package/src/contexts/WidgetRegistryContext.tsx +34 -0
- package/src/contexts/createRegistryContext.tsx +29 -0
- package/src/hooks/useAfterStateUpdated.ts +70 -0
- package/src/hooks/useValidation.test.ts +59 -0
- package/src/hooks/useValidation.ts +95 -0
- package/src/i18n/I18nProvider.tsx +128 -0
- package/src/i18n/LocaleSwitcher.tsx +50 -0
- package/src/i18n/activeLocale.ts +39 -0
- package/src/i18n/buildClientI18n.ts +101 -0
- package/src/i18n/i18n.test.tsx +140 -0
- package/src/i18n/index.ts +12 -0
- package/src/i18n/locales/core/en.ts +274 -0
- package/src/i18n/locales/core/index.ts +5 -0
- package/src/i18n/locales/core/sq.ts +275 -0
- package/src/i18n/useFormatter.ts +42 -0
- package/src/i18n/useLocale.ts +16 -0
- package/src/i18n/useTranslation.ts +17 -0
- package/src/index.ts +244 -0
- package/src/pages/ResourceListPage.tsx +205 -0
- package/src/plugin.ts +110 -0
- package/src/runtime/conditions.test.ts +99 -0
- package/src/runtime/conditions.ts +148 -0
- package/src/runtime/formTraversal.ts +41 -0
- package/src/runtime/serializedFunctions.test.ts +59 -0
- package/src/runtime/serializedFunctions.ts +284 -0
- package/src/slots/Slot.test.tsx +89 -0
- package/src/slots/Slot.tsx +47 -0
- package/src/slots/SlotCluster.test.tsx +95 -0
- package/src/slots/SlotCluster.tsx +107 -0
- package/src/slots/index.ts +15 -0
- package/src/slots/mergeSlots.test.ts +71 -0
- package/src/slots/mergeSlots.ts +40 -0
- package/src/slots/slotNames.test.ts +21 -0
- package/src/slots/types.ts +119 -0
- package/src/styles.css +437 -0
- package/src/table/TableContext.tsx +41 -0
- package/src/table/TableRenderer.test.tsx +197 -0
- package/src/table/TableRenderer.tsx +390 -0
- package/src/table/components/FiltersPanel.tsx +193 -0
- package/src/table/components/TableToolbar.tsx +153 -0
- package/src/table/components/TableToolbarButton.tsx +14 -0
- package/src/table/components/TableView.tsx +106 -0
- package/src/table/defaultRowActions.ts +43 -0
- package/src/table/hooks/useColumnVisibility.test.ts +51 -0
- package/src/table/hooks/useColumnVisibility.ts +71 -0
- package/src/table/hooks/useEditableRows.test.ts +69 -0
- package/src/table/hooks/useEditableRows.ts +89 -0
- package/src/table/hooks/useTableActions.ts +393 -0
- package/src/table/hooks/useTableData.ts +89 -0
- package/src/table/hooks/useTableLayout.ts +45 -0
- package/src/table/hooks/useTableQuery.test.ts +116 -0
- package/src/table/hooks/useTableQuery.ts +172 -0
- package/src/test/mockFetch.ts +67 -0
- package/src/test/setup.ts +25 -0
- package/src/types/index.ts +228 -0
- package/src/utils/classNames.ts +10 -0
- package/src/utils/columnMediaDimensions.ts +45 -0
- package/src/utils/columnVisibilityStorage.ts +55 -0
- package/src/utils/fieldErrors.test.ts +35 -0
- package/src/utils/fieldErrors.ts +27 -0
- package/src/utils/formatValue.test.tsx +65 -0
- package/src/utils/formatValue.tsx +117 -0
- package/src/utils/layoutStorage.ts +52 -0
- package/src/utils/redirectHandler.ts +29 -0
- package/src/utils/tableFormatters.test.ts +54 -0
- package/src/utils/tableFormatters.ts +104 -0
- package/src/utils/widgetVisibilityStorage.ts +38 -0
- package/tailwind.config.js +9 -0
- package/vite.config.ts +17 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import React, { useState } from 'react';
|
|
3
|
+
import { formatValue } from '../../utils/tableFormatters';
|
|
4
|
+
import { getColumnMediaDimensions } from '../../utils/columnMediaDimensions';
|
|
5
|
+
import { Icon } from '../utils/Icon';
|
|
6
|
+
import { DeeplinkWrapper } from './DeeplinkWrapper';
|
|
7
|
+
import { translate } from '../../i18n/activeLocale';
|
|
8
|
+
function ImagePreviewModal({ isOpen, onClose, imageUrl, title }) {
|
|
9
|
+
if (!isOpen)
|
|
10
|
+
return null;
|
|
11
|
+
return (_jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/70 backdrop-blur-sm", onClick: onClose, children: _jsxs("div", { className: "relative max-w-4xl max-h-[90vh] p-2 bg-surface rounded-lg shadow-2xl", onClick: e => e.stopPropagation(), children: [title && (_jsx("div", { className: "px-4 py-2 border-b border-border", children: _jsx("h3", { className: "text-lg font-semibold text-fg", children: title }) })), _jsx("button", { onClick: onClose, className: "absolute top-2 right-2 p-2 rounded-full bg-hover hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors z-10", title: translate('core:modal.close'), children: _jsx(Icon, { name: "X", size: 20, className: "text-fg" }) }), _jsx("div", { className: "p-4", children: _jsx("img", { src: imageUrl, alt: title || translate('core:common.preview'), className: "max-w-full max-h-[80vh] object-contain rounded" }) })] }) }));
|
|
12
|
+
}
|
|
13
|
+
export function ImageColumnComponent({ column, record }) {
|
|
14
|
+
const [previewOpen, setPreviewOpen] = useState(false);
|
|
15
|
+
const [previewUrl, setPreviewUrl] = useState('');
|
|
16
|
+
const rawValue = record[column.name];
|
|
17
|
+
const value = rawValue;
|
|
18
|
+
const hasDeeplink = !!column.deeplink;
|
|
19
|
+
// Apply formatter if present (e.g., to extract nested property or prepend URL)
|
|
20
|
+
const formattedValue = formatValue(rawValue, column, record);
|
|
21
|
+
// Extract URL from new format: { key, storage, url } or use formatted value
|
|
22
|
+
let imageUrl = null;
|
|
23
|
+
if (formattedValue) {
|
|
24
|
+
if (typeof formattedValue === 'object' && formattedValue !== null) {
|
|
25
|
+
// New format: { key, storage, url } or array of such objects
|
|
26
|
+
if (Array.isArray(formattedValue)) {
|
|
27
|
+
imageUrl = formattedValue.map((item) => (typeof item === 'object' && item?.url ? item.url : item));
|
|
28
|
+
}
|
|
29
|
+
else if (formattedValue.url) {
|
|
30
|
+
imageUrl = formattedValue.url;
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
imageUrl = formattedValue;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
imageUrl = formattedValue;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
imageUrl = imageUrl || column.defaultImageUrl;
|
|
41
|
+
if (!imageUrl) {
|
|
42
|
+
return _jsx("span", { className: "text-fg-secondary", children: translate('core:file.no_image') });
|
|
43
|
+
}
|
|
44
|
+
const getImageClasses = (circular) => {
|
|
45
|
+
if (circular) {
|
|
46
|
+
return 'h-full w-full object-cover';
|
|
47
|
+
}
|
|
48
|
+
const classes = ['object-cover'];
|
|
49
|
+
if (column.square) {
|
|
50
|
+
classes.push('rounded-none');
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
classes.push('rounded');
|
|
54
|
+
}
|
|
55
|
+
if (column.clickable || hasDeeplink) {
|
|
56
|
+
classes.push('cursor-pointer hover:opacity-80 transition-opacity');
|
|
57
|
+
}
|
|
58
|
+
return classes.join(' ');
|
|
59
|
+
};
|
|
60
|
+
const handleImageClick = (url) => {
|
|
61
|
+
// If there's a deeplink, don't handle the click here - DeeplinkWrapper will handle it
|
|
62
|
+
if (hasDeeplink) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
// Fall back to existing clickable behavior
|
|
66
|
+
if (!column.clickable)
|
|
67
|
+
return;
|
|
68
|
+
if (column.clickAction === 'link') {
|
|
69
|
+
window.open(url, '_blank');
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
// Default to preview
|
|
73
|
+
setPreviewUrl(url);
|
|
74
|
+
setPreviewOpen(true);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
const dimensions = getColumnMediaDimensions(column, 40);
|
|
78
|
+
const isCircular = !!column.circular;
|
|
79
|
+
// Render single image helper
|
|
80
|
+
const renderImage = (url, extraClasses = '') => {
|
|
81
|
+
const interactive = column.clickable || hasDeeplink;
|
|
82
|
+
const imgElement = isCircular ? (_jsx("span", { className: `inline-flex shrink-0 overflow-hidden rounded-full ${interactive ? 'cursor-pointer hover:opacity-80 transition-opacity' : ''} ${extraClasses}`.trim(), style: dimensions, onClick: () => handleImageClick(url), children: _jsx("img", { src: url, alt: "", className: getImageClasses(true) }) })) : (_jsx("img", { src: url, alt: "", className: `${getImageClasses(false)} ${extraClasses}`.trim(), style: dimensions, onClick: () => handleImageClick(url) }));
|
|
83
|
+
// Wrap in deeplink if exists
|
|
84
|
+
if (hasDeeplink) {
|
|
85
|
+
return (_jsx(DeeplinkWrapper, { column: column, record: record, value: value, className: "inline-block", children: imgElement }));
|
|
86
|
+
}
|
|
87
|
+
return imgElement;
|
|
88
|
+
};
|
|
89
|
+
// Handle arrays (stacked images)
|
|
90
|
+
if (Array.isArray(imageUrl)) {
|
|
91
|
+
const images = column.limit ? imageUrl.slice(0, column.limit) : imageUrl;
|
|
92
|
+
if (column.stacked) {
|
|
93
|
+
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex -space-x-2", children: [images.map((url, idx) => (_jsx(React.Fragment, { children: renderImage(url, 'ring-2 ring-white dark:ring-gray-800') }, idx))), column.limit && imageUrl.length > column.limit && (_jsxs("div", { className: `flex items-center justify-center bg-muted text-fg-secondary text-xs font-medium ring-2 ring-surface ${isCircular ? 'rounded-full' : 'rounded'}`.trim(), style: dimensions, children: ["+", imageUrl.length - column.limit] }))] }), _jsx(ImagePreviewModal, { isOpen: previewOpen, onClose: () => setPreviewOpen(false), imageUrl: previewUrl, title: column.previewTitle })] }));
|
|
94
|
+
}
|
|
95
|
+
return (_jsxs(_Fragment, { children: [_jsx("div", { className: "flex gap-2", children: images.map((url, idx) => (_jsx(React.Fragment, { children: renderImage(url) }, idx))) }), _jsx(ImagePreviewModal, { isOpen: previewOpen, onClose: () => setPreviewOpen(false), imageUrl: previewUrl, title: column.previewTitle })] }));
|
|
96
|
+
}
|
|
97
|
+
return (_jsxs(_Fragment, { children: [renderImage(imageUrl), _jsx(ImagePreviewModal, { isOpen: previewOpen, onClose: () => setPreviewOpen(false), imageUrl: previewUrl, title: column.previewTitle })] }));
|
|
98
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { formatValue } from '../../utils/tableFormatters';
|
|
4
|
+
import { getColumnMediaDimensions } from '../../utils/columnMediaDimensions';
|
|
5
|
+
import { Icon } from '../utils/Icon';
|
|
6
|
+
import { MediaPreviewModal } from '../utils/MediaPreviewModal';
|
|
7
|
+
import { translate } from '../../i18n/activeLocale';
|
|
8
|
+
/**
|
|
9
|
+
* Resolve media type from column config
|
|
10
|
+
*/
|
|
11
|
+
function resolveMediaType(column, record) {
|
|
12
|
+
const rawValue = record[column.name];
|
|
13
|
+
// Check for media type function (serialized as string)
|
|
14
|
+
if (column.mediaTypeFn) {
|
|
15
|
+
try {
|
|
16
|
+
// eslint-disable-next-line no-new-func
|
|
17
|
+
const mediaTypeFn = new Function('return ' + column.mediaTypeFn)();
|
|
18
|
+
const result = mediaTypeFn(rawValue, record);
|
|
19
|
+
if (result === 'image' || result === 'video' || result === 'audio')
|
|
20
|
+
return result;
|
|
21
|
+
return 'image';
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
console.warn('Failed to execute mediaType function');
|
|
25
|
+
return 'image';
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
// Static media type
|
|
29
|
+
if (column.mediaType === 'image' || column.mediaType === 'video' || column.mediaType === 'audio') {
|
|
30
|
+
return column.mediaType;
|
|
31
|
+
}
|
|
32
|
+
// Default to image
|
|
33
|
+
return 'image';
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Resolve thumbnail URL from column config
|
|
37
|
+
* Supports: field name string, function string (serialized), variants, or default thumbnail
|
|
38
|
+
*/
|
|
39
|
+
function resolveThumbnail(column, record) {
|
|
40
|
+
const rawValue = record[column.name];
|
|
41
|
+
// Check for variants.thumbnail.url in new format
|
|
42
|
+
if (rawValue && typeof rawValue === 'object' && rawValue.variants?.thumbnail?.url) {
|
|
43
|
+
return rawValue.variants.thumbnail.url;
|
|
44
|
+
}
|
|
45
|
+
// Check for thumbnail function (serialized as string)
|
|
46
|
+
if (column.thumbnailFn) {
|
|
47
|
+
try {
|
|
48
|
+
// eslint-disable-next-line no-new-func
|
|
49
|
+
const thumbnailFn = new Function('return ' + column.thumbnailFn)();
|
|
50
|
+
const result = thumbnailFn(rawValue, record);
|
|
51
|
+
if (result)
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
console.warn('Failed to execute thumbnail function');
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// Check for thumbnail field name
|
|
59
|
+
if (column.thumbnailField) {
|
|
60
|
+
const fieldName = column.thumbnailField;
|
|
61
|
+
// Support nested field access (e.g., 'mediaFile.thumbnailUrl')
|
|
62
|
+
const fieldValue = fieldName.split('.').reduce((obj, key) => obj?.[key], record);
|
|
63
|
+
if (fieldValue) {
|
|
64
|
+
// If it's a string, use it directly; otherwise check for new format
|
|
65
|
+
if (typeof fieldValue === 'string') {
|
|
66
|
+
return fieldValue;
|
|
67
|
+
}
|
|
68
|
+
if (typeof fieldValue === 'object' && fieldValue?.url) {
|
|
69
|
+
return fieldValue.url;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// Fallback to default thumbnail
|
|
74
|
+
if (column.defaultThumbnail) {
|
|
75
|
+
return column.defaultThumbnail;
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
export function MediaColumnComponent({ column, record }) {
|
|
80
|
+
const [previewOpen, setPreviewOpen] = useState(false);
|
|
81
|
+
const rawValue = record[column.name];
|
|
82
|
+
// Apply formatter if present
|
|
83
|
+
const formattedValue = formatValue(rawValue, column, record);
|
|
84
|
+
// Extract URL from new format: { key, storage, url }
|
|
85
|
+
let mediaUrl = null;
|
|
86
|
+
if (formattedValue) {
|
|
87
|
+
if (typeof formattedValue === 'object' && formattedValue !== null && formattedValue.url) {
|
|
88
|
+
mediaUrl = formattedValue.url;
|
|
89
|
+
}
|
|
90
|
+
else if (typeof formattedValue === 'string') {
|
|
91
|
+
mediaUrl = formattedValue;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// Resolve media type
|
|
95
|
+
const mediaType = resolveMediaType(column, record);
|
|
96
|
+
// Get thumbnail URL (for videos)
|
|
97
|
+
const thumbnailUrl = resolveThumbnail(column, record);
|
|
98
|
+
// Get default image URL (for images)
|
|
99
|
+
const defaultImageUrl = column.defaultImageUrl;
|
|
100
|
+
// Aspect ratio (default 16/9; circular columns force 1:1 via getColumnMediaDimensions)
|
|
101
|
+
const dimensions = getColumnMediaDimensions(column, 40);
|
|
102
|
+
const placeholderIcon = column.placeholderIcon ||
|
|
103
|
+
(mediaType === 'video' ? 'Video' : mediaType === 'audio' ? 'Music' : 'Image');
|
|
104
|
+
const isClickable = column.clickable !== false;
|
|
105
|
+
const getContainerClasses = () => {
|
|
106
|
+
const classes = ['relative', 'flex', 'items-center', 'justify-center', 'overflow-hidden'];
|
|
107
|
+
if (column.circular) {
|
|
108
|
+
classes.push('rounded-full');
|
|
109
|
+
}
|
|
110
|
+
else if (column.square) {
|
|
111
|
+
classes.push('rounded-none');
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
classes.push('rounded');
|
|
115
|
+
}
|
|
116
|
+
if (isClickable && mediaUrl) {
|
|
117
|
+
classes.push('cursor-pointer', 'hover:opacity-80', 'transition-opacity');
|
|
118
|
+
}
|
|
119
|
+
if (mediaType === 'video') {
|
|
120
|
+
classes.push('bg-gray-100', 'dark:bg-gray-800', 'group');
|
|
121
|
+
}
|
|
122
|
+
return classes.join(' ');
|
|
123
|
+
};
|
|
124
|
+
const handleClick = () => {
|
|
125
|
+
if (!isClickable || !mediaUrl)
|
|
126
|
+
return;
|
|
127
|
+
if (column.clickAction === 'link') {
|
|
128
|
+
window.open(mediaUrl, '_blank');
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
setPreviewOpen(true);
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
// If media type is null (hidden), show nothing
|
|
135
|
+
if (mediaType === null) {
|
|
136
|
+
return _jsx("span", { className: "text-fg-secondary", children: "-" });
|
|
137
|
+
}
|
|
138
|
+
// If no media URL
|
|
139
|
+
if (!mediaUrl) {
|
|
140
|
+
// For images, check default image URL
|
|
141
|
+
if (mediaType === 'image' && defaultImageUrl) {
|
|
142
|
+
return (_jsxs(_Fragment, { children: [_jsx("img", { src: defaultImageUrl, alt: "", className: `object-cover ${getContainerClasses()}`, style: dimensions, onClick: handleClick }), isClickable && (_jsx(MediaPreviewModal, { isOpen: previewOpen, onClose: () => setPreviewOpen(false), mediaUrl: defaultImageUrl, mediaType: "image", title: column.previewTitle }))] }));
|
|
143
|
+
}
|
|
144
|
+
// Show placeholder for video/audio
|
|
145
|
+
if (mediaType === 'video' || mediaType === 'audio') {
|
|
146
|
+
return (_jsx("div", { className: `${getContainerClasses()} opacity-50 cursor-not-allowed bg-gray-100 dark:bg-gray-800`, style: dimensions, children: _jsx(Icon, { name: placeholderIcon, size: 20, className: "text-gray-400" }) }));
|
|
147
|
+
}
|
|
148
|
+
return _jsx("span", { className: "text-fg-secondary", children: translate('core:file.no_image') });
|
|
149
|
+
}
|
|
150
|
+
// Render video
|
|
151
|
+
if (mediaType === 'video') {
|
|
152
|
+
return (_jsxs(_Fragment, { children: [_jsx("div", { className: `${getContainerClasses()} bg-gray-100 dark:bg-gray-800`, style: dimensions, onClick: handleClick, children: thumbnailUrl ? (_jsxs(_Fragment, { children: [_jsx("img", { src: thumbnailUrl, alt: translate('core:common.video_thumbnail'), className: "w-full h-full object-cover" }), _jsx("div", { className: "absolute inset-0 flex items-center justify-center bg-black/30 group-hover:bg-black/40 transition-colors", children: _jsx("div", { className: "w-8 h-8 flex items-center justify-center bg-white/90 rounded-full", children: _jsx(Icon, { name: "Play", size: 16, className: "text-gray-800 ml-0.5" }) }) })] })) : (_jsxs(_Fragment, { children: [_jsx(Icon, { name: placeholderIcon, size: 24, className: "text-gray-500 dark:text-gray-400" }), _jsx("div", { className: "absolute bottom-1 right-1 w-4 h-4 flex items-center justify-center bg-accent rounded-full", children: _jsx(Icon, { name: "Play", size: 10, className: "text-white ml-0.5" }) })] })) }), _jsx(MediaPreviewModal, { isOpen: previewOpen, onClose: () => setPreviewOpen(false), mediaUrl: mediaUrl, mediaType: "video", title: column.previewTitle, autoplay: column.autoplay, controls: column.controls !== false, loop: column.loop, muted: column.muted })] }));
|
|
153
|
+
}
|
|
154
|
+
// Render audio
|
|
155
|
+
if (mediaType === 'audio') {
|
|
156
|
+
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: `${getContainerClasses()} bg-linear-to-br from-blue-600 to-purple-600`, style: dimensions, onClick: handleClick, children: [_jsx(Icon, { name: placeholderIcon, size: 24, className: "text-white" }), _jsx("div", { className: "absolute bottom-1 right-1 w-4 h-4 flex items-center justify-center bg-white/90 rounded-full", children: _jsx(Icon, { name: "Play", size: 10, className: "text-gray-800 ml-0.5" }) })] }), _jsx(MediaPreviewModal, { isOpen: previewOpen, onClose: () => setPreviewOpen(false), mediaUrl: mediaUrl, mediaType: "audio", title: column.previewTitle, autoplay: column.autoplay, controls: column.controls !== false, loop: column.loop })] }));
|
|
157
|
+
}
|
|
158
|
+
// Render single image
|
|
159
|
+
return (_jsxs(_Fragment, { children: [_jsx("img", { src: mediaUrl, alt: "", className: `object-cover ${getContainerClasses()}`, style: dimensions, onClick: handleClick }), _jsx(MediaPreviewModal, { isOpen: previewOpen, onClose: () => setPreviewOpen(false), mediaUrl: mediaUrl, mediaType: "image", title: column.previewTitle })] }));
|
|
160
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { ColumnProps } from './TextColumnComponent';
|
|
3
|
+
export interface EditableColumnProps extends ColumnProps {
|
|
4
|
+
onCellChange?: (value: any) => void;
|
|
5
|
+
}
|
|
6
|
+
export declare function SelectColumnComponent({ column, record, onCellChange }: EditableColumnProps): React.JSX.Element;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useEffect } from 'react';
|
|
3
|
+
export function SelectColumnComponent({ column, record, onCellChange }) {
|
|
4
|
+
const [value, setValue] = useState(record[column.name] || '');
|
|
5
|
+
const isDisabled = column.disabled;
|
|
6
|
+
// Update local value when record changes (e.g., after reset)
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
setValue(record[column.name] || '');
|
|
9
|
+
}, [record[column.name]]);
|
|
10
|
+
const handleChange = (e) => {
|
|
11
|
+
if (isDisabled)
|
|
12
|
+
return;
|
|
13
|
+
const newValue = e.target.value;
|
|
14
|
+
setValue(newValue);
|
|
15
|
+
// Notify parent of change
|
|
16
|
+
if (onCellChange) {
|
|
17
|
+
onCellChange(newValue);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
return (_jsxs("select", { value: value, onChange: handleChange, disabled: isDisabled, className: "k-input text-sm py-1.5 px-3 pr-8 rounded-md border border-border focus:outline-none focus:ring-2 focus:ring-ring focus:border-accent disabled:opacity-50 disabled:cursor-not-allowed transition-colors appearance-none bg-no-repeat bg-right", style: {
|
|
21
|
+
backgroundImage: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3E%3C/svg%3E")`,
|
|
22
|
+
backgroundPosition: 'right 0.5rem center',
|
|
23
|
+
backgroundSize: '1.5em 1.5em',
|
|
24
|
+
}, children: [column.placeholder && column.selectablePlaceholder && _jsx("option", { value: "", children: column.placeholder }), column.options &&
|
|
25
|
+
Object.entries(column.options).map(([key, label]) => (_jsx("option", { value: key, children: label }, key)))] }));
|
|
26
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from '../../utils/classNames';
|
|
3
|
+
import { formatValue } from '../../utils/tableFormatters';
|
|
4
|
+
export function TagsColumnComponent({ column, record }) {
|
|
5
|
+
const rawValue = record[column.name];
|
|
6
|
+
// Apply formatter if present
|
|
7
|
+
const value = formatValue(rawValue, column, record);
|
|
8
|
+
if (!value || (Array.isArray(value) && value.length === 0)) {
|
|
9
|
+
return _jsx("span", { className: "text-fg-secondary", children: "-" });
|
|
10
|
+
}
|
|
11
|
+
const tags = Array.isArray(value) ? value : [value];
|
|
12
|
+
const displayTags = column.limit ? tags.slice(0, column.limit) : tags;
|
|
13
|
+
// If no tags to display, return empty
|
|
14
|
+
if (!displayTags || displayTags.length === 0) {
|
|
15
|
+
return _jsx(_Fragment, {});
|
|
16
|
+
}
|
|
17
|
+
return (_jsxs("div", { className: "flex flex-wrap gap-1", children: [displayTags.map((tag, idx) => (_jsx("span", { className: cn('inline-flex items-center px-2 py-0.5 rounded text-xs font-medium', 'bg-accent-soft dark:bg-accent-soft text-accent'), children: String(tag) }, idx))), column.limit && tags.length > column.limit && (_jsxs("span", { className: cn('inline-flex items-center px-2 py-0.5 rounded text-xs font-medium', 'bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-300'), children: ["+", tags.length - column.limit] }))] }));
|
|
18
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { SerializedColumn } from '@maxal_studio/kratosjs';
|
|
3
|
+
export interface ColumnProps {
|
|
4
|
+
column: SerializedColumn;
|
|
5
|
+
record: any;
|
|
6
|
+
rowIndex: number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* TextColumn component for displaying text values
|
|
10
|
+
*/
|
|
11
|
+
export declare function TextColumnComponent({ column, record, rowIndex }: ColumnProps): React.JSX.Element;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from '../../utils/classNames';
|
|
3
|
+
import { formatValue } from '../../utils/tableFormatters';
|
|
4
|
+
import { DeeplinkWrapper } from './DeeplinkWrapper';
|
|
5
|
+
/**
|
|
6
|
+
* TextColumn component for displaying text values
|
|
7
|
+
*/
|
|
8
|
+
export function TextColumnComponent({ column, record, rowIndex }) {
|
|
9
|
+
const value = record[column.name];
|
|
10
|
+
// Handle row index
|
|
11
|
+
if (column.rowIndex) {
|
|
12
|
+
const displayIndex = column.rowIndexFromZero ? rowIndex : rowIndex + 1;
|
|
13
|
+
return _jsx("span", { className: "text-fg", children: displayIndex });
|
|
14
|
+
}
|
|
15
|
+
// Format value (pass entire record for formatStateUsing function)
|
|
16
|
+
const formattedValue = formatValue(value, column, record);
|
|
17
|
+
if (!formattedValue) {
|
|
18
|
+
return _jsx(_Fragment, {});
|
|
19
|
+
}
|
|
20
|
+
const hasDeeplink = !!column.deeplink;
|
|
21
|
+
// Handle arrays
|
|
22
|
+
if (Array.isArray(formattedValue)) {
|
|
23
|
+
const items = column.limit ? formattedValue.slice(0, column.limit) : formattedValue;
|
|
24
|
+
const shouldRenderHtml = column.stripHtml === false;
|
|
25
|
+
if (column.bulleted) {
|
|
26
|
+
return (_jsxs("ul", { className: "list-disc list-inside space-y-1", children: [items.map((item, idx) => {
|
|
27
|
+
const itemText = String(item);
|
|
28
|
+
return (_jsx("li", { className: "text-fg text-sm", children: shouldRenderHtml ? _jsx("span", { dangerouslySetInnerHTML: { __html: itemText } }) : itemText }, idx));
|
|
29
|
+
}), column.limit && formattedValue.length > column.limit && (_jsxs("li", { className: "text-fg-secondary text-sm italic", children: ["+", formattedValue.length - column.limit, " more"] }))] }));
|
|
30
|
+
}
|
|
31
|
+
if (column.listWithLineBreaks) {
|
|
32
|
+
return (_jsxs("div", { className: "space-y-1", children: [items.map((item, idx) => {
|
|
33
|
+
const itemText = String(item);
|
|
34
|
+
return (_jsx("div", { className: "text-fg text-sm", children: shouldRenderHtml ? _jsx("span", { dangerouslySetInnerHTML: { __html: itemText } }) : itemText }, idx));
|
|
35
|
+
}), column.limit && formattedValue.length > column.limit && (_jsxs("div", { className: "text-fg-secondary text-sm italic", children: ["+", formattedValue.length - column.limit, " more"] }))] }));
|
|
36
|
+
}
|
|
37
|
+
// Default: comma-separated
|
|
38
|
+
const joinedText = items.join(', ');
|
|
39
|
+
return (_jsx("span", { className: "text-fg", children: shouldRenderHtml ? _jsx("span", { dangerouslySetInnerHTML: { __html: joinedText } }) : joinedText }));
|
|
40
|
+
}
|
|
41
|
+
// Get styling classes
|
|
42
|
+
const getTextClasses = () => {
|
|
43
|
+
const classes = ['text-fg'];
|
|
44
|
+
if (column.weight) {
|
|
45
|
+
classes.push(`font-${column.weight}`);
|
|
46
|
+
}
|
|
47
|
+
if (column.fontFamily) {
|
|
48
|
+
classes.push(`font-${column.fontFamily}`);
|
|
49
|
+
}
|
|
50
|
+
if (column.size) {
|
|
51
|
+
if (typeof column.size === 'string') {
|
|
52
|
+
classes.push(`text-${column.size}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (column.lineClamp) {
|
|
56
|
+
classes.push(`line-clamp-${column.lineClamp}`);
|
|
57
|
+
}
|
|
58
|
+
if (!column.wrap) {
|
|
59
|
+
classes.push('truncate');
|
|
60
|
+
}
|
|
61
|
+
return cn(classes);
|
|
62
|
+
};
|
|
63
|
+
// Check if HTML should be rendered (stripHtml defaults to true in backend)
|
|
64
|
+
const shouldRenderHtml = column.stripHtml === false;
|
|
65
|
+
const textContent = String(formattedValue);
|
|
66
|
+
// Render badge
|
|
67
|
+
if (column.badge) {
|
|
68
|
+
let badgeColor = 'bg-gray-100 dark:bg-gray-800 text-gray-800 dark:text-gray-200';
|
|
69
|
+
if (column.color) {
|
|
70
|
+
if (typeof column.color === 'object') {
|
|
71
|
+
// Color mapping based on value
|
|
72
|
+
const colorKey = column.color[value];
|
|
73
|
+
if (colorKey) {
|
|
74
|
+
badgeColor = getBadgeColorClasses(colorKey);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
badgeColor = getBadgeColorClasses(column.color);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const badgeElement = (_jsx("span", { className: cn('inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium', badgeColor, hasDeeplink && 'hover:opacity-80'), children: shouldRenderHtml ? _jsx("span", { dangerouslySetInnerHTML: { __html: textContent } }) : textContent }));
|
|
82
|
+
// Wrap badge in deeplink if exists
|
|
83
|
+
if (hasDeeplink) {
|
|
84
|
+
return (_jsx(DeeplinkWrapper, { column: column, record: record, value: value, className: "inline-block", children: badgeElement }));
|
|
85
|
+
}
|
|
86
|
+
return badgeElement;
|
|
87
|
+
}
|
|
88
|
+
// Regular text - render as HTML if stripHtml is false
|
|
89
|
+
const textElement = shouldRenderHtml ? _jsx("span", { dangerouslySetInnerHTML: { __html: textContent } }) : textContent;
|
|
90
|
+
// Wrap in deeplink if exists
|
|
91
|
+
if (hasDeeplink) {
|
|
92
|
+
return (_jsx(DeeplinkWrapper, { column: column, record: record, value: value, className: getTextClasses(), children: textElement }));
|
|
93
|
+
}
|
|
94
|
+
return (_jsx("span", { className: getTextClasses(), children: shouldRenderHtml ? _jsx("span", { dangerouslySetInnerHTML: { __html: textContent } }) : textContent }));
|
|
95
|
+
}
|
|
96
|
+
function getBadgeColorClasses(color) {
|
|
97
|
+
const colorMap = {
|
|
98
|
+
primary: 'bg-accent-soft dark:bg-accent-soft text-accent',
|
|
99
|
+
secondary: 'bg-gray-100 dark:bg-gray-800 text-gray-800 dark:text-gray-200',
|
|
100
|
+
success: 'bg-green-100 dark:bg-green-900/30 text-green-800 dark:text-green-300',
|
|
101
|
+
danger: 'bg-red-100 dark:bg-red-900/30 text-red-800 dark:text-red-300',
|
|
102
|
+
warning: 'bg-yellow-100 dark:bg-yellow-900/30 text-yellow-800 dark:text-yellow-300',
|
|
103
|
+
info: 'bg-cyan-100 dark:bg-cyan-900/30 text-cyan-800 dark:text-cyan-300',
|
|
104
|
+
gray: 'bg-gray-100 dark:bg-gray-800 text-gray-800 dark:text-gray-200',
|
|
105
|
+
};
|
|
106
|
+
return colorMap[color] || colorMap.gray;
|
|
107
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { ColumnProps } from './TextColumnComponent';
|
|
3
|
+
export interface EditableColumnProps extends ColumnProps {
|
|
4
|
+
onCellChange?: (value: any) => void;
|
|
5
|
+
}
|
|
6
|
+
export declare function TextInputColumnComponent({ column, record, onCellChange }: EditableColumnProps): React.JSX.Element;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useEffect } from 'react';
|
|
3
|
+
export function TextInputColumnComponent({ column, record, onCellChange }) {
|
|
4
|
+
const [value, setValue] = useState(record[column.name] || '');
|
|
5
|
+
const isDisabled = column.disabled;
|
|
6
|
+
// Update local value when record changes (e.g., after reset)
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
setValue(record[column.name] || '');
|
|
9
|
+
}, [record[column.name]]);
|
|
10
|
+
const handleChange = (newValue) => {
|
|
11
|
+
setValue(newValue);
|
|
12
|
+
// Notify parent of change
|
|
13
|
+
if (onCellChange) {
|
|
14
|
+
onCellChange(newValue);
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
return (_jsx("input", { type: column.inputType || 'text', value: value, onChange: e => handleChange(e.target.value), disabled: isDisabled, placeholder: column.placeholder, className: "k-input text-sm py-1.5 px-3 w-full rounded-md border border-border focus:outline-none focus:ring-2 focus:ring-ring focus:border-accent disabled:opacity-50 disabled:cursor-not-allowed transition-colors" }));
|
|
18
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { ColumnProps } from './TextColumnComponent';
|
|
3
|
+
export interface EditableColumnProps extends ColumnProps {
|
|
4
|
+
onCellChange?: (value: any) => void;
|
|
5
|
+
}
|
|
6
|
+
export declare function ToggleColumnComponent({ column, record, onCellChange }: EditableColumnProps): React.JSX.Element;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useEffect } from 'react';
|
|
3
|
+
import { Icon } from '../utils/Icon';
|
|
4
|
+
import { cn } from '../../utils/classNames';
|
|
5
|
+
export function ToggleColumnComponent({ column, record, onCellChange }) {
|
|
6
|
+
const [value, setValue] = useState(Boolean(record[column.name]));
|
|
7
|
+
const isDisabled = column.disabled;
|
|
8
|
+
// Update local value when record changes (e.g., after reset)
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
setValue(Boolean(record[column.name]));
|
|
11
|
+
}, [record[column.name]]);
|
|
12
|
+
const handleToggle = () => {
|
|
13
|
+
if (isDisabled)
|
|
14
|
+
return;
|
|
15
|
+
const newValue = !value;
|
|
16
|
+
setValue(newValue);
|
|
17
|
+
// Notify parent of change
|
|
18
|
+
if (onCellChange) {
|
|
19
|
+
onCellChange(newValue);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
// Get colors
|
|
23
|
+
const trackClasses = (value ? column.onColor : column.offColor) ?? (value ? 'bg-accent' : 'bg-gray-200 dark:bg-gray-700');
|
|
24
|
+
return (_jsx("button", { type: "button", onClick: handleToggle, disabled: isDisabled, className: cn('relative inline-flex h-6 w-11 items-center rounded-full transition-colors', 'focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2', 'disabled:opacity-50 disabled:cursor-not-allowed', ...trackClasses.split(/\s+/)), children: _jsxs("span", { className: cn('inline-flex items-center justify-center h-4 w-4 transform rounded-full bg-white transition-transform', value ? 'translate-x-6' : 'translate-x-1'), children: [column.onIcon && value && _jsx(Icon, { name: column.onIcon, size: 10, className: "text-green-600" }), column.offIcon && !value && _jsx(Icon, { name: column.offIcon, size: 10, className: "text-gray-400" })] }) }));
|
|
25
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useRef, useEffect } from 'react';
|
|
3
|
+
import { formatValue } from '../../utils/tableFormatters';
|
|
4
|
+
import { getColumnMediaDimensions } from '../../utils/columnMediaDimensions';
|
|
5
|
+
import { Icon } from '../utils/Icon';
|
|
6
|
+
import { translate } from '../../i18n/activeLocale';
|
|
7
|
+
function VideoPreviewModal({ isOpen, onClose, videoUrl, title, autoplay = false, controls = true, loop = false, muted = false, }) {
|
|
8
|
+
const videoRef = useRef(null);
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
if (isOpen && videoRef.current && autoplay) {
|
|
11
|
+
videoRef.current.play().catch(() => {
|
|
12
|
+
// Autoplay may be blocked by browser
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
}, [isOpen, autoplay]);
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
// Pause video when modal closes
|
|
18
|
+
if (!isOpen && videoRef.current) {
|
|
19
|
+
videoRef.current.pause();
|
|
20
|
+
}
|
|
21
|
+
}, [isOpen]);
|
|
22
|
+
if (!isOpen)
|
|
23
|
+
return null;
|
|
24
|
+
return (_jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm", onClick: onClose, children: _jsxs("div", { className: "relative max-w-4xl w-full mx-4 bg-surface rounded-lg shadow-2xl overflow-hidden", onClick: e => e.stopPropagation(), children: [_jsxs("div", { className: "flex items-center justify-between px-4 py-3 border-b border-border", children: [_jsx("h3", { className: "text-lg font-semibold text-fg", children: title || translate('core:file.video_preview') }), _jsx("button", { onClick: onClose, className: "p-2 rounded-full bg-hover hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors", title: translate('core:modal.close'), children: _jsx(Icon, { name: "X", size: 20, className: "text-fg" }) })] }), _jsx("div", { className: "p-4 bg-black", children: _jsx("video", { ref: videoRef, src: videoUrl, className: "w-full max-h-[70vh] object-contain", controls: controls, loop: loop, muted: muted, playsInline: true }) })] }) }));
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Resolve thumbnail URL from column config
|
|
28
|
+
* Supports: field name string, function string (serialized), variants, or default thumbnail
|
|
29
|
+
*/
|
|
30
|
+
function resolveThumbnail(column, record) {
|
|
31
|
+
const rawValue = record[column.name];
|
|
32
|
+
// Check for variants.thumbnail.url in new format
|
|
33
|
+
if (rawValue && typeof rawValue === 'object' && rawValue.variants?.thumbnail?.url) {
|
|
34
|
+
return rawValue.variants.thumbnail.url;
|
|
35
|
+
}
|
|
36
|
+
// Check for thumbnail function (serialized as string)
|
|
37
|
+
if (column.thumbnailFn) {
|
|
38
|
+
try {
|
|
39
|
+
// eslint-disable-next-line no-new-func
|
|
40
|
+
const thumbnailFn = new Function('return ' + column.thumbnailFn)();
|
|
41
|
+
const result = thumbnailFn(rawValue, record);
|
|
42
|
+
if (result)
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
console.warn('Failed to execute thumbnail function');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// Check for thumbnail field name
|
|
50
|
+
if (column.thumbnailField) {
|
|
51
|
+
const fieldName = column.thumbnailField;
|
|
52
|
+
// Support nested field access (e.g., 'mediaFile.thumbnailUrl')
|
|
53
|
+
const fieldValue = fieldName.split('.').reduce((obj, key) => obj?.[key], record);
|
|
54
|
+
if (fieldValue) {
|
|
55
|
+
// If it's a string, use it directly; otherwise try to format it
|
|
56
|
+
// Also check if it's the new format { url, key, storage }
|
|
57
|
+
if (typeof fieldValue === 'string') {
|
|
58
|
+
return fieldValue;
|
|
59
|
+
}
|
|
60
|
+
if (typeof fieldValue === 'object' && fieldValue?.url) {
|
|
61
|
+
return fieldValue.url;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// Fallback to default thumbnail
|
|
66
|
+
if (column.defaultThumbnail) {
|
|
67
|
+
return column.defaultThumbnail;
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
export function VideoColumnComponent({ column, record }) {
|
|
72
|
+
const [previewOpen, setPreviewOpen] = useState(false);
|
|
73
|
+
const rawValue = record[column.name];
|
|
74
|
+
// Apply formatter if present (e.g., to extract nested property or prepend URL)
|
|
75
|
+
const formattedValue = formatValue(rawValue, column, record);
|
|
76
|
+
// Extract URL from new format: { key, storage, url }
|
|
77
|
+
let videoUrl = null;
|
|
78
|
+
if (formattedValue) {
|
|
79
|
+
if (typeof formattedValue === 'object' && formattedValue !== null && formattedValue.url) {
|
|
80
|
+
videoUrl = formattedValue.url;
|
|
81
|
+
}
|
|
82
|
+
else if (typeof formattedValue === 'string') {
|
|
83
|
+
videoUrl = formattedValue;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// Get thumbnail URL using the resolver
|
|
87
|
+
const thumbnailUrl = resolveThumbnail(column, record);
|
|
88
|
+
const dimensions = getColumnMediaDimensions(column, 40);
|
|
89
|
+
const placeholderIcon = column.placeholderIcon || 'Video';
|
|
90
|
+
const getContainerClasses = () => {
|
|
91
|
+
const classes = [
|
|
92
|
+
'relative',
|
|
93
|
+
'flex',
|
|
94
|
+
'items-center',
|
|
95
|
+
'justify-center',
|
|
96
|
+
'cursor-pointer',
|
|
97
|
+
'overflow-hidden',
|
|
98
|
+
'bg-gray-100',
|
|
99
|
+
'dark:bg-gray-800',
|
|
100
|
+
'hover:opacity-80',
|
|
101
|
+
'transition-opacity',
|
|
102
|
+
'group',
|
|
103
|
+
];
|
|
104
|
+
if (column.circular) {
|
|
105
|
+
classes.push('rounded-full');
|
|
106
|
+
}
|
|
107
|
+
else if (column.square) {
|
|
108
|
+
classes.push('rounded-none');
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
classes.push('rounded');
|
|
112
|
+
}
|
|
113
|
+
return classes.join(' ');
|
|
114
|
+
};
|
|
115
|
+
const handleClick = () => {
|
|
116
|
+
if (videoUrl) {
|
|
117
|
+
setPreviewOpen(true);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
// If no video URL, show disabled state
|
|
121
|
+
if (!videoUrl) {
|
|
122
|
+
return (_jsx("div", { className: `${getContainerClasses()} opacity-50 cursor-not-allowed`, style: dimensions, children: _jsx(Icon, { name: placeholderIcon, size: 20, className: "text-gray-400" }) }));
|
|
123
|
+
}
|
|
124
|
+
return (_jsxs(_Fragment, { children: [_jsx("div", { className: getContainerClasses(), style: dimensions, onClick: handleClick, children: thumbnailUrl ? (_jsxs(_Fragment, { children: [_jsx("img", { src: thumbnailUrl, alt: translate('core:common.video_thumbnail'), className: "w-full h-full object-cover" }), _jsx("div", { className: "absolute inset-0 flex items-center justify-center bg-black/30 group-hover:bg-black/40 transition-colors", children: _jsx("div", { className: "w-8 h-8 flex items-center justify-center bg-white/90 rounded-full", children: _jsx(Icon, { name: "Play", size: 16, className: "text-gray-800 ml-0.5" }) }) })] })) : (_jsxs(_Fragment, { children: [_jsx(Icon, { name: placeholderIcon, size: 24, className: "text-gray-500 dark:text-gray-400" }), _jsx("div", { className: "absolute bottom-1 right-1 w-4 h-4 flex items-center justify-center bg-accent rounded-full", children: _jsx(Icon, { name: "Play", size: 10, className: "text-white ml-0.5" }) })] })) }), _jsx(VideoPreviewModal, { isOpen: previewOpen, onClose: () => setPreviewOpen(false), videoUrl: videoUrl, title: column.previewTitle, autoplay: column.autoplay, controls: column.controls !== false, loop: column.loop, muted: column.muted })] }));
|
|
125
|
+
}
|