2023-08-28 01:28:05 +00:00
|
|
|
// Reference: https://github.com/WordPress/gutenberg/blob/v16.4.0/packages/edit-site/src/components/save-hub/index.js
|
|
|
|
/* eslint-disable @woocommerce/dependency-group */
|
|
|
|
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
|
|
|
/**
|
|
|
|
* External dependencies
|
|
|
|
*/
|
2023-09-15 04:01:02 +00:00
|
|
|
import { useContext, useEffect, useState } from '@wordpress/element';
|
2023-09-12 06:32:50 +00:00
|
|
|
import { useQuery } from '@woocommerce/navigation';
|
2023-08-28 01:28:05 +00:00
|
|
|
import { useSelect, useDispatch } from '@wordpress/data';
|
2023-09-15 04:01:02 +00:00
|
|
|
import {
|
|
|
|
// @ts-ignore No types for this exist yet.
|
|
|
|
__experimentalHStack as HStack,
|
|
|
|
Button,
|
|
|
|
Spinner,
|
|
|
|
} from '@wordpress/components';
|
2023-09-05 06:21:19 +00:00
|
|
|
import { __ } from '@wordpress/i18n';
|
2023-08-28 01:28:05 +00:00
|
|
|
// @ts-ignore No types for this exist yet.
|
|
|
|
import { store as coreStore } from '@wordpress/core-data';
|
|
|
|
// @ts-ignore No types for this exist yet.
|
|
|
|
import { store as blockEditorStore } from '@wordpress/block-editor';
|
|
|
|
// @ts-ignore No types for this exist yet.
|
|
|
|
import { store as noticesStore } from '@wordpress/notices';
|
|
|
|
// @ts-ignore No types for this exist yet.
|
2023-09-05 06:21:19 +00:00
|
|
|
import { useEntitiesSavedStatesIsDirty as useIsDirty } from '@wordpress/editor';
|
2023-08-28 01:28:05 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Internal dependencies
|
|
|
|
*/
|
|
|
|
import { CustomizeStoreContext } from '../';
|
|
|
|
|
|
|
|
const PUBLISH_ON_SAVE_ENTITIES = [
|
|
|
|
{
|
|
|
|
kind: 'postType',
|
|
|
|
name: 'wp_navigation',
|
|
|
|
},
|
|
|
|
];
|
|
|
|
|
|
|
|
export const SaveHub = () => {
|
|
|
|
const saveNoticeId = 'site-edit-save-notice';
|
2023-09-12 06:32:50 +00:00
|
|
|
const urlParams = useQuery();
|
2023-08-28 01:28:05 +00:00
|
|
|
const { sendEvent } = useContext( CustomizeStoreContext );
|
2023-09-15 04:01:02 +00:00
|
|
|
const [ isResolving, setIsResolving ] = useState< boolean >( false );
|
2023-08-28 01:28:05 +00:00
|
|
|
|
|
|
|
// @ts-ignore No types for this exist yet.
|
|
|
|
const { __unstableMarkLastChangeAsPersistent } =
|
|
|
|
useDispatch( blockEditorStore );
|
|
|
|
|
|
|
|
const { createSuccessNotice, createErrorNotice, removeNotice } =
|
|
|
|
useDispatch( noticesStore );
|
|
|
|
|
2023-09-05 06:21:19 +00:00
|
|
|
const {
|
|
|
|
dirtyEntityRecords,
|
|
|
|
isDirty,
|
|
|
|
}: {
|
|
|
|
dirtyEntityRecords: {
|
|
|
|
key?: string | number;
|
|
|
|
kind: string;
|
|
|
|
name: string;
|
|
|
|
property?: string;
|
|
|
|
title: string;
|
|
|
|
}[];
|
|
|
|
isDirty: boolean;
|
|
|
|
} = useIsDirty();
|
|
|
|
|
|
|
|
const { isSaving } = useSelect(
|
|
|
|
( select ) => {
|
|
|
|
return {
|
|
|
|
isSaving: dirtyEntityRecords.some( ( record ) =>
|
2023-08-28 01:28:05 +00:00
|
|
|
// @ts-ignore No types for this exist yet.
|
2023-09-05 06:21:19 +00:00
|
|
|
select( coreStore ).isSavingEntityRecord(
|
|
|
|
record.kind,
|
|
|
|
record.name,
|
|
|
|
record.key
|
|
|
|
)
|
|
|
|
),
|
|
|
|
};
|
|
|
|
},
|
|
|
|
[ dirtyEntityRecords ]
|
|
|
|
);
|
2023-08-28 01:28:05 +00:00
|
|
|
|
|
|
|
const {
|
|
|
|
// @ts-ignore No types for this exist yet.
|
|
|
|
editEntityRecord,
|
|
|
|
// @ts-ignore No types for this exist yet.
|
|
|
|
saveEditedEntityRecord,
|
|
|
|
// @ts-ignore No types for this exist yet.
|
|
|
|
__experimentalSaveSpecifiedEntityEdits: saveSpecifiedEntityEdits,
|
|
|
|
} = useDispatch( coreStore );
|
|
|
|
|
2023-09-05 06:21:19 +00:00
|
|
|
useEffect( () => {
|
|
|
|
dirtyEntityRecords.forEach( ( entity ) => {
|
|
|
|
/* This is a hack to reset the entity record when the user navigates away from editing page to main page.
|
|
|
|
This is needed because Gutenberg does not provide a way to reset the entity record. Replace this when we have a better way to do this.
|
|
|
|
We will need to add different conditions here when we implement editing for other entities.
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (
|
|
|
|
entity.kind === 'root' &&
|
|
|
|
entity.name === 'site' &&
|
|
|
|
entity.property
|
|
|
|
) {
|
|
|
|
// Reset site icon edit
|
|
|
|
editEntityRecord(
|
|
|
|
'root',
|
|
|
|
'site',
|
|
|
|
undefined,
|
|
|
|
{
|
|
|
|
[ entity.property ]: undefined,
|
|
|
|
},
|
|
|
|
{ undoIgnore: true }
|
|
|
|
);
|
2023-09-11 09:48:23 +00:00
|
|
|
} else if (
|
|
|
|
entity.kind === 'root' &&
|
|
|
|
entity.name === 'globalStyles'
|
|
|
|
) {
|
|
|
|
editEntityRecord(
|
|
|
|
entity.kind,
|
|
|
|
entity.name,
|
|
|
|
entity.key,
|
|
|
|
{
|
|
|
|
styles: undefined,
|
|
|
|
settings: undefined,
|
|
|
|
},
|
|
|
|
{ undoIgnore: true }
|
|
|
|
);
|
2023-09-05 06:21:19 +00:00
|
|
|
} else {
|
|
|
|
editEntityRecord(
|
|
|
|
entity.kind,
|
|
|
|
entity.name,
|
|
|
|
entity.key,
|
|
|
|
{
|
|
|
|
selection: undefined,
|
|
|
|
blocks: undefined,
|
|
|
|
content: undefined,
|
|
|
|
},
|
|
|
|
{ undoIgnore: true }
|
|
|
|
);
|
|
|
|
}
|
|
|
|
} );
|
|
|
|
// Only run when path changes.
|
|
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
2023-09-12 06:32:50 +00:00
|
|
|
}, [ urlParams.path ] );
|
2023-08-28 01:28:05 +00:00
|
|
|
|
2023-09-05 06:21:19 +00:00
|
|
|
const save = async () => {
|
2023-08-28 01:28:05 +00:00
|
|
|
removeNotice( saveNoticeId );
|
|
|
|
|
|
|
|
try {
|
2023-09-05 06:21:19 +00:00
|
|
|
for ( const { kind, name, key, property } of dirtyEntityRecords ) {
|
|
|
|
if ( kind === 'root' && name === 'site' ) {
|
|
|
|
await saveSpecifiedEntityEdits( 'root', 'site', undefined, [
|
|
|
|
property,
|
|
|
|
] );
|
|
|
|
} else {
|
|
|
|
if (
|
|
|
|
PUBLISH_ON_SAVE_ENTITIES.some(
|
|
|
|
( typeToPublish ) =>
|
|
|
|
typeToPublish.kind === kind &&
|
|
|
|
typeToPublish.name === name
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
editEntityRecord( kind, name, key, {
|
|
|
|
status: 'publish',
|
|
|
|
} );
|
|
|
|
}
|
2023-08-28 01:28:05 +00:00
|
|
|
|
2023-09-05 06:21:19 +00:00
|
|
|
await saveEditedEntityRecord( kind, name, key );
|
|
|
|
__unstableMarkLastChangeAsPersistent();
|
|
|
|
}
|
2023-08-28 01:28:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
createSuccessNotice( __( 'Site updated.', 'woocommerce' ), {
|
|
|
|
type: 'snackbar',
|
|
|
|
id: saveNoticeId,
|
|
|
|
} );
|
|
|
|
} catch ( error ) {
|
|
|
|
createErrorNotice(
|
|
|
|
`${ __( 'Saving failed.', 'woocommerce' ) } ${ error }`
|
|
|
|
);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const renderButton = () => {
|
2023-09-12 06:32:50 +00:00
|
|
|
if ( urlParams.path === '/customize-store/assembler-hub' ) {
|
2023-08-28 01:28:05 +00:00
|
|
|
return (
|
|
|
|
<Button
|
|
|
|
variant="primary"
|
|
|
|
onClick={ () => {
|
2023-09-15 04:01:02 +00:00
|
|
|
setIsResolving( true );
|
2023-08-28 01:28:05 +00:00
|
|
|
sendEvent( 'FINISH_CUSTOMIZATION' );
|
|
|
|
} }
|
|
|
|
className="edit-site-save-hub__button"
|
|
|
|
// @ts-ignore No types for this exist yet.
|
|
|
|
__next40pxDefaultSize
|
|
|
|
>
|
2023-09-15 04:01:02 +00:00
|
|
|
{ isResolving ? <Spinner /> : __( 'Done', 'woocommerce' ) }
|
2023-08-28 01:28:05 +00:00
|
|
|
</Button>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-09-05 06:21:19 +00:00
|
|
|
// if we have only one unsaved change and it matches current context, we can show a more specific label
|
|
|
|
const label = isSaving
|
|
|
|
? __( 'Saving', 'woocommerce' )
|
|
|
|
: __( 'Save', 'woocommerce' );
|
|
|
|
|
|
|
|
const isDisabled = ! isDirty || isSaving;
|
|
|
|
|
2023-08-28 01:28:05 +00:00
|
|
|
return (
|
2023-09-05 06:21:19 +00:00
|
|
|
<Button
|
|
|
|
variant="primary"
|
|
|
|
onClick={ save }
|
|
|
|
isBusy={ isSaving }
|
|
|
|
disabled={ isDisabled }
|
|
|
|
aria-disabled={ isDisabled }
|
2023-08-28 01:28:05 +00:00
|
|
|
className="edit-site-save-hub__button"
|
2023-09-05 06:21:19 +00:00
|
|
|
// @ts-ignore No types for this exist yet.
|
2023-08-28 01:28:05 +00:00
|
|
|
__next40pxDefaultSize
|
2023-09-05 06:21:19 +00:00
|
|
|
>
|
|
|
|
{ label }
|
|
|
|
</Button>
|
2023-08-28 01:28:05 +00:00
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
return (
|
|
|
|
<HStack className="edit-site-save-hub" alignment="right" spacing={ 4 }>
|
|
|
|
{ renderButton() }
|
|
|
|
</HStack>
|
|
|
|
);
|
|
|
|
};
|