Created
February 10, 2026 15:25
-
-
Save wise-introvert/1ed5388420eebdeb7ce861c322faf689 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Directory structure: | |
| └── src/ | |
| ├── babel.test.config.json | |
| ├── constants.ts | |
| ├── core-scripts.interface.ts | |
| ├── current-admin.interface.ts | |
| ├── index.ts | |
| ├── backend/ | |
| │ ├── index.ts | |
| │ ├── actions/ | |
| │ │ └── index.ts | |
| │ ├── adapters/ | |
| │ │ ├── index.ts | |
| │ │ ├── database/ | |
| │ │ │ └── index.ts | |
| │ │ ├── property/ | |
| │ │ │ └── index.ts | |
| │ │ ├── record/ | |
| │ │ │ ├── index.ts | |
| │ │ │ └── params.type.ts | |
| │ │ └── resource/ | |
| │ │ ├── index.ts | |
| │ │ └── supported-databases.type.ts | |
| │ ├── bundler/ | |
| │ │ ├── index.ts | |
| │ │ └── utils/ | |
| │ │ └── constants.ts | |
| │ ├── controllers/ | |
| │ │ └── index.ts | |
| │ ├── decorators/ | |
| │ │ ├── index.ts | |
| │ │ ├── action/ | |
| │ │ │ └── index.ts | |
| │ │ ├── property/ | |
| │ │ │ ├── index.ts | |
| │ │ │ └── utils/ | |
| │ │ │ ├── index.ts | |
| │ │ │ ├── override-from-options.spec.ts | |
| │ │ │ └── override-from-options.ts | |
| │ │ └── resource/ | |
| │ │ ├── index.ts | |
| │ │ └── utils/ | |
| │ │ ├── flat-sub-properties.ts | |
| │ │ └── index.ts | |
| │ ├── services/ | |
| │ │ ├── index.ts | |
| │ │ ├── action-error-handler/ | |
| │ │ │ └── index.ts | |
| │ │ └── sort-setter/ | |
| │ │ └── index.ts | |
| │ └── utils/ | |
| │ ├── index.ts | |
| │ ├── uploaded-file.type.ts | |
| │ ├── auth/ | |
| │ │ ├── default-auth-provider.ts | |
| │ │ └── index.ts | |
| │ ├── build-feature/ | |
| │ │ └── index.ts | |
| │ ├── errors/ | |
| │ │ ├── configuration-error.ts | |
| │ │ ├── forbidden-error.ts | |
| │ │ ├── index.ts | |
| │ │ ├── not-implemented-error.ts | |
| │ │ └── record-error.ts | |
| │ ├── filter/ | |
| │ │ └── index.ts | |
| │ ├── layout-element-parser/ | |
| │ │ └── index.ts | |
| │ ├── options-parser/ | |
| │ │ └── index.ts | |
| │ ├── populator/ | |
| │ │ ├── index.ts | |
| │ │ ├── populator.spec.ts | |
| │ │ └── populator.ts | |
| │ ├── request-parser/ | |
| │ │ └── index.ts | |
| │ ├── resources-factory/ | |
| │ │ └── index.ts | |
| │ ├── router/ | |
| │ │ ├── index.ts | |
| │ │ └── router.spec.ts | |
| │ └── view-helpers/ | |
| │ ├── index.ts | |
| │ └── view-helpers.spec.ts | |
| ├── frontend/ | |
| │ ├── index.ts | |
| │ ├── login-template.spec.ts | |
| │ ├── components/ | |
| │ │ ├── index.ts | |
| │ │ ├── actions/ | |
| │ │ │ ├── action.props.ts | |
| │ │ │ ├── index.ts | |
| │ │ │ └── utils/ | |
| │ │ │ └── index.ts | |
| │ │ ├── app/ | |
| │ │ │ ├── admin-modal.tsx | |
| │ │ │ ├── app-loader.tsx | |
| │ │ │ ├── auth-background-component.tsx | |
| │ │ │ ├── error-boundary.tsx | |
| │ │ │ ├── footer.tsx | |
| │ │ │ ├── index.ts | |
| │ │ │ ├── action-button/ | |
| │ │ │ │ └── index.ts | |
| │ │ │ ├── action-header/ | |
| │ │ │ │ ├── action-header-props.tsx | |
| │ │ │ │ └── index.ts | |
| │ │ │ ├── language-select/ | |
| │ │ │ │ └── index.ts | |
| │ │ │ ├── records-table/ | |
| │ │ │ │ ├── index.ts | |
| │ │ │ │ └── utils/ | |
| │ │ │ │ ├── display.tsx | |
| │ │ │ │ └── get-bulk-actions-from-records.ts | |
| │ │ │ └── sidebar/ | |
| │ │ │ ├── index.ts | |
| │ │ │ └── sidebar-footer.tsx | |
| │ │ ├── property-type/ | |
| │ │ │ ├── clean-property-component.tsx | |
| │ │ │ ├── record-property-is-equal.ts | |
| │ │ │ ├── array/ | |
| │ │ │ │ ├── add-new-item-translation.tsx | |
| │ │ │ │ ├── index.ts | |
| │ │ │ │ └── list.tsx | |
| │ │ │ ├── boolean/ | |
| │ │ │ │ ├── boolean-property-value.tsx | |
| │ │ │ │ ├── index.ts | |
| │ │ │ │ ├── list.tsx | |
| │ │ │ │ ├── map-value.tsx | |
| │ │ │ │ └── show.tsx | |
| │ │ │ ├── currency/ | |
| │ │ │ │ ├── filter.tsx | |
| │ │ │ │ ├── format-value.ts | |
| │ │ │ │ ├── index.ts | |
| │ │ │ │ ├── list.tsx | |
| │ │ │ │ └── show.tsx | |
| │ │ │ ├── datetime/ | |
| │ │ │ │ ├── index.ts | |
| │ │ │ │ ├── list.tsx | |
| │ │ │ │ ├── map-value.ts | |
| │ │ │ │ ├── show.tsx | |
| │ │ │ │ └── strip-time-from-iso.ts | |
| │ │ │ ├── default-type/ | |
| │ │ │ │ ├── index.ts | |
| │ │ │ │ ├── list.tsx | |
| │ │ │ │ └── show.tsx | |
| │ │ │ ├── docs/ | |
| │ │ │ │ └── on-property-change.doc.md | |
| │ │ │ ├── key-value/ | |
| │ │ │ │ └── index.ts | |
| │ │ │ ├── mixed/ | |
| │ │ │ │ ├── convert-to-sub-property.ts | |
| │ │ │ │ └── index.ts | |
| │ │ │ ├── password/ | |
| │ │ │ │ └── index.ts | |
| │ │ │ ├── phone/ | |
| │ │ │ │ ├── filter.tsx | |
| │ │ │ │ ├── index.ts | |
| │ │ │ │ ├── list.tsx | |
| │ │ │ │ └── show.tsx | |
| │ │ │ ├── reference/ | |
| │ │ │ │ ├── index.ts | |
| │ │ │ │ ├── list.tsx | |
| │ │ │ │ └── show.tsx | |
| │ │ │ ├── richtext/ | |
| │ │ │ │ ├── index.ts | |
| │ │ │ │ ├── list.tsx | |
| │ │ │ │ └── show.tsx | |
| │ │ │ ├── textarea/ | |
| │ │ │ │ ├── index.ts | |
| │ │ │ │ └── show.tsx | |
| │ │ │ └── utils/ | |
| │ │ │ ├── index.ts | |
| │ │ │ ├── property-description/ | |
| │ │ │ │ └── index.ts | |
| │ │ │ └── property-label/ | |
| │ │ │ └── index.ts | |
| │ │ ├── routes/ | |
| │ │ │ ├── index.ts | |
| │ │ │ └── utils/ | |
| │ │ │ └── should-action-re-fetch-data.ts | |
| │ │ └── spec/ | |
| │ │ ├── action-json.factory.ts | |
| │ │ ├── factory.ts | |
| │ │ ├── initialize-translations.ts | |
| │ │ ├── page-json.factory.ts | |
| │ │ ├── property-json.factory.ts | |
| │ │ └── test-context-provider.tsx | |
| │ ├── hoc/ | |
| │ │ ├── index.ts | |
| │ │ └── with-no-ssr.tsx | |
| │ ├── hooks/ | |
| │ │ ├── index.ts | |
| │ │ ├── use-filter-drawer.tsx | |
| │ │ ├── use-notice.ts | |
| │ │ ├── use-action/ | |
| │ │ │ ├── index.ts | |
| │ │ │ ├── use-action-response-handler.ts | |
| │ │ │ └── use-action.doc.md | |
| │ │ ├── use-local-storage/ | |
| │ │ │ ├── index.ts | |
| │ │ │ ├── use-local-storage-result.type.ts | |
| │ │ │ └── use-local-storage.doc.md | |
| │ │ ├── use-record/ | |
| │ │ │ ├── filter-record.ts | |
| │ │ │ ├── index.ts | |
| │ │ │ ├── is-entire-record-given.ts | |
| │ │ │ └── merge-record-response.ts | |
| │ │ ├── use-records/ | |
| │ │ │ ├── index.ts | |
| │ │ │ └── use-records-result.type.ts | |
| │ │ ├── use-resource/ | |
| │ │ │ ├── index.ts | |
| │ │ │ ├── use-resource.doc.md | |
| │ │ │ └── use-resource.ts | |
| │ │ └── use-selected-records/ | |
| │ │ ├── index.ts | |
| │ │ └── use-selected-records-result.type.ts | |
| │ ├── interfaces/ | |
| │ │ ├── index.ts | |
| │ │ ├── modal.interface.ts | |
| │ │ ├── noticeMessage.interface.ts | |
| │ │ ├── page-json.interface.ts | |
| │ │ ├── action/ | |
| │ │ │ ├── action-has-component.ts | |
| │ │ │ ├── build-action-test-id.ts | |
| │ │ │ ├── index.ts | |
| │ │ │ ├── is-bulk-action.ts | |
| │ │ │ ├── is-record-action.ts | |
| │ │ │ └── is-resource-action.ts | |
| │ │ └── property-json/ | |
| │ │ └── index.ts | |
| │ ├── store/ | |
| │ │ ├── index.ts | |
| │ │ ├── actions/ | |
| │ │ │ ├── add-notice.ts | |
| │ │ │ ├── drop-notice.ts | |
| │ │ │ ├── filter-drawer.ts | |
| │ │ │ ├── index.ts | |
| │ │ │ ├── initialize-assets.ts | |
| │ │ │ ├── initialize-branding.ts | |
| │ │ │ ├── initialize-dashboard.ts | |
| │ │ │ ├── initialize-locale.ts | |
| │ │ │ ├── initialize-pages.ts | |
| │ │ │ ├── initialize-paths.ts | |
| │ │ │ ├── initialize-resources.ts | |
| │ │ │ ├── initialize-theme.ts | |
| │ │ │ ├── initialize-versions.ts | |
| │ │ │ ├── modal.ts | |
| │ │ │ ├── route-changed.ts | |
| │ │ │ ├── set-current-admin.ts | |
| │ │ │ ├── set-drawer-preroute.ts | |
| │ │ │ └── set-notice-progress.ts | |
| │ │ ├── reducers/ | |
| │ │ │ ├── assetsReducer.ts | |
| │ │ │ ├── brandingReducer.ts | |
| │ │ │ ├── dashboardReducer.ts | |
| │ │ │ ├── drawerReducer.ts | |
| │ │ │ ├── filterDrawerReducer.ts | |
| │ │ │ ├── index.ts | |
| │ │ │ ├── localesReducer.ts | |
| │ │ │ ├── modalReducer.ts | |
| │ │ │ ├── pagesReducer.ts | |
| │ │ │ ├── pathsReducer.ts | |
| │ │ │ ├── resourcesReducer.ts | |
| │ │ │ ├── routerReducer.ts | |
| │ │ │ ├── sessionReducer.ts | |
| │ │ │ ├── themeReducer.ts | |
| │ │ │ └── versionsReducer.ts | |
| │ │ └── utils/ | |
| │ │ └── pages-to-store.ts | |
| │ └── utils/ | |
| │ ├── data-css-name.ts | |
| │ └── index.ts | |
| ├── locale/ | |
| │ ├── default-config.ts | |
| │ └── index.ts | |
| └── utils/ | |
| ├── error-type.enum.ts | |
| ├── index.ts | |
| ├── theme-bundler.ts | |
| ├── flat/ | |
| │ ├── constants.ts | |
| │ ├── filter-out-params.doc.md | |
| │ ├── filter-out-params.ts | |
| │ ├── flat.types.ts | |
| │ ├── get.doc.md | |
| │ ├── index.ts | |
| │ ├── merge.ts | |
| │ ├── path-parts.type.ts | |
| │ ├── path-to-parts.doc.md | |
| │ ├── path-to-parts.ts | |
| │ ├── property-key-regex.ts | |
| │ ├── select-params.doc.md | |
| │ └── set.doc.md | |
| └── param-converter/ | |
| ├── constants.ts | |
| ├── convert-nested-param.ts | |
| ├── convert-param.spec.ts | |
| ├── convert-param.ts | |
| ├── index.ts | |
| └── param-converter-module.ts | |
| Files Content: | |
| ================================================ | |
| FILE: src/babel.test.config.json | |
| ================================================ | |
| { | |
| "presets": [ | |
| "@babel/preset-react", | |
| [ | |
| "@babel/preset-env", | |
| { | |
| "targets": { | |
| "node": "18" | |
| }, | |
| "loose": true, | |
| "modules": false | |
| } | |
| ], | |
| "@babel/preset-typescript" | |
| ], | |
| "plugins": ["@babel/plugin-syntax-import-assertions"], | |
| "only": ["src/", "spec/"], | |
| "ignore": [ | |
| "src/frontend/assets/scripts/app-bundle.development.js", | |
| "src/frontend/assets/scripts/app-bundle.production.js", | |
| "src/frontend/assets/scripts/global-bundle.development.js", | |
| "src/frontend/assets/scripts/global-bundle.production.js" | |
| ] | |
| } | |
| ================================================ | |
| FILE: src/constants.ts | |
| ================================================ | |
| /* cspell: disable */ | |
| export const DOCS = 'https://docs.adminjs.co' | |
| export const DEFAULT_PATHS = { | |
| rootPath: '/admin', | |
| logoutPath: '/admin/logout', | |
| loginPath: '/admin/login', | |
| refreshTokenPath: '/admin/refresh-token', | |
| } | |
| ================================================ | |
| FILE: src/core-scripts.interface.ts | |
| ================================================ | |
| /** | |
| * @memberof Assets | |
| * @alias CoreScripts | |
| * | |
| * Optional mapping of core AdminJS browser scripts: | |
| * - app.bundle.js | |
| * - components.bundle.js | |
| * - design-system.bundle.js | |
| * - global.bundle.js | |
| * | |
| * You may want to use it if you'd like to version assets for caching. This | |
| * will only work if you have also configured `assetsCDN` in AdminJS options. | |
| * | |
| * Example: | |
| * ``` | |
| * { | |
| * 'app.bundle.js': 'app.bundle.123456.js', | |
| * 'components.bundle.js': 'components.bundle.123456.js', | |
| * 'design-system.bundle.js': 'design-system.bundle.123456.js', | |
| * 'global.bundle.js': 'global.bundle.123456.js', | |
| * } | |
| * ``` | |
| */ | |
| export interface CoreScripts { | |
| /** | |
| * App Bundle | |
| */ | |
| 'app.bundle.js': string; | |
| /** | |
| * Custom Components | |
| */ | |
| 'components.bundle.js': string; | |
| /** | |
| * Design System Bundle | |
| */ | |
| 'design-system.bundle.js': string; | |
| /** | |
| * Global bundle | |
| */ | |
| 'global.bundle.js': string; | |
| } | |
| ================================================ | |
| FILE: src/current-admin.interface.ts | |
| ================================================ | |
| /** | |
| * Currently logged in admin. | |
| * | |
| * ### Usage with TypeScript | |
| * | |
| * ```typescript | |
| * import { CurrentAdmin } from 'adminjs' | |
| * ``` | |
| * | |
| * @alias CurrentAdmin | |
| * @memberof AdminJS | |
| */ | |
| export interface CurrentAdmin { | |
| /** | |
| * Admin has one required field which is an email | |
| */ | |
| email: string; | |
| /** | |
| * Optional title/role of an admin - this will be presented below the email | |
| */ | |
| title?: string; | |
| /** | |
| * Optional url for an avatar photo | |
| */ | |
| avatarUrl?: string; | |
| /** | |
| * Id of your admin user | |
| */ | |
| id?: string; | |
| /** | |
| * Optional ID of theme to use | |
| */ | |
| theme?: string; | |
| /** | |
| * Extra metadata specific to given Auth Provider | |
| */ | |
| _auth?: Record<string, any>; | |
| /** | |
| * Also you can put as many other fields to it as you like. | |
| */ | |
| [key: string]: any; | |
| } | |
| ================================================ | |
| FILE: src/index.ts | |
| ================================================ | |
| import AdminJS from './adminjs.js' | |
| export * from './backend/index.js' | |
| export * from './frontend/index.js' | |
| export * from './locale/index.js' | |
| export * from './utils/index.js' | |
| export * from './constants.js' | |
| export * from './adminjs-options.interface.js' | |
| export * from './current-admin.interface.js' | |
| export default AdminJS | |
| ================================================ | |
| FILE: src/backend/index.ts | |
| ================================================ | |
| export * from './actions/index.js' | |
| export * from './adapters/index.js' | |
| export * from './controllers/index.js' | |
| export * from './decorators/index.js' | |
| export * from './services/index.js' | |
| export * from './utils/index.js' | |
| ================================================ | |
| FILE: src/backend/actions/index.ts | |
| ================================================ | |
| import { DeleteAction } from './delete/delete-action.js' | |
| import { ShowAction } from './show/show-action.js' | |
| import { NewAction } from './new/new-action.js' | |
| import { EditAction } from './edit/edit-action.js' | |
| import { SearchAction } from './search/search-action.js' | |
| import { ListAction } from './list/list-action.js' | |
| import { BulkDeleteAction } from './bulk-delete/bulk-delete-action.js' | |
| import { BuildInActions } from './action.interface.js' | |
| export * from './delete/delete-action.js' | |
| export * from './show/show-action.js' | |
| export * from './new/new-action.js' | |
| export * from './edit/edit-action.js' | |
| export * from './search/search-action.js' | |
| export * from './list/list-action.js' | |
| export * from './bulk-delete/bulk-delete-action.js' | |
| export * from './action.interface.js' | |
| export const ACTIONS: {[key in BuildInActions]: any} = { | |
| new: NewAction, | |
| list: ListAction, | |
| show: ShowAction, | |
| edit: EditAction, | |
| delete: DeleteAction, | |
| bulkDelete: BulkDeleteAction, | |
| search: SearchAction, | |
| } | |
| ================================================ | |
| FILE: src/backend/adapters/index.ts | |
| ================================================ | |
| export * from './database/index.js' | |
| export * from './property/index.js' | |
| export * from './record/index.js' | |
| export * from './resource/index.js' | |
| ================================================ | |
| FILE: src/backend/adapters/database/index.ts | |
| ================================================ | |
| export { default as BaseDatabase } from './base-database.js' | |
| ================================================ | |
| FILE: src/backend/adapters/property/index.ts | |
| ================================================ | |
| export { default as BaseProperty } from './base-property.js' | |
| export type { PropertyType } from './base-property.js' | |
| ================================================ | |
| FILE: src/backend/adapters/record/index.ts | |
| ================================================ | |
| export { default as BaseRecord } from './base-record.js' | |
| export * from './params.type.js' | |
| ================================================ | |
| FILE: src/backend/adapters/record/params.type.ts | |
| ================================================ | |
| /** | |
| * @alias ParamsTypeValue | |
| * @memberof BaseRecord | |
| */ | |
| export type ParamsTypeValue = string | |
| | number | |
| | boolean | |
| | null | |
| | undefined | |
| | [] | |
| | Record<string, unknown> | |
| | File | |
| /** | |
| * @alias ParamsType | |
| * @memberof BaseRecord | |
| */ | |
| export type ParamsType = Record<string, any> | |
| // TODO: change ^^^any to ParamsTypeValue | |
| ================================================ | |
| FILE: src/backend/adapters/resource/index.ts | |
| ================================================ | |
| export { default as BaseResource } from './base-resource.js' | |
| export * from './supported-databases.type.js' | |
| ================================================ | |
| FILE: src/backend/adapters/resource/supported-databases.type.ts | |
| ================================================ | |
| export type SupportedDatabasesType = 'MySQL' | |
| | 'MariaDB' | |
| | 'Postgres' | |
| | 'CockroachDB' | |
| | 'SQLite' | |
| | 'MicrosoftSQLServer' | |
| | 'Oracle' | |
| | 'SAPHana' | |
| | 'MongoDB' | |
| | 'other' | |
| ================================================ | |
| FILE: src/backend/bundler/index.ts | |
| ================================================ | |
| export { default as appBundler } from './app.bundler.js' | |
| export { default as globalsBundler } from './globals.bundler.js' | |
| export { default as componentsBundler } from './components.bundler.js' | |
| export { default as generateUserComponentEntry } from './generate-user-component-entry.js' | |
| export * from './utils/constants.js' | |
| export { AssetBundler } from './utils/asset-bundler.js' | |
| ================================================ | |
| FILE: src/backend/bundler/utils/constants.ts | |
| ================================================ | |
| import path from 'path' | |
| export const NODE_ENV = process.env.NODE_ENV === 'production' ? 'production' : 'development' | |
| const DEFAULT_TMP_DIR = '.adminjs' | |
| export const ADMIN_JS_TMP_DIR = typeof process === 'object' | |
| ? process.env.ADMIN_JS_TMP_DIR || DEFAULT_TMP_DIR | |
| : DEFAULT_TMP_DIR | |
| export const COMPONENTS_ENTRY_PATH = path.join(ADMIN_JS_TMP_DIR, 'entry.js') | |
| export const COMPONENTS_OUTPUT_PATH = path.join(ADMIN_JS_TMP_DIR, 'bundle.js') | |
| ================================================ | |
| FILE: src/backend/controllers/index.ts | |
| ================================================ | |
| export { default as AppController } from './app-controller.js' | |
| export { default as ApiController } from './api-controller.js' | |
| ================================================ | |
| FILE: src/backend/decorators/index.ts | |
| ================================================ | |
| export * from './action/index.js' | |
| export * from './property/index.js' | |
| export * from './resource/index.js' | |
| ================================================ | |
| FILE: src/backend/decorators/action/index.ts | |
| ================================================ | |
| export { default as ActionDecorator } from './action-decorator.js' | |
| ================================================ | |
| FILE: src/backend/decorators/property/index.ts | |
| ================================================ | |
| export { PropertyDecorator } from './property-decorator.js' | |
| export type { default as PropertyOptions } from './property-options.interface.js' | |
| export * from './property-options.interface.js' | |
| ================================================ | |
| FILE: src/backend/decorators/property/utils/index.ts | |
| ================================================ | |
| export * from './override-from-options.js' | |
| ================================================ | |
| FILE: src/backend/decorators/property/utils/override-from-options.spec.ts | |
| ================================================ | |
| import { expect } from 'chai' | |
| import sinon from 'sinon' | |
| import { PropertyType, BaseProperty } from '../../../adapters/property/index.js' | |
| import { overrideFromOptions } from './override-from-options.js' | |
| describe('overrideFromOptions', () => { | |
| const propertyName = 'type' | |
| const rawValue = 'boolean' | |
| const optionsValue = 'string' | |
| let property: BaseProperty | |
| beforeEach(() => { | |
| property = sinon.createStubInstance(BaseProperty, { | |
| [propertyName]: sinon.stub<[], PropertyType>().returns(rawValue), | |
| }) as unknown as BaseProperty | |
| }) | |
| it('returns value from BaseProperty function when options are not given', () => { | |
| expect(overrideFromOptions(propertyName, property, {})).to.eq(rawValue) | |
| }) | |
| it('returns value from options it is given', () => { | |
| expect(overrideFromOptions(propertyName, property, { | |
| [propertyName]: optionsValue, | |
| })).to.eq(optionsValue) | |
| }) | |
| }) | |
| ================================================ | |
| FILE: src/backend/decorators/property/utils/override-from-options.ts | |
| ================================================ | |
| import { BaseProperty } from '../../../adapters/property/index.js' | |
| import PropertyOptions from '../property-options.interface.js' | |
| export type OverridableFromOptionsType = keyof Pick<BaseProperty, | |
| 'isSortable' | | |
| 'type' | | |
| 'isId' | | |
| 'isRequired' | | |
| 'isTitle' | |
| > | |
| export function overrideFromOptions<T extends OverridableFromOptionsType>( | |
| optionName: T, | |
| property: BaseProperty, | |
| options: PropertyOptions, | |
| ): ReturnType<BaseProperty[OverridableFromOptionsType]> | null | undefined { | |
| if (typeof options[optionName] === 'undefined') { | |
| return property[optionName]() | |
| } | |
| return options[optionName] | |
| } | |
| ================================================ | |
| FILE: src/backend/decorators/resource/index.ts | |
| ================================================ | |
| export { default as ResourceDecorator } from './resource-decorator.js' | |
| export * from './resource-options.interface.js' | |
| ================================================ | |
| FILE: src/backend/decorators/resource/utils/flat-sub-properties.ts | |
| ================================================ | |
| import PropertyDecorator from '../../property/property-decorator.js' | |
| /** | |
| * Bu default all subProperties are nested as an array in root Property. This is easy for | |
| * adapter to maintain. But in AdminJS core we need a fast way to access them by path. | |
| * | |
| * This function changes an array to object recursively (for nested subProperties) so they | |
| * could be accessed via properties['path.to.sub.property'] | |
| * | |
| * @param {PropertyDecorator} rootProperty | |
| * | |
| * @return {Record<PropertyDecorator>} | |
| * @private | |
| */ | |
| export const flatSubProperties = ( | |
| rootProperty: PropertyDecorator, | |
| ): Record<string, PropertyDecorator> => ( | |
| rootProperty.subProperties().reduce((subMemo, subProperty) => Object.assign( | |
| subMemo, | |
| { [subProperty.propertyPath]: subProperty }, | |
| flatSubProperties(subProperty), | |
| ), {}) | |
| ) | |
| ================================================ | |
| FILE: src/backend/decorators/resource/utils/index.ts | |
| ================================================ | |
| export * from './find-sub-property.js' | |
| export * from './flat-sub-properties.js' | |
| export * from './get-navigation.js' | |
| export * from './decorate-properties.js' | |
| export * from './decorate-actions.js' | |
| export * from './get-property-by-key.js' | |
| ================================================ | |
| FILE: src/backend/services/index.ts | |
| ================================================ | |
| export * from './action-error-handler/index.js' | |
| export * from './sort-setter/index.js' | |
| ================================================ | |
| FILE: src/backend/services/action-error-handler/index.ts | |
| ================================================ | |
| export { default as actionErrorHandler } from './action-error-handler.js' | |
| ================================================ | |
| FILE: src/backend/services/sort-setter/index.ts | |
| ================================================ | |
| export { default as SortSetter } from './sort-setter.js' | |
| ================================================ | |
| FILE: src/backend/utils/index.ts | |
| ================================================ | |
| export * from './auth/index.js' | |
| export * from './build-feature/index.js' | |
| export * from './errors/index.js' | |
| export * from './filter/index.js' | |
| export * from './layout-element-parser/index.js' | |
| export * from './options-parser/index.js' | |
| export * from './populator/index.js' | |
| export * from './request-parser/index.js' | |
| export * from './resources-factory/index.js' | |
| export * from './view-helpers/index.js' | |
| export * from './router/index.js' | |
| export * from './uploaded-file.type.js' | |
| export * from './component-loader.js' | |
| ================================================ | |
| FILE: src/backend/utils/uploaded-file.type.ts | |
| ================================================ | |
| /** | |
| * File uploaded via FormData to the backend. | |
| * | |
| * @memberof AdminJS | |
| * @alias UploadedFile | |
| */ | |
| export type UploadedFile = { | |
| /** | |
| * The size of the uploaded file in bytes. | |
| * this property says how many bytes of the file have been written to disk yet. | |
| */ | |
| size: number; | |
| /** | |
| * The path this file is being written to. | |
| */ | |
| path: string; | |
| /** | |
| * The mime type of this file, according to the uploading client. | |
| */ | |
| type: string; | |
| /** | |
| * The name this file had according to the uploading client. | |
| */ | |
| name: string | null; | |
| } | |
| ================================================ | |
| FILE: src/backend/utils/auth/default-auth-provider.ts | |
| ================================================ | |
| import { AuthProviderConfig, AuthenticatePayload, BaseAuthProvider, LoginHandlerOptions } from './base-auth-provider.js' | |
| export interface DefaultAuthenticatePayload extends AuthenticatePayload { | |
| email: string; | |
| password: string; | |
| } | |
| // eslint-disable-next-line @typescript-eslint/no-empty-interface | |
| export interface DefaultAuthProviderConfig extends AuthProviderConfig<DefaultAuthenticatePayload> {} | |
| export class DefaultAuthProvider extends BaseAuthProvider { | |
| protected readonly authenticate | |
| constructor({ authenticate }: DefaultAuthProviderConfig) { | |
| super() | |
| this.authenticate = authenticate | |
| } | |
| override async handleLogin(opts: LoginHandlerOptions, context) { | |
| const { data = {} } = opts | |
| const { email, password } = data | |
| return this.authenticate({ email, password }, context) | |
| } | |
| } | |
| ================================================ | |
| FILE: src/backend/utils/auth/index.ts | |
| ================================================ | |
| export * from './base-auth-provider.js' | |
| export * from './default-auth-provider.js' | |
| ================================================ | |
| FILE: src/backend/utils/build-feature/index.ts | |
| ================================================ | |
| export * from './build-feature.js' | |
| ================================================ | |
| FILE: src/backend/utils/errors/configuration-error.ts | |
| ================================================ | |
| import { ErrorTypeEnum } from '../../../utils/error-type.enum.js' | |
| import * as CONSTANTS from '../../../constants.js' | |
| const buildUrl = (page: string): string => ( | |
| `${CONSTANTS.DOCS}/${page}` | |
| ) | |
| /** | |
| * Error which is thrown when user messed up something in the configuration | |
| * | |
| * @category Errors | |
| */ | |
| export class ConfigurationError extends Error { | |
| /** | |
| * @param {string} fnName name of the function, base on which error will | |
| * print on the output link to the method documentation. | |
| * @param {string} message | |
| */ | |
| constructor(message, fnName) { | |
| const msg = ` | |
| ${message} | |
| More information can be found at: ${buildUrl(fnName)} | |
| ` | |
| super(msg) | |
| this.message = msg | |
| this.name = ErrorTypeEnum.Configuration | |
| } | |
| } | |
| export default ConfigurationError | |
| ================================================ | |
| FILE: src/backend/utils/errors/forbidden-error.ts | |
| ================================================ | |
| import { ErrorTypeEnum } from '../../../utils/error-type.enum.js' | |
| import RecordError from './record-error.js' | |
| /** | |
| * Error which is thrown when user | |
| * doesn't have an access to a given resource/action. | |
| * | |
| * @category Errors | |
| */ | |
| export class ForbiddenError extends Error { | |
| /** | |
| * HTTP Status code: 403 | |
| */ | |
| public statusCode: number | |
| /** | |
| * Base error message and type which is stored in the record | |
| */ | |
| public baseError: RecordError | |
| /** | |
| * Any custom message which should be seen in the UI | |
| */ | |
| public baseMessage?: string | |
| /** | |
| * @param {string} [message] | |
| */ | |
| constructor(message?: string) { | |
| const defaultMessage = 'You cannot perform this action' | |
| super(defaultMessage) | |
| this.statusCode = 403 | |
| this.baseMessage = message | |
| this.baseError = { | |
| message: message ?? defaultMessage, | |
| type: ErrorTypeEnum.Forbidden, | |
| } | |
| this.name = ErrorTypeEnum.Forbidden | |
| } | |
| } | |
| export default ForbiddenError | |
| ================================================ | |
| FILE: src/backend/utils/errors/index.ts | |
| ================================================ | |
| export * from './app-error.js' | |
| export * from './configuration-error.js' | |
| export * from './forbidden-error.js' | |
| export * from './not-found-error.js' | |
| export * from './not-implemented-error.js' | |
| export * from './record-error.js' | |
| export * from './validation-error.js' | |
| ================================================ | |
| FILE: src/backend/utils/errors/not-implemented-error.ts | |
| ================================================ | |
| import { DOCS } from '../../../constants.js' | |
| const buildUrl = (fnName: string): string => { | |
| if (fnName) { | |
| let obj | |
| let fn | |
| if (fnName.indexOf('.') > 0) { | |
| [obj, fn] = fnName.split('.') | |
| fn = `.${fn}` | |
| } else { | |
| [obj, fn] = fnName.split('#') | |
| } | |
| return `${DOCS}/${obj}.html#${fn}` | |
| } | |
| return DOCS | |
| } | |
| /** | |
| * Error which is thrown when an abstract method is not implemented | |
| * | |
| * @category Errors | |
| */ | |
| export class NotImplementedError extends Error { | |
| /** | |
| * @param {string} fnName name of the function, base on which error will | |
| * print on the output link to the method documentation. | |
| */ | |
| constructor(fnName: string) { | |
| const message = ` | |
| You have to implement the method: ${fnName} | |
| Check out the documentation at: ${buildUrl(fnName)} | |
| ` | |
| super(message) | |
| this.message = message | |
| } | |
| } | |
| export default NotImplementedError | |
| ================================================ | |
| FILE: src/backend/utils/errors/record-error.ts | |
| ================================================ | |
| import { ErrorTypeEnum } from '../../../utils/error-type.enum.js' | |
| /** | |
| * Record Error | |
| * @alias RecordError | |
| * @memberof ValidationError | |
| */ | |
| export type RecordError = { | |
| /** | |
| * error type (i.e. required) | |
| */ | |
| type?: ErrorTypeEnum | string | |
| /** | |
| * Code of message | |
| */ | |
| message: string | |
| } | |
| export default RecordError | |
| ================================================ | |
| FILE: src/backend/utils/filter/index.ts | |
| ================================================ | |
| export * from './filter.js' | |
| ================================================ | |
| FILE: src/backend/utils/layout-element-parser/index.ts | |
| ================================================ | |
| export * from './layout-element-parser.js' | |
| ================================================ | |
| FILE: src/backend/utils/options-parser/index.ts | |
| ================================================ | |
| export * from './options-parser.js' | |
| ================================================ | |
| FILE: src/backend/utils/populator/index.ts | |
| ================================================ | |
| export * from './populator.js' | |
| export * from './populate-property.js' | |
| ================================================ | |
| FILE: src/backend/utils/populator/populator.spec.ts | |
| ================================================ | |
| import { expect } from 'chai' | |
| import populator from './populator.js' | |
| describe('populator', () => { | |
| context('empty array given as params', () => { | |
| it('returns empty array when no records are given', async () => { | |
| const records = await populator([]) | |
| expect(records).to.have.lengthOf(0) | |
| }) | |
| }) | |
| }) | |
| ================================================ | |
| FILE: src/backend/utils/populator/populator.ts | |
| ================================================ | |
| import BaseRecord from '../../adapters/record/base-record.js' | |
| import { populateProperty } from './populate-property.js' | |
| import { ActionContext } from '../../actions/index.js' | |
| /** | |
| * @load ./populator.doc.md | |
| * @param {Array<BaseRecord>} records | |
| * @param context | |
| * @new In version 3.3 | |
| */ | |
| export async function populator( | |
| records: Array<BaseRecord>, | |
| context?:ActionContext, | |
| ): Promise<Array<BaseRecord>> { | |
| if (!records || !records.length) { | |
| return records | |
| } | |
| const resourceDecorator = records[0].resource.decorate() | |
| const allProperties = Object.values(resourceDecorator.getFlattenProperties()) | |
| const references = allProperties.filter((p) => !!p.reference()) | |
| await Promise.all(references.map(async (propertyDecorator) => { | |
| await populateProperty(records, propertyDecorator, context) | |
| })) | |
| return records | |
| } | |
| export default populator | |
| ================================================ | |
| FILE: src/backend/utils/request-parser/index.ts | |
| ================================================ | |
| export * from './request-parser.js' | |
| ================================================ | |
| FILE: src/backend/utils/resources-factory/index.ts | |
| ================================================ | |
| export { default as ResourcesFactory } from './resources-factory.js' | |
| ================================================ | |
| FILE: src/backend/utils/router/index.ts | |
| ================================================ | |
| export * from './router.js' | |
| ================================================ | |
| FILE: src/backend/utils/router/router.spec.ts | |
| ================================================ | |
| import { expect } from 'chai' | |
| import Router from './router.js' | |
| describe('Router', function () { | |
| it('has both assets and routes', function () { | |
| expect(Router.assets).not.to.be.undefined | |
| expect(Router.routes).not.to.be.undefined | |
| }) | |
| it('returns development bundle by default', function () { | |
| const asset = Router.assets.find((a) => a.path === '/frontend/assets/app.bundle.js') | |
| expect(asset && asset.src).to.contain('scripts/app-bundle.development.js') | |
| }) | |
| }) | |
| ================================================ | |
| FILE: src/backend/utils/view-helpers/index.ts | |
| ================================================ | |
| export * from './view-helpers.js' | |
| ================================================ | |
| FILE: src/backend/utils/view-helpers/view-helpers.spec.ts | |
| ================================================ | |
| import { expect } from 'chai' | |
| import ViewHelpers from './view-helpers.js' | |
| describe('ViewHelpers', function () { | |
| describe('#urlBuilder', function () { | |
| it('returns joined path for default rootUrl', function () { | |
| const h = new ViewHelpers({}) | |
| expect(h.urlBuilder(['my', 'path'])).to.equal('/admin/my/path') | |
| }) | |
| it('returns correct url when user gives admin root path not starting with /', function () { | |
| const h = new ViewHelpers({ options: { rootPath: 'admin' } }) | |
| expect(h.urlBuilder(['my', 'path'])).to.equal('/admin/my/path') | |
| }) | |
| it('returns correct url for rootPath set to /', function () { | |
| const h = new ViewHelpers({ options: { rootPath: '/' } }) | |
| expect(h.urlBuilder(['my', 'path'])).to.equal('/my/path') | |
| }) | |
| }) | |
| }) | |
| ================================================ | |
| FILE: src/frontend/index.ts | |
| ================================================ | |
| export * from './components/index.js' | |
| export * from './hoc/index.js' | |
| export * from './hooks/index.js' | |
| export * from './interfaces/index.js' | |
| export * from './store/index.js' | |
| export * from './utils/index.js' | |
| ================================================ | |
| FILE: src/frontend/login-template.spec.ts | |
| ================================================ | |
| import { expect } from 'chai' | |
| import loginTemplate from './login-template.js' | |
| import AdminJS from '../adminjs.js' | |
| describe('login-template', function () { | |
| const action = '/login' | |
| it('renders error message', async function () { | |
| const adminJs = new AdminJS({}) | |
| const errorMessage = 'Something went wrong' | |
| const html = await loginTemplate(adminJs, { action, errorMessage }) | |
| expect(html).to.contain(errorMessage) | |
| }) | |
| }) | |
| ================================================ | |
| FILE: src/frontend/components/index.ts | |
| ================================================ | |
| export * from './actions/index.js' | |
| export * from './app/index.js' | |
| export * from './login/index.js' | |
| export * from './property-type/index.js' | |
| export * from './routes/index.js' | |
| export * from './application.js' | |
| ================================================ | |
| FILE: src/frontend/components/actions/action.props.ts | |
| ================================================ | |
| import { Dispatch, SetStateAction } from 'react' | |
| import { ActionJSON, RecordJSON, ResourceJSON } from '../../interfaces/index.js' | |
| /** | |
| * Props which are passed to all action components | |
| * @alias ActionProps | |
| * @memberof BaseActionComponent | |
| */ | |
| export type ActionProps = { | |
| /** | |
| * Action object describing the action | |
| */ | |
| action: ActionJSON; | |
| /** | |
| * Object of type: {@link ResourceJSON} | |
| */ | |
| resource: ResourceJSON; | |
| /** | |
| * Selected record. Passed for actions with "record" actionType | |
| */ | |
| record?: RecordJSON; | |
| /** | |
| * Selected records. Passed for actions with "bulk" actionType | |
| */ | |
| records?: Array<RecordJSON>; | |
| /** | |
| * Sets tag in a header of an action. It is a function taking tag as an argument | |
| */ | |
| setTag?: Dispatch<SetStateAction<string>>; | |
| } | |
| ================================================ | |
| FILE: src/frontend/components/actions/index.ts | |
| ================================================ | |
| import { New } from './new.js' | |
| import { Edit } from './edit.js' | |
| import { Show } from './show.js' | |
| import { List } from './list.js' | |
| import { BulkDelete } from './bulk-delete.js' | |
| export * from './new.js' | |
| export * from './action.props.js' | |
| export * from './edit.js' | |
| export * from './show.js' | |
| export * from './list.js' | |
| export * from './bulk-delete.js' | |
| export * from './utils/index.js' | |
| export const actions = { | |
| new: New, | |
| edit: Edit, | |
| show: Show, | |
| list: List, | |
| bulkDelete: BulkDelete, | |
| } | |
| ================================================ | |
| FILE: src/frontend/components/actions/utils/index.ts | |
| ================================================ | |
| export * from './layout-element-renderer.js' | |
| ================================================ | |
| FILE: src/frontend/components/app/admin-modal.tsx | |
| ================================================ | |
| import { Modal } from '@adminjs/design-system' | |
| import React, { FC } from 'react' | |
| import { useSelector } from 'react-redux' | |
| import { ReduxState } from '../../store/index.js' | |
| export const AdminModal: FC = () => { | |
| const modalState = useSelector((state: ReduxState) => state.modal) | |
| return modalState.show ? <Modal {...modalState.modalProps} /> : null | |
| } | |
| export default AdminModal | |
| ================================================ | |
| FILE: src/frontend/components/app/app-loader.tsx | |
| ================================================ | |
| import { Box, Loader } from '@adminjs/design-system' | |
| import React, { FC } from 'react' | |
| export const AppLoader: FC = () => ( | |
| <Box width="100%" height="100%" flex alignItems="center" justifyContent="center"> | |
| <Loader /> | |
| </Box> | |
| ) | |
| ================================================ | |
| FILE: src/frontend/components/app/auth-background-component.tsx | |
| ================================================ | |
| import React from 'react' | |
| import allowOverride from '../../hoc/allow-override.js' | |
| const AuthenticationBackgroundComponent: React.FC = () => null | |
| const OverridableAuthenticationBackgroundComponent = allowOverride(AuthenticationBackgroundComponent, 'AuthenticationBackgroundComponent') | |
| export { | |
| OverridableAuthenticationBackgroundComponent as default, | |
| OverridableAuthenticationBackgroundComponent as AuthenticationBackgroundComponent, | |
| AuthenticationBackgroundComponent as OriginalAuthenticationBackgroundComponent, | |
| } | |
| ================================================ | |
| FILE: src/frontend/components/app/error-boundary.tsx | |
| ================================================ | |
| import React, { ReactNode } from 'react' | |
| import { Text, MessageBox } from '@adminjs/design-system' | |
| import { useTranslation } from '../../hooks/index.js' | |
| type State = { | |
| error: any; | |
| } | |
| const ErrorMessage: React.FC<State> = ({ error }) => { | |
| const { translateMessage } = useTranslation() | |
| return ( | |
| <MessageBox m="xxl" variant="danger" message="Javascript Error"> | |
| <Text>{error.toString()}</Text> | |
| <Text mt="default">{translateMessage('seeConsoleForMore')}</Text> | |
| </MessageBox> | |
| ) | |
| } | |
| export class ErrorBoundary extends React.Component<any, State> { | |
| constructor(props) { | |
| super(props) | |
| this.state = { | |
| error: null, | |
| } | |
| } | |
| componentDidCatch(error): void { | |
| this.setState({ error }) | |
| } | |
| render(): ReactNode { | |
| const { children } = this.props | |
| const { error } = this.state | |
| if (error !== null) { | |
| return (<ErrorMessage error={error} />) | |
| } | |
| return children || null | |
| } | |
| } | |
| export default ErrorBoundary | |
| ================================================ | |
| FILE: src/frontend/components/app/footer.tsx | |
| ================================================ | |
| import React from 'react' | |
| import allowOverride from '../../hoc/allow-override.js' | |
| const Footer: React.FC = () => null | |
| const OverridableFooter = allowOverride(Footer, 'Footer') | |
| export { | |
| OverridableFooter as default, | |
| OverridableFooter as Footer, | |
| Footer as OriginalFooter, | |
| } | |
| ================================================ | |
| FILE: src/frontend/components/app/index.ts | |
| ================================================ | |
| export * from './action-button/index.js' | |
| export * from './action-header/index.js' | |
| export * from './admin-modal.js' | |
| export * from './app-loader.js' | |
| export * from './auth-background-component.js' | |
| export * from './base-action-component.js' | |
| export * from './breadcrumbs.js' | |
| export * from './default-dashboard.js' | |
| export * from './drawer-portal.js' | |
| export * from './error-boundary.js' | |
| export * from './error-message.js' | |
| export * from './filter-drawer.js' | |
| export * from './logged-in.js' | |
| export * from './notice.js' | |
| export * from './records-table/index.js' | |
| export * from './sidebar/index.js' | |
| export * from './sort-link.js' | |
| export { default as SortLink } from './sort-link.js' | |
| export * from './top-bar.js' | |
| export * from './version.js' | |
| export * from './footer.js' | |
| ================================================ | |
| FILE: src/frontend/components/app/action-button/index.ts | |
| ================================================ | |
| export * from './action-button.js' | |
| ================================================ | |
| FILE: src/frontend/components/app/action-header/action-header-props.tsx | |
| ================================================ | |
| import { ActionJSON, RecordJSON, ResourceJSON } from '../../../interfaces/index.js' | |
| import { ActionResponse } from '../../../../backend/actions/action.interface.js' | |
| /** | |
| * @memberof ActionHeader | |
| * @alias ActionHeaderProps | |
| */ | |
| export type ActionHeaderProps = { | |
| /** Resource for the action */ | |
| resource: ResourceJSON; | |
| /** Optional record - for _record_ actions */ | |
| record?: RecordJSON; | |
| /** If given, action header will render Filter button */ | |
| toggleFilter?: (() => any) | boolean; | |
| /** | |
| * It indicates if action without a component was performed. | |
| */ | |
| actionPerformed?: (action: ActionResponse) => any; | |
| /** An action objet */ | |
| action: ActionJSON; | |
| /** Optional tag which will be rendered as a {@link Badge} */ | |
| tag?: string; | |
| /** If set, component wont render actions */ | |
| omitActions?: boolean; | |
| }; | |
| ================================================ | |
| FILE: src/frontend/components/app/action-header/index.ts | |
| ================================================ | |
| export * from './action-header.js' | |
| export * from './action-header-props.js' | |
| ================================================ | |
| FILE: src/frontend/components/app/language-select/index.ts | |
| ================================================ | |
| export * from './language-select.js' | |
| ================================================ | |
| FILE: src/frontend/components/app/records-table/index.ts | |
| ================================================ | |
| export * from './no-records.js' | |
| export * from './property-header.js' | |
| export * from './record-in-list.js' | |
| export * from './records-table-header.js' | |
| export * from './records-table.js' | |
| export * from './selected-records.js' | |
| ================================================ | |
| FILE: src/frontend/components/app/records-table/utils/display.tsx | |
| ================================================ | |
| export const display = (isTitle: boolean): Array<string> => [ | |
| isTitle ? 'table-cell' : 'none', | |
| isTitle ? 'table-cell' : 'none', | |
| 'table-cell', | |
| 'table-cell', | |
| ] | |
| ================================================ | |
| FILE: src/frontend/components/app/records-table/utils/get-bulk-actions-from-records.ts | |
| ================================================ | |
| import { ActionJSON, RecordJSON } from '../../../../interfaces/index.js' | |
| const getBulkActionsFromRecords = (records: Array<RecordJSON>): Array<ActionJSON> => { | |
| const actions = Object.values(records.reduce((memo, record) => ({ | |
| ...memo, | |
| ...record.bulkActions.reduce((actionsMemo, action) => ({ | |
| ...actionsMemo, | |
| [action.name]: action, | |
| }), {} as Record<string, ActionJSON>), | |
| }), {} as Record<string, ActionJSON>)) | |
| return actions | |
| } | |
| export default getBulkActionsFromRecords | |
| ================================================ | |
| FILE: src/frontend/components/app/sidebar/index.ts | |
| ================================================ | |
| import Sidebar from './sidebar.js' | |
| export * from './sidebar-resource-section.js' | |
| export { Sidebar } | |
| ================================================ | |
| FILE: src/frontend/components/app/sidebar/sidebar-footer.tsx | |
| ================================================ | |
| import React from 'react' | |
| import { Box, MadeWithLove } from '@adminjs/design-system' | |
| import { useSelector } from 'react-redux' | |
| import { BrandingOptions } from '../../../../adminjs-options.interface.js' | |
| import allowOverride from '../../../hoc/allow-override.js' | |
| import { ReduxState } from '../../../store/index.js' | |
| const SidebarFooter: React.FC = () => { | |
| const branding = useSelector<ReduxState, BrandingOptions>((state) => state.branding) | |
| return ( | |
| <Box mt="lg" mb="md" data-css="sidebar-footer"> | |
| {branding.withMadeWithLove && <MadeWithLove />} | |
| </Box> | |
| ) | |
| } | |
| export default allowOverride(SidebarFooter, 'SidebarFooter') | |
| export { SidebarFooter as OriginalSidebarFooter, SidebarFooter } | |
| ================================================ | |
| FILE: src/frontend/components/property-type/clean-property-component.tsx | |
| ================================================ | |
| import React, { FC, useMemo } from 'react' | |
| import { BasePropertyProps } from './base-property-props.js' | |
| import { BasePropertyComponent } from './base-property-component.js' | |
| /** | |
| * This component is the same as `BasePropertyComponent` but it will not render | |
| * custom components. Use this in your custom components to render the default | |
| * property component. | |
| * | |
| * This is useful if you want your custom component to appear custom only for | |
| * specific `where` value and default for all others. | |
| */ | |
| const CleanPropertyComponent: FC<BasePropertyProps> = (props) => { | |
| const { property } = props | |
| const cleanProperty = useMemo(() => ({ ...property, components: {} }), [property]) | |
| return <BasePropertyComponent {...props} property={cleanProperty} /> | |
| } | |
| export default CleanPropertyComponent | |
| ================================================ | |
| FILE: src/frontend/components/property-type/record-property-is-equal.ts | |
| ================================================ | |
| /* eslint-disable import/prefer-default-export */ | |
| import { EditPropertyProps, ShowPropertyProps } from './base-property-props.js' | |
| /** | |
| * Function used in React memo to compare if previous property value and next | |
| * property value are the same. | |
| * | |
| * @private | |
| */ | |
| export const recordPropertyIsEqual = ( | |
| prevProps: Readonly<EditPropertyProps | ShowPropertyProps>, | |
| nextProps: Readonly<EditPropertyProps | ShowPropertyProps>, | |
| ): boolean => { | |
| const prevValue = prevProps.record.params[prevProps.property.path] | |
| const nextValue = nextProps.record.params[nextProps.property.path] | |
| const prevError = prevProps.record.errors[prevProps.property.path] | |
| const nextError = nextProps.record.errors[nextProps.property.path] | |
| return prevValue === nextValue && prevError === nextError | |
| } | |
| ================================================ | |
| FILE: src/frontend/components/property-type/array/add-new-item-translation.tsx | |
| ================================================ | |
| import React from 'react' | |
| import { Button, ButtonProps, Icon } from '@adminjs/design-system' | |
| import { useTranslation } from '../../../hooks/index.js' | |
| import { ResourceJSON, PropertyJSON } from '../../../interfaces/index.js' | |
| type AddNewItemButtonProps = { | |
| resource: ResourceJSON; | |
| property: PropertyJSON; | |
| } & ButtonProps | |
| const AddNewItemButton: React.FC<AddNewItemButtonProps> = (props) => { | |
| const { resource, property, ...btnProps } = props | |
| const { translateProperty, translateButton } = useTranslation() | |
| const label = translateProperty( | |
| `${property.path}.addNewItem`, | |
| resource.id, | |
| { | |
| defaultValue: translateButton('addNewItem', resource.id), | |
| }, | |
| ) | |
| return ( | |
| <Button type="button" variant="outlined" {...btnProps}> | |
| <Icon icon="Plus" /> | |
| {label} | |
| </Button> | |
| ) | |
| } | |
| export default AddNewItemButton | |
| ================================================ | |
| FILE: src/frontend/components/property-type/array/index.ts | |
| ================================================ | |
| import Edit from './edit.js' | |
| import List from './list.js' | |
| import Show from './show.js' | |
| export { | |
| Show as show, | |
| Edit as edit, | |
| List as list, | |
| } | |
| ================================================ | |
| FILE: src/frontend/components/property-type/array/list.tsx | |
| ================================================ | |
| import React from 'react' | |
| import { useTranslation } from '../../../hooks/use-translation.js' | |
| import { flat } from '../../../../utils/index.js' | |
| import { ShowPropertyProps } from '../base-property-props.js' | |
| import allowOverride from '../../../hoc/allow-override.js' | |
| const List: React.FC<ShowPropertyProps> = (props) => { | |
| const { property, record } = props | |
| const values = flat.get(record.params, property.path) || [] | |
| const { translateProperty } = useTranslation() | |
| return ( | |
| <span>{`${translateProperty('length')}: ${values.length}`}</span> | |
| ) | |
| } | |
| export default allowOverride(List, 'DefaultArrayListProperty') | |
| ================================================ | |
| FILE: src/frontend/components/property-type/boolean/boolean-property-value.tsx | |
| ================================================ | |
| import React from 'react' | |
| import { Badge } from '@adminjs/design-system' | |
| import { ShowPropertyProps } from '../base-property-props.js' | |
| import { useTranslation } from '../../../hooks/index.js' | |
| import mapValue from './map-value.js' | |
| import allowOverride from '../../../hoc/allow-override.js' | |
| const BooleanPropertyValue: React.FC<ShowPropertyProps> = (props) => { | |
| const { record, property, resource } = props | |
| const { tl } = useTranslation() | |
| const rawValue = record?.params[property.path] | |
| if (typeof rawValue === 'undefined' || rawValue === '') { | |
| return null | |
| } | |
| const base = mapValue(rawValue) | |
| const translation = tl(`${property.path}.${rawValue}`, resource.id, { | |
| defaultValue: base, | |
| }) | |
| return ( | |
| <Badge outline size="sm">{translation}</Badge> | |
| ) | |
| } | |
| export default allowOverride(BooleanPropertyValue, 'BooleanPropertyValue') | |
| ================================================ | |
| FILE: src/frontend/components/property-type/boolean/index.ts | |
| ================================================ | |
| import Edit from './edit.js' | |
| import Show from './show.js' | |
| import List from './list.js' | |
| import Filter from './filter.js' | |
| export { | |
| Edit as edit, | |
| Show as show, | |
| List as list, | |
| Filter as filter, | |
| } | |
| ================================================ | |
| FILE: src/frontend/components/property-type/boolean/list.tsx | |
| ================================================ | |
| import React from 'react' | |
| import BooleanPropertyValue from './boolean-property-value.js' | |
| import { ShowPropertyProps } from '../base-property-props.js' | |
| import allowOverride from '../../../hoc/allow-override.js' | |
| const List: React.FC<ShowPropertyProps> = (props) => ( | |
| <BooleanPropertyValue {...props} /> | |
| ) | |
| export default allowOverride(List, 'DefaultBooleanListProperty') | |
| ================================================ | |
| FILE: src/frontend/components/property-type/boolean/map-value.tsx | |
| ================================================ | |
| export default (value): 'Yes' | 'No' | '' => { | |
| if (typeof value === 'undefined') { | |
| return '' | |
| } | |
| return value ? 'Yes' : 'No' | |
| } | |
| ================================================ | |
| FILE: src/frontend/components/property-type/boolean/show.tsx | |
| ================================================ | |
| import React from 'react' | |
| import { ValueGroup } from '@adminjs/design-system' | |
| import BooleanPropertyValue from './boolean-property-value.js' | |
| import { ShowPropertyProps } from '../base-property-props.js' | |
| import allowOverride from '../../../hoc/allow-override.js' | |
| import { useTranslation } from '../../../hooks/index.js' | |
| const Show: React.FC<ShowPropertyProps> = (props) => { | |
| const { property } = props | |
| const { translateProperty } = useTranslation() | |
| return ( | |
| <ValueGroup label={translateProperty(property.label, property.resourceId)}> | |
| <BooleanPropertyValue {...props} /> | |
| </ValueGroup> | |
| ) | |
| } | |
| export default allowOverride(Show, 'DefaultBooleanShowProperty') | |
| ================================================ | |
| FILE: src/frontend/components/property-type/currency/filter.tsx | |
| ================================================ | |
| import { CurrencyInput, CurrencyInputProps, FormGroup } from '@adminjs/design-system' | |
| import React, { FC } from 'react' | |
| import { EditPropertyProps } from '../base-property-props.js' | |
| import { PropertyLabel } from '../utils/property-label/index.js' | |
| import allowOverride from '../../../hoc/allow-override.js' | |
| const Filter: FC<EditPropertyProps> = (props) => { | |
| const { onChange, property, filter } = props | |
| const handleChange = (value) => { | |
| onChange(property.path, value) | |
| } | |
| return ( | |
| <FormGroup variant="filter"> | |
| <PropertyLabel property={property} filter /> | |
| <CurrencyInput | |
| id={property.path} | |
| name={`filter-${property.path}`} | |
| onValueChange={handleChange} | |
| value={filter[property.path]} | |
| {...property.props as CurrencyInputProps} | |
| /> | |
| </FormGroup> | |
| ) | |
| } | |
| export default allowOverride(Filter, 'DefaultCurrencyFilterProperty') | |
| ================================================ | |
| FILE: src/frontend/components/property-type/currency/format-value.ts | |
| ================================================ | |
| import { formatCurrencyProperty } from '@adminjs/design-system' | |
| const optionsKeys: string[] = [ | |
| 'value', | |
| 'decimalSeparator', | |
| 'groupSeparator', | |
| 'disableGroupSeparators', | |
| 'intlConfig', | |
| 'decimalScale', | |
| 'prefix', | |
| 'suffix', | |
| ] | |
| const pickFormatOptions = (props: Record<string, string>) => { | |
| const pickedProps = Object.keys(props).reduce((acc, curr) => { | |
| if (optionsKeys.includes(curr as any)) { | |
| if (props[curr] !== null && props[curr] !== undefined) { | |
| acc[curr] = props[curr].toString() | |
| } | |
| } | |
| return acc | |
| }, {}) | |
| return pickedProps | |
| } | |
| const formatValue = (value: string, props: Record<string, string> = {}): string => { | |
| const formatOptions = pickFormatOptions({ value, ...props }) | |
| return formatCurrencyProperty(formatOptions as any) | |
| } | |
| export default formatValue | |
| ================================================ | |
| FILE: src/frontend/components/property-type/currency/index.ts | |
| ================================================ | |
| import Edit from './edit.js' | |
| import Filter from './filter.js' | |
| import List from './list.js' | |
| import Show from './show.js' | |
| export { | |
| Edit as edit, | |
| Filter as filter, | |
| List as list, | |
| Show as show, | |
| } | |
| ================================================ | |
| FILE: src/frontend/components/property-type/currency/list.tsx | |
| ================================================ | |
| import React from 'react' | |
| import formatValue from './format-value.js' | |
| import allowOverride from '../../../hoc/allow-override.js' | |
| import { ShowPropertyProps } from '../base-property-props.js' | |
| const List: React.FC<ShowPropertyProps> = (props) => { | |
| const { property, record } = props | |
| const value = formatValue(record.params[property.path], property.props) | |
| return <span>{value}</span> | |
| } | |
| export default allowOverride(List, 'DefaultCurrencyListProperty') | |
| ================================================ | |
| FILE: src/frontend/components/property-type/currency/show.tsx | |
| ================================================ | |
| import { ValueGroup } from '@adminjs/design-system' | |
| import React, { FC } from 'react' | |
| import { EditPropertyProps } from '../base-property-props.js' | |
| import formatValue from './format-value.js' | |
| import allowOverride from '../../../hoc/allow-override.js' | |
| import { useTranslation } from '../../../hooks/index.js' | |
| const Show: FC<EditPropertyProps> = (props) => { | |
| const { property, record } = props | |
| const value = `${record.params[property.path]}` | |
| const { translateProperty } = useTranslation() | |
| return ( | |
| <ValueGroup label={translateProperty(property.label, property.resourceId)}> | |
| {formatValue(value, property.props)} | |
| </ValueGroup> | |
| ) | |
| } | |
| export default allowOverride(Show, 'DefaultCurrencyShowProperty') | |
| ================================================ | |
| FILE: src/frontend/components/property-type/datetime/index.ts | |
| ================================================ | |
| import Edit from './edit.js' | |
| import Show from './show.js' | |
| import List from './list.js' | |
| import Filter from './filter.js' | |
| export { | |
| Edit as edit, | |
| Show as show, | |
| List as list, | |
| Filter as filter, | |
| } | |
| ================================================ | |
| FILE: src/frontend/components/property-type/datetime/list.tsx | |
| ================================================ | |
| import React from 'react' | |
| import mapValue from './map-value.js' | |
| import allowOverride from '../../../hoc/allow-override.js' | |
| import { ShowPropertyProps } from '../base-property-props.js' | |
| const List: React.FC<ShowPropertyProps> = (props) => { | |
| const { property, record } = props | |
| const value = mapValue(record.params[property.path], property.type) | |
| return ( | |
| <span>{value}</span> | |
| ) | |
| } | |
| export default allowOverride(List, 'DefaultDatetimeListProperty') | |
| ================================================ | |
| FILE: src/frontend/components/property-type/datetime/map-value.ts | |
| ================================================ | |
| import { formatDateProperty } from '@adminjs/design-system' | |
| import { PropertyType } from '../../../../backend/adapters/property/base-property.js' | |
| import { stripTimeFromISO } from './strip-time-from-iso.js' | |
| export default (value: Date, propertyType: PropertyType): string => { | |
| if (!value) { | |
| return '' | |
| } | |
| const date = propertyType === 'date' ? new Date(`${stripTimeFromISO(value)}T00:00:00`) : new Date(value) | |
| if (date) { | |
| return formatDateProperty(date, propertyType) | |
| } | |
| return '' | |
| } | |
| ================================================ | |
| FILE: src/frontend/components/property-type/datetime/show.tsx | |
| ================================================ | |
| import React from 'react' | |
| import { ValueGroup } from '@adminjs/design-system' | |
| import allowOverride from '../../../hoc/allow-override.js' | |
| import mapValue from './map-value.js' | |
| import { ShowPropertyProps } from '../base-property-props.js' | |
| import { useTranslation } from '../../../hooks/index.js' | |
| const Show: React.FC<ShowPropertyProps> = (props) => { | |
| const { property, record } = props | |
| const { translateProperty } = useTranslation() | |
| const value = mapValue(record.params[property.path], property.type) | |
| return ( | |
| <ValueGroup label={translateProperty(property.label, property.resourceId)}> | |
| {value} | |
| </ValueGroup> | |
| ) | |
| } | |
| export default allowOverride(Show, 'DefaultDatetimeShowProperty') | |
| ================================================ | |
| FILE: src/frontend/components/property-type/datetime/strip-time-from-iso.ts | |
| ================================================ | |
| export const stripTimeFromISO = (date: string | Date | null): string | null => { | |
| if (date === null) return null | |
| if (typeof date === 'string') { | |
| return date.replace(/T\d{2}:\d{2}:\d{2}\.\d{3}Z$/, '') | |
| } | |
| return date.toISOString().replace(/T\d{2}:\d{2}:\d{2}\.\d{3}Z$/, '') | |
| } | |
| ================================================ | |
| FILE: src/frontend/components/property-type/default-type/index.ts | |
| ================================================ | |
| import Show from './show.js' | |
| import Edit from './edit.js' | |
| import Filter from './filter.js' | |
| import List from './list.js' | |
| export { | |
| Show as show, | |
| Edit as edit, | |
| Filter as filter, | |
| List as list, | |
| } | |
| ================================================ | |
| FILE: src/frontend/components/property-type/default-type/list.tsx | |
| ================================================ | |
| import React from 'react' | |
| import DefaultPropertyValue from './default-property-value.js' | |
| import allowOverride from '../../../hoc/allow-override.js' | |
| import { ShowPropertyProps } from '../base-property-props.js' | |
| const List: React.FC<ShowPropertyProps> = (props) => (<DefaultPropertyValue {...props} />) | |
| export default allowOverride(List, 'DefaultListProperty') | |
| ================================================ | |
| FILE: src/frontend/components/property-type/default-type/show.tsx | |
| ================================================ | |
| import React from 'react' | |
| import { ValueGroup } from '@adminjs/design-system' | |
| import allowOverride from '../../../hoc/allow-override.js' | |
| import { ShowPropertyProps } from '../base-property-props.js' | |
| import DefaultPropertyValue from './default-property-value.js' | |
| import { useTranslation } from '../../../hooks/index.js' | |
| const Show: React.FC<ShowPropertyProps> = (props) => { | |
| const { property } = props | |
| const { translateProperty } = useTranslation() | |
| return ( | |
| <ValueGroup label={translateProperty(property.label, property.resourceId)}> | |
| <DefaultPropertyValue {...props} /> | |
| </ValueGroup> | |
| ) | |
| } | |
| export default allowOverride(Show, 'DefaultShowProperty') | |
| ================================================ | |
| FILE: src/frontend/components/property-type/docs/on-property-change.doc.md | |
| ================================================ | |
| On change callback - It can take: | |
| * one argument which is an entire {@link RecordJSON} | |
| * 2 arguments - one __property.path__ and the second one: __value__. | |
| * Used by the __edit__ and __filter__ components. | |
| Let's take a look at an example of the edit component | |
| It has one button: "Set Name". When this button is clicked - it triggers `onChange` callback | |
| function. In this case, we are passing an updated record, so that we can change the value of another | |
| property: `name`. | |
| ```javascript | |
| import React from 'react' | |
| import { Button, Box } from '@adminjs/design-system' | |
| const ValueTrigger = (props) => { | |
| const { onChange, record } = props | |
| const handleClick = (): void => { | |
| onChange({ | |
| ...record, | |
| params: { | |
| ...record.params, | |
| name: 'my new name', | |
| }, | |
| }) | |
| } | |
| return ( | |
| <Box mb="xxl"> | |
| <Button type="button" onClick={handleClick}>Set Name</Button> | |
| </Box> | |
| ) | |
| } | |
| export default ValueTrigger | |
| ``` | |
| ================================================ | |
| FILE: src/frontend/components/property-type/key-value/index.ts | |
| ================================================ | |
| import Edit from './edit.js' | |
| import Show from './show.js' | |
| export { | |
| Edit as edit, | |
| Show as show, | |
| } | |
| ================================================ | |
| FILE: src/frontend/components/property-type/mixed/convert-to-sub-property.ts | |
| ================================================ | |
| import { DELIMITER } from '../../../../utils/flat/constants.js' | |
| import { PropertyJSON, BasePropertyJSON } from '../../../interfaces/index.js' | |
| export function convertToSubProperty( | |
| property: PropertyJSON, | |
| subProperty: BasePropertyJSON, | |
| ): PropertyJSON { | |
| const [subPropertyPath] = subProperty.name.split(DELIMITER).slice(-1) | |
| return { | |
| ...subProperty, | |
| path: [property.path, subPropertyPath].join(DELIMITER), | |
| } | |
| } | |
| ================================================ | |
| FILE: src/frontend/components/property-type/mixed/index.ts | |
| ================================================ | |
| import Edit from './edit.js' | |
| import Show from './show.js' | |
| import List from './list.js' | |
| export { | |
| Show as show, | |
| Edit as edit, | |
| List as list, | |
| } | |
| ================================================ | |
| FILE: src/frontend/components/property-type/password/index.ts | |
| ================================================ | |
| /* eslint-disable import/prefer-default-export */ | |
| import Edit from './edit.js' | |
| export { | |
| Edit as edit, | |
| } | |
| ================================================ | |
| FILE: src/frontend/components/property-type/phone/filter.tsx | |
| ================================================ | |
| import { PhoneInput, PhoneInputProps, FormGroup } from '@adminjs/design-system' | |
| import React, { FC, useCallback } from 'react' | |
| import { FilterPropertyProps } from '../base-property-props.js' | |
| import { PropertyLabel } from '../utils/property-label/index.js' | |
| import allowOverride from '../../../hoc/allow-override.js' | |
| const Filter: FC<FilterPropertyProps> = (props) => { | |
| const { onChange, property, filter } = props | |
| const handleChange = useCallback((value) => { | |
| onChange(property.path, value) | |
| }, []) | |
| return ( | |
| <FormGroup variant="filter"> | |
| <PropertyLabel property={property} filter /> | |
| <PhoneInput | |
| id={property.path} | |
| inputProps={{ name: `filter-${property.path}` }} | |
| onChange={handleChange} | |
| value={filter[property.path]} | |
| {...property.props as PhoneInputProps} | |
| /> | |
| </FormGroup> | |
| ) | |
| } | |
| export default allowOverride(Filter, 'DefaultPhoneFilterProperty') | |
| ================================================ | |
| FILE: src/frontend/components/property-type/phone/index.ts | |
| ================================================ | |
| import Edit from './edit.js' | |
| import Filter from './filter.js' | |
| import List from './list.js' | |
| import Show from './show.js' | |
| export { | |
| Edit as edit, | |
| Filter as filter, | |
| List as list, | |
| Show as show, | |
| } | |
| ================================================ | |
| FILE: src/frontend/components/property-type/phone/list.tsx | |
| ================================================ | |
| import React, { FC } from 'react' | |
| import DefaultPropertyValue from '../default-type/default-property-value.js' | |
| import allowOverride from '../../../hoc/allow-override.js' | |
| import { ShowPropertyProps } from '../base-property-props.js' | |
| const List: FC<ShowPropertyProps> = (props) => <DefaultPropertyValue {...props} /> | |
| export default allowOverride(List, 'DefaultPhoneListProperty') | |
| ================================================ | |
| FILE: src/frontend/components/property-type/phone/show.tsx | |
| ================================================ | |
| import React, { FC } from 'react' | |
| import { ValueGroup } from '@adminjs/design-system' | |
| import { ShowPropertyProps } from '../base-property-props.js' | |
| import DefaultPropertyValue from '../default-type/default-property-value.js' | |
| import allowOverride from '../../../hoc/allow-override.js' | |
| import { useTranslation } from '../../../hooks/index.js' | |
| const Show: FC<ShowPropertyProps> = (props) => { | |
| const { property } = props | |
| const { translateProperty } = useTranslation() | |
| return ( | |
| <ValueGroup label={translateProperty(property.label, property.resourceId)}> | |
| <DefaultPropertyValue {...props} /> | |
| </ValueGroup> | |
| ) | |
| } | |
| export default allowOverride(Show, 'DefaultPhoneShowProperty') | |
| ================================================ | |
| FILE: src/frontend/components/property-type/reference/index.ts | |
| ================================================ | |
| import Edit from './edit.js' | |
| import Show from './show.js' | |
| import List from './list.js' | |
| import Filter from './filter.js' | |
| export { | |
| Edit as edit, | |
| Show as show, | |
| List as list, | |
| Filter as filter, | |
| } | |
| ================================================ | |
| FILE: src/frontend/components/property-type/reference/list.tsx | |
| ================================================ | |
| import React from 'react' | |
| import ReferenceValue from './reference-value.js' | |
| import { ShowPropertyProps } from '../base-property-props.js' | |
| import allowOverride from '../../../hoc/allow-override.js' | |
| const List: React.FC<ShowPropertyProps> = (props) => ( | |
| <ReferenceValue {...props} /> | |
| ) | |
| export default allowOverride(List, 'DefaultReferenceListProperty') | |
| ================================================ | |
| FILE: src/frontend/components/property-type/reference/show.tsx | |
| ================================================ | |
| import React from 'react' | |
| import { ValueGroup } from '@adminjs/design-system' | |
| import ReferenceValue from './reference-value.js' | |
| import { ShowPropertyProps } from '../base-property-props.js' | |
| import allowOverride from '../../../hoc/allow-override.js' | |
| import { useTranslation } from '../../../hooks/index.js' | |
| const Show: React.FC<ShowPropertyProps> = (props) => { | |
| const { property, record } = props | |
| const { translateProperty } = useTranslation() | |
| return ( | |
| <ValueGroup label={translateProperty(property.label, property.resourceId)}> | |
| <ReferenceValue | |
| property={property} | |
| record={record} | |
| /> | |
| </ValueGroup> | |
| ) | |
| } | |
| export default allowOverride(Show, 'DefaultReferenceShowProperty') | |
| ================================================ | |
| FILE: src/frontend/components/property-type/richtext/index.ts | |
| ================================================ | |
| import Edit from './edit.js' | |
| import Show from './show.js' | |
| import List from './list.js' | |
| export { | |
| Edit as edit, | |
| Show as show, | |
| List as list, | |
| } | |
| ================================================ | |
| FILE: src/frontend/components/property-type/richtext/list.tsx | |
| ================================================ | |
| import truncate from 'lodash/truncate.js' | |
| import React, { FC } from 'react' | |
| import { ShowPropertyProps } from '../base-property-props.js' | |
| import allowOverride from '../../../hoc/allow-override.js' | |
| const stripHtml = (html: string): string => { | |
| const el = window.document.createElement('DIV') | |
| el.innerHTML = html | |
| return el.textContent || el.innerText || '' | |
| } | |
| const List: FC<ShowPropertyProps> = (props) => { | |
| const { property, record } = props | |
| const maxLength = property.custom?.maxLength || 15 | |
| const value: string = record.params[property.path] || '' | |
| const textValue = stripHtml(value) | |
| return <>{truncate(textValue, { length: maxLength, separator: ' ' })}</> | |
| } | |
| export default allowOverride(List, 'DefaultReferenceListProperty') | |
| ================================================ | |
| FILE: src/frontend/components/property-type/richtext/show.tsx | |
| ================================================ | |
| import { Box, Text, ValueGroup } from '@adminjs/design-system' | |
| import React, { FC } from 'react' | |
| import xss from 'xss' | |
| import { EditPropertyProps } from '../base-property-props.js' | |
| import allowOverride from '../../../hoc/allow-override.js' | |
| import { useTranslation } from '../../../hooks/index.js' | |
| type InnerHtmlProp = { | |
| __html: string; | |
| } | |
| const Show: FC<EditPropertyProps> = (props) => { | |
| const { property, record } = props | |
| const { translateProperty } = useTranslation() | |
| const value: string = record.params[property.path] || '' | |
| const createMarkup = (html: string): InnerHtmlProp => ({ __html: xss(html) }) | |
| return ( | |
| <ValueGroup label={translateProperty(property.label, property.resourceId)}> | |
| <Box py="xl" px={['0', 'xl']} border="default"> | |
| <Text dangerouslySetInnerHTML={createMarkup(value)} /> | |
| </Box> | |
| </ValueGroup> | |
| ) | |
| } | |
| export default allowOverride(Show, 'DefaultRichtextShowProperty') | |
| ================================================ | |
| FILE: src/frontend/components/property-type/textarea/index.ts | |
| ================================================ | |
| import Show from './show.js' | |
| import Edit from './edit.js' | |
| export { | |
| Show as show, | |
| Edit as edit, | |
| } | |
| ================================================ | |
| FILE: src/frontend/components/property-type/textarea/show.tsx | |
| ================================================ | |
| import React from 'react' | |
| import { ValueGroup } from '@adminjs/design-system' | |
| import allowOverride from '../../../hoc/allow-override.js' | |
| import { ShowPropertyProps } from '../base-property-props.js' | |
| import { useTranslation } from '../../../hooks/index.js' | |
| const Show: React.FC<ShowPropertyProps> = (props) => { | |
| const { property, record } = props | |
| const { translateProperty } = useTranslation() | |
| const value = record.params[property.path] || '' | |
| return ( | |
| <ValueGroup label={translateProperty(property.label, property.resourceId)}> | |
| {value.split(/(?:\r\n|\r|\n)/g).map((line, i) => ( | |
| // eslint-disable-next-line react/no-array-index-key | |
| <React.Fragment key={i}> | |
| {line} | |
| <br /> | |
| </React.Fragment> | |
| ))} | |
| </ValueGroup> | |
| ) | |
| } | |
| export default allowOverride(Show, 'DefaultTextareaShowProperty') | |
| ================================================ | |
| FILE: src/frontend/components/property-type/utils/index.ts | |
| ================================================ | |
| export * from './property-label/index.js' | |
| export * from './property-description/index.js' | |
| ================================================ | |
| FILE: src/frontend/components/property-type/utils/property-description/index.ts | |
| ================================================ | |
| export * from './property-description.js' | |
| ================================================ | |
| FILE: src/frontend/components/property-type/utils/property-label/index.ts | |
| ================================================ | |
| export * from './property-label.js' | |
| ================================================ | |
| FILE: src/frontend/components/routes/index.ts | |
| ================================================ | |
| export { default as DashboardRoute } from './dashboard.js' | |
| export { default as RecordActionRoute } from './record-action.js' | |
| export { default as ResourceActionRoute } from './resource-action.js' | |
| export { default as BulkActionRoute } from './bulk-action.js' | |
| export { default as PageRoute } from './page.js' | |
| export { default as ResourceRoute } from './resource.js' | |
| ================================================ | |
| FILE: src/frontend/components/routes/utils/should-action-re-fetch-data.ts | |
| ================================================ | |
| import { RecordActionParams, BulkActionParams, ResourceActionParams } from '../../../../backend/utils/view-helpers/view-helpers.js' | |
| type AnyActionParams = RecordActionParams & ResourceActionParams & BulkActionParams | |
| /** | |
| * Indicates if route action should be updated, meaning whether it should fetch | |
| * new data from the backend. | |
| * @private | |
| * | |
| * @param {AnyActionParams} currentMatchParams | |
| * @param {AnyActionParams} newMatchParams | |
| * @return {boolean} | |
| */ | |
| const shouldActionReFetchData = ( | |
| currentMatchParams: Partial<AnyActionParams>, | |
| newMatchParams: Partial<AnyActionParams>, | |
| ): boolean => { | |
| const { | |
| resourceId, | |
| recordId, | |
| actionName, | |
| } = currentMatchParams | |
| const { | |
| resourceId: newResourceId, | |
| recordId: newRecordId, | |
| actionName: newActionName, | |
| } = newMatchParams | |
| return resourceId !== newResourceId | |
| || recordId !== newRecordId | |
| || actionName !== newActionName | |
| } | |
| export default shouldActionReFetchData | |
| ================================================ | |
| FILE: src/frontend/components/spec/action-json.factory.ts | |
| ================================================ | |
| import { factory } from 'factory-girl' | |
| import { ActionJSON } from '../../interfaces/index.js' | |
| factory.define<ActionJSON>('ActionJSON', Object, { | |
| actionType: 'record', | |
| showInDrawer: true, | |
| name: factory.sequence('ActionJSON.name', (n) => `action${n}`), | |
| label: factory.sequence('ActionJSON.label', (n) => `action ${n}`), | |
| showFilter: false, | |
| showResourceActions: true, | |
| resourceId: 'resource', | |
| hideActionHeader: false, | |
| containerWidth: 1, | |
| layout: null, | |
| variant: 'default', | |
| parent: null, | |
| hasHandler: true, | |
| custom: {}, | |
| }) | |
| ================================================ | |
| FILE: src/frontend/components/spec/factory.ts | |
| ================================================ | |
| // eslint-disable-next-line import/no-extraneous-dependencies | |
| import { factory } from 'factory-girl' | |
| import './action-json.factory.js' | |
| import './page-json.factory.js' | |
| import './property-json.factory.js' | |
| import './record-json.factory.js' | |
| import './resource-json.factory.js' | |
| export default factory | |
| ================================================ | |
| FILE: src/frontend/components/spec/initialize-translations.ts | |
| ================================================ | |
| import i18n from 'i18next' | |
| import { initReactI18next } from 'react-i18next' | |
| i18n.use(initReactI18next).init({ | |
| lng: 'en', | |
| }) | |
| ================================================ | |
| FILE: src/frontend/components/spec/page-json.factory.ts | |
| ================================================ | |
| import { factory } from 'factory-girl' | |
| import { PageJSON } from '../../interfaces/index.js' | |
| factory.define<PageJSON>('PageJSON', Object, { | |
| name: factory.sequence('PageJSON.name', (n) => `page${n}`), | |
| component: factory.sequence('PageJSON.component', (n) => `Component${n}`), | |
| }) | |
| ================================================ | |
| FILE: src/frontend/components/spec/property-json.factory.ts | |
| ================================================ | |
| import { factory } from 'factory-girl' | |
| import { PropertyJSON } from '../../interfaces/index.js' | |
| factory.define<PropertyJSON>('PropertyJSON', Object, { | |
| custom: {}, | |
| isTitle: false, | |
| isId: false, | |
| isSortable: true, | |
| availableValues: null, | |
| label: factory.sequence('JSONProperty.label', (n) => `someProperty${n}`), | |
| name: factory.sequence('JSONProperty.name', (n) => `someProperty${n}`), | |
| position: factory.sequence('JSONProperty.position', (n) => n), | |
| type: 'string', | |
| reference: null, | |
| isDisabled: false, | |
| isArray: false, | |
| isDraggable: false, | |
| subProperties: [], | |
| isRequired: true, | |
| components: undefined, | |
| path: factory.sequence('JSONProperty.path', (n) => `someProperty${n}`), | |
| propertyPath: factory.sequence('JSONProperty.propertyPath', (n) => `someProperty${n}`), | |
| resourceId: 'someResourceId', | |
| isVirtual: false, | |
| props: {}, | |
| hideLabel: false, | |
| }) | |
| ================================================ | |
| FILE: src/frontend/components/spec/test-context-provider.tsx | |
| ================================================ | |
| import React, { ReactNode } from 'react' | |
| import { StaticRouter } from 'react-router-dom/server.js' | |
| import { combineStyles } from '@adminjs/design-system' | |
| // @ts-ignore Note: Ignore while @adminjs/design-system/styled-components doesn't export types | |
| import { ThemeProvider } from '@adminjs/design-system/styled-components' | |
| import { I18nextProvider } from 'react-i18next' | |
| import { defaultLocale } from '../../../locale/index.js' | |
| import initTranslations from '../../utils/adminjs.i18n.js' | |
| const theme = combineStyles({}) | |
| type Props = { | |
| children: ReactNode; | |
| location?: string; | |
| } | |
| const TestContextProvider: React.FC<Props> = (props) => { | |
| const { children, location } = props | |
| const { i18n } = initTranslations(defaultLocale) | |
| return ( | |
| <ThemeProvider theme={theme}> | |
| <I18nextProvider i18n={i18n}> | |
| <StaticRouter location={location || '/'}> | |
| {children} | |
| </StaticRouter> | |
| </I18nextProvider> | |
| </ThemeProvider> | |
| ) | |
| } | |
| export default TestContextProvider | |
| ================================================ | |
| FILE: src/frontend/hoc/index.ts | |
| ================================================ | |
| export * from './allow-override.js' | |
| export * from './with-no-ssr.js' | |
| export * from './with-notice.js' | |
| ================================================ | |
| FILE: src/frontend/hoc/with-no-ssr.tsx | |
| ================================================ | |
| import React, { ComponentType, useEffect, useState } from 'react' | |
| /** | |
| * A higher-order component that prevents a component from rendering server-side | |
| * | |
| * @template P - The props object of the wrapped component | |
| * @param {React.ComponentType<P>} Component - The component to be wrapped | |
| * @returns {React.FC<P>} A new component that renders the given component client-side only | |
| */ | |
| // eslint-disable-next-line max-len | |
| const withNoSSR = <P extends Record<string, unknown>>(Component: ComponentType<P>) => (props: P) => { | |
| const [isClient, setIsClient] = useState(false) | |
| /** | |
| * Sets isClient to true when the component is mounted on the client side | |
| */ | |
| useEffect(() => { | |
| setIsClient(true) | |
| }, []) | |
| // Renders nothing if the component is not mounted on the client side | |
| if (!isClient) return null | |
| // Renders the wrapped component with the given props if it's mounted on the client side | |
| return <Component {...props} /> | |
| } | |
| export { | |
| withNoSSR as default, | |
| withNoSSR, | |
| } | |
| ================================================ | |
| FILE: src/frontend/hooks/index.ts | |
| ================================================ | |
| export * from './use-action/index.js' | |
| export * from './use-current-admin.js' | |
| export * from './use-filter-drawer.js' | |
| export * from './use-history-listen.js' | |
| export * from './use-local-storage/index.js' | |
| export * from './use-modal.js' | |
| export * from './use-navigation-resources.js' | |
| export * from './use-notice.js' | |
| export * from './use-query-params.js' | |
| export * from './use-record/index.js' | |
| export * from './use-records/index.js' | |
| export * from './use-resource/index.js' | |
| export * from './use-selected-records/index.js' | |
| export * from './use-translation.js' | |
| ================================================ | |
| FILE: src/frontend/hooks/use-filter-drawer.tsx | |
| ================================================ | |
| import { useDispatch, useSelector } from 'react-redux' | |
| import { useEffect, useState } from 'react' | |
| import { hideFilterDrawer, showFilterDrawer } from '../store/actions/filter-drawer.js' | |
| import { ReduxState } from '../store/index.js' | |
| import { useQueryParams } from './use-query-params.js' | |
| export const useFilterDrawer = () => { | |
| const [filtersCount, setFiltersCount] = useState(0) | |
| const dispatch = useDispatch() | |
| const isVisible = useSelector((state: ReduxState) => state.filterDrawer.isVisible) | |
| const { filters = {} } = useQueryParams() | |
| useEffect(() => { | |
| setFiltersCount(Object.keys(filters).length) | |
| }, [filters]) | |
| const toggleFilter = () => { | |
| dispatch(isVisible ? hideFilterDrawer() : showFilterDrawer()) | |
| } | |
| const open = () => { | |
| dispatch(showFilterDrawer()) | |
| } | |
| const close = () => { | |
| dispatch(hideFilterDrawer()) | |
| } | |
| return { | |
| filtersCount, | |
| isVisible, | |
| toggleFilter, | |
| open, | |
| close, | |
| } | |
| } | |
| ================================================ | |
| FILE: src/frontend/hooks/use-notice.ts | |
| ================================================ | |
| import { useDispatch } from 'react-redux' | |
| import { type NoticeMessage } from '../interfaces/noticeMessage.interface.js' | |
| import { addNotice, type AddNoticeResponse } from '../store/actions/add-notice.js' | |
| /** | |
| * @memberof useNotice | |
| * @alias AddNotice | |
| */ | |
| export type AddNotice = (notice: NoticeMessage) => AddNoticeResponse | |
| /** | |
| * @classdesc | |
| * Hook which allows you to add notice message to the app. | |
| * | |
| * ```javascript | |
| * import { useNotice, Button } from 'adminjs' | |
| * | |
| * const myComponent = () => { | |
| * const sendNotice = useNotice() | |
| * return ( | |
| * <Button onClick={() => sendNotice({ message: 'I am awesome' })}>I am awesome</Button> | |
| * ) | |
| * } | |
| * ``` | |
| * | |
| * @class | |
| * @subcategory Hooks | |
| * @bundle | |
| * @hideconstructor | |
| */ | |
| export const useNotice = (): AddNotice => { | |
| const dispatch = useDispatch() | |
| return (notice) => dispatch(addNotice(notice)) | |
| } | |
| export default useNotice | |
| ================================================ | |
| FILE: src/frontend/hooks/use-action/index.ts | |
| ================================================ | |
| export * from './use-action.js' | |
| export * from './use-action-response-handler.js' | |
| export * from './use-action.types.js' | |
| ================================================ | |
| FILE: src/frontend/hooks/use-action/use-action-response-handler.ts | |
| ================================================ | |
| /* eslint-disable @typescript-eslint/explicit-function-return-type */ | |
| import { useNavigate, useLocation } from 'react-router' | |
| import { ActionResponse } from '../../../backend/actions/action.interface.js' | |
| import { appendForceRefresh } from '../../components/actions/utils/append-force-refresh.js' | |
| import { ActionCallCallback } from './index.js' | |
| import { useNotice } from '../use-notice.js' | |
| export const useActionResponseHandler = (onActionCall?: ActionCallCallback) => { | |
| const location = useLocation() | |
| const navigate = useNavigate() | |
| const addNotice = useNotice() | |
| return (response: ActionResponse) => { | |
| const { data } = response | |
| if (data.notice) { | |
| addNotice(data.notice) | |
| } | |
| if (data.redirectUrl && location.pathname !== data.redirectUrl) { | |
| const appended = appendForceRefresh(data.redirectUrl) | |
| navigate(appended) | |
| } | |
| if (onActionCall) { | |
| onActionCall(data) | |
| } | |
| } | |
| } | |
| ================================================ | |
| FILE: src/frontend/hooks/use-action/use-action.doc.md | |
| ================================================ | |
| The hook which allows you to use {@link ActionJSON} to perform actual actions on the backend. | |
| Base on the action type and parameters (like {@link ActionJSON.guard}) it behaves differently. | |
| ### Usage | |
| ```javascript | |
| import { useAction } from 'adminjs' | |
| import { Button } from '@adminjs/design-system' | |
| const myComponent = ({ action }) => { | |
| const { href, handleClick } = useAction(action, { | |
| resourceId, recordId, recordIds, | |
| }, actionPerformed) | |
| return ( | |
| <Button as="a" onClick={handleClick} href={href}>Click this action</Button> | |
| ) | |
| } | |
| ``` | |
| ================================================ | |
| FILE: src/frontend/hooks/use-local-storage/index.ts | |
| ================================================ | |
| export * from './use-local-storage.js' | |
| export * from './use-local-storage-result.type.js' | |
| ================================================ | |
| FILE: src/frontend/hooks/use-local-storage/use-local-storage-result.type.ts | |
| ================================================ | |
| import React from 'react' | |
| export type UseLocalStorageResult<T> = [ | |
| T, | |
| React.Dispatch<React.SetStateAction<T>> | |
| ]; | |
| /** | |
| * Result of the {@link useLocalStorage}. | |
| * It is a tuple containing value and the setter | |
| * | |
| * @typedef {Array} UseLocalStorageResult | |
| * @memberof useLocalStorage | |
| * @alias UseLocalStorageResult | |
| * @property {T} [0] the value stored in the local store | |
| * @property {React.Dispatch<React.SetStateAction<T>>} [1] value setter compatible with react | |
| * useState | |
| */ | |
| ================================================ | |
| FILE: src/frontend/hooks/use-local-storage/use-local-storage.doc.md | |
| ================================================ | |
| The hook which allows you to store particular data into local storage. | |
| It works very similar to `useState` with the exception that it requires the key under which data | |
| will be stored. | |
| ### Usage | |
| ```javascript | |
| import { useLocalStorage } from 'adminjs' | |
| const MyRecordActionComponent = (props) => { | |
| const [isOpen, setIsOpen] = useLocalStorage('isSidebarOpen', false) | |
| // .... | |
| return ( | |
| <Box> | |
| { isOpen ? ( | |
| <Drawer> | |
| Drawer content | |
| </Drawer> | |
| ) : ''} | |
| </Box> | |
| ) | |
| } | |
| export default MyRecordActionComponent | |
| ``` | |
| Returns {@link UseRecordResult}. | |
| ================================================ | |
| FILE: src/frontend/hooks/use-record/filter-record.ts | |
| ================================================ | |
| import { flat } from '../../../utils/flat/index.js' | |
| import { RecordJSON } from '../../interfaces/index.js' | |
| import { UseRecordOptions } from './use-record.type.js' | |
| export const filterRecordParams = function<T extends RecordJSON> ( | |
| record: T, | |
| options: UseRecordOptions = {}, | |
| ): T { | |
| if (options.includeParams && record) { | |
| return { | |
| ...record, | |
| params: flat.selectParams(record.params || {}, options.includeParams), | |
| } | |
| } | |
| return record | |
| } | |
| export const isPropertyPermitted = (propertyName, options: UseRecordOptions = {}): boolean => { | |
| const { includeParams } = options | |
| if (includeParams) { | |
| const parts = flat.pathToParts(propertyName, { skipArrayIndexes: true }) | |
| return parts.some((part) => includeParams.includes(part)) | |
| } | |
| return true | |
| } | |
| ================================================ | |
| FILE: src/frontend/hooks/use-record/index.ts | |
| ================================================ | |
| export * from './use-record.js' | |
| export * from './use-record.type.js' | |
| export * from './is-entire-record-given.js' | |
| export * from './merge-record-response.js' | |
| export * from './params-to-form-data.js' | |
| export * from './update-record.js' | |
| ================================================ | |
| FILE: src/frontend/hooks/use-record/is-entire-record-given.ts | |
| ================================================ | |
| import { RecordJSON } from '../../interfaces/index.js' | |
| const isEntireRecordGiven = ( | |
| propertyOrRecord: RecordJSON | string, | |
| value?: string, | |
| ): boolean => !!(typeof value === 'undefined' | |
| // user can pass property and omit value. This makes sense when | |
| // third argument of the function (selectedRecord) is passed to onChange | |
| // callback | |
| && !(typeof propertyOrRecord === 'string') | |
| // we assume that only params has to be given | |
| && propertyOrRecord.params) | |
| export { | |
| isEntireRecordGiven as default, | |
| isEntireRecordGiven, | |
| } | |
| ================================================ | |
| FILE: src/frontend/hooks/use-record/merge-record-response.ts | |
| ================================================ | |
| import { RecordJSON } from '../../interfaces/index.js' | |
| import { RecordActionResponse } from '../../../backend/actions/action.interface.js' | |
| /** | |
| * Handlers of all [Actions]{@link Action} of type `record` returns record. | |
| * Depending on a place and response we have to merge what was returned | |
| * to the actual state. It is done in following places: | |
| * - {@link useRecord} hook | |
| * - {@link RecordInList} component | |
| * - {@link RecordAction} component | |
| * | |
| * @private | |
| */ | |
| const mergeRecordResponse = (record: RecordJSON, response: RecordActionResponse): RecordJSON => ({ | |
| // we start from the response because it can have different recordActions or bulkActions | |
| ...(response.record || record), | |
| // records has to be reset every time because so that user wont | |
| // see old errors which are not relevant anymore | |
| errors: response.record.errors, | |
| populated: { ...record.populated, ...response.record.populated }, | |
| params: { ...record.params, ...response.record.params }, | |
| }) | |
| export default mergeRecordResponse | |
| ================================================ | |
| FILE: src/frontend/hooks/use-records/index.ts | |
| ================================================ | |
| export * from './use-records.js' | |
| export * from './use-records-result.type.js' | |
| ================================================ | |
| FILE: src/frontend/hooks/use-records/use-records-result.type.ts | |
| ================================================ | |
| import { AxiosResponse } from 'axios' | |
| import { ListActionResponse } from '../../../backend/index.js' | |
| import { RecordJSON } from '../../interfaces/index.js' | |
| /** | |
| * Result of the {@link useRecords} hook. | |
| * It is a object containing multiple tools you can use in your component | |
| * @memberof useRecords | |
| * @alias UseRecordsResult | |
| */ | |
| export type UseRecordsResult = { | |
| /** | |
| * Array of records fetched from the backend | |
| */ | |
| records: Array<RecordJSON>; | |
| /** loading state */ | |
| loading: boolean; | |
| /** current page (in pagination) */ | |
| page: number; | |
| /** perPage limit returned by the backend */ | |
| perPage: number; | |
| /** total number of pages in for current query */ | |
| total: number; | |
| /** sort direction */ | |
| direction: 'asc' | 'desc'; | |
| /** field used as a sortBy column */ | |
| sortBy?: string; | |
| /** function which triggers fetching the data */ | |
| fetchData: () => Promise<AxiosResponse<ListActionResponse>>; | |
| } | |
| ================================================ | |
| FILE: src/frontend/hooks/use-resource/index.ts | |
| ================================================ | |
| export * from './use-resource.js' | |
| ================================================ | |
| FILE: src/frontend/hooks/use-resource/use-resource.doc.md | |
| ================================================ | |
| The hook which allows you to get {@link ResourceJSON} object for a particular resource ID from the store. | |
| ### Usage | |
| ```javascript | |
| import { useResource } from 'adminjs' | |
| const MyRecordActionComponent = (props) => { | |
| const UsersResource = useResource('Users') | |
| const { properties } = UsersResource | |
| // .... | |
| } | |
| export default MyRecordActionComponent | |
| ``` | |
| ================================================ | |
| FILE: src/frontend/hooks/use-resource/use-resource.ts | |
| ================================================ | |
| import { useSelector } from 'react-redux' | |
| import { ResourceJSON } from '../../interfaces/resource-json.interface.js' | |
| import { ReduxState } from '../../store/store.js' | |
| /** | |
| * @load ./use-resource.doc.md | |
| * @subcategory Hooks | |
| * @class | |
| * @hideconstructor | |
| * @bundle | |
| * @param {string} resourceId Id of a resource you want to get | |
| */ | |
| const useResource = (resourceId: string): ResourceJSON | undefined => { | |
| const resources = useSelector((state: ReduxState) => state.resources) | |
| const foundResource = resources.find((resource) => resource.id === resourceId) | |
| return foundResource | |
| } | |
| export { | |
| useResource as default, | |
| useResource, | |
| } | |
| ================================================ | |
| FILE: src/frontend/hooks/use-selected-records/index.ts | |
| ================================================ | |
| export * from './use-selected-records.js' | |
| export * from './use-selected-records-result.type.js' | |
| ================================================ | |
| FILE: src/frontend/hooks/use-selected-records/use-selected-records-result.type.ts | |
| ================================================ | |
| import { RecordJSON } from '../../interfaces/index.js' | |
| /** | |
| * Result of the {@link useSelectedRecords} hook. | |
| * It is a object containing multiple tools you can use in your component | |
| * @memberof useSelectedRecords | |
| * @alias UseSelectedRecordsResult | |
| */ | |
| export type UseSelectedRecordsResult = { | |
| /** Array of selected records */ | |
| selectedRecords: Array<RecordJSON>; | |
| /** Sets selected records */ | |
| setSelectedRecords: (records: Array<RecordJSON>) => void; | |
| /** handler function for single select action */ | |
| handleSelect: (record: RecordJSON) => void; | |
| /** handler function for `select all records` action */ | |
| handleSelectAll: () => void; | |
| } | |
| ================================================ | |
| FILE: src/frontend/interfaces/index.ts | |
| ================================================ | |
| export * from './action/index.js' | |
| export * from './modal.interface.js' | |
| export * from './noticeMessage.interface.js' | |
| export * from './page-json.interface.js' | |
| export * from './property-json/index.js' | |
| export * from './record-json.interface.js' | |
| export * from './resource-json.interface.js' | |
| ================================================ | |
| FILE: src/frontend/interfaces/modal.interface.ts | |
| ================================================ | |
| import type { ModalProps } from '@adminjs/design-system' | |
| import { SHOW_MODAL, HIDE_MODAL } from '../store/index.js' | |
| export interface ModalData { | |
| modalProps: ModalProps; | |
| type?: 'alert' | 'confirm'; | |
| resourceId?: string; | |
| confirmAction?: () => void; | |
| } | |
| export type ModalFunctions = { | |
| openModal: (data: ModalData) => void | |
| closeModal: () => void | |
| } | |
| export type ShowModalResponse = { | |
| type: typeof SHOW_MODAL | |
| data: ModalData; | |
| } | |
| export type HideModalResponse = { | |
| type: typeof HIDE_MODAL; | |
| } | |
| ================================================ | |
| FILE: src/frontend/interfaces/noticeMessage.interface.ts | |
| ================================================ | |
| import { type MessageBoxProps } from '@adminjs/design-system' | |
| import { type TOptions } from 'i18next' | |
| import { type ReactNode } from 'react' | |
| /** | |
| * NoticeMessage which can be presented as a "Toast" message. | |
| * @alias NoticeMessage | |
| */ | |
| export type NoticeMessage = { | |
| message: string | |
| // Extra 'error' to handle backwards. Error is replaced to danger in notification box | |
| type?: MessageBoxProps['variant'] | 'error' | |
| options?: TOptions | |
| resourceId?: string | |
| body?: ReactNode | |
| } | |
| ================================================ | |
| FILE: src/frontend/interfaces/page-json.interface.ts | |
| ================================================ | |
| import type { IconProps } from '@adminjs/design-system' | |
| /** | |
| * Representing the page in the sidebar | |
| * @subcategory Frontend | |
| */ | |
| export interface PageJSON { | |
| /** | |
| * Page name | |
| */ | |
| name: string | |
| /** | |
| * Page component. Bundled with {@link ComponentLoader} | |
| */ | |
| component: string | |
| /** | |
| * Page icon | |
| */ | |
| icon?: IconProps['icon'] | |
| } | |
| ================================================ | |
| FILE: src/frontend/interfaces/action/action-has-component.ts | |
| ================================================ | |
| import { ActionJSON } from './action-json.interface.js' | |
| export const actionHasDisabledComponent = (action: ActionJSON): boolean => ( | |
| typeof action.component !== 'undefined' && action.component === false | |
| ) | |
| export const actionHasCustomComponent = (action: ActionJSON): boolean => ( | |
| typeof action.component === 'string' | |
| ) | |
| ================================================ | |
| FILE: src/frontend/interfaces/action/build-action-test-id.ts | |
| ================================================ | |
| import { ActionJSON } from './action-json.interface.js' | |
| export const buildActionTestId = (action: ActionJSON): string => `action-${action.name}` | |
| ================================================ | |
| FILE: src/frontend/interfaces/action/index.ts | |
| ================================================ | |
| export * from './action-has-component.js' | |
| export * from './action-href.js' | |
| export * from './action-json.interface.js' | |
| export * from './build-action-api-call-trigger.js' | |
| export * from './build-action-test-id.js' | |
| export * from './build-action-click-handler.js' | |
| export * from './call-action-api.js' | |
| export * from './is-bulk-action.js' | |
| export * from './is-resource-action.js' | |
| export * from './is-record-action.js' | |
| ================================================ | |
| FILE: src/frontend/interfaces/action/is-bulk-action.ts | |
| ================================================ | |
| import { BulkActionParams } from '../../../backend/utils/view-helpers/view-helpers.js' | |
| import { ActionJSON } from '../action/index.js' | |
| import { DifferentActionParams } from '../../hooks/use-action/use-action.types.js' | |
| export const isBulkAction = ( | |
| params: DifferentActionParams, | |
| action: ActionJSON, | |
| ): params is BulkActionParams => 'recordIds' in params && action.actionType === 'bulk' | |
| ================================================ | |
| FILE: src/frontend/interfaces/action/is-record-action.ts | |
| ================================================ | |
| import { RecordActionParams } from '../../../backend/utils/view-helpers/view-helpers.js' | |
| import { ActionJSON } from '../action/index.js' | |
| import { DifferentActionParams } from '../../hooks/use-action/use-action.types.js' | |
| export const isRecordAction = ( | |
| params: DifferentActionParams, | |
| action: ActionJSON, | |
| ): params is RecordActionParams => ( | |
| 'recordId' in params && action.actionType === 'record' | |
| ) | |
| ================================================ | |
| FILE: src/frontend/interfaces/action/is-resource-action.ts | |
| ================================================ | |
| import { ResourceActionParams } from '../../../backend/utils/view-helpers/view-helpers.js' | |
| import { ActionJSON } from '../action/index.js' | |
| import { DifferentActionParams } from '../../hooks/use-action/use-action.types.js' | |
| export const isResourceAction = ( | |
| params: DifferentActionParams, | |
| action: ActionJSON, | |
| ): params is ResourceActionParams => 'recordIds' in params && action.actionType === 'resource' | |
| ================================================ | |
| FILE: src/frontend/interfaces/property-json/index.ts | |
| ================================================ | |
| export * from './property-json.interface.js' | |
| ================================================ | |
| FILE: src/frontend/store/index.ts | |
| ================================================ | |
| export * from './actions/index.js' | |
| export * from './initialize-store.js' | |
| export * from './reducers/index.js' | |
| export * from './store.js' | |
| export { default as createStore } from './store.js' | |
| ================================================ | |
| FILE: src/frontend/store/actions/add-notice.ts | |
| ================================================ | |
| import { type NoticeMessage } from '../../interfaces/noticeMessage.interface.js' | |
| import { type NoticeMessageInState } from '../reducers/noticesReducer.js' | |
| export const ADD_NOTICE = 'ADD_NOTICE' | |
| export type AddNoticeResponse = { | |
| type: typeof ADD_NOTICE | |
| data: NoticeMessageInState | |
| } | |
| export const addNotice = (data: NoticeMessage): AddNoticeResponse => ({ | |
| type: ADD_NOTICE, | |
| data: { | |
| id: `notice-${Date.now() + Math.random()}`, | |
| progress: 0, | |
| ...data, | |
| }, | |
| }) | |
| ================================================ | |
| FILE: src/frontend/store/actions/drop-notice.ts | |
| ================================================ | |
| export const DROP_NOTICE = 'DROP_NOTICE' | |
| export type DropNoticeResponse = { | |
| type: typeof DROP_NOTICE | |
| data: { | |
| noticeId: string | |
| } | |
| } | |
| export const dropNotice = (noticeId: string): DropNoticeResponse => ({ | |
| type: 'DROP_NOTICE', | |
| data: { noticeId }, | |
| }) | |
| ================================================ | |
| FILE: src/frontend/store/actions/filter-drawer.ts | |
| ================================================ | |
| export const OPEN_FILTER_DRAWER = 'OPEN_FILTER_DRAWER' | |
| export const CLOSE_FILTER_DRAWER = 'CLOSE_FILTER_DRAWER' | |
| export type FilterDrawerAction = | |
| | { type: typeof OPEN_FILTER_DRAWER; isVisible: true } | |
| | { type: typeof CLOSE_FILTER_DRAWER; isVisible: false } | |
| export const showFilterDrawer = (): FilterDrawerAction => ({ | |
| type: OPEN_FILTER_DRAWER, | |
| isVisible: true, | |
| }) | |
| export const hideFilterDrawer = (): FilterDrawerAction => ({ | |
| type: CLOSE_FILTER_DRAWER, | |
| isVisible: false, | |
| }) | |
| ================================================ | |
| FILE: src/frontend/store/actions/index.ts | |
| ================================================ | |
| export * from './add-notice.js' | |
| export * from './drop-notice.js' | |
| export * from './initialize-assets.js' | |
| export * from './initialize-branding.js' | |
| export * from './initialize-dashboard.js' | |
| export * from './initialize-locale.js' | |
| export * from './initialize-pages.js' | |
| export * from './initialize-paths.js' | |
| export * from './initialize-resources.js' | |
| export * from './initialize-theme.js' | |
| export * from './initialize-versions.js' | |
| export * from './modal.js' | |
| export * from './route-changed.js' | |
| export * from './set-current-admin.js' | |
| export * from './set-drawer-preroute.js' | |
| export * from './set-notice-progress.js' | |
| ================================================ | |
| FILE: src/frontend/store/actions/initialize-assets.ts | |
| ================================================ | |
| import type { Assets } from '../../../adminjs-options.interface.js' | |
| export const ASSETS_INITIALIZE = 'ASSETS_INITIALIZE' | |
| export type initializeAssetsResponse = { | |
| type: string; | |
| data: Assets; | |
| } | |
| export const initializeAssets = (data: Assets): initializeAssetsResponse => ({ | |
| type: ASSETS_INITIALIZE, | |
| data, | |
| }) | |
| ================================================ | |
| FILE: src/frontend/store/actions/initialize-branding.ts | |
| ================================================ | |
| import type { BrandingOptions } from '../../../adminjs-options.interface.js' | |
| export const BRANDING_INITIALIZE = 'BRANDING_INITIALIZE' | |
| export type InitializeBrandingResponse = { | |
| type: typeof BRANDING_INITIALIZE | |
| data: BrandingOptions | |
| } | |
| export const initializeBranding = (data: BrandingOptions): InitializeBrandingResponse => ({ | |
| type: BRANDING_INITIALIZE, | |
| data, | |
| }) | |
| ================================================ | |
| FILE: src/frontend/store/actions/initialize-dashboard.ts | |
| ================================================ | |
| import { DashboardInState } from '../reducers/dashboardReducer.js' | |
| export const DASHBOARD_INITIALIZE = 'DASHBOARD_INITIALIZE' | |
| export type InitializeDashboardResponse = { | |
| type: typeof DASHBOARD_INITIALIZE | |
| data: DashboardInState | |
| } | |
| export const initializeDashboard = (data: DashboardInState): InitializeDashboardResponse => ({ | |
| type: DASHBOARD_INITIALIZE, | |
| data, | |
| }) | |
| ================================================ | |
| FILE: src/frontend/store/actions/initialize-locale.ts | |
| ================================================ | |
| import { Locale } from '../../../locale/config.js' | |
| export const LOCALE_INITIALIZE = 'LOCALE_INITIALIZE' | |
| export type InitializeLocaleResponse = { | |
| type: typeof LOCALE_INITIALIZE; | |
| data: Locale; | |
| } | |
| export const initializeLocale = (data: Locale): InitializeLocaleResponse => ({ | |
| type: LOCALE_INITIALIZE, | |
| data, | |
| }) | |
| ================================================ | |
| FILE: src/frontend/store/actions/initialize-pages.ts | |
| ================================================ | |
| import { AdminPage } from '../../../adminjs-options.interface.js' | |
| export const PAGES_INITIALIZE = 'PAGES_INITIALIZE' | |
| export type InitializePagesResponse = { | |
| type: typeof PAGES_INITIALIZE; | |
| data: Array<AdminPage>; | |
| } | |
| export const initializePages = (data: Array<AdminPage>): InitializePagesResponse => ({ | |
| type: PAGES_INITIALIZE, | |
| data, | |
| }) | |
| ================================================ | |
| FILE: src/frontend/store/actions/initialize-paths.ts | |
| ================================================ | |
| import type { PathsInState } from '../reducers/pathsReducer.js' | |
| export const PATHS_INITIALIZE = 'PATHS_INITIALIZE' | |
| export type InitializePathsResponse = { | |
| type: typeof PATHS_INITIALIZE | |
| data: PathsInState | |
| } | |
| export const initializePaths = (data: PathsInState): InitializePathsResponse => ({ | |
| type: PATHS_INITIALIZE, | |
| data, | |
| }) | |
| ================================================ | |
| FILE: src/frontend/store/actions/initialize-resources.ts | |
| ================================================ | |
| import { ResourceJSON } from '../../interfaces/index.js' | |
| export const RESOURCES_INITIALIZE = 'RESOURCES_INITIALIZE' | |
| export type InitializeResourcesResponse = { | |
| type: typeof RESOURCES_INITIALIZE; | |
| data: Array<ResourceJSON>; | |
| } | |
| export const initializeResources = (data: Array<ResourceJSON>): InitializeResourcesResponse => ({ | |
| type: RESOURCES_INITIALIZE, | |
| data, | |
| }) | |
| ================================================ | |
| FILE: src/frontend/store/actions/initialize-theme.ts | |
| ================================================ | |
| import type { ThemeInState } from '../reducers/themeReducer.js' | |
| export const THEME_INITIALIZE = 'THEME_INITIALIZE' | |
| export type initializeThemeResponse = { | |
| type: typeof THEME_INITIALIZE | |
| data: ThemeInState | |
| } | |
| export const initializeTheme = (data: ThemeInState): initializeThemeResponse => ({ | |
| type: THEME_INITIALIZE, | |
| data, | |
| }) | |
| ================================================ | |
| FILE: src/frontend/store/actions/initialize-versions.ts | |
| ================================================ | |
| import { VersionProps } from '../../../adminjs-options.interface.js' | |
| export const VERSIONS_INITIALIZE = 'VERSIONS_INITIALIZE' | |
| export type InitializeVersionsResponse = { | |
| type: typeof VERSIONS_INITIALIZE | |
| data: VersionProps | |
| } | |
| export const initializeVersions = (data: VersionProps): InitializeVersionsResponse => ({ | |
| type: VERSIONS_INITIALIZE, | |
| data, | |
| }) | |
| ================================================ | |
| FILE: src/frontend/store/actions/modal.ts | |
| ================================================ | |
| import type { ModalData, ShowModalResponse, HideModalResponse } from '../../interfaces/index.js' | |
| export const SHOW_MODAL = 'SHOW_MODAL' | |
| export const HIDE_MODAL = 'HIDE_MODAL' | |
| export const showModal = (data: ModalData): ShowModalResponse => ({ | |
| type: SHOW_MODAL, | |
| data, | |
| }) | |
| export const hideModal = (): HideModalResponse => ({ | |
| type: HIDE_MODAL, | |
| }) | |
| ================================================ | |
| FILE: src/frontend/store/actions/route-changed.ts | |
| ================================================ | |
| import type { useLocation } from 'react-router' | |
| export const INITIAL_ROUTE = 'INITIAL_ROUTE' | |
| export const ROUTE_CHANGED = 'ROUTE_CHANGED' | |
| export type RouteChangedResponse = { | |
| type: typeof ROUTE_CHANGED | |
| data: any | |
| } | |
| export const initializeRoute = ( | |
| location: Partial<ReturnType<typeof useLocation>>, | |
| ): RouteChangedResponse => ({ | |
| type: ROUTE_CHANGED, | |
| data: location, | |
| }) | |
| export const changeRoute = (location: ReturnType<typeof useLocation>): RouteChangedResponse => ({ | |
| type: ROUTE_CHANGED, | |
| data: location, | |
| }) | |
| ================================================ | |
| FILE: src/frontend/store/actions/set-current-admin.ts | |
| ================================================ | |
| import type { SessionInState } from '../reducers/sessionReducer.js' | |
| export const SESSION_INITIALIZE = 'SESSION_INITIALIZE' | |
| export type SetCurrentAdminResponse = { | |
| type: typeof SESSION_INITIALIZE | |
| data: SessionInState | |
| } | |
| export const setCurrentAdmin = (data: SessionInState = null): SetCurrentAdminResponse => ({ | |
| type: SESSION_INITIALIZE, | |
| data, | |
| }) | |
| ================================================ | |
| FILE: src/frontend/store/actions/set-drawer-preroute.ts | |
| ================================================ | |
| import type { useLocation } from 'react-router' | |
| export const DRAWER_PREROUTE_SET = 'DRAWER_PREROUTE_SET' | |
| export type SetDrawerPreRouteResponse = { | |
| type: typeof DRAWER_PREROUTE_SET | |
| data: { | |
| previousRoute: Partial<ReturnType<typeof useLocation>> | null | |
| } | |
| } | |
| export const setDrawerPreRoute = (data: { | |
| previousRoute: Partial<ReturnType<typeof useLocation>> | null | |
| }): SetDrawerPreRouteResponse => ({ | |
| type: DRAWER_PREROUTE_SET, | |
| data, | |
| }) | |
| ================================================ | |
| FILE: src/frontend/store/actions/set-notice-progress.ts | |
| ================================================ | |
| export const SET_NOTICE_PROGRESS = 'SET_NOTICE_PROGRESS' | |
| export type SetNoticeProgress = { | |
| noticeId: string; | |
| progress: number; | |
| } | |
| export type SetNoticeProgressResponse = { | |
| type: typeof SET_NOTICE_PROGRESS; | |
| data: SetNoticeProgress; | |
| } | |
| export const setNoticeProgress = (data: SetNoticeProgress): SetNoticeProgressResponse => ({ | |
| type: SET_NOTICE_PROGRESS, | |
| data, | |
| }) | |
| ================================================ | |
| FILE: src/frontend/store/reducers/assetsReducer.ts | |
| ================================================ | |
| import type { Assets } from '../../../adminjs-options.interface.js' | |
| import { ASSETS_INITIALIZE } from '../actions/initialize-assets.js' | |
| export const assetsReducer = ( | |
| state = {}, | |
| action: { | |
| type: string | |
| data: Assets | |
| }, | |
| ) => { | |
| switch (action.type) { | |
| case ASSETS_INITIALIZE: | |
| return action.data | |
| default: | |
| return state | |
| } | |
| } | |
| ================================================ | |
| FILE: src/frontend/store/reducers/brandingReducer.ts | |
| ================================================ | |
| import type { BrandingOptions } from '../../../adminjs-options.interface.js' | |
| import { BRANDING_INITIALIZE } from '../actions/initialize-branding.js' | |
| export const brandingReducer = ( | |
| state = {}, | |
| action: { | |
| type: string | |
| data: BrandingOptions | |
| }, | |
| ) => { | |
| switch (action.type) { | |
| case BRANDING_INITIALIZE: | |
| return action.data | |
| default: | |
| return state | |
| } | |
| } | |
| ================================================ | |
| FILE: src/frontend/store/reducers/dashboardReducer.ts | |
| ================================================ | |
| import { DASHBOARD_INITIALIZE } from '../actions/initialize-dashboard.js' | |
| export type DashboardInState = { | |
| component?: string | |
| } | |
| export const dashboardReducer = ( | |
| state = {}, | |
| action: { | |
| type: string | |
| data: DashboardInState | |
| }, | |
| ): DashboardInState => { | |
| switch (action.type) { | |
| case DASHBOARD_INITIALIZE: | |
| return action.data | |
| default: | |
| return state | |
| } | |
| } | |
| ================================================ | |
| FILE: src/frontend/store/reducers/drawerReducer.ts | |
| ================================================ | |
| import { DRAWER_PREROUTE_SET, SetDrawerPreRouteResponse } from '../actions/set-drawer-preroute.js' | |
| export type DrawerInState = SetDrawerPreRouteResponse['data'] | |
| export const drawerReducer = ( | |
| state: DrawerInState = { previousRoute: null }, | |
| action: { | |
| type: string | |
| data: DrawerInState | |
| }, | |
| ) => { | |
| switch (action.type) { | |
| case DRAWER_PREROUTE_SET: { | |
| return { | |
| ...state, | |
| ...action.data, | |
| } | |
| } | |
| default: { | |
| return state | |
| } | |
| } | |
| } | |
| ================================================ | |
| FILE: src/frontend/store/reducers/filterDrawerReducer.ts | |
| ================================================ | |
| import { | |
| CLOSE_FILTER_DRAWER, | |
| OPEN_FILTER_DRAWER, | |
| type FilterDrawerAction, | |
| } from '../actions/filter-drawer.js' | |
| export type FilterDrawerInState = ReturnType<typeof filterDrawerReducer> | |
| const initialState = { | |
| isVisible: false, | |
| } | |
| export const filterDrawerReducer = (state = initialState, action: FilterDrawerAction) => { | |
| switch (action.type) { | |
| case OPEN_FILTER_DRAWER: { | |
| return { ...state, isVisible: action.isVisible } | |
| } | |
| case CLOSE_FILTER_DRAWER: { | |
| return { ...state, isVisible: action.isVisible } | |
| } | |
| default: { | |
| return state | |
| } | |
| } | |
| } | |
| ================================================ | |
| FILE: src/frontend/store/reducers/index.ts | |
| ================================================ | |
| export * from './assetsReducer.js' | |
| export * from './brandingReducer.js' | |
| export * from './dashboardReducer.js' | |
| export * from './drawerReducer.js' | |
| export * from './filterDrawerReducer.js' | |
| export * from './localesReducer.js' | |
| export * from './modalReducer.js' | |
| export * from './noticesReducer.js' | |
| export * from './pagesReducer.js' | |
| export * from './pathsReducer.js' | |
| export * from './resourcesReducer.js' | |
| export * from './routerReducer.js' | |
| export * from './sessionReducer.js' | |
| export * from './themeReducer.js' | |
| export * from './versionsReducer.js' | |
| ================================================ | |
| FILE: src/frontend/store/reducers/localesReducer.ts | |
| ================================================ | |
| import type { Locale } from '../../../locale/config.js' | |
| import { LOCALE_INITIALIZE } from '../actions/initialize-locale.js' | |
| export type LolcaleInState = Locale | |
| const defaultLocale = { language: 'en', translations: {} } as Locale | |
| export const localesReducer = ( | |
| state: Locale = defaultLocale, | |
| action: { | |
| type: string | |
| data: Locale | |
| }, | |
| ) => { | |
| switch (action.type) { | |
| case LOCALE_INITIALIZE: | |
| return action.data | |
| default: | |
| return state | |
| } | |
| } | |
| ================================================ | |
| FILE: src/frontend/store/reducers/modalReducer.ts | |
| ================================================ | |
| import type { ModalData } from '../../interfaces/index.js' | |
| import { HIDE_MODAL, SHOW_MODAL } from '../actions/modal.js' | |
| export type ModalInState = (ModalData & { show: true }) | { show: false } | |
| export const modalReducer = ( | |
| state: ModalInState = { show: false }, | |
| action: { | |
| type: string | |
| data: ModalData | |
| }, | |
| ): ModalInState => { | |
| switch (action.type) { | |
| case SHOW_MODAL: { | |
| return { | |
| ...action.data, | |
| show: true, | |
| } | |
| } | |
| case HIDE_MODAL: { | |
| return { show: false } | |
| } | |
| default: | |
| return state | |
| } | |
| } | |
| ================================================ | |
| FILE: src/frontend/store/reducers/pagesReducer.ts | |
| ================================================ | |
| import type { PageJSON } from '../../interfaces/page-json.interface.js' | |
| import { PAGES_INITIALIZE } from '../actions/initialize-pages.js' | |
| export type PagesInState = Array<PageJSON> | |
| export const pagesReducer = ( | |
| state: PagesInState = [], | |
| action: { | |
| type: string | |
| data: PagesInState | |
| }, | |
| ) => { | |
| switch (action.type) { | |
| case PAGES_INITIALIZE: | |
| return action.data | |
| default: | |
| return state | |
| } | |
| } | |
| ================================================ | |
| FILE: src/frontend/store/reducers/pathsReducer.ts | |
| ================================================ | |
| import { DEFAULT_PATHS } from '../../../constants.js' | |
| import { PATHS_INITIALIZE } from '../actions/initialize-paths.js' | |
| export type PathsInState = { | |
| rootPath: string; | |
| logoutPath: string; | |
| loginPath: string; | |
| assetsCDN?: string; | |
| }; | |
| export const pathsReducer = ( | |
| state: PathsInState = DEFAULT_PATHS, | |
| action: { | |
| type: string; | |
| data: PathsInState; | |
| }, | |
| ): PathsInState => { | |
| switch (action.type) { | |
| case PATHS_INITIALIZE: | |
| return action.data | |
| default: | |
| return state | |
| } | |
| } | |
| ================================================ | |
| FILE: src/frontend/store/reducers/resourcesReducer.ts | |
| ================================================ | |
| import type { ResourceJSON } from '../../interfaces/resource-json.interface.js' | |
| import { RESOURCES_INITIALIZE } from '../actions/initialize-resources.js' | |
| export type ResourcesInState = Array<ResourceJSON> | |
| export const resourcesReducer = ( | |
| state: ResourcesInState = [], | |
| action: { | |
| type: string | |
| data: ResourcesInState | |
| }, | |
| ) => { | |
| switch (action.type) { | |
| case RESOURCES_INITIALIZE: | |
| return action.data | |
| default: | |
| return state | |
| } | |
| } | |
| ================================================ | |
| FILE: src/frontend/store/reducers/routerReducer.ts | |
| ================================================ | |
| import type { Location } from 'react-router' | |
| import { ROUTE_CHANGED, INITIAL_ROUTE } from '../actions/route-changed.js' | |
| export type RouterInState = { | |
| from: Partial<Location> | |
| to: Partial<Location> | |
| } | |
| export const routerReducer = ( | |
| state: RouterInState = { from: {}, to: {} }, | |
| action: { | |
| type: typeof INITIAL_ROUTE | typeof ROUTE_CHANGED | |
| data: any | |
| }, | |
| ) => { | |
| switch (action.type) { | |
| case INITIAL_ROUTE: | |
| return { | |
| ...state, | |
| from: { ...action.data }, | |
| } | |
| case ROUTE_CHANGED: | |
| return { | |
| from: { ...state.to }, | |
| to: { ...action.data }, | |
| } | |
| default: | |
| return state | |
| } | |
| } | |
| ================================================ | |
| FILE: src/frontend/store/reducers/sessionReducer.ts | |
| ================================================ | |
| import type { CurrentAdmin } from '../../../current-admin.interface.js' | |
| import { SESSION_INITIALIZE } from '../actions/set-current-admin.js' | |
| export type SessionInState = CurrentAdmin | null | |
| export const sessionReducer = ( | |
| state: SessionInState = null, | |
| action: { | |
| type: string | |
| data: SessionInState | |
| }, | |
| ) => { | |
| switch (action.type) { | |
| case SESSION_INITIALIZE: | |
| return action.data | |
| default: | |
| return state | |
| } | |
| } | |
| ================================================ | |
| FILE: src/frontend/store/reducers/themeReducer.ts | |
| ================================================ | |
| import type { ThemeConfig } from '../../../adminjs-options.interface.js' | |
| import { THEME_INITIALIZE } from '../actions/initialize-theme.js' | |
| export type ThemeInState = (ThemeConfig & { availableThemes?: ThemeConfig[] }) | null | |
| export const themeReducer = ( | |
| state: ThemeInState = null, | |
| action: { | |
| type: string | |
| data: ThemeInState | |
| }, | |
| ) => { | |
| switch (action.type) { | |
| case THEME_INITIALIZE: | |
| return action.data | |
| default: | |
| return state | |
| } | |
| } | |
| ================================================ | |
| FILE: src/frontend/store/reducers/versionsReducer.ts | |
| ================================================ | |
| import { VersionProps } from '../../../adminjs-options.interface.js' | |
| import { VERSIONS_INITIALIZE } from '../actions/initialize-versions.js' | |
| export const versionsReducer = ( | |
| state = {}, | |
| action: { | |
| type: string; | |
| data: VersionProps; | |
| }, | |
| ) => { | |
| switch (action.type) { | |
| case VERSIONS_INITIALIZE: | |
| return { | |
| admin: action.data.admin, | |
| app: action.data.app, | |
| } | |
| default: | |
| return state | |
| } | |
| } | |
| ================================================ | |
| FILE: src/frontend/store/utils/pages-to-store.ts | |
| ================================================ | |
| /* eslint-disable implicit-arrow-linebreak */ | |
| import type { AdminJSOptions } from '../../../adminjs-options.interface.js' | |
| import type { PageJSON } from '../../interfaces/index.js' | |
| export const pagesToStore = (pages: AdminJSOptions['pages'] = {}): Array<PageJSON> => | |
| Object.entries(pages).map(([key, adminPage]) => ({ | |
| name: key, | |
| component: adminPage.component, | |
| icon: adminPage.icon, | |
| })) | |
| ================================================ | |
| FILE: src/frontend/utils/data-css-name.ts | |
| ================================================ | |
| /* eslint-disable max-len */ | |
| export const getDataCss = (...args: (string | number)[]) => args.join('-') | |
| export const getResourceElementCss = (resourceId: string, suffix: string) => getDataCss(resourceId, suffix) | |
| export const getActionElementCss = (resourceId: string, actionName: string, suffix: string) => getDataCss(resourceId, actionName, suffix) | |
| ================================================ | |
| FILE: src/frontend/utils/index.ts | |
| ================================================ | |
| export * from './api-client.js' | |
| export * from './overridable-component.js' | |
| export * from './data-css-name.js' | |
| ================================================ | |
| FILE: src/locale/default-config.ts | |
| ================================================ | |
| import type { InitOptions } from 'i18next' | |
| import startCase from 'lodash/startCase.js' | |
| import type { Locale } from './config.js' | |
| const DEFAULT_LOAD = 'currentOnly' | |
| export const DEFAULT_NS = 'translation' | |
| export const defaultLocale: Locale = { | |
| language: 'en', | |
| translations: {}, | |
| availableLanguages: ['en'], | |
| } | |
| export const defaultConfig: InitOptions = { | |
| debug: process.env.NODE_ENV === 'development', | |
| partialBundledLanguages: true, | |
| interpolation: { | |
| escapeValue: false, | |
| }, | |
| ns: [DEFAULT_NS], | |
| defaultNS: DEFAULT_NS, | |
| fallbackNS: DEFAULT_NS, | |
| load: DEFAULT_LOAD, | |
| react: { | |
| useSuspense: false, | |
| }, | |
| resources: {}, | |
| parseMissingKeyHandler: (key, defaultValue) => defaultValue ?? startCase(key), | |
| get initImmediate(): boolean { | |
| return typeof window !== 'undefined' | |
| }, | |
| } | |
| ================================================ | |
| FILE: src/locale/index.ts | |
| ================================================ | |
| import type { LocaleTranslations } from './config.js' | |
| import deLocale from './de/translation.json' with { type: 'json' } | |
| import enLocale from './en/translation.json' with { type: 'json' } | |
| import esLocale from './es/translation.json' with { type: 'json' } | |
| import itLocale from './it/translation.json' with { type: 'json' } | |
| import jaLocale from './ja/translation.json' with { type: 'json' } | |
| import plLocale from './pl/translation.json' with { type: 'json' } | |
| import ptBrLocale from './pt-BR/translation.json' with { type: 'json' } | |
| import uaLocale from './ua/translation.json' with { type: 'json' } | |
| import zhCNLocale from './zh-CN/translation.json' with { type: 'json' } | |
| export * from './config.js' | |
| export * from './default-config.js' | |
| export const locales: Record<string, LocaleTranslations> = { | |
| de: deLocale, | |
| en: enLocale, | |
| es: esLocale, | |
| it: itLocale, | |
| ja: jaLocale, | |
| pl: plLocale, | |
| 'pt-BR': ptBrLocale, | |
| ua: uaLocale, | |
| 'zh-CN': zhCNLocale, | |
| } | |
| ================================================ | |
| FILE: src/utils/error-type.enum.ts | |
| ================================================ | |
| // eslint-disable-next-line no-shadow | |
| export enum ErrorTypeEnum { | |
| App = 'AppError', | |
| Configuration = 'ConfigurationError', | |
| Forbidden = 'ForbiddenError', | |
| NotFound = 'NotFoundError', | |
| NotImplemented = 'NotImplementedError', | |
| Record = 'RecordError', | |
| Validation = 'ValidationError', | |
| } | |
| export default ErrorTypeEnum | |
| ================================================ | |
| FILE: src/utils/index.ts | |
| ================================================ | |
| export * from './error-type.enum.js' | |
| export * from './translate-functions.factory.js' | |
| export * from './flat/index.js' | |
| export * from './param-converter/index.js' | |
| ================================================ | |
| FILE: src/utils/theme-bundler.ts | |
| ================================================ | |
| import { createRequire } from 'node:module' | |
| import path from 'path' | |
| const require = createRequire(import.meta.url) | |
| const getAdminjsThemesDir = () => path.parse(require.resolve('@adminjs/themes')).dir | |
| export const bundlePath = (theme: string): string => path.join(getAdminjsThemesDir(), `themes/${theme}/theme.bundle.js`) | |
| export const stylePath = (theme: string): string => path.join(getAdminjsThemesDir(), `themes/${theme}/style.css`) | |
| ================================================ | |
| FILE: src/utils/flat/constants.ts | |
| ================================================ | |
| const DELIMITER = '.' | |
| export { DELIMITER } | |
| ================================================ | |
| FILE: src/utils/flat/filter-out-params.doc.md | |
| ================================================ | |
| From all keys in `params` it removes this passed in an argument. | |
| ### Example | |
| ```javascript | |
| import flat from '@adminjs' | |
| const params = { | |
| name: 'John', | |
| 'education.school.name': 'Harvard', | |
| 'education.school.id': 123, | |
| } | |
| flat.filterOutParams(params, 'education.school') | |
| // results to { | |
| // name: 'John', | |
| // } | |
| flat.filterOurParams(params, 'name') | |
| // results to { | |
| // 'education.school.name': 'Harvard', | |
| // 'education.school.id': 123, | |
| // } | |
| ================================================ | |
| FILE: src/utils/flat/filter-out-params.ts | |
| ================================================ | |
| import { propertyKeyRegex } from './property-key-regex.js' | |
| import { FlattenParams } from './flat.types.js' | |
| /** | |
| * @load ./filter-out-params.doc.md | |
| * @memberof module:flat | |
| * @param {FlattenParams} params | |
| * @param {string | Array<string>} properties | |
| * @returns {FlattenParams} | |
| */ | |
| const filterOutParams = ( | |
| params: FlattenParams, | |
| properties: string | Array<string>, | |
| ): FlattenParams => { | |
| const propertyArray = Array.isArray(properties) ? properties : [properties] | |
| return propertyArray | |
| .filter((propertyPath) => !!propertyPath) | |
| .reduce((globalFiltered, propertyPath) => { | |
| const regex = propertyKeyRegex(propertyPath) | |
| return Object.keys(globalFiltered) | |
| .filter((key) => !key.match(regex)) | |
| .reduce((memo, key) => { | |
| memo[key] = (params[key] as string) | |
| return memo | |
| }, {} as FlattenParams) | |
| }, params) | |
| } | |
| export { filterOutParams } | |
| ================================================ | |
| FILE: src/utils/flat/flat.types.ts | |
| ================================================ | |
| /** | |
| * Type of flatten params. | |
| * | |
| * @memberof module:flat | |
| * @alias FlattenParams | |
| */ | |
| export type FlattenParams = { | |
| [key: string]: FlattenValue; | |
| } | |
| export type FlattenValue = string | |
| | boolean | |
| | number | |
| | Date | |
| | null | |
| | [] | |
| | Record<string, unknown> | |
| | File | |
| /** | |
| * @memberof module:flat | |
| * @alias GetOptions | |
| */ | |
| export type GetOptions = { | |
| /** | |
| * Indicates if all the "less related" siblings should be included. This option takes care of | |
| * fetching elements in nested arrays. Let's say you have keys: `nested.0.array.0` and ` | |
| * `nested.1.array.0.`. With `includeAllSiblings` you will fetch all nested.N.array elements. | |
| */ | |
| includeAllSiblings?: boolean; | |
| } | |
| /** | |
| * Available types for flatten values. This is an Union of types: | |
| * - `string` | |
| * - `boolean` | |
| * - `number` | |
| * - `Date` | |
| * - `null` | |
| * - `[]` (empty array) | |
| * - `{}` (empty object) | |
| * - `File` | |
| * @memberof module:flat | |
| * @alias FlattenValue | |
| * @typedef {Union} FlattenValue | |
| */ | |
| ================================================ | |
| FILE: src/utils/flat/get.doc.md | |
| ================================================ | |
| Returns sub-property from the flatten params. When the property path is not given function returns | |
| an entire unflatten object. | |
| ### Example | |
| ```javascript | |
| import flat from '@adminjs' | |
| const params = { | |
| name: 'John', | |
| 'education.school.name': 'Harvard', | |
| 'education.school.id': 123, | |
| } | |
| const data = flat.get(params, 'education.school') | |
| // results to { | |
| // name: 'Harvard', | |
| // id: 321, | |
| // } | |
| // value is undefined | |
| const data = flat.get(params) | |
| // results to { | |
| // name: 'John', | |
| // education: { | |
| // school: { | |
| // name: 'Harvard', | |
| // id: 321, | |
| // } | |
| // } | |
| // } | |
| ``` | |
| ================================================ | |
| FILE: src/utils/flat/index.ts | |
| ================================================ | |
| export * from './flat-module.js' | |
| export * from './flat.types.js' | |
| ================================================ | |
| FILE: src/utils/flat/merge.ts | |
| ================================================ | |
| import flat from 'flat' | |
| import { FlattenParams } from './flat.types.js' | |
| import { set } from './set.js' | |
| /** | |
| * Merges params together and returns flatten result | |
| * | |
| * @param {any} params | |
| * @param {Array<any>} ...mergeParams | |
| * @returns {FlattenParams} | |
| * @memberof module:flat | |
| */ | |
| const merge = (params: any = {}, ...mergeParams: Array<any>): FlattenParams => { | |
| const flattenParams = flat.flatten(params) | |
| // reverse because we merge from right | |
| return mergeParams.reverse().reduce((globalMemo, mergeParam) => ( | |
| Object.keys(mergeParam) | |
| .reduce((memo, key) => (set(memo, key, mergeParam[key])), globalMemo) | |
| ), flattenParams as Record<string, any>) | |
| } | |
| export { merge } | |
| ================================================ | |
| FILE: src/utils/flat/path-parts.type.ts | |
| ================================================ | |
| export type PathParts = Array<string> | |
| ================================================ | |
| FILE: src/utils/flat/path-to-parts.doc.md | |
| ================================================ | |
| the Long story short this method: | |
| - changes: `nested.nested2.normalInner` | |
| - to `["nested", "nested.nested2", "nested.nested2.normalInner"]` | |
| So it can be used to search for the param in a {@link FlattenParams} object. | |
| Formally it changes path in "flatten" notation, to an Array of all possible | |
| keys, which could have searched property. | |
| When `skipArrayIndexes` is set to true it also it takes care of the arrays, which are | |
| separated by numbers (indexes). Then it: | |
| - changes: `nested.0.normalInner.1` | |
| - to: `nested.normalInner` | |
| Everything because when we look for a property of a given path it can be inside a | |
| mixed property. So first, we have to find top-level mixed property, and then, | |
| step by step, find inside each of them. | |
| ================================================ | |
| FILE: src/utils/flat/path-to-parts.ts | |
| ================================================ | |
| import { PathParts } from './path-parts.type.js' | |
| /** | |
| * @memberof module:flat | |
| * @alias PathToPartsOptions | |
| */ | |
| export type PathToPartsOptions = { | |
| /** | |
| * Indicates if array indexes should be skipped from the outcome. | |
| */ | |
| skipArrayIndexes?: boolean; | |
| } | |
| /** | |
| * @load ./path-to-parts.doc.md | |
| * @param {string} propertyPath | |
| * @param {PathToPartsOptions} options | |
| * @returns {PathParts} | |
| * | |
| * @memberof module:flat | |
| * @alias pathToParts | |
| */ | |
| const pathToParts = (propertyPath: string, options: PathToPartsOptions = {}): PathParts => { | |
| let allParts = propertyPath.split('.') | |
| if (options.skipArrayIndexes) { | |
| // eslint-disable-next-line no-restricted-globals | |
| allParts = allParts.filter((part) => isNaN(+part)) | |
| } | |
| return allParts.reduce((memo, part) => { | |
| if (memo.length) { | |
| return [ | |
| ...memo, | |
| [memo[memo.length - 1], part].join('.'), | |
| ] | |
| } | |
| return [part] | |
| }, [] as Array<string>) | |
| } | |
| export { pathToParts } | |
| ================================================ | |
| FILE: src/utils/flat/property-key-regex.ts | |
| ================================================ | |
| import { DELIMITER } from './constants.js' | |
| import { GetOptions } from './flat.types.js' | |
| // this is the regex used to find all existing properties starting with a key | |
| export const propertyKeyRegex = (propertyPath: string, options?: GetOptions): RegExp => { | |
| const delimiter = new RegExp(`\\${DELIMITER}`, 'g') | |
| const escapedDelimiter = `\\${DELIMITER}` | |
| // but for `nested.1.property.0` it will produce `nested(\.|\.\d+\.)1(\.|\.\d+\.)property.0` | |
| // and this is intentional because user can give an one index in property path for with deeply | |
| // nested arrays | |
| const escapedDelimiterOrIndex = `(${escapedDelimiter}|${escapedDelimiter}\\d+${escapedDelimiter})` | |
| const path = options?.includeAllSiblings | |
| ? propertyPath.replace(delimiter, escapedDelimiterOrIndex) | |
| : propertyPath.replace(delimiter, escapedDelimiter) | |
| return new RegExp(`^${path}($|${escapedDelimiter})`, '') | |
| } | |
| ================================================ | |
| FILE: src/utils/flat/select-params.doc.md | |
| ================================================ | |
| From all keys in `params` it selects only those passed in arguments. | |
| ### Example | |
| ```javascript | |
| import flat from '@adminjs' | |
| const params = { | |
| name: 'John', | |
| 'education.school.name': 'Harvard', | |
| 'education.school.id': 123, | |
| } | |
| flat.selectParams(params, 'education.school') | |
| // results to { | |
| // 'education.school.name': 'Harvard', | |
| // 'education.school.id': 123, | |
| // } | |
| flat.selectParams(params, 'education.school.id', 'name') | |
| // results to { | |
| // 'name': 'John', | |
| // 'education.school.id': 123, | |
| // } | |
| ================================================ | |
| FILE: src/utils/flat/set.doc.md | |
| ================================================ | |
| Updates the flatten param object with a given value. Value can be anything and, this anything will | |
| be flattened and added to `params`. | |
| `params` is not mutated here. | |
| ### Example | |
| ```javascript | |
| import flat from '@adminjs' | |
| const params = { | |
| name: 'John', | |
| 'education.school.name': 'Harvard', | |
| 'education.school.id': 123, | |
| } | |
| const data = flat.set(params, 'education.shool', { | |
| name: 'Yale', | |
| id: 321, | |
| }) | |
| // results to data === { | |
| // name: 'John', | |
| // 'education.school.name': 'Yale`, | |
| // 'education.school.id': 321, | |
| // } | |
| // value is undefined | |
| const data = flat.set(params, 'education') // results to data === { name: 'John' } | |
| ``` | |
| ================================================ | |
| FILE: src/utils/param-converter/constants.ts | |
| ================================================ | |
| const DELIMITER = '.' | |
| export { DELIMITER } | |
| ================================================ | |
| FILE: src/utils/param-converter/convert-nested-param.ts | |
| ================================================ | |
| import { BasePropertyJSON } from '../../frontend/interfaces/property-json/property-json.interface.js' | |
| import { DELIMITER } from './constants.js' | |
| import { convertParam } from './convert-param.js' | |
| const convertNestedParam = ( | |
| parentValue: Record<string, any>, | |
| subProperty: BasePropertyJSON, | |
| ): Record<string, any> => { | |
| const path = subProperty.propertyPath.split(DELIMITER).slice(-1)[0] | |
| const { type = 'string' } = subProperty | |
| let value = parentValue[path] | |
| if (type === 'mixed' && value) { | |
| const nestedSubProperties = subProperty.subProperties | |
| for (const nestedSubProperty of nestedSubProperties) { | |
| if (subProperty.isArray) { | |
| value = [...value].map((element) => convertNestedParam(element, nestedSubProperty)) | |
| } else { | |
| value = convertNestedParam(value, nestedSubProperty) | |
| } | |
| } | |
| } else { | |
| value = convertParam(value, subProperty.type) | |
| } | |
| return { | |
| ...parentValue, | |
| [path]: value, | |
| } | |
| } | |
| export { convertNestedParam } | |
| ================================================ | |
| FILE: src/utils/param-converter/convert-param.spec.ts | |
| ================================================ | |
| import { expect } from 'chai' | |
| import { convertParam } from './convert-param.js' | |
| describe('module:paramConverter.convertParam', () => { | |
| it('should convert numeric strings to Number', () => { | |
| expect(convertParam('123', 'number')).to.equal(123) | |
| }) | |
| it('should convert bool strings to Boolean', () => { | |
| /* | |
| This will actually evaluate any string with length > 0 to true | |
| Ideally, additional validation should be added to convertParam | |
| */ | |
| expect(convertParam('true', 'boolean')).to.equal(true) | |
| }) | |
| it('should convert datetime strings to Date', () => { | |
| expect(convertParam('2021-11-08', 'datetime').getTime()).to.equal(new Date('2021-11-08').getTime()) | |
| }) | |
| it('should leave other values unchanged', () => { | |
| expect(convertParam(null, 'some other type')).to.equal(null) | |
| }) | |
| }) | |
| ================================================ | |
| FILE: src/utils/param-converter/convert-param.ts | |
| ================================================ | |
| const convertParam = (value: any, propertyType: string): any => { | |
| if (value === null || typeof value === 'undefined') { | |
| return value | |
| } | |
| if (propertyType === 'number') { | |
| return Number(value) | |
| } | |
| if (propertyType === 'boolean') { | |
| return Boolean(value) | |
| } | |
| if (['datetime', 'date'].includes(propertyType)) { | |
| return new Date(value) | |
| } | |
| return value | |
| } | |
| export { convertParam } | |
| ================================================ | |
| FILE: src/utils/param-converter/index.ts | |
| ================================================ | |
| export * from './param-converter-module.js' | |
| ================================================ | |
| FILE: src/utils/param-converter/param-converter-module.ts | |
| ================================================ | |
| import { DELIMITER } from './constants.js' | |
| import { convertNestedParam } from './convert-nested-param.js' | |
| import { convertParam } from './convert-param.js' | |
| import { prepareParams } from './prepare-params.js' | |
| export type ParamConverterModuleType = { | |
| convertParam: typeof convertParam; | |
| convertNestedParam: typeof convertNestedParam; | |
| prepareParams: typeof prepareParams; | |
| DELIMITER: typeof DELIMITER; | |
| } | |
| export const paramConverter: ParamConverterModuleType = { | |
| convertParam, | |
| convertNestedParam, | |
| DELIMITER, | |
| prepareParams, | |
| } | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment