node_modules ignore

This commit is contained in:
2025-05-08 23:43:47 +02:00
parent e19d52f172
commit 4574544c9f
65041 changed files with 10593536 additions and 0 deletions

View File

@@ -0,0 +1,166 @@
'use strict';
var React = require('react');
var strapiAdmin = require('@strapi/admin/strapi-admin');
var designSystem = require('@strapi/design-system');
var qs = require('qs');
var reactIntl = require('react-intl');
var collections = require('../constants/collections.js');
var hooks$1 = require('../constants/hooks.js');
var app = require('../modules/app.js');
var hooks = require('../modules/hooks.js');
var contentTypes = require('../services/contentTypes.js');
var init = require('../services/init.js');
var translations = require('../utils/translations.js');
const { MUTATE_COLLECTION_TYPES_LINKS, MUTATE_SINGLE_TYPES_LINKS } = hooks$1.HOOKS;
const useContentManagerInitData = ()=>{
const { toggleNotification } = strapiAdmin.useNotification();
const dispatch = hooks.useTypedDispatch();
const runHookWaterfall = strapiAdmin.useStrapiApp('useContentManagerInitData', (state)=>state.runHookWaterfall);
const { notifyStatus } = designSystem.useNotifyAT();
const { formatMessage } = reactIntl.useIntl();
const { _unstableFormatAPIError: formatAPIError } = strapiAdmin.useAPIErrorHandler(translations.getTranslation);
const checkUserHasPermissions = strapiAdmin.useAuth('useContentManagerInitData', (state)=>state.checkUserHasPermissions);
const state = hooks.useTypedSelector((state)=>state['content-manager'].app);
const initialDataQuery = init.useGetInitialDataQuery(undefined, {
/**
* TODO: remove this when the CTB has been refactored to use redux-toolkit-query
* and it can invalidate the cache on mutation
*/ refetchOnMountOrArgChange: true
});
React.useEffect(()=>{
if (initialDataQuery.data) {
notifyStatus(formatMessage({
id: translations.getTranslation('App.schemas.data-loaded'),
defaultMessage: 'The schemas have been successfully loaded.'
}));
}
}, [
formatMessage,
initialDataQuery.data,
notifyStatus
]);
React.useEffect(()=>{
if (initialDataQuery.error) {
toggleNotification({
type: 'danger',
message: formatAPIError(initialDataQuery.error)
});
}
}, [
formatAPIError,
initialDataQuery.error,
toggleNotification
]);
const contentTypeSettingsQuery = contentTypes.useGetAllContentTypeSettingsQuery();
React.useEffect(()=>{
if (contentTypeSettingsQuery.error) {
toggleNotification({
type: 'danger',
message: formatAPIError(contentTypeSettingsQuery.error)
});
}
}, [
formatAPIError,
contentTypeSettingsQuery.error,
toggleNotification
]);
const formatData = async (components, contentTypes, fieldSizes, contentTypeConfigurations)=>{
/**
* We group these by the two types we support. We do with an object because we can use default
* values of arrays to make sure we always have an array to manipulate further on if, for example,
* a user has not made any single types.
*
* This means we have to manually add new content types to this hook if we add a new type but
* the safety is worth it.
*/ const { collectionType: collectionTypeLinks, singleType: singleTypeLinks } = contentTypes.reduce((acc, model)=>{
acc[model.kind].push(model);
return acc;
}, {
collectionType: [],
singleType: []
});
const collectionTypeSectionLinks = generateLinks(collectionTypeLinks, 'collectionTypes', contentTypeConfigurations);
const singleTypeSectionLinks = generateLinks(singleTypeLinks, 'singleTypes');
// Collection Types verifications
const collectionTypeLinksPermissions = await Promise.all(collectionTypeSectionLinks.map(({ permissions })=>checkUserHasPermissions(permissions)));
const authorizedCollectionTypeLinks = collectionTypeSectionLinks.filter((_, index)=>collectionTypeLinksPermissions[index].length > 0);
// Single Types verifications
const singleTypeLinksPermissions = await Promise.all(singleTypeSectionLinks.map(({ permissions })=>checkUserHasPermissions(permissions)));
const authorizedSingleTypeLinks = singleTypeSectionLinks.filter((_, index)=>singleTypeLinksPermissions[index].length > 0);
const { ctLinks } = runHookWaterfall(MUTATE_COLLECTION_TYPES_LINKS, {
ctLinks: authorizedCollectionTypeLinks,
models: contentTypes
});
const { stLinks } = runHookWaterfall(MUTATE_SINGLE_TYPES_LINKS, {
stLinks: authorizedSingleTypeLinks,
models: contentTypes
});
dispatch(app.setInitialData({
authorizedCollectionTypeLinks: ctLinks,
authorizedSingleTypeLinks: stLinks,
components,
contentTypeSchemas: contentTypes,
fieldSizes
}));
};
React.useEffect(()=>{
if (initialDataQuery.data && contentTypeSettingsQuery.data) {
formatData(initialDataQuery.data.components, initialDataQuery.data.contentTypes, initialDataQuery.data.fieldSizes, contentTypeSettingsQuery.data);
}
}, [
initialDataQuery.data,
contentTypeSettingsQuery.data
]);
return {
...state
};
};
const generateLinks = (links, type, configurations = [])=>{
return links.filter((link)=>link.isDisplayed).map((link)=>{
const collectionTypesPermissions = [
{
action: 'plugin::content-manager.explorer.create',
subject: link.uid
},
{
action: 'plugin::content-manager.explorer.read',
subject: link.uid
}
];
const singleTypesPermissions = [
{
action: 'plugin::content-manager.explorer.read',
subject: link.uid
}
];
const permissions = type === 'collectionTypes' ? collectionTypesPermissions : singleTypesPermissions;
const currentContentTypeConfig = configurations.find(({ uid })=>uid === link.uid);
let search = null;
if (currentContentTypeConfig) {
const searchParams = {
page: 1,
pageSize: currentContentTypeConfig.settings.pageSize,
sort: `${currentContentTypeConfig.settings.defaultSortBy}:${currentContentTypeConfig.settings.defaultSortOrder}`
};
search = qs.stringify(searchParams, {
encode: false
});
}
return {
permissions,
search,
kind: link.kind,
title: link.info.displayName,
to: `/content-manager/${link.kind === 'collectionType' ? collections.COLLECTION_TYPES : collections.SINGLE_TYPES}/${link.uid}`,
uid: link.uid,
// Used for the list item key in the helper plugin
name: link.uid,
isDisplayed: link.isDisplayed
};
});
};
exports.useContentManagerInitData = useContentManagerInitData;
//# sourceMappingURL=useContentManagerInitData.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,164 @@
import { useEffect } from 'react';
import { useNotification, useStrapiApp, useAPIErrorHandler, useAuth } from '@strapi/admin/strapi-admin';
import { useNotifyAT } from '@strapi/design-system';
import { stringify } from 'qs';
import { useIntl } from 'react-intl';
import { COLLECTION_TYPES, SINGLE_TYPES } from '../constants/collections.mjs';
import { HOOKS } from '../constants/hooks.mjs';
import { setInitialData } from '../modules/app.mjs';
import { useTypedDispatch, useTypedSelector } from '../modules/hooks.mjs';
import { useGetAllContentTypeSettingsQuery } from '../services/contentTypes.mjs';
import { useGetInitialDataQuery } from '../services/init.mjs';
import { getTranslation } from '../utils/translations.mjs';
const { MUTATE_COLLECTION_TYPES_LINKS, MUTATE_SINGLE_TYPES_LINKS } = HOOKS;
const useContentManagerInitData = ()=>{
const { toggleNotification } = useNotification();
const dispatch = useTypedDispatch();
const runHookWaterfall = useStrapiApp('useContentManagerInitData', (state)=>state.runHookWaterfall);
const { notifyStatus } = useNotifyAT();
const { formatMessage } = useIntl();
const { _unstableFormatAPIError: formatAPIError } = useAPIErrorHandler(getTranslation);
const checkUserHasPermissions = useAuth('useContentManagerInitData', (state)=>state.checkUserHasPermissions);
const state = useTypedSelector((state)=>state['content-manager'].app);
const initialDataQuery = useGetInitialDataQuery(undefined, {
/**
* TODO: remove this when the CTB has been refactored to use redux-toolkit-query
* and it can invalidate the cache on mutation
*/ refetchOnMountOrArgChange: true
});
useEffect(()=>{
if (initialDataQuery.data) {
notifyStatus(formatMessage({
id: getTranslation('App.schemas.data-loaded'),
defaultMessage: 'The schemas have been successfully loaded.'
}));
}
}, [
formatMessage,
initialDataQuery.data,
notifyStatus
]);
useEffect(()=>{
if (initialDataQuery.error) {
toggleNotification({
type: 'danger',
message: formatAPIError(initialDataQuery.error)
});
}
}, [
formatAPIError,
initialDataQuery.error,
toggleNotification
]);
const contentTypeSettingsQuery = useGetAllContentTypeSettingsQuery();
useEffect(()=>{
if (contentTypeSettingsQuery.error) {
toggleNotification({
type: 'danger',
message: formatAPIError(contentTypeSettingsQuery.error)
});
}
}, [
formatAPIError,
contentTypeSettingsQuery.error,
toggleNotification
]);
const formatData = async (components, contentTypes, fieldSizes, contentTypeConfigurations)=>{
/**
* We group these by the two types we support. We do with an object because we can use default
* values of arrays to make sure we always have an array to manipulate further on if, for example,
* a user has not made any single types.
*
* This means we have to manually add new content types to this hook if we add a new type but
* the safety is worth it.
*/ const { collectionType: collectionTypeLinks, singleType: singleTypeLinks } = contentTypes.reduce((acc, model)=>{
acc[model.kind].push(model);
return acc;
}, {
collectionType: [],
singleType: []
});
const collectionTypeSectionLinks = generateLinks(collectionTypeLinks, 'collectionTypes', contentTypeConfigurations);
const singleTypeSectionLinks = generateLinks(singleTypeLinks, 'singleTypes');
// Collection Types verifications
const collectionTypeLinksPermissions = await Promise.all(collectionTypeSectionLinks.map(({ permissions })=>checkUserHasPermissions(permissions)));
const authorizedCollectionTypeLinks = collectionTypeSectionLinks.filter((_, index)=>collectionTypeLinksPermissions[index].length > 0);
// Single Types verifications
const singleTypeLinksPermissions = await Promise.all(singleTypeSectionLinks.map(({ permissions })=>checkUserHasPermissions(permissions)));
const authorizedSingleTypeLinks = singleTypeSectionLinks.filter((_, index)=>singleTypeLinksPermissions[index].length > 0);
const { ctLinks } = runHookWaterfall(MUTATE_COLLECTION_TYPES_LINKS, {
ctLinks: authorizedCollectionTypeLinks,
models: contentTypes
});
const { stLinks } = runHookWaterfall(MUTATE_SINGLE_TYPES_LINKS, {
stLinks: authorizedSingleTypeLinks,
models: contentTypes
});
dispatch(setInitialData({
authorizedCollectionTypeLinks: ctLinks,
authorizedSingleTypeLinks: stLinks,
components,
contentTypeSchemas: contentTypes,
fieldSizes
}));
};
useEffect(()=>{
if (initialDataQuery.data && contentTypeSettingsQuery.data) {
formatData(initialDataQuery.data.components, initialDataQuery.data.contentTypes, initialDataQuery.data.fieldSizes, contentTypeSettingsQuery.data);
}
}, [
initialDataQuery.data,
contentTypeSettingsQuery.data
]);
return {
...state
};
};
const generateLinks = (links, type, configurations = [])=>{
return links.filter((link)=>link.isDisplayed).map((link)=>{
const collectionTypesPermissions = [
{
action: 'plugin::content-manager.explorer.create',
subject: link.uid
},
{
action: 'plugin::content-manager.explorer.read',
subject: link.uid
}
];
const singleTypesPermissions = [
{
action: 'plugin::content-manager.explorer.read',
subject: link.uid
}
];
const permissions = type === 'collectionTypes' ? collectionTypesPermissions : singleTypesPermissions;
const currentContentTypeConfig = configurations.find(({ uid })=>uid === link.uid);
let search = null;
if (currentContentTypeConfig) {
const searchParams = {
page: 1,
pageSize: currentContentTypeConfig.settings.pageSize,
sort: `${currentContentTypeConfig.settings.defaultSortBy}:${currentContentTypeConfig.settings.defaultSortOrder}`
};
search = stringify(searchParams, {
encode: false
});
}
return {
permissions,
search,
kind: link.kind,
title: link.info.displayName,
to: `/content-manager/${link.kind === 'collectionType' ? COLLECTION_TYPES : SINGLE_TYPES}/${link.uid}`,
uid: link.uid,
// Used for the list item key in the helper plugin
name: link.uid,
isDisplayed: link.isDisplayed
};
});
};
export { useContentManagerInitData };
//# sourceMappingURL=useContentManagerInitData.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,115 @@
'use strict';
var React = require('react');
var strapiAdmin = require('@strapi/admin/strapi-admin');
var init = require('../services/init.js');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
/**
* @internal
* @description Given a model UID, return the schema and the schemas
* of the associated components within said model's schema. A wrapper
* implementation around the `useGetInitialDataQuery` with a unique
* `selectFromResult` function to memoize the calculation.
*
* If no model is provided, the hook will return all the schemas.
*/ const useContentTypeSchema = (model)=>{
const { toggleNotification } = strapiAdmin.useNotification();
const { _unstableFormatAPIError: formatAPIError } = strapiAdmin.useAPIErrorHandler();
const { data, error, isLoading, isFetching } = init.useGetInitialDataQuery(undefined);
const { components, contentType, contentTypes } = React__namespace.useMemo(()=>{
const contentType = data?.contentTypes.find((ct)=>ct.uid === model);
const componentsByKey = data?.components.reduce((acc, component)=>{
acc[component.uid] = component;
return acc;
}, {});
const components = extractContentTypeComponents(contentType?.attributes, componentsByKey);
return {
components: Object.keys(components).length === 0 ? undefined : components,
contentType,
contentTypes: data?.contentTypes ?? []
};
}, [
model,
data
]);
React__namespace.useEffect(()=>{
if (error) {
toggleNotification({
type: 'danger',
message: formatAPIError(error)
});
}
}, [
toggleNotification,
error,
formatAPIError
]);
return {
// This must be memoized to avoid inifiinite re-renders where the empty object is different everytime.
components: React__namespace.useMemo(()=>components ?? {}, [
components
]),
schema: contentType,
schemas: contentTypes,
isLoading: isLoading || isFetching
};
};
/* -------------------------------------------------------------------------------------------------
* extractContentTypeComponents
* -----------------------------------------------------------------------------------------------*/ /**
* @internal
* @description Extracts the components used in a content type's attributes recursively.
*/ const extractContentTypeComponents = (attributes = {}, allComponents = {})=>{
const getComponents = (attributes)=>{
return attributes.reduce((acc, attribute)=>{
/**
* If the attribute is a component or dynamiczone, we need to recursively
* extract the component UIDs from its attributes.
*/ if (attribute.type === 'component') {
const componentAttributes = Object.values(allComponents[attribute.component]?.attributes ?? {});
acc.push(attribute.component, ...getComponents(componentAttributes));
} else if (attribute.type === 'dynamiczone') {
acc.push(...attribute.components, /**
* Dynamic zones have an array of components, so we flatMap over them
* performing the same search as above.
*/ ...attribute.components.flatMap((componentUid)=>{
const componentAttributes = Object.values(allComponents[componentUid]?.attributes ?? {});
return getComponents(componentAttributes);
}));
}
return acc;
}, []);
};
const componentUids = getComponents(Object.values(attributes));
const uniqueComponentUids = [
...new Set(componentUids)
];
const componentsByKey = uniqueComponentUids.reduce((acc, uid)=>{
acc[uid] = allComponents[uid];
return acc;
}, {});
return componentsByKey;
};
exports.extractContentTypeComponents = extractContentTypeComponents;
exports.useContentTypeSchema = useContentTypeSchema;
//# sourceMappingURL=useContentTypeSchema.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,93 @@
import * as React from 'react';
import { useNotification, useAPIErrorHandler } from '@strapi/admin/strapi-admin';
import { useGetInitialDataQuery } from '../services/init.mjs';
/**
* @internal
* @description Given a model UID, return the schema and the schemas
* of the associated components within said model's schema. A wrapper
* implementation around the `useGetInitialDataQuery` with a unique
* `selectFromResult` function to memoize the calculation.
*
* If no model is provided, the hook will return all the schemas.
*/ const useContentTypeSchema = (model)=>{
const { toggleNotification } = useNotification();
const { _unstableFormatAPIError: formatAPIError } = useAPIErrorHandler();
const { data, error, isLoading, isFetching } = useGetInitialDataQuery(undefined);
const { components, contentType, contentTypes } = React.useMemo(()=>{
const contentType = data?.contentTypes.find((ct)=>ct.uid === model);
const componentsByKey = data?.components.reduce((acc, component)=>{
acc[component.uid] = component;
return acc;
}, {});
const components = extractContentTypeComponents(contentType?.attributes, componentsByKey);
return {
components: Object.keys(components).length === 0 ? undefined : components,
contentType,
contentTypes: data?.contentTypes ?? []
};
}, [
model,
data
]);
React.useEffect(()=>{
if (error) {
toggleNotification({
type: 'danger',
message: formatAPIError(error)
});
}
}, [
toggleNotification,
error,
formatAPIError
]);
return {
// This must be memoized to avoid inifiinite re-renders where the empty object is different everytime.
components: React.useMemo(()=>components ?? {}, [
components
]),
schema: contentType,
schemas: contentTypes,
isLoading: isLoading || isFetching
};
};
/* -------------------------------------------------------------------------------------------------
* extractContentTypeComponents
* -----------------------------------------------------------------------------------------------*/ /**
* @internal
* @description Extracts the components used in a content type's attributes recursively.
*/ const extractContentTypeComponents = (attributes = {}, allComponents = {})=>{
const getComponents = (attributes)=>{
return attributes.reduce((acc, attribute)=>{
/**
* If the attribute is a component or dynamiczone, we need to recursively
* extract the component UIDs from its attributes.
*/ if (attribute.type === 'component') {
const componentAttributes = Object.values(allComponents[attribute.component]?.attributes ?? {});
acc.push(attribute.component, ...getComponents(componentAttributes));
} else if (attribute.type === 'dynamiczone') {
acc.push(...attribute.components, /**
* Dynamic zones have an array of components, so we flatMap over them
* performing the same search as above.
*/ ...attribute.components.flatMap((componentUid)=>{
const componentAttributes = Object.values(allComponents[componentUid]?.attributes ?? {});
return getComponents(componentAttributes);
}));
}
return acc;
}, []);
};
const componentUids = getComponents(Object.values(attributes));
const uniqueComponentUids = [
...new Set(componentUids)
];
const componentsByKey = uniqueComponentUids.reduce((acc, uid)=>{
acc[uid] = allComponents[uid];
return acc;
}, {});
return componentsByKey;
};
export { extractContentTypeComponents, useContentTypeSchema };
//# sourceMappingURL=useContentTypeSchema.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,22 @@
'use strict';
var React = require('react');
function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = React.useState(value);
React.useEffect(()=>{
const handler = setTimeout(()=>{
setDebouncedValue(value);
}, delay);
return ()=>{
clearTimeout(handler);
};
}, [
value,
delay
]);
return debouncedValue;
}
exports.useDebounce = useDebounce;
//# sourceMappingURL=useDebounce.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"useDebounce.js","sources":["../../../admin/src/hooks/useDebounce.ts"],"sourcesContent":["import { useEffect, useState } from 'react';\n\nexport function useDebounce<TValue>(value: TValue, delay: number): TValue {\n const [debouncedValue, setDebouncedValue] = useState(value);\n\n useEffect(() => {\n const handler = setTimeout(() => {\n setDebouncedValue(value);\n }, delay);\n\n return () => {\n clearTimeout(handler);\n };\n }, [value, delay]);\n\n return debouncedValue;\n}\n"],"names":["useDebounce","value","delay","debouncedValue","setDebouncedValue","useState","useEffect","handler","setTimeout","clearTimeout"],"mappings":";;;;AAEO,SAASA,WAAAA,CAAoBC,KAAa,EAAEC,KAAa,EAAA;AAC9D,IAAA,MAAM,CAACC,cAAAA,EAAgBC,iBAAkB,CAAA,GAAGC,cAASJ,CAAAA,KAAAA,CAAAA;IAErDK,eAAU,CAAA,IAAA;AACR,QAAA,MAAMC,UAAUC,UAAW,CAAA,IAAA;YACzBJ,iBAAkBH,CAAAA,KAAAA,CAAAA;SACjBC,EAAAA,KAAAA,CAAAA;QAEH,OAAO,IAAA;YACLO,YAAaF,CAAAA,OAAAA,CAAAA;AACf,SAAA;KACC,EAAA;AAACN,QAAAA,KAAAA;AAAOC,QAAAA;AAAM,KAAA,CAAA;IAEjB,OAAOC,cAAAA;AACT;;;;"}

View File

@@ -0,0 +1,20 @@
import { useState, useEffect } from 'react';
function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(()=>{
const handler = setTimeout(()=>{
setDebouncedValue(value);
}, delay);
return ()=>{
clearTimeout(handler);
};
}, [
value,
delay
]);
return debouncedValue;
}
export { useDebounce };
//# sourceMappingURL=useDebounce.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"useDebounce.mjs","sources":["../../../admin/src/hooks/useDebounce.ts"],"sourcesContent":["import { useEffect, useState } from 'react';\n\nexport function useDebounce<TValue>(value: TValue, delay: number): TValue {\n const [debouncedValue, setDebouncedValue] = useState(value);\n\n useEffect(() => {\n const handler = setTimeout(() => {\n setDebouncedValue(value);\n }, delay);\n\n return () => {\n clearTimeout(handler);\n };\n }, [value, delay]);\n\n return debouncedValue;\n}\n"],"names":["useDebounce","value","delay","debouncedValue","setDebouncedValue","useState","useEffect","handler","setTimeout","clearTimeout"],"mappings":";;AAEO,SAASA,WAAAA,CAAoBC,KAAa,EAAEC,KAAa,EAAA;AAC9D,IAAA,MAAM,CAACC,cAAAA,EAAgBC,iBAAkB,CAAA,GAAGC,QAASJ,CAAAA,KAAAA,CAAAA;IAErDK,SAAU,CAAA,IAAA;AACR,QAAA,MAAMC,UAAUC,UAAW,CAAA,IAAA;YACzBJ,iBAAkBH,CAAAA,KAAAA,CAAAA;SACjBC,EAAAA,KAAAA,CAAAA;QAEH,OAAO,IAAA;YACLO,YAAaF,CAAAA,OAAAA,CAAAA;AACf,SAAA;KACC,EAAA;AAACN,QAAAA,KAAAA;AAAOC,QAAAA;AAAM,KAAA,CAAA;IAEjB,OAAOC,cAAAA;AACT;;;;"}

View File

@@ -0,0 +1,244 @@
'use strict';
var React = require('react');
var strapiAdmin = require('@strapi/admin/strapi-admin');
var reactIntl = require('react-intl');
var reactRouterDom = require('react-router-dom');
var yup = require('yup');
var collections = require('../constants/collections.js');
var data = require('../pages/EditView/utils/data.js');
var forms = require('../pages/EditView/utils/forms.js');
var documents = require('../services/documents.js');
var api = require('../utils/api.js');
var validation = require('../utils/validation.js');
var useContentTypeSchema = require('./useContentTypeSchema.js');
var useDocumentLayout = require('./useDocumentLayout.js');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
/* -------------------------------------------------------------------------------------------------
* useDocument
* -----------------------------------------------------------------------------------------------*/ /**
* @alpha
* @public
* @description Returns a document based on the model, collection type & id passed as arguments.
* Also extracts its schema from the redux cache to be used for creating a validation schema.
* @example
* ```tsx
* const { id, model, collectionType } = useParams<{ id: string; model: string; collectionType: string }>();
*
* if(!model || !collectionType) return null;
*
* const { document, isLoading, validate } = useDocument({ documentId: id, model, collectionType, params: { locale: 'en-GB' } })
* const { update } = useDocumentActions()
*
* const onSubmit = async (document: Document) => {
* const errors = validate(document);
*
* if(errors) {
* // handle errors
* }
*
* await update({ collectionType, model, id }, document)
* }
* ```
*
* @see {@link https://contributor.strapi.io/docs/core/content-manager/hooks/use-document} for more information
*/ const useDocument = (args, opts)=>{
const { toggleNotification } = strapiAdmin.useNotification();
const { _unstableFormatAPIError: formatAPIError } = strapiAdmin.useAPIErrorHandler();
const { formatMessage } = reactIntl.useIntl();
const { currentData: data$1, isLoading: isLoadingDocument, isFetching: isFetchingDocument, error, refetch } = documents.useGetDocumentQuery(args, {
...opts,
skip: !args.documentId && args.collectionType !== collections.SINGLE_TYPES || opts?.skip
});
const document = data$1?.data;
const meta = data$1?.meta;
const { components, schema, schemas, isLoading: isLoadingSchema } = useContentTypeSchema.useContentTypeSchema(args.model);
const isSingleType = schema?.kind === 'singleType';
const getTitle = (mainField)=>{
// Always use mainField if it's not an id
if (mainField !== 'id' && document?.[mainField]) {
return document[mainField];
}
// When it's a singleType without a mainField, use the contentType displayName
if (isSingleType && schema?.info.displayName) {
return schema.info.displayName;
}
// Otherwise, use a fallback
return formatMessage({
id: 'content-manager.containers.untitled',
defaultMessage: 'Untitled'
});
};
React__namespace.useEffect(()=>{
if (error) {
toggleNotification({
type: 'danger',
message: formatAPIError(error)
});
}
}, [
toggleNotification,
error,
formatAPIError,
args.collectionType
]);
const validationSchema = React__namespace.useMemo(()=>{
if (!schema) {
return null;
}
return validation.createYupSchema(schema.attributes, components);
}, [
schema,
components
]);
const validate = React__namespace.useCallback((document)=>{
if (!validationSchema) {
throw new Error('There is no validation schema generated, this is likely due to the schema not being loaded yet.');
}
try {
validationSchema.validateSync(document, {
abortEarly: false,
strict: true
});
return null;
} catch (error) {
if (error instanceof yup.ValidationError) {
return strapiAdmin.getYupValidationErrors(error);
}
throw error;
}
}, [
validationSchema
]);
/**
* Here we prepare the form for editing, we need to:
* - remove prohibited fields from the document (passwords | ADD YOURS WHEN THERES A NEW ONE)
* - swap out count objects on relations for empty arrays
* - set __temp_key__ on array objects for drag & drop
*
* We also prepare the form for new documents, so we need to:
* - set default values on fields
*/ const getInitialFormValues = React__namespace.useCallback((isCreatingDocument = false)=>{
if (!document && !isCreatingDocument && !isSingleType || !schema) {
return undefined;
}
/**
* Check that we have an ID so we know the
* document has been created in some way.
*/ const form = document?.id ? document : forms.createDefaultForm(schema, components);
return data.transformDocument(schema, components)(form);
}, [
document,
isSingleType,
schema,
components
]);
const isLoading = isLoadingDocument || isFetchingDocument || isLoadingSchema;
const hasError = !!error;
return {
components,
document,
meta,
isLoading,
hasError,
schema,
schemas,
validate,
getTitle,
getInitialFormValues,
refetch
};
};
/* -------------------------------------------------------------------------------------------------
* useDoc
* -----------------------------------------------------------------------------------------------*/ /**
* @internal this hook uses the router to extract the model, collection type & id from the url.
* therefore, it shouldn't be used outside of the content-manager because it won't work as intended.
*/ const useDoc = ()=>{
const { id, slug, collectionType, origin } = reactRouterDom.useParams();
const [{ query }] = strapiAdmin.useQueryParams();
const params = React__namespace.useMemo(()=>api.buildValidParams(query), [
query
]);
if (!collectionType) {
throw new Error('Could not find collectionType in url params');
}
if (!slug) {
throw new Error('Could not find model in url params');
}
const document = useDocument({
documentId: origin || id,
model: slug,
collectionType,
params
}, {
skip: id === 'create' || !origin && !id && collectionType !== collections.SINGLE_TYPES
});
const returnId = origin || id === 'create' ? undefined : id;
return {
collectionType,
model: slug,
id: returnId,
...document
};
};
/**
* @public
* @experimental
* Content manager context hooks for plugin development.
* Make sure to use this hook inside the content manager.
*/ const useContentManagerContext = ()=>{
const { collectionType, model, id, components, isLoading: isLoadingDoc, schema, schemas } = useDoc();
const layout = useDocumentLayout.useDocumentLayout(model);
const form = strapiAdmin.useForm('useContentManagerContext', (state)=>state);
const isSingleType = collectionType === collections.SINGLE_TYPES;
const slug = model;
const isCreatingEntry = id === 'create';
useContentTypeSchema.useContentTypeSchema();
const isLoading = isLoadingDoc || layout.isLoading;
const error = layout.error;
return {
error,
isLoading,
// Base metadata
model,
collectionType,
id,
slug,
isCreatingEntry,
isSingleType,
hasDraftAndPublish: schema?.options?.draftAndPublish ?? false,
// All schema infos
components,
contentType: schema,
contentTypes: schemas,
// Form state
form,
// layout infos
layout
};
};
exports.useContentManagerContext = useContentManagerContext;
exports.useDoc = useDoc;
exports.useDocument = useDocument;
//# sourceMappingURL=useDocument.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,221 @@
import * as React from 'react';
import { useNotification, useAPIErrorHandler, getYupValidationErrors, useForm, useQueryParams } from '@strapi/admin/strapi-admin';
import { useIntl } from 'react-intl';
import { useParams } from 'react-router-dom';
import { ValidationError } from 'yup';
import { SINGLE_TYPES } from '../constants/collections.mjs';
import { transformDocument } from '../pages/EditView/utils/data.mjs';
import { createDefaultForm } from '../pages/EditView/utils/forms.mjs';
import { useGetDocumentQuery } from '../services/documents.mjs';
import { buildValidParams } from '../utils/api.mjs';
import { createYupSchema } from '../utils/validation.mjs';
import { useContentTypeSchema } from './useContentTypeSchema.mjs';
import { useDocumentLayout } from './useDocumentLayout.mjs';
/* -------------------------------------------------------------------------------------------------
* useDocument
* -----------------------------------------------------------------------------------------------*/ /**
* @alpha
* @public
* @description Returns a document based on the model, collection type & id passed as arguments.
* Also extracts its schema from the redux cache to be used for creating a validation schema.
* @example
* ```tsx
* const { id, model, collectionType } = useParams<{ id: string; model: string; collectionType: string }>();
*
* if(!model || !collectionType) return null;
*
* const { document, isLoading, validate } = useDocument({ documentId: id, model, collectionType, params: { locale: 'en-GB' } })
* const { update } = useDocumentActions()
*
* const onSubmit = async (document: Document) => {
* const errors = validate(document);
*
* if(errors) {
* // handle errors
* }
*
* await update({ collectionType, model, id }, document)
* }
* ```
*
* @see {@link https://contributor.strapi.io/docs/core/content-manager/hooks/use-document} for more information
*/ const useDocument = (args, opts)=>{
const { toggleNotification } = useNotification();
const { _unstableFormatAPIError: formatAPIError } = useAPIErrorHandler();
const { formatMessage } = useIntl();
const { currentData: data, isLoading: isLoadingDocument, isFetching: isFetchingDocument, error, refetch } = useGetDocumentQuery(args, {
...opts,
skip: !args.documentId && args.collectionType !== SINGLE_TYPES || opts?.skip
});
const document = data?.data;
const meta = data?.meta;
const { components, schema, schemas, isLoading: isLoadingSchema } = useContentTypeSchema(args.model);
const isSingleType = schema?.kind === 'singleType';
const getTitle = (mainField)=>{
// Always use mainField if it's not an id
if (mainField !== 'id' && document?.[mainField]) {
return document[mainField];
}
// When it's a singleType without a mainField, use the contentType displayName
if (isSingleType && schema?.info.displayName) {
return schema.info.displayName;
}
// Otherwise, use a fallback
return formatMessage({
id: 'content-manager.containers.untitled',
defaultMessage: 'Untitled'
});
};
React.useEffect(()=>{
if (error) {
toggleNotification({
type: 'danger',
message: formatAPIError(error)
});
}
}, [
toggleNotification,
error,
formatAPIError,
args.collectionType
]);
const validationSchema = React.useMemo(()=>{
if (!schema) {
return null;
}
return createYupSchema(schema.attributes, components);
}, [
schema,
components
]);
const validate = React.useCallback((document)=>{
if (!validationSchema) {
throw new Error('There is no validation schema generated, this is likely due to the schema not being loaded yet.');
}
try {
validationSchema.validateSync(document, {
abortEarly: false,
strict: true
});
return null;
} catch (error) {
if (error instanceof ValidationError) {
return getYupValidationErrors(error);
}
throw error;
}
}, [
validationSchema
]);
/**
* Here we prepare the form for editing, we need to:
* - remove prohibited fields from the document (passwords | ADD YOURS WHEN THERES A NEW ONE)
* - swap out count objects on relations for empty arrays
* - set __temp_key__ on array objects for drag & drop
*
* We also prepare the form for new documents, so we need to:
* - set default values on fields
*/ const getInitialFormValues = React.useCallback((isCreatingDocument = false)=>{
if (!document && !isCreatingDocument && !isSingleType || !schema) {
return undefined;
}
/**
* Check that we have an ID so we know the
* document has been created in some way.
*/ const form = document?.id ? document : createDefaultForm(schema, components);
return transformDocument(schema, components)(form);
}, [
document,
isSingleType,
schema,
components
]);
const isLoading = isLoadingDocument || isFetchingDocument || isLoadingSchema;
const hasError = !!error;
return {
components,
document,
meta,
isLoading,
hasError,
schema,
schemas,
validate,
getTitle,
getInitialFormValues,
refetch
};
};
/* -------------------------------------------------------------------------------------------------
* useDoc
* -----------------------------------------------------------------------------------------------*/ /**
* @internal this hook uses the router to extract the model, collection type & id from the url.
* therefore, it shouldn't be used outside of the content-manager because it won't work as intended.
*/ const useDoc = ()=>{
const { id, slug, collectionType, origin } = useParams();
const [{ query }] = useQueryParams();
const params = React.useMemo(()=>buildValidParams(query), [
query
]);
if (!collectionType) {
throw new Error('Could not find collectionType in url params');
}
if (!slug) {
throw new Error('Could not find model in url params');
}
const document = useDocument({
documentId: origin || id,
model: slug,
collectionType,
params
}, {
skip: id === 'create' || !origin && !id && collectionType !== SINGLE_TYPES
});
const returnId = origin || id === 'create' ? undefined : id;
return {
collectionType,
model: slug,
id: returnId,
...document
};
};
/**
* @public
* @experimental
* Content manager context hooks for plugin development.
* Make sure to use this hook inside the content manager.
*/ const useContentManagerContext = ()=>{
const { collectionType, model, id, components, isLoading: isLoadingDoc, schema, schemas } = useDoc();
const layout = useDocumentLayout(model);
const form = useForm('useContentManagerContext', (state)=>state);
const isSingleType = collectionType === SINGLE_TYPES;
const slug = model;
const isCreatingEntry = id === 'create';
useContentTypeSchema();
const isLoading = isLoadingDoc || layout.isLoading;
const error = layout.error;
return {
error,
isLoading,
// Base metadata
model,
collectionType,
id,
slug,
isCreatingEntry,
isSingleType,
hasDraftAndPublish: schema?.options?.draftAndPublish ?? false,
// All schema infos
components,
contentType: schema,
contentTypes: schemas,
// Form state
form,
// layout infos
layout
};
};
export { useContentManagerContext, useDoc, useDocument };
//# sourceMappingURL=useDocument.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,607 @@
'use strict';
var React = require('react');
var strapiAdmin = require('@strapi/admin/strapi-admin');
var reactIntl = require('react-intl');
var reactRouterDom = require('react-router-dom');
var RelationModal = require('../pages/EditView/components/FormInputs/Relations/RelationModal.js');
var Preview = require('../preview/pages/Preview.js');
var documents = require('../services/documents.js');
var translations = require('../utils/translations.js');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
const DEFAULT_UNEXPECTED_ERROR_MSG = {
id: 'notification.error',
defaultMessage: 'An error occurred, please try again'
};
/**
* @alpha
* @public
* @description Contains all the operations that can be performed on a single document.
* Designed to be able to be used anywhere within a Strapi app. The hooks will handle
* notifications should the operation fail, however the response is always returned incase
* the user needs to handle side-effects.
* @example
* ```tsx
* import { Form } from '@strapi/admin/admin';
*
* const { id, model, collectionType } = useParams<{ id: string; model: string; collectionType: string }>();
* const { update } = useDocumentActions();
*
* const handleSubmit = async (data) => {
* await update({ collectionType, model, documentId: id }, data);
* }
*
* return <Form method="PUT" onSubmit={handleSubmit} />
* ```
*
* @see {@link https://contributor.strapi.io/docs/core/content-manager/hooks/use-document-operations} for more information
*/ const useDocumentActions = ()=>{
const { toggleNotification } = strapiAdmin.useNotification();
const { formatMessage } = reactIntl.useIntl();
const { trackUsage } = strapiAdmin.useTracking();
const { _unstableFormatAPIError: formatAPIError } = strapiAdmin.useAPIErrorHandler();
const navigate = reactRouterDom.useNavigate();
const setCurrentStep = strapiAdmin.useGuidedTour('useDocumentActions', (state)=>state.setCurrentStep);
// Get metadata from context providers for tracking purposes
const previewContext = Preview.usePreviewContext('useDocumentActions', ()=>true, false);
const relationContext = RelationModal.useRelationModal('useDocumentActions', ()=>true, false);
const fromPreview = previewContext != undefined;
const fromRelationModal = relationContext != undefined;
const [deleteDocument, { isLoading: isDeleting }] = documents.useDeleteDocumentMutation();
const _delete = React__namespace.useCallback(async ({ collectionType, model, documentId, params }, trackerProperty)=>{
try {
trackUsage('willDeleteEntry', trackerProperty);
const res = await deleteDocument({
collectionType,
model,
documentId,
params
});
if ('error' in res) {
toggleNotification({
type: 'danger',
message: formatAPIError(res.error)
});
return {
error: res.error
};
}
toggleNotification({
type: 'success',
message: formatMessage({
id: translations.getTranslation('success.record.delete'),
defaultMessage: 'Deleted document'
})
});
trackUsage('didDeleteEntry', trackerProperty);
return res.data;
} catch (err) {
toggleNotification({
type: 'danger',
message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
});
trackUsage('didNotDeleteEntry', {
error: err,
...trackerProperty
});
throw err;
}
}, [
trackUsage,
deleteDocument,
toggleNotification,
formatMessage,
formatAPIError
]);
const [deleteManyDocuments, { isLoading: isDeletingMany }] = documents.useDeleteManyDocumentsMutation();
const deleteMany = React__namespace.useCallback(async ({ model, documentIds, params })=>{
try {
trackUsage('willBulkDeleteEntries');
const res = await deleteManyDocuments({
model,
documentIds,
params
});
if ('error' in res) {
toggleNotification({
type: 'danger',
message: formatAPIError(res.error)
});
return {
error: res.error
};
}
toggleNotification({
type: 'success',
title: formatMessage({
id: translations.getTranslation('success.records.delete'),
defaultMessage: 'Successfully deleted.'
}),
message: ''
});
trackUsage('didBulkDeleteEntries');
return res.data;
} catch (err) {
toggleNotification({
type: 'danger',
message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
});
trackUsage('didNotBulkDeleteEntries');
throw err;
}
}, [
trackUsage,
deleteManyDocuments,
toggleNotification,
formatMessage,
formatAPIError
]);
const [discardDocument, { isLoading: isDiscardingDocument }] = documents.useDiscardDocumentMutation();
const discard = React__namespace.useCallback(async ({ collectionType, model, documentId, params })=>{
try {
const res = await discardDocument({
collectionType,
model,
documentId,
params
});
if ('error' in res) {
toggleNotification({
type: 'danger',
message: formatAPIError(res.error)
});
return {
error: res.error
};
}
toggleNotification({
type: 'success',
message: formatMessage({
id: 'content-manager.success.record.discard',
defaultMessage: 'Changes discarded'
})
});
return res.data;
} catch (err) {
toggleNotification({
type: 'danger',
message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
});
throw err;
}
}, [
discardDocument,
formatAPIError,
formatMessage,
toggleNotification
]);
const [publishDocument, { isLoading: isPublishing }] = documents.usePublishDocumentMutation();
const publish = React__namespace.useCallback(async ({ collectionType, model, documentId, params }, data)=>{
try {
trackUsage('willPublishEntry', {
documentId
});
const res = await publishDocument({
collectionType,
model,
documentId,
data,
params
});
if ('error' in res) {
toggleNotification({
type: 'danger',
message: formatAPIError(res.error)
});
return {
error: res.error
};
}
trackUsage('didPublishEntry', {
documentId,
fromPreview,
fromRelationModal
});
toggleNotification({
type: 'success',
message: formatMessage({
id: translations.getTranslation('success.record.publish'),
defaultMessage: 'Published document'
})
});
return res.data;
} catch (err) {
toggleNotification({
type: 'danger',
message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
});
throw err;
}
}, [
trackUsage,
publishDocument,
fromPreview,
fromRelationModal,
toggleNotification,
formatMessage,
formatAPIError
]);
const [publishManyDocuments, { isLoading: isPublishingMany }] = documents.usePublishManyDocumentsMutation();
const publishMany = React__namespace.useCallback(async ({ model, documentIds, params })=>{
try {
// TODO Confirm tracking events for bulk publish?
const res = await publishManyDocuments({
model,
documentIds,
params
});
if ('error' in res) {
toggleNotification({
type: 'danger',
message: formatAPIError(res.error)
});
return {
error: res.error
};
}
toggleNotification({
type: 'success',
message: formatMessage({
id: translations.getTranslation('success.record.publish'),
defaultMessage: 'Published document'
})
});
return res.data;
} catch (err) {
toggleNotification({
type: 'danger',
message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
});
throw err;
}
}, [
// trackUsage,
publishManyDocuments,
toggleNotification,
formatMessage,
formatAPIError
]);
const [updateDocument, { isLoading: isUpdating }] = documents.useUpdateDocumentMutation();
const update = React__namespace.useCallback(async ({ collectionType, model, documentId, params }, data, trackerProperty)=>{
try {
trackUsage('willEditEntry', trackerProperty);
const res = await updateDocument({
collectionType,
model,
documentId,
data,
params
});
if ('error' in res) {
toggleNotification({
type: 'danger',
message: formatAPIError(res.error)
});
trackUsage('didNotEditEntry', {
error: res.error,
...trackerProperty
});
return {
error: res.error
};
}
trackUsage('didEditEntry', {
...trackerProperty,
documentId: res.data.data.documentId,
fromPreview,
fromRelationModal
});
toggleNotification({
type: 'success',
message: formatMessage({
id: translations.getTranslation('success.record.save'),
defaultMessage: 'Saved document'
})
});
return res.data;
} catch (err) {
trackUsage('didNotEditEntry', {
error: err,
...trackerProperty
});
toggleNotification({
type: 'danger',
message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
});
throw err;
}
}, [
trackUsage,
updateDocument,
fromPreview,
fromRelationModal,
toggleNotification,
formatMessage,
formatAPIError
]);
const [unpublishDocument] = documents.useUnpublishDocumentMutation();
const unpublish = React__namespace.useCallback(async ({ collectionType, model, documentId, params }, discardDraft = false)=>{
try {
trackUsage('willUnpublishEntry');
const res = await unpublishDocument({
collectionType,
model,
documentId,
params,
data: {
discardDraft
}
});
if ('error' in res) {
toggleNotification({
type: 'danger',
message: formatAPIError(res.error)
});
return {
error: res.error
};
}
trackUsage('didUnpublishEntry');
toggleNotification({
type: 'success',
message: formatMessage({
id: translations.getTranslation('success.record.unpublish'),
defaultMessage: 'Unpublished document'
})
});
return res.data;
} catch (err) {
toggleNotification({
type: 'danger',
message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
});
throw err;
}
}, [
trackUsage,
unpublishDocument,
toggleNotification,
formatMessage,
formatAPIError
]);
const [unpublishManyDocuments, { isLoading: isUnpublishingMany }] = documents.useUnpublishManyDocumentsMutation();
const unpublishMany = React__namespace.useCallback(async ({ model, documentIds, params })=>{
try {
trackUsage('willBulkUnpublishEntries');
const res = await unpublishManyDocuments({
model,
documentIds,
params
});
if ('error' in res) {
toggleNotification({
type: 'danger',
message: formatAPIError(res.error)
});
return {
error: res.error
};
}
trackUsage('didBulkUnpublishEntries');
toggleNotification({
type: 'success',
title: formatMessage({
id: translations.getTranslation('success.records.unpublish'),
defaultMessage: 'Successfully unpublished.'
}),
message: ''
});
return res.data;
} catch (err) {
toggleNotification({
type: 'danger',
message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
});
trackUsage('didNotBulkUnpublishEntries');
throw err;
}
}, [
trackUsage,
unpublishManyDocuments,
toggleNotification,
formatMessage,
formatAPIError
]);
const [createDocument] = documents.useCreateDocumentMutation();
const create = React__namespace.useCallback(async ({ model, params }, data, trackerProperty)=>{
try {
const res = await createDocument({
model,
data,
params
});
if ('error' in res) {
toggleNotification({
type: 'danger',
message: formatAPIError(res.error)
});
trackUsage('didNotCreateEntry', {
error: res.error,
...trackerProperty
});
return {
error: res.error
};
}
trackUsage('didCreateEntry', {
...trackerProperty,
documentId: res.data.data.documentId,
fromPreview,
fromRelationModal
});
toggleNotification({
type: 'success',
message: formatMessage({
id: translations.getTranslation('success.record.save'),
defaultMessage: 'Saved document'
})
});
setCurrentStep('contentManager.success');
return res.data;
} catch (err) {
toggleNotification({
type: 'danger',
message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
});
trackUsage('didNotCreateEntry', {
error: err,
...trackerProperty
});
throw err;
}
}, [
createDocument,
formatAPIError,
formatMessage,
fromPreview,
fromRelationModal,
setCurrentStep,
toggleNotification,
trackUsage
]);
const [autoCloneDocument] = documents.useAutoCloneDocumentMutation();
const autoClone = React__namespace.useCallback(async ({ model, sourceId })=>{
try {
const res = await autoCloneDocument({
model,
sourceId
});
if ('error' in res) {
return {
error: res.error
};
}
toggleNotification({
type: 'success',
message: formatMessage({
id: translations.getTranslation('success.record.clone'),
defaultMessage: 'Cloned document'
})
});
return res.data;
} catch (err) {
toggleNotification({
type: 'danger',
message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
});
throw err;
}
}, [
autoCloneDocument,
formatMessage,
toggleNotification
]);
const [cloneDocument] = documents.useCloneDocumentMutation();
const clone = React__namespace.useCallback(async ({ model, documentId, params }, body, trackerProperty)=>{
try {
const { id: _id, ...restBody } = body;
/**
* If we're cloning we want to post directly to this endpoint
* so that the relations even if they're not listed in the EditView
* are correctly attached to the entry.
*/ const res = await cloneDocument({
model,
sourceId: documentId,
data: restBody,
params
});
if ('error' in res) {
toggleNotification({
type: 'danger',
message: formatAPIError(res.error)
});
trackUsage('didNotCreateEntry', {
error: res.error,
...trackerProperty
});
return {
error: res.error
};
}
trackUsage('didCreateEntry', trackerProperty);
toggleNotification({
type: 'success',
message: formatMessage({
id: translations.getTranslation('success.record.clone'),
defaultMessage: 'Cloned document'
})
});
// Redirect to normal edit view
navigate(`../../${res.data.data.documentId}`, {
relative: 'path'
});
return res.data;
} catch (err) {
toggleNotification({
type: 'danger',
message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
});
trackUsage('didNotCreateEntry', {
error: err,
...trackerProperty
});
throw err;
}
}, [
cloneDocument,
trackUsage,
toggleNotification,
formatMessage,
formatAPIError,
navigate
]);
const [getDoc] = documents.useLazyGetDocumentQuery();
const getDocument = React__namespace.useCallback(async (args)=>{
const { data } = await getDoc(args);
return data;
}, [
getDoc
]);
return {
isLoading: isPublishing || isUpdating || isDiscardingDocument || isDeleting || isDeletingMany || isUnpublishingMany || isPublishingMany,
autoClone,
clone,
create,
delete: _delete,
deleteMany,
discard,
getDocument,
publish,
publishMany,
unpublish,
unpublishMany,
update
};
};
exports.useDocumentActions = useDocumentActions;
//# sourceMappingURL=useDocumentActions.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,586 @@
import * as React from 'react';
import { useNotification, useTracking, useAPIErrorHandler, useGuidedTour } from '@strapi/admin/strapi-admin';
import { useIntl } from 'react-intl';
import { useNavigate } from 'react-router-dom';
import { useRelationModal } from '../pages/EditView/components/FormInputs/Relations/RelationModal.mjs';
import { usePreviewContext } from '../preview/pages/Preview.mjs';
import { useDeleteDocumentMutation, useDeleteManyDocumentsMutation, useDiscardDocumentMutation, usePublishDocumentMutation, usePublishManyDocumentsMutation, useUpdateDocumentMutation, useUnpublishDocumentMutation, useUnpublishManyDocumentsMutation, useCreateDocumentMutation, useAutoCloneDocumentMutation, useCloneDocumentMutation, useLazyGetDocumentQuery } from '../services/documents.mjs';
import { getTranslation } from '../utils/translations.mjs';
const DEFAULT_UNEXPECTED_ERROR_MSG = {
id: 'notification.error',
defaultMessage: 'An error occurred, please try again'
};
/**
* @alpha
* @public
* @description Contains all the operations that can be performed on a single document.
* Designed to be able to be used anywhere within a Strapi app. The hooks will handle
* notifications should the operation fail, however the response is always returned incase
* the user needs to handle side-effects.
* @example
* ```tsx
* import { Form } from '@strapi/admin/admin';
*
* const { id, model, collectionType } = useParams<{ id: string; model: string; collectionType: string }>();
* const { update } = useDocumentActions();
*
* const handleSubmit = async (data) => {
* await update({ collectionType, model, documentId: id }, data);
* }
*
* return <Form method="PUT" onSubmit={handleSubmit} />
* ```
*
* @see {@link https://contributor.strapi.io/docs/core/content-manager/hooks/use-document-operations} for more information
*/ const useDocumentActions = ()=>{
const { toggleNotification } = useNotification();
const { formatMessage } = useIntl();
const { trackUsage } = useTracking();
const { _unstableFormatAPIError: formatAPIError } = useAPIErrorHandler();
const navigate = useNavigate();
const setCurrentStep = useGuidedTour('useDocumentActions', (state)=>state.setCurrentStep);
// Get metadata from context providers for tracking purposes
const previewContext = usePreviewContext('useDocumentActions', ()=>true, false);
const relationContext = useRelationModal('useDocumentActions', ()=>true, false);
const fromPreview = previewContext != undefined;
const fromRelationModal = relationContext != undefined;
const [deleteDocument, { isLoading: isDeleting }] = useDeleteDocumentMutation();
const _delete = React.useCallback(async ({ collectionType, model, documentId, params }, trackerProperty)=>{
try {
trackUsage('willDeleteEntry', trackerProperty);
const res = await deleteDocument({
collectionType,
model,
documentId,
params
});
if ('error' in res) {
toggleNotification({
type: 'danger',
message: formatAPIError(res.error)
});
return {
error: res.error
};
}
toggleNotification({
type: 'success',
message: formatMessage({
id: getTranslation('success.record.delete'),
defaultMessage: 'Deleted document'
})
});
trackUsage('didDeleteEntry', trackerProperty);
return res.data;
} catch (err) {
toggleNotification({
type: 'danger',
message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
});
trackUsage('didNotDeleteEntry', {
error: err,
...trackerProperty
});
throw err;
}
}, [
trackUsage,
deleteDocument,
toggleNotification,
formatMessage,
formatAPIError
]);
const [deleteManyDocuments, { isLoading: isDeletingMany }] = useDeleteManyDocumentsMutation();
const deleteMany = React.useCallback(async ({ model, documentIds, params })=>{
try {
trackUsage('willBulkDeleteEntries');
const res = await deleteManyDocuments({
model,
documentIds,
params
});
if ('error' in res) {
toggleNotification({
type: 'danger',
message: formatAPIError(res.error)
});
return {
error: res.error
};
}
toggleNotification({
type: 'success',
title: formatMessage({
id: getTranslation('success.records.delete'),
defaultMessage: 'Successfully deleted.'
}),
message: ''
});
trackUsage('didBulkDeleteEntries');
return res.data;
} catch (err) {
toggleNotification({
type: 'danger',
message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
});
trackUsage('didNotBulkDeleteEntries');
throw err;
}
}, [
trackUsage,
deleteManyDocuments,
toggleNotification,
formatMessage,
formatAPIError
]);
const [discardDocument, { isLoading: isDiscardingDocument }] = useDiscardDocumentMutation();
const discard = React.useCallback(async ({ collectionType, model, documentId, params })=>{
try {
const res = await discardDocument({
collectionType,
model,
documentId,
params
});
if ('error' in res) {
toggleNotification({
type: 'danger',
message: formatAPIError(res.error)
});
return {
error: res.error
};
}
toggleNotification({
type: 'success',
message: formatMessage({
id: 'content-manager.success.record.discard',
defaultMessage: 'Changes discarded'
})
});
return res.data;
} catch (err) {
toggleNotification({
type: 'danger',
message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
});
throw err;
}
}, [
discardDocument,
formatAPIError,
formatMessage,
toggleNotification
]);
const [publishDocument, { isLoading: isPublishing }] = usePublishDocumentMutation();
const publish = React.useCallback(async ({ collectionType, model, documentId, params }, data)=>{
try {
trackUsage('willPublishEntry', {
documentId
});
const res = await publishDocument({
collectionType,
model,
documentId,
data,
params
});
if ('error' in res) {
toggleNotification({
type: 'danger',
message: formatAPIError(res.error)
});
return {
error: res.error
};
}
trackUsage('didPublishEntry', {
documentId,
fromPreview,
fromRelationModal
});
toggleNotification({
type: 'success',
message: formatMessage({
id: getTranslation('success.record.publish'),
defaultMessage: 'Published document'
})
});
return res.data;
} catch (err) {
toggleNotification({
type: 'danger',
message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
});
throw err;
}
}, [
trackUsage,
publishDocument,
fromPreview,
fromRelationModal,
toggleNotification,
formatMessage,
formatAPIError
]);
const [publishManyDocuments, { isLoading: isPublishingMany }] = usePublishManyDocumentsMutation();
const publishMany = React.useCallback(async ({ model, documentIds, params })=>{
try {
// TODO Confirm tracking events for bulk publish?
const res = await publishManyDocuments({
model,
documentIds,
params
});
if ('error' in res) {
toggleNotification({
type: 'danger',
message: formatAPIError(res.error)
});
return {
error: res.error
};
}
toggleNotification({
type: 'success',
message: formatMessage({
id: getTranslation('success.record.publish'),
defaultMessage: 'Published document'
})
});
return res.data;
} catch (err) {
toggleNotification({
type: 'danger',
message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
});
throw err;
}
}, [
// trackUsage,
publishManyDocuments,
toggleNotification,
formatMessage,
formatAPIError
]);
const [updateDocument, { isLoading: isUpdating }] = useUpdateDocumentMutation();
const update = React.useCallback(async ({ collectionType, model, documentId, params }, data, trackerProperty)=>{
try {
trackUsage('willEditEntry', trackerProperty);
const res = await updateDocument({
collectionType,
model,
documentId,
data,
params
});
if ('error' in res) {
toggleNotification({
type: 'danger',
message: formatAPIError(res.error)
});
trackUsage('didNotEditEntry', {
error: res.error,
...trackerProperty
});
return {
error: res.error
};
}
trackUsage('didEditEntry', {
...trackerProperty,
documentId: res.data.data.documentId,
fromPreview,
fromRelationModal
});
toggleNotification({
type: 'success',
message: formatMessage({
id: getTranslation('success.record.save'),
defaultMessage: 'Saved document'
})
});
return res.data;
} catch (err) {
trackUsage('didNotEditEntry', {
error: err,
...trackerProperty
});
toggleNotification({
type: 'danger',
message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
});
throw err;
}
}, [
trackUsage,
updateDocument,
fromPreview,
fromRelationModal,
toggleNotification,
formatMessage,
formatAPIError
]);
const [unpublishDocument] = useUnpublishDocumentMutation();
const unpublish = React.useCallback(async ({ collectionType, model, documentId, params }, discardDraft = false)=>{
try {
trackUsage('willUnpublishEntry');
const res = await unpublishDocument({
collectionType,
model,
documentId,
params,
data: {
discardDraft
}
});
if ('error' in res) {
toggleNotification({
type: 'danger',
message: formatAPIError(res.error)
});
return {
error: res.error
};
}
trackUsage('didUnpublishEntry');
toggleNotification({
type: 'success',
message: formatMessage({
id: getTranslation('success.record.unpublish'),
defaultMessage: 'Unpublished document'
})
});
return res.data;
} catch (err) {
toggleNotification({
type: 'danger',
message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
});
throw err;
}
}, [
trackUsage,
unpublishDocument,
toggleNotification,
formatMessage,
formatAPIError
]);
const [unpublishManyDocuments, { isLoading: isUnpublishingMany }] = useUnpublishManyDocumentsMutation();
const unpublishMany = React.useCallback(async ({ model, documentIds, params })=>{
try {
trackUsage('willBulkUnpublishEntries');
const res = await unpublishManyDocuments({
model,
documentIds,
params
});
if ('error' in res) {
toggleNotification({
type: 'danger',
message: formatAPIError(res.error)
});
return {
error: res.error
};
}
trackUsage('didBulkUnpublishEntries');
toggleNotification({
type: 'success',
title: formatMessage({
id: getTranslation('success.records.unpublish'),
defaultMessage: 'Successfully unpublished.'
}),
message: ''
});
return res.data;
} catch (err) {
toggleNotification({
type: 'danger',
message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
});
trackUsage('didNotBulkUnpublishEntries');
throw err;
}
}, [
trackUsage,
unpublishManyDocuments,
toggleNotification,
formatMessage,
formatAPIError
]);
const [createDocument] = useCreateDocumentMutation();
const create = React.useCallback(async ({ model, params }, data, trackerProperty)=>{
try {
const res = await createDocument({
model,
data,
params
});
if ('error' in res) {
toggleNotification({
type: 'danger',
message: formatAPIError(res.error)
});
trackUsage('didNotCreateEntry', {
error: res.error,
...trackerProperty
});
return {
error: res.error
};
}
trackUsage('didCreateEntry', {
...trackerProperty,
documentId: res.data.data.documentId,
fromPreview,
fromRelationModal
});
toggleNotification({
type: 'success',
message: formatMessage({
id: getTranslation('success.record.save'),
defaultMessage: 'Saved document'
})
});
setCurrentStep('contentManager.success');
return res.data;
} catch (err) {
toggleNotification({
type: 'danger',
message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
});
trackUsage('didNotCreateEntry', {
error: err,
...trackerProperty
});
throw err;
}
}, [
createDocument,
formatAPIError,
formatMessage,
fromPreview,
fromRelationModal,
setCurrentStep,
toggleNotification,
trackUsage
]);
const [autoCloneDocument] = useAutoCloneDocumentMutation();
const autoClone = React.useCallback(async ({ model, sourceId })=>{
try {
const res = await autoCloneDocument({
model,
sourceId
});
if ('error' in res) {
return {
error: res.error
};
}
toggleNotification({
type: 'success',
message: formatMessage({
id: getTranslation('success.record.clone'),
defaultMessage: 'Cloned document'
})
});
return res.data;
} catch (err) {
toggleNotification({
type: 'danger',
message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
});
throw err;
}
}, [
autoCloneDocument,
formatMessage,
toggleNotification
]);
const [cloneDocument] = useCloneDocumentMutation();
const clone = React.useCallback(async ({ model, documentId, params }, body, trackerProperty)=>{
try {
const { id: _id, ...restBody } = body;
/**
* If we're cloning we want to post directly to this endpoint
* so that the relations even if they're not listed in the EditView
* are correctly attached to the entry.
*/ const res = await cloneDocument({
model,
sourceId: documentId,
data: restBody,
params
});
if ('error' in res) {
toggleNotification({
type: 'danger',
message: formatAPIError(res.error)
});
trackUsage('didNotCreateEntry', {
error: res.error,
...trackerProperty
});
return {
error: res.error
};
}
trackUsage('didCreateEntry', trackerProperty);
toggleNotification({
type: 'success',
message: formatMessage({
id: getTranslation('success.record.clone'),
defaultMessage: 'Cloned document'
})
});
// Redirect to normal edit view
navigate(`../../${res.data.data.documentId}`, {
relative: 'path'
});
return res.data;
} catch (err) {
toggleNotification({
type: 'danger',
message: formatMessage(DEFAULT_UNEXPECTED_ERROR_MSG)
});
trackUsage('didNotCreateEntry', {
error: err,
...trackerProperty
});
throw err;
}
}, [
cloneDocument,
trackUsage,
toggleNotification,
formatMessage,
formatAPIError,
navigate
]);
const [getDoc] = useLazyGetDocumentQuery();
const getDocument = React.useCallback(async (args)=>{
const { data } = await getDoc(args);
return data;
}, [
getDoc
]);
return {
isLoading: isPublishing || isUpdating || isDiscardingDocument || isDeleting || isDeletingMany || isUnpublishingMany || isPublishingMany,
autoClone,
clone,
create,
delete: _delete,
deleteMany,
discard,
getDocument,
publish,
publishMany,
unpublish,
unpublishMany,
update
};
};
export { useDocumentActions };
//# sourceMappingURL=useDocumentActions.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,57 @@
'use strict';
var React = require('react');
var strapiAdmin = require('@strapi/admin/strapi-admin');
var useDocument = require('./useDocument.js');
var RelationModal = require('../pages/EditView/components/FormInputs/Relations/RelationModal.js');
var api = require('../utils/api.js');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
function useDocumentContext(consumerName) {
// Try to get state from the relation modal context first
const currentRelationDocumentMeta = RelationModal.useRelationModal(consumerName, (state)=>state.currentDocumentMeta, false);
const currentRelationDocument = RelationModal.useRelationModal(consumerName, (state)=>state.currentDocument, false);
// Then try to get the same state from the URL
const { collectionType, model, id: documentId } = useDocument.useDoc();
const [{ query }] = strapiAdmin.useQueryParams();
// TODO: look into why we never seem to pass any params
const params = React__namespace.useMemo(()=>api.buildValidParams(query ?? {}), [
query
]);
const urlDocumentMeta = {
collectionType,
model,
documentId: documentId,
params
};
const urlDocument = useDocument.useDocument(urlDocumentMeta);
/**
* If there's modal state, use it in priority as it's the most specific
* Fallback to the state derived from the URL, which is the default behavior,
* used for the edit view, history and preview.
*/ return {
currentDocumentMeta: currentRelationDocumentMeta ?? urlDocumentMeta,
currentDocument: currentRelationDocument ?? urlDocument
};
}
exports.useDocumentContext = useDocumentContext;
//# sourceMappingURL=useDocumentContext.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"useDocumentContext.js","sources":["../../../admin/src/hooks/useDocumentContext.ts"],"sourcesContent":["import * as React from 'react';\n\nimport { useQueryParams } from '@strapi/admin/strapi-admin';\n\nimport { useDoc, useDocument, type UseDocument } from '../hooks/useDocument';\nimport { useRelationModal } from '../pages/EditView/components/FormInputs/Relations/RelationModal';\nimport { buildValidParams } from '../utils/api';\n\ninterface DocumentMeta {\n /**\n * The equivalent of the \":id\" url param value\n * i.e. gus5a67jcboa3o2zjnz39mb1\n */\n documentId?: string;\n /**\n * The equivalent of the url \":slug\" param value\n * i.e. api::articles.article\n */\n model: string;\n /**\n * The equivalent of the url \":collectionType\" param value\n * i.e. collection-types or single-types\n */\n collectionType: string;\n /**\n * Query params object\n * i.e. { locale: 'fr' }\n */\n params?: Record<string, string | string[] | null>;\n}\n\ninterface DocumentContextValue {\n currentDocumentMeta: DocumentMeta;\n currentDocument: ReturnType<UseDocument>;\n}\n\nfunction useDocumentContext(consumerName: string): DocumentContextValue {\n // Try to get state from the relation modal context first\n const currentRelationDocumentMeta = useRelationModal(\n consumerName,\n (state) => state.currentDocumentMeta,\n false\n );\n const currentRelationDocument = useRelationModal(\n consumerName,\n (state) => state.currentDocument,\n false\n );\n\n // Then try to get the same state from the URL\n const { collectionType, model, id: documentId } = useDoc();\n const [{ query }] = useQueryParams();\n\n // TODO: look into why we never seem to pass any params\n const params = React.useMemo(() => buildValidParams(query ?? {}), [query]);\n const urlDocumentMeta: DocumentMeta = { collectionType, model, documentId: documentId!, params };\n const urlDocument = useDocument(urlDocumentMeta);\n\n /**\n * If there's modal state, use it in priority as it's the most specific\n * Fallback to the state derived from the URL, which is the default behavior,\n * used for the edit view, history and preview.\n */\n return {\n currentDocumentMeta: currentRelationDocumentMeta ?? urlDocumentMeta,\n currentDocument: currentRelationDocument ?? urlDocument,\n };\n}\n\nexport { useDocumentContext };\nexport type { DocumentMeta };\n"],"names":["useDocumentContext","consumerName","currentRelationDocumentMeta","useRelationModal","state","currentDocumentMeta","currentRelationDocument","currentDocument","collectionType","model","id","documentId","useDoc","query","useQueryParams","params","React","useMemo","buildValidParams","urlDocumentMeta","urlDocument","useDocument"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAoCA,SAASA,mBAAmBC,YAAoB,EAAA;;AAE9C,IAAA,MAAMC,8BAA8BC,8BAClCF,CAAAA,YAAAA,EACA,CAACG,KAAUA,GAAAA,KAAAA,CAAMC,mBAAmB,EACpC,KAAA,CAAA;AAEF,IAAA,MAAMC,0BAA0BH,8BAC9BF,CAAAA,YAAAA,EACA,CAACG,KAAUA,GAAAA,KAAAA,CAAMG,eAAe,EAChC,KAAA,CAAA;;IAIF,MAAM,EAAEC,cAAc,EAAEC,KAAK,EAAEC,EAAIC,EAAAA,UAAU,EAAE,GAAGC,kBAAAA,EAAAA;AAClD,IAAA,MAAM,CAAC,EAAEC,KAAK,EAAE,CAAC,GAAGC,0BAAAA,EAAAA;;IAGpB,MAAMC,MAAAA,GAASC,iBAAMC,OAAO,CAAC,IAAMC,oBAAiBL,CAAAA,KAAAA,IAAS,EAAK,CAAA,EAAA;AAACA,QAAAA;AAAM,KAAA,CAAA;AACzE,IAAA,MAAMM,eAAgC,GAAA;AAAEX,QAAAA,cAAAA;AAAgBC,QAAAA,KAAAA;QAAOE,UAAYA,EAAAA,UAAAA;AAAaI,QAAAA;AAAO,KAAA;AAC/F,IAAA,MAAMK,cAAcC,uBAAYF,CAAAA,eAAAA,CAAAA;AAEhC;;;;AAIC,MACD,OAAO;AACLd,QAAAA,mBAAAA,EAAqBH,2BAA+BiB,IAAAA,eAAAA;AACpDZ,QAAAA,eAAAA,EAAiBD,uBAA2Bc,IAAAA;AAC9C,KAAA;AACF;;;;"}

View File

@@ -0,0 +1,36 @@
import * as React from 'react';
import { useQueryParams } from '@strapi/admin/strapi-admin';
import { useDoc, useDocument } from './useDocument.mjs';
import { useRelationModal } from '../pages/EditView/components/FormInputs/Relations/RelationModal.mjs';
import { buildValidParams } from '../utils/api.mjs';
function useDocumentContext(consumerName) {
// Try to get state from the relation modal context first
const currentRelationDocumentMeta = useRelationModal(consumerName, (state)=>state.currentDocumentMeta, false);
const currentRelationDocument = useRelationModal(consumerName, (state)=>state.currentDocument, false);
// Then try to get the same state from the URL
const { collectionType, model, id: documentId } = useDoc();
const [{ query }] = useQueryParams();
// TODO: look into why we never seem to pass any params
const params = React.useMemo(()=>buildValidParams(query ?? {}), [
query
]);
const urlDocumentMeta = {
collectionType,
model,
documentId: documentId,
params
};
const urlDocument = useDocument(urlDocumentMeta);
/**
* If there's modal state, use it in priority as it's the most specific
* Fallback to the state derived from the URL, which is the default behavior,
* used for the edit view, history and preview.
*/ return {
currentDocumentMeta: currentRelationDocumentMeta ?? urlDocumentMeta,
currentDocument: currentRelationDocument ?? urlDocument
};
}
export { useDocumentContext };
//# sourceMappingURL=useDocumentContext.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"useDocumentContext.mjs","sources":["../../../admin/src/hooks/useDocumentContext.ts"],"sourcesContent":["import * as React from 'react';\n\nimport { useQueryParams } from '@strapi/admin/strapi-admin';\n\nimport { useDoc, useDocument, type UseDocument } from '../hooks/useDocument';\nimport { useRelationModal } from '../pages/EditView/components/FormInputs/Relations/RelationModal';\nimport { buildValidParams } from '../utils/api';\n\ninterface DocumentMeta {\n /**\n * The equivalent of the \":id\" url param value\n * i.e. gus5a67jcboa3o2zjnz39mb1\n */\n documentId?: string;\n /**\n * The equivalent of the url \":slug\" param value\n * i.e. api::articles.article\n */\n model: string;\n /**\n * The equivalent of the url \":collectionType\" param value\n * i.e. collection-types or single-types\n */\n collectionType: string;\n /**\n * Query params object\n * i.e. { locale: 'fr' }\n */\n params?: Record<string, string | string[] | null>;\n}\n\ninterface DocumentContextValue {\n currentDocumentMeta: DocumentMeta;\n currentDocument: ReturnType<UseDocument>;\n}\n\nfunction useDocumentContext(consumerName: string): DocumentContextValue {\n // Try to get state from the relation modal context first\n const currentRelationDocumentMeta = useRelationModal(\n consumerName,\n (state) => state.currentDocumentMeta,\n false\n );\n const currentRelationDocument = useRelationModal(\n consumerName,\n (state) => state.currentDocument,\n false\n );\n\n // Then try to get the same state from the URL\n const { collectionType, model, id: documentId } = useDoc();\n const [{ query }] = useQueryParams();\n\n // TODO: look into why we never seem to pass any params\n const params = React.useMemo(() => buildValidParams(query ?? {}), [query]);\n const urlDocumentMeta: DocumentMeta = { collectionType, model, documentId: documentId!, params };\n const urlDocument = useDocument(urlDocumentMeta);\n\n /**\n * If there's modal state, use it in priority as it's the most specific\n * Fallback to the state derived from the URL, which is the default behavior,\n * used for the edit view, history and preview.\n */\n return {\n currentDocumentMeta: currentRelationDocumentMeta ?? urlDocumentMeta,\n currentDocument: currentRelationDocument ?? urlDocument,\n };\n}\n\nexport { useDocumentContext };\nexport type { DocumentMeta };\n"],"names":["useDocumentContext","consumerName","currentRelationDocumentMeta","useRelationModal","state","currentDocumentMeta","currentRelationDocument","currentDocument","collectionType","model","id","documentId","useDoc","query","useQueryParams","params","React","useMemo","buildValidParams","urlDocumentMeta","urlDocument","useDocument"],"mappings":";;;;;;AAoCA,SAASA,mBAAmBC,YAAoB,EAAA;;AAE9C,IAAA,MAAMC,8BAA8BC,gBAClCF,CAAAA,YAAAA,EACA,CAACG,KAAUA,GAAAA,KAAAA,CAAMC,mBAAmB,EACpC,KAAA,CAAA;AAEF,IAAA,MAAMC,0BAA0BH,gBAC9BF,CAAAA,YAAAA,EACA,CAACG,KAAUA,GAAAA,KAAAA,CAAMG,eAAe,EAChC,KAAA,CAAA;;IAIF,MAAM,EAAEC,cAAc,EAAEC,KAAK,EAAEC,EAAIC,EAAAA,UAAU,EAAE,GAAGC,MAAAA,EAAAA;AAClD,IAAA,MAAM,CAAC,EAAEC,KAAK,EAAE,CAAC,GAAGC,cAAAA,EAAAA;;IAGpB,MAAMC,MAAAA,GAASC,MAAMC,OAAO,CAAC,IAAMC,gBAAiBL,CAAAA,KAAAA,IAAS,EAAK,CAAA,EAAA;AAACA,QAAAA;AAAM,KAAA,CAAA;AACzE,IAAA,MAAMM,eAAgC,GAAA;AAAEX,QAAAA,cAAAA;AAAgBC,QAAAA,KAAAA;QAAOE,UAAYA,EAAAA,UAAAA;AAAaI,QAAAA;AAAO,KAAA;AAC/F,IAAA,MAAMK,cAAcC,WAAYF,CAAAA,eAAAA,CAAAA;AAEhC;;;;AAIC,MACD,OAAO;AACLd,QAAAA,mBAAAA,EAAqBH,2BAA+BiB,IAAAA,eAAAA;AACpDZ,QAAAA,eAAAA,EAAiBD,uBAA2Bc,IAAAA;AAC9C,KAAA;AACF;;;;"}

View File

@@ -0,0 +1,311 @@
'use strict';
var React = require('react');
var strapiAdmin = require('@strapi/admin/strapi-admin');
var hooks = require('../constants/hooks.js');
var contentTypes = require('../services/contentTypes.js');
var attributes = require('../utils/attributes.js');
var useContentTypeSchema = require('./useContentTypeSchema.js');
var useDocument = require('./useDocument.js');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
/* -------------------------------------------------------------------------------------------------
* useDocumentLayout
* -----------------------------------------------------------------------------------------------*/ const DEFAULT_SETTINGS = {
bulkable: false,
filterable: false,
searchable: false,
pagination: false,
defaultSortBy: '',
defaultSortOrder: 'asc',
mainField: 'id',
pageSize: 10
};
/**
* @alpha
* @description This hook is used to get the layouts for either the edit view or list view of a specific content-type
* including the layouts for the components used in the content-type. It also runs the mutation hook waterfall so the data
* is consistent wherever it is used. It's a light wrapper around the `useDocument` hook, but provides the `skip` option a document
* is not fetched, however, it does fetch the schemas & components if they do not already exist in the cache.
*
* If the fetch fails, it will display a notification to the user.
*
* @example
* ```tsx
* const { model } = useParams<{ model: string }>();
* const { edit: { schema: layout } } = useDocumentLayout(model);
*
* return layout.map(panel => panel.map(row => row.map(field => <Field.Root {...field} />)))
* ```
*
*/ const useDocumentLayout = (model)=>{
const { schema, components } = useDocument.useDocument({
model,
collectionType: ''
}, {
skip: true
});
const [{ query }] = strapiAdmin.useQueryParams();
const runHookWaterfall = strapiAdmin.useStrapiApp('useDocumentLayout', (state)=>state.runHookWaterfall);
const { toggleNotification } = strapiAdmin.useNotification();
const { _unstableFormatAPIError: formatAPIError } = strapiAdmin.useAPIErrorHandler();
const { isLoading: isLoadingSchemas, schemas } = useContentTypeSchema.useContentTypeSchema();
const { data, isLoading: isLoadingConfigs, error, isFetching: isFetchingConfigs } = contentTypes.useGetContentTypeConfigurationQuery(model);
const isLoading = isLoadingSchemas || isFetchingConfigs || isLoadingConfigs;
React__namespace.useEffect(()=>{
if (error) {
toggleNotification({
type: 'danger',
message: formatAPIError(error)
});
}
}, [
error,
formatAPIError,
toggleNotification
]);
const editLayout = React__namespace.useMemo(()=>data && !isLoading ? formatEditLayout(data, {
schemas,
schema,
components
}) : {
layout: [],
components: {},
metadatas: {},
options: {},
settings: DEFAULT_SETTINGS
}, [
data,
isLoading,
schemas,
schema,
components
]);
const listLayout = React__namespace.useMemo(()=>{
return data && !isLoading ? formatListLayout(data, {
schemas,
schema,
components
}) : {
layout: [],
metadatas: {},
options: {},
settings: DEFAULT_SETTINGS
};
}, [
data,
isLoading,
schemas,
schema,
components
]);
const { layout: edit } = React__namespace.useMemo(()=>runHookWaterfall(hooks.HOOKS.MUTATE_EDIT_VIEW_LAYOUT, {
layout: editLayout,
query
}), [
editLayout,
query,
runHookWaterfall
]);
return {
error,
isLoading,
edit,
list: listLayout
};
};
/* -------------------------------------------------------------------------------------------------
* useDocLayout
* -----------------------------------------------------------------------------------------------*/ /**
* @internal this hook uses the internal useDoc hook, as such it shouldn't be used outside of the
* content-manager because it won't work as intended.
*/ const useDocLayout = ()=>{
const { model } = useDocument.useDoc();
return useDocumentLayout(model);
};
/**
* @internal
* @description takes the configuration data, the schema & the components used in the schema and formats the edit view
* versions of the schema & components. This is then used to render the edit view of the content-type.
*/ const formatEditLayout = (data, { schemas, schema, components })=>{
let currentPanelIndex = 0;
/**
* The fields arranged by the panels, new panels are made for dynamic zones only.
*/ const panelledEditAttributes = convertEditLayoutToFieldLayouts(data.contentType.layouts.edit, schema?.attributes, data.contentType.metadatas, {
configurations: data.components,
schemas: components
}, schemas).reduce((panels, row)=>{
if (row.some((field)=>field.type === 'dynamiczone')) {
panels.push([
row
]);
currentPanelIndex += 2;
} else {
if (!panels[currentPanelIndex]) {
panels.push([
row
]);
} else {
panels[currentPanelIndex].push(row);
}
}
return panels;
}, []);
const componentEditAttributes = Object.entries(data.components).reduce((acc, [uid, configuration])=>{
acc[uid] = {
layout: convertEditLayoutToFieldLayouts(configuration.layouts.edit, components[uid].attributes, configuration.metadatas, {
configurations: data.components,
schemas: components
}),
settings: {
...configuration.settings,
icon: components[uid].info.icon,
displayName: components[uid].info.displayName
}
};
return acc;
}, {});
const editMetadatas = Object.entries(data.contentType.metadatas).reduce((acc, [attribute, metadata])=>{
return {
...acc,
[attribute]: metadata.edit
};
}, {});
return {
layout: panelledEditAttributes,
components: componentEditAttributes,
metadatas: editMetadatas,
settings: {
...data.contentType.settings,
displayName: schema?.info.displayName
},
options: {
...schema?.options,
...schema?.pluginOptions,
...data.contentType.options
}
};
};
/* -------------------------------------------------------------------------------------------------
* convertEditLayoutToFieldLayouts
* -----------------------------------------------------------------------------------------------*/ /**
* @internal
* @description takes the edit layout from either a content-type or a component
* and formats it into a generic object that can be used to correctly render
* the form fields.
*/ const convertEditLayoutToFieldLayouts = (rows, attributes$1 = {}, metadatas, components, schemas = [])=>{
return rows.map((row)=>row.map((field)=>{
const attribute = attributes$1[field.name];
if (!attribute) {
return null;
}
const { edit: metadata } = metadatas[field.name];
const settings = attribute.type === 'component' && components ? components.configurations[attribute.component].settings : {};
return {
attribute,
disabled: !metadata.editable,
hint: metadata.description,
label: metadata.label ?? '',
name: field.name,
// @ts-expect-error mainField does exist on the metadata for a relation.
mainField: attributes.getMainField(attribute, metadata.mainField || settings.mainField, {
schemas,
components: components?.schemas ?? {}
}),
placeholder: metadata.placeholder ?? '',
required: attribute.required ?? false,
size: field.size,
unique: 'unique' in attribute ? attribute.unique : false,
visible: metadata.visible ?? true,
type: attribute.type
};
}).filter((field)=>field !== null));
};
/* -------------------------------------------------------------------------------------------------
* formatListLayout
* -----------------------------------------------------------------------------------------------*/ /**
* @internal
* @description takes the complete configuration data, the schema & the components used in the schema and
* formats a list view layout for the content-type. This is much simpler than the edit view layout as there
* are less options to consider.
*/ const formatListLayout = (data, { schemas, schema, components })=>{
const listMetadatas = Object.entries(data.contentType.metadatas).reduce((acc, [attribute, metadata])=>{
return {
...acc,
[attribute]: metadata.list
};
}, {});
/**
* The fields arranged by the panels, new panels are made for dynamic zones only.
*/ const listAttributes = convertListLayoutToFieldLayouts(data.contentType.layouts.list, schema?.attributes, listMetadatas, {
configurations: data.components,
schemas: components
}, schemas);
return {
layout: listAttributes,
settings: {
...data.contentType.settings,
displayName: schema?.info.displayName
},
metadatas: listMetadatas,
options: {
...schema?.options,
...schema?.pluginOptions,
...data.contentType.options
}
};
};
/* -------------------------------------------------------------------------------------------------
* convertListLayoutToFieldLayouts
* -----------------------------------------------------------------------------------------------*/ /**
* @internal
* @description takes the columns from the list view configuration and formats them into a generic object
* combinining metadata and attribute data.
*
* @note We do use this to reformat the list of strings when updating the displayed headers for the list view.
*/ const convertListLayoutToFieldLayouts = (columns, attributes$1 = {}, metadatas, components, schemas = [])=>{
return columns.map((name)=>{
const attribute = attributes$1[name];
if (!attribute) {
return null;
}
const metadata = metadatas[name];
const settings = attribute.type === 'component' && components ? components.configurations[attribute.component].settings : {};
return {
attribute,
label: metadata.label ?? '',
mainField: attributes.getMainField(attribute, metadata.mainField || settings.mainField, {
schemas,
components: components?.schemas ?? {}
}),
name: name,
searchable: metadata.searchable ?? true,
sortable: metadata.sortable ?? true
};
}).filter((field)=>field !== null);
};
exports.DEFAULT_SETTINGS = DEFAULT_SETTINGS;
exports.convertEditLayoutToFieldLayouts = convertEditLayoutToFieldLayouts;
exports.convertListLayoutToFieldLayouts = convertListLayoutToFieldLayouts;
exports.useDocLayout = useDocLayout;
exports.useDocumentLayout = useDocumentLayout;
//# sourceMappingURL=useDocumentLayout.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,286 @@
import * as React from 'react';
import { useQueryParams, useStrapiApp, useNotification, useAPIErrorHandler } from '@strapi/admin/strapi-admin';
import { HOOKS } from '../constants/hooks.mjs';
import { useGetContentTypeConfigurationQuery } from '../services/contentTypes.mjs';
import { getMainField } from '../utils/attributes.mjs';
import { useContentTypeSchema } from './useContentTypeSchema.mjs';
import { useDocument, useDoc } from './useDocument.mjs';
/* -------------------------------------------------------------------------------------------------
* useDocumentLayout
* -----------------------------------------------------------------------------------------------*/ const DEFAULT_SETTINGS = {
bulkable: false,
filterable: false,
searchable: false,
pagination: false,
defaultSortBy: '',
defaultSortOrder: 'asc',
mainField: 'id',
pageSize: 10
};
/**
* @alpha
* @description This hook is used to get the layouts for either the edit view or list view of a specific content-type
* including the layouts for the components used in the content-type. It also runs the mutation hook waterfall so the data
* is consistent wherever it is used. It's a light wrapper around the `useDocument` hook, but provides the `skip` option a document
* is not fetched, however, it does fetch the schemas & components if they do not already exist in the cache.
*
* If the fetch fails, it will display a notification to the user.
*
* @example
* ```tsx
* const { model } = useParams<{ model: string }>();
* const { edit: { schema: layout } } = useDocumentLayout(model);
*
* return layout.map(panel => panel.map(row => row.map(field => <Field.Root {...field} />)))
* ```
*
*/ const useDocumentLayout = (model)=>{
const { schema, components } = useDocument({
model,
collectionType: ''
}, {
skip: true
});
const [{ query }] = useQueryParams();
const runHookWaterfall = useStrapiApp('useDocumentLayout', (state)=>state.runHookWaterfall);
const { toggleNotification } = useNotification();
const { _unstableFormatAPIError: formatAPIError } = useAPIErrorHandler();
const { isLoading: isLoadingSchemas, schemas } = useContentTypeSchema();
const { data, isLoading: isLoadingConfigs, error, isFetching: isFetchingConfigs } = useGetContentTypeConfigurationQuery(model);
const isLoading = isLoadingSchemas || isFetchingConfigs || isLoadingConfigs;
React.useEffect(()=>{
if (error) {
toggleNotification({
type: 'danger',
message: formatAPIError(error)
});
}
}, [
error,
formatAPIError,
toggleNotification
]);
const editLayout = React.useMemo(()=>data && !isLoading ? formatEditLayout(data, {
schemas,
schema,
components
}) : {
layout: [],
components: {},
metadatas: {},
options: {},
settings: DEFAULT_SETTINGS
}, [
data,
isLoading,
schemas,
schema,
components
]);
const listLayout = React.useMemo(()=>{
return data && !isLoading ? formatListLayout(data, {
schemas,
schema,
components
}) : {
layout: [],
metadatas: {},
options: {},
settings: DEFAULT_SETTINGS
};
}, [
data,
isLoading,
schemas,
schema,
components
]);
const { layout: edit } = React.useMemo(()=>runHookWaterfall(HOOKS.MUTATE_EDIT_VIEW_LAYOUT, {
layout: editLayout,
query
}), [
editLayout,
query,
runHookWaterfall
]);
return {
error,
isLoading,
edit,
list: listLayout
};
};
/* -------------------------------------------------------------------------------------------------
* useDocLayout
* -----------------------------------------------------------------------------------------------*/ /**
* @internal this hook uses the internal useDoc hook, as such it shouldn't be used outside of the
* content-manager because it won't work as intended.
*/ const useDocLayout = ()=>{
const { model } = useDoc();
return useDocumentLayout(model);
};
/**
* @internal
* @description takes the configuration data, the schema & the components used in the schema and formats the edit view
* versions of the schema & components. This is then used to render the edit view of the content-type.
*/ const formatEditLayout = (data, { schemas, schema, components })=>{
let currentPanelIndex = 0;
/**
* The fields arranged by the panels, new panels are made for dynamic zones only.
*/ const panelledEditAttributes = convertEditLayoutToFieldLayouts(data.contentType.layouts.edit, schema?.attributes, data.contentType.metadatas, {
configurations: data.components,
schemas: components
}, schemas).reduce((panels, row)=>{
if (row.some((field)=>field.type === 'dynamiczone')) {
panels.push([
row
]);
currentPanelIndex += 2;
} else {
if (!panels[currentPanelIndex]) {
panels.push([
row
]);
} else {
panels[currentPanelIndex].push(row);
}
}
return panels;
}, []);
const componentEditAttributes = Object.entries(data.components).reduce((acc, [uid, configuration])=>{
acc[uid] = {
layout: convertEditLayoutToFieldLayouts(configuration.layouts.edit, components[uid].attributes, configuration.metadatas, {
configurations: data.components,
schemas: components
}),
settings: {
...configuration.settings,
icon: components[uid].info.icon,
displayName: components[uid].info.displayName
}
};
return acc;
}, {});
const editMetadatas = Object.entries(data.contentType.metadatas).reduce((acc, [attribute, metadata])=>{
return {
...acc,
[attribute]: metadata.edit
};
}, {});
return {
layout: panelledEditAttributes,
components: componentEditAttributes,
metadatas: editMetadatas,
settings: {
...data.contentType.settings,
displayName: schema?.info.displayName
},
options: {
...schema?.options,
...schema?.pluginOptions,
...data.contentType.options
}
};
};
/* -------------------------------------------------------------------------------------------------
* convertEditLayoutToFieldLayouts
* -----------------------------------------------------------------------------------------------*/ /**
* @internal
* @description takes the edit layout from either a content-type or a component
* and formats it into a generic object that can be used to correctly render
* the form fields.
*/ const convertEditLayoutToFieldLayouts = (rows, attributes = {}, metadatas, components, schemas = [])=>{
return rows.map((row)=>row.map((field)=>{
const attribute = attributes[field.name];
if (!attribute) {
return null;
}
const { edit: metadata } = metadatas[field.name];
const settings = attribute.type === 'component' && components ? components.configurations[attribute.component].settings : {};
return {
attribute,
disabled: !metadata.editable,
hint: metadata.description,
label: metadata.label ?? '',
name: field.name,
// @ts-expect-error mainField does exist on the metadata for a relation.
mainField: getMainField(attribute, metadata.mainField || settings.mainField, {
schemas,
components: components?.schemas ?? {}
}),
placeholder: metadata.placeholder ?? '',
required: attribute.required ?? false,
size: field.size,
unique: 'unique' in attribute ? attribute.unique : false,
visible: metadata.visible ?? true,
type: attribute.type
};
}).filter((field)=>field !== null));
};
/* -------------------------------------------------------------------------------------------------
* formatListLayout
* -----------------------------------------------------------------------------------------------*/ /**
* @internal
* @description takes the complete configuration data, the schema & the components used in the schema and
* formats a list view layout for the content-type. This is much simpler than the edit view layout as there
* are less options to consider.
*/ const formatListLayout = (data, { schemas, schema, components })=>{
const listMetadatas = Object.entries(data.contentType.metadatas).reduce((acc, [attribute, metadata])=>{
return {
...acc,
[attribute]: metadata.list
};
}, {});
/**
* The fields arranged by the panels, new panels are made for dynamic zones only.
*/ const listAttributes = convertListLayoutToFieldLayouts(data.contentType.layouts.list, schema?.attributes, listMetadatas, {
configurations: data.components,
schemas: components
}, schemas);
return {
layout: listAttributes,
settings: {
...data.contentType.settings,
displayName: schema?.info.displayName
},
metadatas: listMetadatas,
options: {
...schema?.options,
...schema?.pluginOptions,
...data.contentType.options
}
};
};
/* -------------------------------------------------------------------------------------------------
* convertListLayoutToFieldLayouts
* -----------------------------------------------------------------------------------------------*/ /**
* @internal
* @description takes the columns from the list view configuration and formats them into a generic object
* combinining metadata and attribute data.
*
* @note We do use this to reformat the list of strings when updating the displayed headers for the list view.
*/ const convertListLayoutToFieldLayouts = (columns, attributes = {}, metadatas, components, schemas = [])=>{
return columns.map((name)=>{
const attribute = attributes[name];
if (!attribute) {
return null;
}
const metadata = metadatas[name];
const settings = attribute.type === 'component' && components ? components.configurations[attribute.component].settings : {};
return {
attribute,
label: metadata.label ?? '',
mainField: getMainField(attribute, metadata.mainField || settings.mainField, {
schemas,
components: components?.schemas ?? {}
}),
name: name,
searchable: metadata.searchable ?? true,
sortable: metadata.sortable ?? true
};
}).filter((field)=>field !== null);
};
export { DEFAULT_SETTINGS, convertEditLayoutToFieldLayouts, convertListLayoutToFieldLayouts, useDocLayout, useDocumentLayout };
//# sourceMappingURL=useDocumentLayout.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,193 @@
'use strict';
var React = require('react');
var reactDnd = require('react-dnd');
var useKeyboardDragAndDrop = require('./useKeyboardDragAndDrop.js');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
const DIRECTIONS = {
UPWARD: 'upward',
DOWNWARD: 'downward'
};
const DROP_SENSITIVITY = {
REGULAR: 'regular',
IMMEDIATE: 'immediate'
};
/**
* A utility hook abstracting the general drag and drop hooks from react-dnd.
* Centralising the same behaviours and by default offering keyboard support.
*/ const useDragAndDrop = (active, { type = 'STRAPI_DND', index, item, onStart, onEnd, onGrabItem, onDropItem, onCancel, onMoveItem, dropSensitivity = DROP_SENSITIVITY.REGULAR })=>{
const objectRef = React__namespace.useRef(null);
const [{ handlerId, isOver }, dropRef] = reactDnd.useDrop({
accept: type,
collect (monitor) {
return {
handlerId: monitor.getHandlerId(),
isOver: monitor.isOver({
shallow: true
})
};
},
drop (item) {
const draggedIndex = item.index;
const newIndex = index;
if (isOver && onDropItem) {
onDropItem(draggedIndex, newIndex);
}
},
hover (item, monitor) {
if (!objectRef.current || !onMoveItem) {
return;
}
const dragIndex = item.index;
const newIndex = index;
const hoverBoundingRect = objectRef.current?.getBoundingClientRect();
const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
const clientOffset = monitor.getClientOffset();
if (!clientOffset) return;
const hoverClientY = clientOffset && clientOffset.y - hoverBoundingRect.top;
if (typeof dragIndex === 'number' && typeof newIndex === 'number') {
if (dragIndex === newIndex) {
// Don't replace items with themselves
return;
}
if (dropSensitivity === DROP_SENSITIVITY.REGULAR) {
// Dragging downwards
if (dragIndex < newIndex && hoverClientY < hoverMiddleY) {
return;
}
// Dragging upwards
if (dragIndex > newIndex && hoverClientY > hoverMiddleY) {
return;
}
}
// Time to actually perform the action
onMoveItem(newIndex, dragIndex);
item.index = newIndex;
} else {
// Using numbers as indices doesn't work for nested list items with path like [1, 1, 0]
if (Array.isArray(dragIndex) && Array.isArray(newIndex)) {
// Indices comparison to find item position in nested list
const minLength = Math.min(dragIndex.length, newIndex.length);
let areEqual = true;
let isLessThan = false;
let isGreaterThan = false;
for(let i = 0; i < minLength; i++){
if (dragIndex[i] < newIndex[i]) {
isLessThan = true;
areEqual = false;
break;
} else if (dragIndex[i] > newIndex[i]) {
isGreaterThan = true;
areEqual = false;
break;
}
}
// Don't replace items with themselves
if (areEqual && dragIndex.length === newIndex.length) {
return;
}
if (dropSensitivity === DROP_SENSITIVITY.REGULAR) {
// Dragging downwards
if (isLessThan && !isGreaterThan && hoverClientY < hoverMiddleY) {
return;
}
// Dragging upwards
if (isGreaterThan && !isLessThan && hoverClientY > hoverMiddleY) {
return;
}
}
}
onMoveItem(newIndex, dragIndex);
item.index = newIndex;
}
}
});
const getDragDirection = (monitor)=>{
if (monitor && monitor.isDragging() && !monitor.didDrop() && monitor.getInitialClientOffset() && monitor.getClientOffset()) {
const deltaY = monitor.getInitialClientOffset().y - monitor.getClientOffset().y;
if (deltaY > 0) return DIRECTIONS.UPWARD;
if (deltaY < 0) return DIRECTIONS.DOWNWARD;
return null;
}
return null;
};
const [{ isDragging, direction }, dragRef, dragPreviewRef] = reactDnd.useDrag({
type,
item () {
if (onStart) {
onStart();
}
/**
* This will be attached and it helps define the preview sizes
* when a component is flexy e.g. Relations
*/ const { width } = objectRef.current?.getBoundingClientRect() ?? {};
return {
index,
width,
...item
};
},
end () {
if (onEnd) {
onEnd();
}
},
canDrag: active,
/**
* This is useful when the item is in a virtualized list.
* However, if we don't have an ID then we want the libraries
* defaults to take care of this.
*/ isDragging: item?.id ? (monitor)=>{
return item.id === monitor.getItem().id;
} : undefined,
collect: (monitor)=>({
isDragging: monitor.isDragging(),
initialOffset: monitor.getInitialClientOffset(),
currentOffset: monitor.getClientOffset(),
direction: getDragDirection(monitor)
})
});
const handleKeyDown = useKeyboardDragAndDrop.useKeyboardDragAndDrop(active, index, {
onGrabItem,
onDropItem,
onCancel,
onMoveItem
});
return [
{
handlerId,
isDragging,
handleKeyDown,
isOverDropTarget: isOver,
direction
},
objectRef,
dropRef,
dragRef,
dragPreviewRef
];
};
exports.DIRECTIONS = DIRECTIONS;
exports.DROP_SENSITIVITY = DROP_SENSITIVITY;
exports.useDragAndDrop = useDragAndDrop;
//# sourceMappingURL=useDragAndDrop.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,170 @@
import * as React from 'react';
import { useDrop, useDrag } from 'react-dnd';
import { useKeyboardDragAndDrop } from './useKeyboardDragAndDrop.mjs';
const DIRECTIONS = {
UPWARD: 'upward',
DOWNWARD: 'downward'
};
const DROP_SENSITIVITY = {
REGULAR: 'regular',
IMMEDIATE: 'immediate'
};
/**
* A utility hook abstracting the general drag and drop hooks from react-dnd.
* Centralising the same behaviours and by default offering keyboard support.
*/ const useDragAndDrop = (active, { type = 'STRAPI_DND', index, item, onStart, onEnd, onGrabItem, onDropItem, onCancel, onMoveItem, dropSensitivity = DROP_SENSITIVITY.REGULAR })=>{
const objectRef = React.useRef(null);
const [{ handlerId, isOver }, dropRef] = useDrop({
accept: type,
collect (monitor) {
return {
handlerId: monitor.getHandlerId(),
isOver: monitor.isOver({
shallow: true
})
};
},
drop (item) {
const draggedIndex = item.index;
const newIndex = index;
if (isOver && onDropItem) {
onDropItem(draggedIndex, newIndex);
}
},
hover (item, monitor) {
if (!objectRef.current || !onMoveItem) {
return;
}
const dragIndex = item.index;
const newIndex = index;
const hoverBoundingRect = objectRef.current?.getBoundingClientRect();
const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
const clientOffset = monitor.getClientOffset();
if (!clientOffset) return;
const hoverClientY = clientOffset && clientOffset.y - hoverBoundingRect.top;
if (typeof dragIndex === 'number' && typeof newIndex === 'number') {
if (dragIndex === newIndex) {
// Don't replace items with themselves
return;
}
if (dropSensitivity === DROP_SENSITIVITY.REGULAR) {
// Dragging downwards
if (dragIndex < newIndex && hoverClientY < hoverMiddleY) {
return;
}
// Dragging upwards
if (dragIndex > newIndex && hoverClientY > hoverMiddleY) {
return;
}
}
// Time to actually perform the action
onMoveItem(newIndex, dragIndex);
item.index = newIndex;
} else {
// Using numbers as indices doesn't work for nested list items with path like [1, 1, 0]
if (Array.isArray(dragIndex) && Array.isArray(newIndex)) {
// Indices comparison to find item position in nested list
const minLength = Math.min(dragIndex.length, newIndex.length);
let areEqual = true;
let isLessThan = false;
let isGreaterThan = false;
for(let i = 0; i < minLength; i++){
if (dragIndex[i] < newIndex[i]) {
isLessThan = true;
areEqual = false;
break;
} else if (dragIndex[i] > newIndex[i]) {
isGreaterThan = true;
areEqual = false;
break;
}
}
// Don't replace items with themselves
if (areEqual && dragIndex.length === newIndex.length) {
return;
}
if (dropSensitivity === DROP_SENSITIVITY.REGULAR) {
// Dragging downwards
if (isLessThan && !isGreaterThan && hoverClientY < hoverMiddleY) {
return;
}
// Dragging upwards
if (isGreaterThan && !isLessThan && hoverClientY > hoverMiddleY) {
return;
}
}
}
onMoveItem(newIndex, dragIndex);
item.index = newIndex;
}
}
});
const getDragDirection = (monitor)=>{
if (monitor && monitor.isDragging() && !monitor.didDrop() && monitor.getInitialClientOffset() && monitor.getClientOffset()) {
const deltaY = monitor.getInitialClientOffset().y - monitor.getClientOffset().y;
if (deltaY > 0) return DIRECTIONS.UPWARD;
if (deltaY < 0) return DIRECTIONS.DOWNWARD;
return null;
}
return null;
};
const [{ isDragging, direction }, dragRef, dragPreviewRef] = useDrag({
type,
item () {
if (onStart) {
onStart();
}
/**
* This will be attached and it helps define the preview sizes
* when a component is flexy e.g. Relations
*/ const { width } = objectRef.current?.getBoundingClientRect() ?? {};
return {
index,
width,
...item
};
},
end () {
if (onEnd) {
onEnd();
}
},
canDrag: active,
/**
* This is useful when the item is in a virtualized list.
* However, if we don't have an ID then we want the libraries
* defaults to take care of this.
*/ isDragging: item?.id ? (monitor)=>{
return item.id === monitor.getItem().id;
} : undefined,
collect: (monitor)=>({
isDragging: monitor.isDragging(),
initialOffset: monitor.getInitialClientOffset(),
currentOffset: monitor.getClientOffset(),
direction: getDragDirection(monitor)
})
});
const handleKeyDown = useKeyboardDragAndDrop(active, index, {
onGrabItem,
onDropItem,
onCancel,
onMoveItem
});
return [
{
handlerId,
isDragging,
handleKeyDown,
isOverDropTarget: isOver,
direction
},
objectRef,
dropRef,
dragRef,
dragPreviewRef
];
};
export { DIRECTIONS, DROP_SENSITIVITY, useDragAndDrop };
//# sourceMappingURL=useDragAndDrop.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,94 @@
'use strict';
var React = require('react');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
/**
* Utility hook designed to implement keyboard accessibile drag and drop by
* returning an onKeyDown handler to be passed to the drag icon button.
*
* @internal - You should use `useDragAndDrop` instead.
*/ const useKeyboardDragAndDrop = (active, index, { onCancel, onDropItem, onGrabItem, onMoveItem })=>{
const [isSelected, setIsSelected] = React__namespace.useState(false);
const handleMove = (movement)=>{
if (!isSelected) {
return;
}
if (typeof index === 'number' && onMoveItem) {
if (movement === 'UP') {
onMoveItem(index - 1, index);
} else if (movement === 'DOWN') {
onMoveItem(index + 1, index);
}
}
};
const handleDragClick = ()=>{
if (isSelected) {
if (onDropItem) {
onDropItem(index);
}
setIsSelected(false);
} else {
if (onGrabItem) {
onGrabItem(index);
}
setIsSelected(true);
}
};
const handleCancel = ()=>{
if (isSelected) {
setIsSelected(false);
if (onCancel) {
onCancel(index);
}
}
};
const handleKeyDown = (e)=>{
if (!active) {
return;
}
if (e.key === 'Tab' && !isSelected) {
return;
}
e.preventDefault();
switch(e.key){
case ' ':
case 'Enter':
handleDragClick();
break;
case 'Escape':
handleCancel();
break;
case 'ArrowDown':
case 'ArrowRight':
handleMove('DOWN');
break;
case 'ArrowUp':
case 'ArrowLeft':
handleMove('UP');
break;
}
};
return handleKeyDown;
};
exports.useKeyboardDragAndDrop = useKeyboardDragAndDrop;
//# sourceMappingURL=useKeyboardDragAndDrop.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"useKeyboardDragAndDrop.js","sources":["../../../admin/src/hooks/useKeyboardDragAndDrop.ts"],"sourcesContent":["import * as React from 'react';\n\nexport type UseKeyboardDragAndDropCallbacks<TIndex extends number | Array<number> = number> = {\n onCancel?: (index: TIndex) => void;\n onDropItem?: (currentIndex: TIndex, newIndex?: TIndex) => void;\n onGrabItem?: (index: TIndex) => void;\n onMoveItem?: (newIndex: TIndex, currentIndex: TIndex) => void;\n};\n\n/**\n * Utility hook designed to implement keyboard accessibile drag and drop by\n * returning an onKeyDown handler to be passed to the drag icon button.\n *\n * @internal - You should use `useDragAndDrop` instead.\n */\nexport const useKeyboardDragAndDrop = <TIndex extends number | Array<number> = number>(\n active: boolean,\n index: TIndex,\n { onCancel, onDropItem, onGrabItem, onMoveItem }: UseKeyboardDragAndDropCallbacks<TIndex>\n) => {\n const [isSelected, setIsSelected] = React.useState(false);\n\n const handleMove = (movement: 'UP' | 'DOWN') => {\n if (!isSelected) {\n return;\n }\n if (typeof index === 'number' && onMoveItem) {\n if (movement === 'UP') {\n onMoveItem((index - 1) as TIndex, index);\n } else if (movement === 'DOWN') {\n onMoveItem((index + 1) as TIndex, index);\n }\n }\n };\n\n const handleDragClick = () => {\n if (isSelected) {\n if (onDropItem) {\n onDropItem(index);\n }\n setIsSelected(false);\n } else {\n if (onGrabItem) {\n onGrabItem(index);\n }\n setIsSelected(true);\n }\n };\n\n const handleCancel = () => {\n if (isSelected) {\n setIsSelected(false);\n\n if (onCancel) {\n onCancel(index);\n }\n }\n };\n\n const handleKeyDown = <E extends Element>(e: React.KeyboardEvent<E>) => {\n if (!active) {\n return;\n }\n\n if (e.key === 'Tab' && !isSelected) {\n return;\n }\n\n e.preventDefault();\n\n switch (e.key) {\n case ' ':\n case 'Enter':\n handleDragClick();\n break;\n\n case 'Escape':\n handleCancel();\n break;\n\n case 'ArrowDown':\n case 'ArrowRight':\n handleMove('DOWN');\n break;\n\n case 'ArrowUp':\n case 'ArrowLeft':\n handleMove('UP');\n break;\n\n default:\n }\n };\n\n return handleKeyDown;\n};\n"],"names":["useKeyboardDragAndDrop","active","index","onCancel","onDropItem","onGrabItem","onMoveItem","isSelected","setIsSelected","React","useState","handleMove","movement","handleDragClick","handleCancel","handleKeyDown","e","key","preventDefault"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AASA;;;;;AAKC,IACM,MAAMA,sBAAyB,GAAA,CACpCC,QACAC,KACA,EAAA,EAAEC,QAAQ,EAAEC,UAAU,EAAEC,UAAU,EAAEC,UAAU,EAA2C,GAAA;AAEzF,IAAA,MAAM,CAACC,UAAYC,EAAAA,aAAAA,CAAc,GAAGC,gBAAAA,CAAMC,QAAQ,CAAC,KAAA,CAAA;AAEnD,IAAA,MAAMC,aAAa,CAACC,QAAAA,GAAAA;AAClB,QAAA,IAAI,CAACL,UAAY,EAAA;AACf,YAAA;AACF;QACA,IAAI,OAAOL,KAAU,KAAA,QAAA,IAAYI,UAAY,EAAA;AAC3C,YAAA,IAAIM,aAAa,IAAM,EAAA;AACrBN,gBAAAA,UAAAA,CAAYJ,QAAQ,CAAcA,EAAAA,KAAAA,CAAAA;aAC7B,MAAA,IAAIU,aAAa,MAAQ,EAAA;AAC9BN,gBAAAA,UAAAA,CAAYJ,QAAQ,CAAcA,EAAAA,KAAAA,CAAAA;AACpC;AACF;AACF,KAAA;AAEA,IAAA,MAAMW,eAAkB,GAAA,IAAA;AACtB,QAAA,IAAIN,UAAY,EAAA;AACd,YAAA,IAAIH,UAAY,EAAA;gBACdA,UAAWF,CAAAA,KAAAA,CAAAA;AACb;YACAM,aAAc,CAAA,KAAA,CAAA;SACT,MAAA;AACL,YAAA,IAAIH,UAAY,EAAA;gBACdA,UAAWH,CAAAA,KAAAA,CAAAA;AACb;YACAM,aAAc,CAAA,IAAA,CAAA;AAChB;AACF,KAAA;AAEA,IAAA,MAAMM,YAAe,GAAA,IAAA;AACnB,QAAA,IAAIP,UAAY,EAAA;YACdC,aAAc,CAAA,KAAA,CAAA;AAEd,YAAA,IAAIL,QAAU,EAAA;gBACZA,QAASD,CAAAA,KAAAA,CAAAA;AACX;AACF;AACF,KAAA;AAEA,IAAA,MAAMa,gBAAgB,CAAoBC,CAAAA,GAAAA;AACxC,QAAA,IAAI,CAACf,MAAQ,EAAA;AACX,YAAA;AACF;AAEA,QAAA,IAAIe,CAAEC,CAAAA,GAAG,KAAK,KAAA,IAAS,CAACV,UAAY,EAAA;AAClC,YAAA;AACF;AAEAS,QAAAA,CAAAA,CAAEE,cAAc,EAAA;AAEhB,QAAA,OAAQF,EAAEC,GAAG;YACX,KAAK,GAAA;YACL,KAAK,OAAA;AACHJ,gBAAAA,eAAAA,EAAAA;AACA,gBAAA;YAEF,KAAK,QAAA;AACHC,gBAAAA,YAAAA,EAAAA;AACA,gBAAA;YAEF,KAAK,WAAA;YACL,KAAK,YAAA;gBACHH,UAAW,CAAA,MAAA,CAAA;AACX,gBAAA;YAEF,KAAK,SAAA;YACL,KAAK,WAAA;gBACHA,UAAW,CAAA,IAAA,CAAA;AACX,gBAAA;AAGJ;AACF,KAAA;IAEA,OAAOI,aAAAA;AACT;;;;"}

View File

@@ -0,0 +1,73 @@
import * as React from 'react';
/**
* Utility hook designed to implement keyboard accessibile drag and drop by
* returning an onKeyDown handler to be passed to the drag icon button.
*
* @internal - You should use `useDragAndDrop` instead.
*/ const useKeyboardDragAndDrop = (active, index, { onCancel, onDropItem, onGrabItem, onMoveItem })=>{
const [isSelected, setIsSelected] = React.useState(false);
const handleMove = (movement)=>{
if (!isSelected) {
return;
}
if (typeof index === 'number' && onMoveItem) {
if (movement === 'UP') {
onMoveItem(index - 1, index);
} else if (movement === 'DOWN') {
onMoveItem(index + 1, index);
}
}
};
const handleDragClick = ()=>{
if (isSelected) {
if (onDropItem) {
onDropItem(index);
}
setIsSelected(false);
} else {
if (onGrabItem) {
onGrabItem(index);
}
setIsSelected(true);
}
};
const handleCancel = ()=>{
if (isSelected) {
setIsSelected(false);
if (onCancel) {
onCancel(index);
}
}
};
const handleKeyDown = (e)=>{
if (!active) {
return;
}
if (e.key === 'Tab' && !isSelected) {
return;
}
e.preventDefault();
switch(e.key){
case ' ':
case 'Enter':
handleDragClick();
break;
case 'Escape':
handleCancel();
break;
case 'ArrowDown':
case 'ArrowRight':
handleMove('DOWN');
break;
case 'ArrowUp':
case 'ArrowLeft':
handleMove('UP');
break;
}
};
return handleKeyDown;
};
export { useKeyboardDragAndDrop };
//# sourceMappingURL=useKeyboardDragAndDrop.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"useKeyboardDragAndDrop.mjs","sources":["../../../admin/src/hooks/useKeyboardDragAndDrop.ts"],"sourcesContent":["import * as React from 'react';\n\nexport type UseKeyboardDragAndDropCallbacks<TIndex extends number | Array<number> = number> = {\n onCancel?: (index: TIndex) => void;\n onDropItem?: (currentIndex: TIndex, newIndex?: TIndex) => void;\n onGrabItem?: (index: TIndex) => void;\n onMoveItem?: (newIndex: TIndex, currentIndex: TIndex) => void;\n};\n\n/**\n * Utility hook designed to implement keyboard accessibile drag and drop by\n * returning an onKeyDown handler to be passed to the drag icon button.\n *\n * @internal - You should use `useDragAndDrop` instead.\n */\nexport const useKeyboardDragAndDrop = <TIndex extends number | Array<number> = number>(\n active: boolean,\n index: TIndex,\n { onCancel, onDropItem, onGrabItem, onMoveItem }: UseKeyboardDragAndDropCallbacks<TIndex>\n) => {\n const [isSelected, setIsSelected] = React.useState(false);\n\n const handleMove = (movement: 'UP' | 'DOWN') => {\n if (!isSelected) {\n return;\n }\n if (typeof index === 'number' && onMoveItem) {\n if (movement === 'UP') {\n onMoveItem((index - 1) as TIndex, index);\n } else if (movement === 'DOWN') {\n onMoveItem((index + 1) as TIndex, index);\n }\n }\n };\n\n const handleDragClick = () => {\n if (isSelected) {\n if (onDropItem) {\n onDropItem(index);\n }\n setIsSelected(false);\n } else {\n if (onGrabItem) {\n onGrabItem(index);\n }\n setIsSelected(true);\n }\n };\n\n const handleCancel = () => {\n if (isSelected) {\n setIsSelected(false);\n\n if (onCancel) {\n onCancel(index);\n }\n }\n };\n\n const handleKeyDown = <E extends Element>(e: React.KeyboardEvent<E>) => {\n if (!active) {\n return;\n }\n\n if (e.key === 'Tab' && !isSelected) {\n return;\n }\n\n e.preventDefault();\n\n switch (e.key) {\n case ' ':\n case 'Enter':\n handleDragClick();\n break;\n\n case 'Escape':\n handleCancel();\n break;\n\n case 'ArrowDown':\n case 'ArrowRight':\n handleMove('DOWN');\n break;\n\n case 'ArrowUp':\n case 'ArrowLeft':\n handleMove('UP');\n break;\n\n default:\n }\n };\n\n return handleKeyDown;\n};\n"],"names":["useKeyboardDragAndDrop","active","index","onCancel","onDropItem","onGrabItem","onMoveItem","isSelected","setIsSelected","React","useState","handleMove","movement","handleDragClick","handleCancel","handleKeyDown","e","key","preventDefault"],"mappings":";;AASA;;;;;AAKC,IACM,MAAMA,sBAAyB,GAAA,CACpCC,QACAC,KACA,EAAA,EAAEC,QAAQ,EAAEC,UAAU,EAAEC,UAAU,EAAEC,UAAU,EAA2C,GAAA;AAEzF,IAAA,MAAM,CAACC,UAAYC,EAAAA,aAAAA,CAAc,GAAGC,KAAAA,CAAMC,QAAQ,CAAC,KAAA,CAAA;AAEnD,IAAA,MAAMC,aAAa,CAACC,QAAAA,GAAAA;AAClB,QAAA,IAAI,CAACL,UAAY,EAAA;AACf,YAAA;AACF;QACA,IAAI,OAAOL,KAAU,KAAA,QAAA,IAAYI,UAAY,EAAA;AAC3C,YAAA,IAAIM,aAAa,IAAM,EAAA;AACrBN,gBAAAA,UAAAA,CAAYJ,QAAQ,CAAcA,EAAAA,KAAAA,CAAAA;aAC7B,MAAA,IAAIU,aAAa,MAAQ,EAAA;AAC9BN,gBAAAA,UAAAA,CAAYJ,QAAQ,CAAcA,EAAAA,KAAAA,CAAAA;AACpC;AACF;AACF,KAAA;AAEA,IAAA,MAAMW,eAAkB,GAAA,IAAA;AACtB,QAAA,IAAIN,UAAY,EAAA;AACd,YAAA,IAAIH,UAAY,EAAA;gBACdA,UAAWF,CAAAA,KAAAA,CAAAA;AACb;YACAM,aAAc,CAAA,KAAA,CAAA;SACT,MAAA;AACL,YAAA,IAAIH,UAAY,EAAA;gBACdA,UAAWH,CAAAA,KAAAA,CAAAA;AACb;YACAM,aAAc,CAAA,IAAA,CAAA;AAChB;AACF,KAAA;AAEA,IAAA,MAAMM,YAAe,GAAA,IAAA;AACnB,QAAA,IAAIP,UAAY,EAAA;YACdC,aAAc,CAAA,KAAA,CAAA;AAEd,YAAA,IAAIL,QAAU,EAAA;gBACZA,QAASD,CAAAA,KAAAA,CAAAA;AACX;AACF;AACF,KAAA;AAEA,IAAA,MAAMa,gBAAgB,CAAoBC,CAAAA,GAAAA;AACxC,QAAA,IAAI,CAACf,MAAQ,EAAA;AACX,YAAA;AACF;AAEA,QAAA,IAAIe,CAAEC,CAAAA,GAAG,KAAK,KAAA,IAAS,CAACV,UAAY,EAAA;AAClC,YAAA;AACF;AAEAS,QAAAA,CAAAA,CAAEE,cAAc,EAAA;AAEhB,QAAA,OAAQF,EAAEC,GAAG;YACX,KAAK,GAAA;YACL,KAAK,OAAA;AACHJ,gBAAAA,eAAAA,EAAAA;AACA,gBAAA;YAEF,KAAK,QAAA;AACHC,gBAAAA,YAAAA,EAAAA;AACA,gBAAA;YAEF,KAAK,WAAA;YACL,KAAK,YAAA;gBACHH,UAAW,CAAA,MAAA,CAAA;AACX,gBAAA;YAEF,KAAK,SAAA;YACL,KAAK,WAAA;gBACHA,UAAW,CAAA,IAAA,CAAA;AACX,gBAAA;AAGJ;AACF,KAAA;IAEA,OAAOI,aAAAA;AACT;;;;"}

View File

@@ -0,0 +1,61 @@
'use strict';
var React = require('react');
var strapiAdmin = require('@strapi/admin/strapi-admin');
const componentStore = new Map();
/**
* @description A hook to lazy load custom field components
*/ const useLazyComponents = (componentUids = [])=>{
const [lazyComponentStore, setLazyComponentStore] = React.useState(Object.fromEntries(componentStore));
/**
* Start loading only if there are any components passed in
* and there are some new to load
*/ const newUids = componentUids.filter((uid)=>!componentStore.get(uid));
const [loading, setLoading] = React.useState(()=>!!newUids.length);
const getCustomField = strapiAdmin.useStrapiApp('useLazyComponents', (state)=>state.customFields.get);
React.useEffect(()=>{
const setStore = (store)=>{
setLazyComponentStore(store);
setLoading(false);
};
const lazyLoadComponents = async (uids, components)=>{
const modules = await Promise.all(components);
uids.forEach((uid, index)=>{
componentStore.set(uid, modules[index].default);
});
setStore(Object.fromEntries(componentStore));
};
if (newUids.length > 0) {
setLoading(true);
const componentPromises = newUids.reduce((arrayOfPromises, uid)=>{
const customField = getCustomField(uid);
if (customField) {
arrayOfPromises.push(customField.components.Input());
}
return arrayOfPromises;
}, []);
if (componentPromises.length > 0) {
lazyLoadComponents(newUids, componentPromises);
}
}
}, [
newUids,
getCustomField
]);
/**
* Wrap this in a callback so it can be used in
* effects to cleanup the cached store if required
*/ const cleanup = React.useCallback(()=>{
componentStore.clear();
setLazyComponentStore({});
}, []);
return {
isLazyLoading: loading,
lazyComponentStore,
cleanup
};
};
exports.useLazyComponents = useLazyComponents;
//# sourceMappingURL=useLazyComponents.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,59 @@
import { useState, useEffect, useCallback } from 'react';
import { useStrapiApp } from '@strapi/admin/strapi-admin';
const componentStore = new Map();
/**
* @description A hook to lazy load custom field components
*/ const useLazyComponents = (componentUids = [])=>{
const [lazyComponentStore, setLazyComponentStore] = useState(Object.fromEntries(componentStore));
/**
* Start loading only if there are any components passed in
* and there are some new to load
*/ const newUids = componentUids.filter((uid)=>!componentStore.get(uid));
const [loading, setLoading] = useState(()=>!!newUids.length);
const getCustomField = useStrapiApp('useLazyComponents', (state)=>state.customFields.get);
useEffect(()=>{
const setStore = (store)=>{
setLazyComponentStore(store);
setLoading(false);
};
const lazyLoadComponents = async (uids, components)=>{
const modules = await Promise.all(components);
uids.forEach((uid, index)=>{
componentStore.set(uid, modules[index].default);
});
setStore(Object.fromEntries(componentStore));
};
if (newUids.length > 0) {
setLoading(true);
const componentPromises = newUids.reduce((arrayOfPromises, uid)=>{
const customField = getCustomField(uid);
if (customField) {
arrayOfPromises.push(customField.components.Input());
}
return arrayOfPromises;
}, []);
if (componentPromises.length > 0) {
lazyLoadComponents(newUids, componentPromises);
}
}
}, [
newUids,
getCustomField
]);
/**
* Wrap this in a callback so it can be used in
* effects to cleanup the cached store if required
*/ const cleanup = useCallback(()=>{
componentStore.clear();
setLazyComponentStore({});
}, []);
return {
isLazyLoading: loading,
lazyComponentStore,
cleanup
};
};
export { useLazyComponents };
//# sourceMappingURL=useLazyComponents.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,28 @@
'use strict';
var React = require('react');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
const useOnce = (effect)=>React__namespace.useEffect(effect, emptyDeps);
const emptyDeps = [];
exports.useOnce = useOnce;
//# sourceMappingURL=useOnce.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"useOnce.js","sources":["../../../admin/src/hooks/useOnce.ts"],"sourcesContent":["/* eslint-disable react-hooks/exhaustive-deps */\nimport * as React from 'react';\n\nexport const useOnce = (effect: React.EffectCallback) => React.useEffect(effect, emptyDeps);\n\nconst emptyDeps: React.DependencyList = [];\n"],"names":["useOnce","effect","React","useEffect","emptyDeps"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAGO,MAAMA,UAAU,CAACC,MAAAA,GAAiCC,iBAAMC,SAAS,CAACF,QAAQG,SAAW;AAE5F,MAAMA,YAAkC,EAAE;;;;"}

View File

@@ -0,0 +1,7 @@
import * as React from 'react';
const useOnce = (effect)=>React.useEffect(effect, emptyDeps);
const emptyDeps = [];
export { useOnce };
//# sourceMappingURL=useOnce.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"useOnce.mjs","sources":["../../../admin/src/hooks/useOnce.ts"],"sourcesContent":["/* eslint-disable react-hooks/exhaustive-deps */\nimport * as React from 'react';\n\nexport const useOnce = (effect: React.EffectCallback) => React.useEffect(effect, emptyDeps);\n\nconst emptyDeps: React.DependencyList = [];\n"],"names":["useOnce","effect","React","useEffect","emptyDeps"],"mappings":";;AAGO,MAAMA,UAAU,CAACC,MAAAA,GAAiCC,MAAMC,SAAS,CAACF,QAAQG,SAAW;AAE5F,MAAMA,YAAkC,EAAE;;;;"}

View File

@@ -0,0 +1,16 @@
'use strict';
var React = require('react');
const usePrev = (value)=>{
const ref = React.useRef();
React.useEffect(()=>{
ref.current = value;
}, [
value
]);
return ref.current;
};
exports.usePrev = usePrev;
//# sourceMappingURL=usePrev.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"usePrev.js","sources":["../../../admin/src/hooks/usePrev.ts"],"sourcesContent":["import { useEffect, useRef } from 'react';\n\nexport const usePrev = <T>(value: T): T | undefined => {\n const ref = useRef<T>();\n\n useEffect(() => {\n ref.current = value;\n }, [value]);\n\n return ref.current;\n};\n"],"names":["usePrev","value","ref","useRef","useEffect","current"],"mappings":";;;;AAEO,MAAMA,UAAU,CAAIC,KAAAA,GAAAA;AACzB,IAAA,MAAMC,GAAMC,GAAAA,YAAAA,EAAAA;IAEZC,eAAU,CAAA,IAAA;AACRF,QAAAA,GAAAA,CAAIG,OAAO,GAAGJ,KAAAA;KACb,EAAA;AAACA,QAAAA;AAAM,KAAA,CAAA;AAEV,IAAA,OAAOC,IAAIG,OAAO;AACpB;;;;"}

View File

@@ -0,0 +1,14 @@
import { useRef, useEffect } from 'react';
const usePrev = (value)=>{
const ref = useRef();
useEffect(()=>{
ref.current = value;
}, [
value
]);
return ref.current;
};
export { usePrev };
//# sourceMappingURL=usePrev.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"usePrev.mjs","sources":["../../../admin/src/hooks/usePrev.ts"],"sourcesContent":["import { useEffect, useRef } from 'react';\n\nexport const usePrev = <T>(value: T): T | undefined => {\n const ref = useRef<T>();\n\n useEffect(() => {\n ref.current = value;\n }, [value]);\n\n return ref.current;\n};\n"],"names":["usePrev","value","ref","useRef","useEffect","current"],"mappings":";;AAEO,MAAMA,UAAU,CAAIC,KAAAA,GAAAA;AACzB,IAAA,MAAMC,GAAMC,GAAAA,MAAAA,EAAAA;IAEZC,SAAU,CAAA,IAAA;AACRF,QAAAA,GAAAA,CAAIG,OAAO,GAAGJ,KAAAA;KACb,EAAA;AAACA,QAAAA;AAAM,KAAA,CAAA;AAEV,IAAA,OAAOC,IAAIG,OAAO;AACpB;;;;"}