@wordpress/boot 0.2.0 → 0.2.1-next.dc3f6d3c1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build-module/components/app/index.js +6 -1
- package/build-module/components/app/index.js.map +2 -2
- package/build-module/components/app/router.js +42 -27
- package/build-module/components/app/router.js.map +2 -2
- package/build-module/components/canvas/back-button.js +1 -1
- package/build-module/components/canvas/back-button.js.map +1 -1
- package/build-module/components/canvas/index.js +42 -17
- package/build-module/components/canvas/index.js.map +2 -2
- package/build-module/components/navigation/dropdown-item/index.js +1 -1
- package/build-module/components/navigation/dropdown-item/index.js.map +1 -1
- package/build-module/components/navigation/navigation-item/index.js +1 -1
- package/build-module/components/navigation/navigation-item/index.js.map +1 -1
- package/build-module/components/navigation/navigation-screen/index.js +1 -1
- package/build-module/components/navigation/navigation-screen/index.js.map +1 -1
- package/build-module/components/navigation/router-link-item.js +3 -1
- package/build-module/components/navigation/router-link-item.js.map +2 -2
- package/build-module/components/navigation/use-sidebar-parent.js +3 -1
- package/build-module/components/navigation/use-sidebar-parent.js.map +2 -2
- package/build-module/components/root/index.js +6 -9
- package/build-module/components/root/index.js.map +2 -2
- package/build-module/components/root/single-page.js +6 -9
- package/build-module/components/root/single-page.js.map +2 -2
- package/build-module/components/save-button/index.js +116 -0
- package/build-module/components/save-button/index.js.map +7 -0
- package/build-module/components/sidebar/index.js +8 -2
- package/build-module/components/sidebar/index.js.map +2 -2
- package/build-module/components/site-hub/index.js +1 -1
- package/build-module/components/site-hub/index.js.map +1 -1
- package/build-module/components/site-icon/index.js +1 -1
- package/build-module/components/site-icon/index.js.map +1 -1
- package/build-module/components/site-icon-link/index.js +4 -2
- package/build-module/components/site-icon-link/index.js.map +2 -2
- package/build-module/index.js +75 -53
- package/build-module/index.js.map +2 -2
- package/build-module/store/actions.js +9 -1
- package/build-module/store/actions.js.map +2 -2
- package/build-module/store/reducer.js +11 -0
- package/build-module/store/reducer.js.map +2 -2
- package/build-style/style-rtl.css +71 -51
- package/build-style/style.css +71 -51
- package/build-types/components/app/index.d.ts +2 -1
- package/build-types/components/app/index.d.ts.map +1 -1
- package/build-types/components/app/router.d.ts +3 -0
- package/build-types/components/app/router.d.ts.map +1 -1
- package/build-types/components/canvas/index.d.ts.map +1 -1
- package/build-types/components/navigation/router-link-item.d.ts +1 -3
- package/build-types/components/navigation/router-link-item.d.ts.map +1 -1
- package/build-types/components/navigation/use-sidebar-parent.d.ts.map +1 -1
- package/build-types/components/root/index.d.ts.map +1 -1
- package/build-types/components/root/single-page.d.ts.map +1 -1
- package/build-types/components/save-button/index.d.ts +6 -0
- package/build-types/components/save-button/index.d.ts.map +1 -0
- package/build-types/components/sidebar/index.d.ts.map +1 -1
- package/build-types/components/site-icon-link/index.d.ts.map +1 -1
- package/build-types/index.d.ts +1 -0
- package/build-types/index.d.ts.map +1 -1
- package/build-types/store/actions.d.ts +6 -1
- package/build-types/store/actions.d.ts.map +1 -1
- package/build-types/store/reducer.d.ts.map +1 -1
- package/build-types/store/types.d.ts +23 -3
- package/build-types/store/types.d.ts.map +1 -1
- package/package.json +21 -22
- package/src/components/app/index.tsx +7 -0
- package/src/components/app/router.tsx +67 -35
- package/src/components/canvas/index.tsx +35 -11
- package/src/components/navigation/router-link-item.tsx +8 -1
- package/src/components/navigation/use-sidebar-parent.ts +8 -5
- package/src/components/root/index.tsx +4 -8
- package/src/components/root/single-page.tsx +4 -8
- package/src/components/save-button/index.tsx +121 -0
- package/src/components/save-button/style.scss +3 -0
- package/src/components/sidebar/index.tsx +4 -0
- package/src/components/sidebar/style.scss +4 -0
- package/src/components/site-icon-link/index.tsx +5 -2
- package/src/index.tsx +1 -0
- package/src/store/actions.ts +9 -0
- package/src/store/reducer.ts +12 -0
- package/src/store/types.ts +25 -3
- package/tsconfig.json +1 -0
- package/tsconfig.tsbuildinfo +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wordpress/boot",
|
|
3
|
-
"version": "0.2.0",
|
|
3
|
+
"version": "0.2.1-next.dc3f6d3c1.0",
|
|
4
4
|
"description": "Minimal boot package for WordPress admin pages.",
|
|
5
5
|
"author": "The WordPress Contributors",
|
|
6
6
|
"license": "GPL-2.0-or-later",
|
|
@@ -34,26 +34,25 @@
|
|
|
34
34
|
"wpScriptModuleExports": "./build-module/index.js",
|
|
35
35
|
"types": "build-types",
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@
|
|
38
|
-
"@
|
|
39
|
-
"@wordpress/
|
|
40
|
-
"@wordpress/
|
|
41
|
-
"@wordpress/
|
|
42
|
-
"@wordpress/
|
|
43
|
-
"@wordpress/
|
|
44
|
-
"@wordpress/
|
|
45
|
-
"@wordpress/
|
|
46
|
-
"@wordpress/
|
|
47
|
-
"@wordpress/
|
|
48
|
-
"@wordpress/
|
|
49
|
-
"@wordpress/
|
|
50
|
-
"@wordpress/
|
|
51
|
-
"@wordpress/
|
|
52
|
-
"@wordpress/
|
|
53
|
-
"@wordpress/
|
|
54
|
-
"@wordpress/
|
|
55
|
-
"@wordpress/
|
|
56
|
-
"@wordpress/url": "^4.35.0",
|
|
37
|
+
"@wordpress/admin-ui": "^1.3.1-next.dc3f6d3c1.0",
|
|
38
|
+
"@wordpress/commands": "^1.35.1-next.dc3f6d3c1.0",
|
|
39
|
+
"@wordpress/components": "^30.8.2-next.dc3f6d3c1.0",
|
|
40
|
+
"@wordpress/compose": "^7.35.1-next.dc3f6d3c1.0",
|
|
41
|
+
"@wordpress/core-data": "^7.35.1-next.dc3f6d3c1.0",
|
|
42
|
+
"@wordpress/data": "^10.35.1-next.dc3f6d3c1.0",
|
|
43
|
+
"@wordpress/editor": "^14.35.2-next.dc3f6d3c1.0",
|
|
44
|
+
"@wordpress/element": "^6.35.1-next.dc3f6d3c1.0",
|
|
45
|
+
"@wordpress/html-entities": "^4.35.1-next.dc3f6d3c1.0",
|
|
46
|
+
"@wordpress/i18n": "^6.8.1-next.dc3f6d3c1.0",
|
|
47
|
+
"@wordpress/icons": "^11.2.1-next.dc3f6d3c1.0",
|
|
48
|
+
"@wordpress/keyboard-shortcuts": "^5.35.1-next.dc3f6d3c1.0",
|
|
49
|
+
"@wordpress/keycodes": "^4.35.1-next.dc3f6d3c1.0",
|
|
50
|
+
"@wordpress/lazy-editor": "^1.1.1-next.dc3f6d3c1.0",
|
|
51
|
+
"@wordpress/primitives": "^4.35.1-next.dc3f6d3c1.0",
|
|
52
|
+
"@wordpress/private-apis": "^1.35.1-next.dc3f6d3c1.0",
|
|
53
|
+
"@wordpress/route": "^0.1.1-next.dc3f6d3c1.0",
|
|
54
|
+
"@wordpress/theme": "^0.2.1-next.dc3f6d3c1.0",
|
|
55
|
+
"@wordpress/url": "^4.35.1-next.dc3f6d3c1.0",
|
|
57
56
|
"clsx": "^2.1.1"
|
|
58
57
|
},
|
|
59
58
|
"peerDependencies": {
|
|
@@ -63,5 +62,5 @@
|
|
|
63
62
|
"publishConfig": {
|
|
64
63
|
"access": "public"
|
|
65
64
|
},
|
|
66
|
-
"gitHead": "
|
|
65
|
+
"gitHead": "f73b5e69b34fbaccfb8c47783f4f993059ff1a41"
|
|
67
66
|
}
|
|
@@ -22,10 +22,12 @@ export async function init( {
|
|
|
22
22
|
mountId,
|
|
23
23
|
menuItems,
|
|
24
24
|
routes,
|
|
25
|
+
initModules,
|
|
25
26
|
}: {
|
|
26
27
|
mountId: string;
|
|
27
28
|
menuItems?: MenuItem[];
|
|
28
29
|
routes?: Route[];
|
|
30
|
+
initModules?: string[];
|
|
29
31
|
} ) {
|
|
30
32
|
( menuItems ?? [] ).forEach( ( menuItem ) => {
|
|
31
33
|
dispatch( store ).registerMenuItem( menuItem.id, menuItem );
|
|
@@ -35,6 +37,11 @@ export async function init( {
|
|
|
35
37
|
dispatch( store ).registerRoute( route );
|
|
36
38
|
} );
|
|
37
39
|
|
|
40
|
+
for ( const moduleId of initModules ?? [] ) {
|
|
41
|
+
const module = await import( moduleId );
|
|
42
|
+
await module.init();
|
|
43
|
+
}
|
|
44
|
+
|
|
38
45
|
// Render the app
|
|
39
46
|
const rootElement = document.getElementById( mountId );
|
|
40
47
|
if ( rootElement ) {
|
|
@@ -1,30 +1,37 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* External dependencies
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
5
|
-
createRouter,
|
|
6
|
-
createRootRoute,
|
|
7
|
-
createRoute,
|
|
8
|
-
RouterProvider,
|
|
9
|
-
createBrowserHistory,
|
|
10
|
-
type AnyRoute,
|
|
11
|
-
redirect,
|
|
12
|
-
} from '@tanstack/react-router';
|
|
13
|
-
import { parseHref } from '@tanstack/history';
|
|
14
4
|
import type { ComponentType } from 'react';
|
|
15
5
|
|
|
16
6
|
/**
|
|
17
7
|
* WordPress dependencies
|
|
18
8
|
*/
|
|
19
9
|
import { __ } from '@wordpress/i18n';
|
|
20
|
-
import {
|
|
10
|
+
import { useState, useEffect } from '@wordpress/element';
|
|
21
11
|
import { Page } from '@wordpress/admin-ui';
|
|
12
|
+
import {
|
|
13
|
+
privateApis as routePrivateApis,
|
|
14
|
+
type AnyRoute,
|
|
15
|
+
} from '@wordpress/route';
|
|
22
16
|
|
|
23
17
|
/**
|
|
24
18
|
* Internal dependencies
|
|
25
19
|
*/
|
|
26
20
|
import Root from '../root';
|
|
27
|
-
import type { Route, RouteLoaderContext } from '../../store/types';
|
|
21
|
+
import type { CanvasData, Route, RouteLoaderContext } from '../../store/types';
|
|
22
|
+
import { unlock } from '../../lock-unlock';
|
|
23
|
+
import Canvas from '../canvas';
|
|
24
|
+
|
|
25
|
+
const {
|
|
26
|
+
createLazyRoute,
|
|
27
|
+
createRouter,
|
|
28
|
+
createRootRoute,
|
|
29
|
+
createRoute,
|
|
30
|
+
RouterProvider,
|
|
31
|
+
createBrowserHistory,
|
|
32
|
+
parseHref,
|
|
33
|
+
useMatches,
|
|
34
|
+
} = unlock( routePrivateApis );
|
|
28
35
|
|
|
29
36
|
// Not found component
|
|
30
37
|
function NotFoundComponent() {
|
|
@@ -40,10 +47,20 @@ function NotFoundComponent() {
|
|
|
40
47
|
function RouteComponent( {
|
|
41
48
|
stage: Stage,
|
|
42
49
|
inspector: Inspector,
|
|
50
|
+
canvas: CustomCanvas,
|
|
43
51
|
}: {
|
|
44
52
|
stage?: ComponentType;
|
|
45
53
|
inspector?: ComponentType;
|
|
54
|
+
canvas?: ComponentType;
|
|
46
55
|
} ) {
|
|
56
|
+
// Get canvas data from the current route's loader
|
|
57
|
+
const matches = useMatches();
|
|
58
|
+
const currentMatch = matches[ matches.length - 1 ];
|
|
59
|
+
const canvasData = ( currentMatch?.loaderData as any )?.canvas as
|
|
60
|
+
| CanvasData
|
|
61
|
+
| null
|
|
62
|
+
| undefined;
|
|
63
|
+
|
|
47
64
|
return (
|
|
48
65
|
<>
|
|
49
66
|
{ Stage && (
|
|
@@ -56,6 +73,18 @@ function RouteComponent( {
|
|
|
56
73
|
<Inspector />
|
|
57
74
|
</div>
|
|
58
75
|
) }
|
|
76
|
+
{ /* Render custom canvas when canvas() returns null */ }
|
|
77
|
+
{ canvasData === null && CustomCanvas && (
|
|
78
|
+
<div className="boot-layout__canvas">
|
|
79
|
+
<CustomCanvas />
|
|
80
|
+
</div>
|
|
81
|
+
) }
|
|
82
|
+
{ /* Render default canvas when canvas() returns CanvasData */ }
|
|
83
|
+
{ canvasData && (
|
|
84
|
+
<div className="boot-layout__canvas">
|
|
85
|
+
<Canvas canvas={ canvasData } />
|
|
86
|
+
</div>
|
|
87
|
+
) }
|
|
59
88
|
</>
|
|
60
89
|
);
|
|
61
90
|
}
|
|
@@ -71,27 +100,9 @@ async function createRouteFromDefinition(
|
|
|
71
100
|
route: Route,
|
|
72
101
|
parentRoute: AnyRoute
|
|
73
102
|
) {
|
|
74
|
-
// Create lazy components for stage and inspector surfaces
|
|
75
|
-
const SurfacesModule = route.content_module
|
|
76
|
-
? lazy( async () => {
|
|
77
|
-
const module = await import( route.content_module! );
|
|
78
|
-
// Return a component that renders the surfaces
|
|
79
|
-
return {
|
|
80
|
-
default: () => (
|
|
81
|
-
<RouteComponent
|
|
82
|
-
stage={ module.stage }
|
|
83
|
-
inspector={ module.inspector }
|
|
84
|
-
/>
|
|
85
|
-
),
|
|
86
|
-
};
|
|
87
|
-
} )
|
|
88
|
-
: () => null;
|
|
89
|
-
|
|
90
103
|
// Load route module for lifecycle functions if specified
|
|
91
104
|
let routeConfig: {
|
|
92
|
-
beforeLoad?: (
|
|
93
|
-
context: RouteLoaderContext & { redirect: Function }
|
|
94
|
-
) => void | Promise< void >;
|
|
105
|
+
beforeLoad?: ( context: RouteLoaderContext ) => void | Promise< void >;
|
|
95
106
|
loader?: ( context: RouteLoaderContext ) => Promise< unknown >;
|
|
96
107
|
canvas?: ( context: RouteLoaderContext ) => Promise< any >;
|
|
97
108
|
} = {};
|
|
@@ -101,7 +112,8 @@ async function createRouteFromDefinition(
|
|
|
101
112
|
routeConfig = module.route || {};
|
|
102
113
|
}
|
|
103
114
|
|
|
104
|
-
|
|
115
|
+
// Create route without component initially
|
|
116
|
+
let tanstackRoute = createRoute( {
|
|
105
117
|
getParentRoute: () => parentRoute,
|
|
106
118
|
path: route.path,
|
|
107
119
|
beforeLoad: routeConfig.beforeLoad
|
|
@@ -109,7 +121,6 @@ async function createRouteFromDefinition(
|
|
|
109
121
|
routeConfig.beforeLoad!( {
|
|
110
122
|
params: opts.params || {},
|
|
111
123
|
search: opts.search || {},
|
|
112
|
-
redirect,
|
|
113
124
|
} )
|
|
114
125
|
: undefined,
|
|
115
126
|
loader: async ( opts: any ) => {
|
|
@@ -134,8 +145,28 @@ async function createRouteFromDefinition(
|
|
|
134
145
|
};
|
|
135
146
|
},
|
|
136
147
|
loaderDeps: ( opts: any ) => opts.search,
|
|
137
|
-
component: SurfacesModule,
|
|
138
148
|
} );
|
|
149
|
+
|
|
150
|
+
// Chain .lazy() to preload content module on intent
|
|
151
|
+
tanstackRoute = tanstackRoute.lazy( async () => {
|
|
152
|
+
const module = route.content_module
|
|
153
|
+
? await import( route.content_module )
|
|
154
|
+
: {};
|
|
155
|
+
|
|
156
|
+
return createLazyRoute( route.path )( {
|
|
157
|
+
component: function Component() {
|
|
158
|
+
return (
|
|
159
|
+
<RouteComponent
|
|
160
|
+
stage={ module.stage }
|
|
161
|
+
inspector={ module.inspector }
|
|
162
|
+
canvas={ module.canvas }
|
|
163
|
+
/>
|
|
164
|
+
);
|
|
165
|
+
},
|
|
166
|
+
} );
|
|
167
|
+
} );
|
|
168
|
+
|
|
169
|
+
return tanstackRoute;
|
|
139
170
|
}
|
|
140
171
|
|
|
141
172
|
/**
|
|
@@ -171,7 +202,7 @@ function createPathHistory() {
|
|
|
171
202
|
const pathHref = `${ path }${ url.hash }`;
|
|
172
203
|
return parseHref( pathHref, window.history.state );
|
|
173
204
|
},
|
|
174
|
-
createHref: ( href ) => {
|
|
205
|
+
createHref: ( href: string ) => {
|
|
175
206
|
const searchParams = new URLSearchParams( window.location.search );
|
|
176
207
|
searchParams.set( 'p', href );
|
|
177
208
|
return `${ window.location.pathname }?${ searchParams }`;
|
|
@@ -201,6 +232,7 @@ export default function Router( {
|
|
|
201
232
|
const newRouter = createRouter( {
|
|
202
233
|
history,
|
|
203
234
|
routeTree,
|
|
235
|
+
defaultPreload: 'intent',
|
|
204
236
|
defaultNotFoundComponent: NotFoundComponent,
|
|
205
237
|
} );
|
|
206
238
|
setRouter( newRouter );
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { useState, useEffect } from '@wordpress/element';
|
|
5
5
|
import { Spinner } from '@wordpress/components';
|
|
6
|
+
import { useNavigate } from '@wordpress/route';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Internal dependencies
|
|
@@ -23,6 +24,7 @@ interface CanvasProps {
|
|
|
23
24
|
*/
|
|
24
25
|
export default function Canvas( { canvas }: CanvasProps ) {
|
|
25
26
|
const [ Editor, setEditor ] = useState< any >( null );
|
|
27
|
+
const navigate = useNavigate();
|
|
26
28
|
|
|
27
29
|
useEffect( () => {
|
|
28
30
|
// Dynamically import the lazy-editor module
|
|
@@ -63,17 +65,39 @@ export default function Canvas( { canvas }: CanvasProps ) {
|
|
|
63
65
|
|
|
64
66
|
// Render the editor with canvas data
|
|
65
67
|
return (
|
|
66
|
-
<div
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
68
|
+
<div style={ { height: '100%', position: 'relative' } }>
|
|
69
|
+
<div
|
|
70
|
+
style={ { height: '100%' } }
|
|
71
|
+
// @ts-expect-error inert not typed properly
|
|
72
|
+
inert={ canvas.isPreview ? 'true' : undefined }
|
|
73
|
+
>
|
|
74
|
+
<Editor
|
|
75
|
+
postType={ canvas.postType }
|
|
76
|
+
postId={ canvas.postId }
|
|
77
|
+
settings={ { isPreviewMode: canvas.isPreview } }
|
|
78
|
+
backButton={ backButton }
|
|
79
|
+
/>
|
|
80
|
+
</div>
|
|
81
|
+
{ canvas.isPreview && canvas.editLink && (
|
|
82
|
+
<div
|
|
83
|
+
onClick={ () => navigate( { to: canvas.editLink } ) }
|
|
84
|
+
onKeyDown={ ( e ) => {
|
|
85
|
+
if ( e.key === 'Enter' || e.key === ' ' ) {
|
|
86
|
+
e.preventDefault();
|
|
87
|
+
navigate( { to: canvas.editLink } );
|
|
88
|
+
}
|
|
89
|
+
} }
|
|
90
|
+
style={ {
|
|
91
|
+
position: 'absolute',
|
|
92
|
+
inset: 0,
|
|
93
|
+
cursor: 'pointer',
|
|
94
|
+
zIndex: 1,
|
|
95
|
+
} }
|
|
96
|
+
role="button"
|
|
97
|
+
tabIndex={ 0 }
|
|
98
|
+
aria-label="Click to edit"
|
|
99
|
+
/>
|
|
100
|
+
) }
|
|
77
101
|
</div>
|
|
78
102
|
);
|
|
79
103
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* External dependencies
|
|
3
3
|
*/
|
|
4
|
-
import { createLink } from '@tanstack/react-router';
|
|
5
4
|
import type { ForwardedRef } from 'react';
|
|
6
5
|
|
|
7
6
|
/**
|
|
@@ -9,6 +8,14 @@ import type { ForwardedRef } from 'react';
|
|
|
9
8
|
*/
|
|
10
9
|
import { forwardRef } from '@wordpress/element';
|
|
11
10
|
import { __experimentalItem as Item } from '@wordpress/components';
|
|
11
|
+
import { privateApis as routePrivateApis } from '@wordpress/route';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Internal dependencies
|
|
15
|
+
*/
|
|
16
|
+
import { unlock } from '../../lock-unlock';
|
|
17
|
+
|
|
18
|
+
const { createLink } = unlock( routePrivateApis );
|
|
12
19
|
|
|
13
20
|
function AnchorOnlyItem(
|
|
14
21
|
props: React.ComponentProps< typeof Item >,
|
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* External dependencies
|
|
3
|
-
*/
|
|
4
|
-
import { useRouter, useMatches } from '@tanstack/react-router';
|
|
5
|
-
|
|
6
1
|
/**
|
|
7
2
|
* WordPress dependencies
|
|
8
3
|
*/
|
|
4
|
+
import { privateApis as routePrivateApis } from '@wordpress/route';
|
|
9
5
|
import { useEffect, useState } from '@wordpress/element';
|
|
10
6
|
import { useSelect } from '@wordpress/data';
|
|
11
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Internal dependencies
|
|
10
|
+
*/
|
|
11
|
+
import { unlock } from '../../lock-unlock';
|
|
12
|
+
|
|
13
|
+
const { useRouter, useMatches } = unlock( routePrivateApis );
|
|
14
|
+
|
|
12
15
|
/**
|
|
13
16
|
* Internal dependencies
|
|
14
17
|
*/
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* External dependencies
|
|
3
3
|
*/
|
|
4
|
-
import { Outlet, useMatches } from '@tanstack/react-router';
|
|
5
4
|
import clsx from 'clsx';
|
|
6
5
|
|
|
7
6
|
/**
|
|
8
7
|
* WordPress dependencies
|
|
9
8
|
*/
|
|
9
|
+
import { privateApis as routePrivateApis } from '@wordpress/route';
|
|
10
10
|
// @ts-expect-error Commands is not typed properly.
|
|
11
11
|
import { CommandMenu } from '@wordpress/commands';
|
|
12
12
|
import { privateApis as themePrivateApis } from '@wordpress/theme';
|
|
@@ -15,19 +15,20 @@ import { privateApis as themePrivateApis } from '@wordpress/theme';
|
|
|
15
15
|
* Internal dependencies
|
|
16
16
|
*/
|
|
17
17
|
import Sidebar from '../sidebar';
|
|
18
|
-
import Canvas from '../canvas';
|
|
19
18
|
import SavePanel from '../save-panel';
|
|
20
19
|
import { unlock } from '../../lock-unlock';
|
|
21
20
|
import type { CanvasData } from '../../store/types';
|
|
22
21
|
import './style.scss';
|
|
23
22
|
|
|
24
23
|
const { ThemeProvider } = unlock( themePrivateApis );
|
|
24
|
+
const { useMatches, Outlet } = unlock( routePrivateApis );
|
|
25
25
|
|
|
26
26
|
export default function Root() {
|
|
27
27
|
const matches = useMatches();
|
|
28
28
|
const currentMatch = matches[ matches.length - 1 ];
|
|
29
29
|
const canvas = ( currentMatch?.loaderData as any )?.canvas as
|
|
30
30
|
| CanvasData
|
|
31
|
+
| null
|
|
31
32
|
| undefined;
|
|
32
33
|
const isFullScreen = canvas && ! canvas.isPreview;
|
|
33
34
|
|
|
@@ -36,7 +37,7 @@ export default function Root() {
|
|
|
36
37
|
<ThemeProvider color={ { bg: '#1d2327', primary: '#3858e9' } }>
|
|
37
38
|
<div
|
|
38
39
|
className={ clsx( 'boot-layout', {
|
|
39
|
-
'has-canvas': !! canvas,
|
|
40
|
+
'has-canvas': !! canvas || canvas === null,
|
|
40
41
|
'has-full-canvas': isFullScreen,
|
|
41
42
|
} ) }
|
|
42
43
|
>
|
|
@@ -52,11 +53,6 @@ export default function Root() {
|
|
|
52
53
|
color={ { bg: '#ffffff', primary: '#3858e9' } }
|
|
53
54
|
>
|
|
54
55
|
<Outlet />
|
|
55
|
-
{ canvas && (
|
|
56
|
-
<div className="boot-layout__canvas">
|
|
57
|
-
<Canvas canvas={ canvas } />
|
|
58
|
-
</div>
|
|
59
|
-
) }
|
|
60
56
|
</ThemeProvider>
|
|
61
57
|
</div>
|
|
62
58
|
</div>
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* External dependencies
|
|
3
3
|
*/
|
|
4
|
-
import { Outlet, useMatches } from '@tanstack/react-router';
|
|
5
4
|
import clsx from 'clsx';
|
|
6
5
|
|
|
7
6
|
/**
|
|
8
7
|
* WordPress dependencies
|
|
9
8
|
*/
|
|
9
|
+
import { privateApis as routePrivateApis } from '@wordpress/route';
|
|
10
10
|
// @ts-expect-error Commands is not typed properly.
|
|
11
11
|
import { CommandMenu } from '@wordpress/commands';
|
|
12
12
|
import { privateApis as themePrivateApis } from '@wordpress/theme';
|
|
@@ -14,12 +14,12 @@ import { privateApis as themePrivateApis } from '@wordpress/theme';
|
|
|
14
14
|
/**
|
|
15
15
|
* Internal dependencies
|
|
16
16
|
*/
|
|
17
|
-
import Canvas from '../canvas';
|
|
18
17
|
import SavePanel from '../save-panel';
|
|
19
18
|
import { unlock } from '../../lock-unlock';
|
|
20
19
|
import type { CanvasData } from '../../store/types';
|
|
21
20
|
import './style.scss';
|
|
22
21
|
|
|
22
|
+
const { useMatches, Outlet } = unlock( routePrivateApis );
|
|
23
23
|
const { ThemeProvider } = unlock( themePrivateApis );
|
|
24
24
|
|
|
25
25
|
/**
|
|
@@ -31,6 +31,7 @@ export default function RootSinglePage() {
|
|
|
31
31
|
const currentMatch = matches[ matches.length - 1 ];
|
|
32
32
|
const canvas = ( currentMatch?.loaderData as any )?.canvas as
|
|
33
33
|
| CanvasData
|
|
34
|
+
| null
|
|
34
35
|
| undefined;
|
|
35
36
|
const isFullScreen = canvas && ! canvas.isPreview;
|
|
36
37
|
|
|
@@ -39,7 +40,7 @@ export default function RootSinglePage() {
|
|
|
39
40
|
<ThemeProvider color={ { bg: '#1d2327', primary: '#3858e9' } }>
|
|
40
41
|
<div
|
|
41
42
|
className={ clsx( 'boot-layout boot-layout--single-page', {
|
|
42
|
-
'has-canvas': !! canvas,
|
|
43
|
+
'has-canvas': !! canvas || canvas === null,
|
|
43
44
|
'has-full-canvas': isFullScreen,
|
|
44
45
|
} ) }
|
|
45
46
|
>
|
|
@@ -50,11 +51,6 @@ export default function RootSinglePage() {
|
|
|
50
51
|
color={ { bg: '#ffffff', primary: '#3858e9' } }
|
|
51
52
|
>
|
|
52
53
|
<Outlet />
|
|
53
|
-
{ canvas && (
|
|
54
|
-
<div className="boot-layout__canvas">
|
|
55
|
-
<Canvas canvas={ canvas } />
|
|
56
|
-
</div>
|
|
57
|
-
) }
|
|
58
54
|
</ThemeProvider>
|
|
59
55
|
</div>
|
|
60
56
|
</div>
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WordPress dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { useEffect, useState } from '@wordpress/element';
|
|
5
|
+
import { useSelect } from '@wordpress/data';
|
|
6
|
+
import { _n, __, sprintf } from '@wordpress/i18n';
|
|
7
|
+
import { store as coreStore } from '@wordpress/core-data';
|
|
8
|
+
import { displayShortcut, rawShortcut } from '@wordpress/keycodes';
|
|
9
|
+
import { check } from '@wordpress/icons';
|
|
10
|
+
import { EntitiesSavedStates } from '@wordpress/editor';
|
|
11
|
+
import { Button, Modal, Tooltip } from '@wordpress/components';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Internal dependencies
|
|
15
|
+
*/
|
|
16
|
+
import './style.scss';
|
|
17
|
+
import useSaveShortcut from '../save-panel/use-save-shortcut';
|
|
18
|
+
|
|
19
|
+
export default function SaveButton() {
|
|
20
|
+
const [ isSaveViewOpen, setIsSaveViewOpened ] = useState( false );
|
|
21
|
+
const { isSaving, dirtyEntityRecordsCount } = useSelect( ( select ) => {
|
|
22
|
+
const { isSavingEntityRecord, __experimentalGetDirtyEntityRecords } =
|
|
23
|
+
select( coreStore );
|
|
24
|
+
const dirtyEntityRecords = __experimentalGetDirtyEntityRecords();
|
|
25
|
+
return {
|
|
26
|
+
isSaving: dirtyEntityRecords.some( ( record ) =>
|
|
27
|
+
isSavingEntityRecord( record.kind, record.name, record.key )
|
|
28
|
+
),
|
|
29
|
+
dirtyEntityRecordsCount: dirtyEntityRecords.length,
|
|
30
|
+
};
|
|
31
|
+
}, [] );
|
|
32
|
+
const [ showSavedState, setShowSavedState ] = useState( false );
|
|
33
|
+
|
|
34
|
+
useEffect( () => {
|
|
35
|
+
if ( isSaving ) {
|
|
36
|
+
// Proactively expect to show saved state. This is done once save
|
|
37
|
+
// starts to avoid race condition where setting it after would cause
|
|
38
|
+
// the button to be unmounted before state is updated.
|
|
39
|
+
setShowSavedState( true );
|
|
40
|
+
}
|
|
41
|
+
}, [ isSaving ] );
|
|
42
|
+
|
|
43
|
+
const hasChanges = dirtyEntityRecordsCount > 0;
|
|
44
|
+
|
|
45
|
+
// Handle save failure case: If we were showing saved state but saving
|
|
46
|
+
// failed, reset to show changes again.
|
|
47
|
+
useEffect( () => {
|
|
48
|
+
if ( ! isSaving && hasChanges ) {
|
|
49
|
+
setShowSavedState( false );
|
|
50
|
+
}
|
|
51
|
+
}, [ isSaving, hasChanges ] );
|
|
52
|
+
|
|
53
|
+
function hideSavedState() {
|
|
54
|
+
if ( showSavedState ) {
|
|
55
|
+
setShowSavedState( false );
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const shouldShowButton = hasChanges || showSavedState;
|
|
60
|
+
|
|
61
|
+
useSaveShortcut( { openSavePanel: () => setIsSaveViewOpened( true ) } );
|
|
62
|
+
|
|
63
|
+
if ( ! shouldShowButton ) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const isInSavedState = showSavedState && ! hasChanges;
|
|
68
|
+
const disabled = isSaving || isInSavedState;
|
|
69
|
+
|
|
70
|
+
const getLabel = () => {
|
|
71
|
+
if ( isInSavedState ) {
|
|
72
|
+
return __( 'Saved' );
|
|
73
|
+
}
|
|
74
|
+
return sprintf(
|
|
75
|
+
// translators: %d: number of unsaved changes (number).
|
|
76
|
+
_n(
|
|
77
|
+
'Review %d change…',
|
|
78
|
+
'Review %d changes…',
|
|
79
|
+
dirtyEntityRecordsCount
|
|
80
|
+
),
|
|
81
|
+
dirtyEntityRecordsCount
|
|
82
|
+
);
|
|
83
|
+
};
|
|
84
|
+
const label = getLabel();
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<>
|
|
88
|
+
<Tooltip
|
|
89
|
+
text={ hasChanges ? label : undefined }
|
|
90
|
+
shortcut={ displayShortcut.primary( 's' ) }
|
|
91
|
+
>
|
|
92
|
+
<Button
|
|
93
|
+
variant="primary"
|
|
94
|
+
size="compact"
|
|
95
|
+
onClick={ () => setIsSaveViewOpened( true ) }
|
|
96
|
+
onBlur={ hideSavedState }
|
|
97
|
+
disabled={ disabled }
|
|
98
|
+
accessibleWhenDisabled
|
|
99
|
+
isBusy={ isSaving }
|
|
100
|
+
aria-keyshortcuts={ rawShortcut.primary( 's' ) }
|
|
101
|
+
className="boot-save-button"
|
|
102
|
+
icon={ isInSavedState ? check : undefined }
|
|
103
|
+
>
|
|
104
|
+
{ label }
|
|
105
|
+
</Button>
|
|
106
|
+
</Tooltip>
|
|
107
|
+
{ isSaveViewOpen && (
|
|
108
|
+
<Modal
|
|
109
|
+
title={ __( 'Review changes' ) }
|
|
110
|
+
onRequestClose={ () => setIsSaveViewOpened( false ) }
|
|
111
|
+
size="small"
|
|
112
|
+
>
|
|
113
|
+
<EntitiesSavedStates
|
|
114
|
+
close={ () => setIsSaveViewOpened( false ) }
|
|
115
|
+
variant="inline"
|
|
116
|
+
/>
|
|
117
|
+
</Modal>
|
|
118
|
+
) }
|
|
119
|
+
</>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import SiteHub from '../site-hub';
|
|
5
5
|
import Navigation from '../navigation';
|
|
6
|
+
import SaveButton from '../save-button';
|
|
6
7
|
import './style.scss';
|
|
7
8
|
|
|
8
9
|
export default function Sidebar() {
|
|
@@ -12,6 +13,9 @@ export default function Sidebar() {
|
|
|
12
13
|
<div className="boot-sidebar__content">
|
|
13
14
|
<Navigation />
|
|
14
15
|
</div>
|
|
16
|
+
<div className="boot-sidebar__footer">
|
|
17
|
+
<SaveButton />
|
|
18
|
+
</div>
|
|
15
19
|
</div>
|
|
16
20
|
);
|
|
17
21
|
}
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* WordPress dependencies
|
|
3
3
|
*/
|
|
4
|
-
import { Link,
|
|
4
|
+
import { Link, privateApis as routePrivateApis } from '@wordpress/route';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Internal dependencies
|
|
8
8
|
*/
|
|
9
|
+
import { unlock } from '../../lock-unlock';
|
|
9
10
|
import SiteIcon from '../site-icon';
|
|
10
11
|
import './style.scss';
|
|
11
12
|
|
|
13
|
+
const { useCanGoBack, useRouter } = unlock( routePrivateApis );
|
|
14
|
+
|
|
12
15
|
function SiteIconLink( {
|
|
13
16
|
to,
|
|
14
17
|
isBackButton,
|
package/src/index.tsx
CHANGED