Skip to content

Commit cdb4729

Browse files
committed
Add default autosave restore UX
1 parent 2c75c3f commit cdb4729

8 files changed

Lines changed: 587 additions & 85 deletions

File tree

packages/docs/site/docs/developers/06-apis/query-api/01-index.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ You can go ahead and try it out. The Playground will automatically install the t
3838
| `multisite` | `no` | Enables the WordPress multisite mode. Accepts `yes` or `no`. |
3939
| `import-site` | | Imports site files and database from a ZIP file specified by a URL. |
4040
| `import-wxr` | | Imports site content from a WXR file specified by a URL. It uses the WordPress Importer plugin, so the default admin user must be logged in. |
41-
| `site-slug` | | Selects which site to load from browser storage. If the specified site does not exist, the user will be prompted to save a new site with the specified slug. |
41+
| `site-slug` | | Selects which site to load from browser storage. If the specified site does not exist, Playground creates a new browser-saved site with the specified slug unless temporary storage is requested. |
42+
| `storage` | | Controls whether the Playground is saved by default. Use `storage=temp` to create an unsaved temporary Playground that is reset when the page is refreshed or closed. |
4243
| `language` | `en_US` | Sets the locale for the WordPress instance. This must be used in combination with `networking=yes` otherwise WordPress won't be able to download translations. |
4344
| `core-pr` | | Installs a specific https://github.com/WordPress/wordpress-develop core PR. Accepts the PR number. For example, `core-pr=6883`. |
4445
| `gutenberg-pr` | | Installs a specific https://github.com/WordPress/gutenberg PR. Accepts the PR number. For example, `gutenberg-pr=65337`. |

packages/playground/website/playwright/e2e/query-api.spec.ts

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const LatestSupportedWordPressVersion = Object.keys(
1717

1818
test('should load PHP 8.3 by default', async ({ website, wordpress }) => {
1919
// Navigate to the website
20-
await website.goto('./?url=/phpinfo.php');
20+
await website.goto('./?storage=temp&url=/phpinfo.php');
2121
await expect(wordpress.locator('h1.p').first()).toContainText(
2222
'PHP Version 8.3'
2323
);
@@ -157,7 +157,7 @@ test('should load WordPress latest by default', async ({
157157
website,
158158
wordpress,
159159
}) => {
160-
await website.goto('./?url=/wp-admin/');
160+
await website.goto('./?storage=temp&url=/wp-admin/');
161161

162162
const expectedBodyClass =
163163
'version-' + LatestSupportedWordPressVersion.replace('.', '-');
@@ -170,7 +170,7 @@ test('should load WordPress 6.3 when requested', async ({
170170
website,
171171
wordpress,
172172
}) => {
173-
await website.goto('./?wp=6.3&url=/wp-admin/');
173+
await website.goto('./?storage=temp&wp=6.3&url=/wp-admin/');
174174
await expect(wordpress.locator(`body.branch-6-3`)).toContainText(
175175
'Dashboard'
176176
);
@@ -180,7 +180,9 @@ test('should disable networking when requested', async ({
180180
website,
181181
wordpress,
182182
}) => {
183-
await website.goto('./?networking=no&url=/wp-admin/plugin-install.php');
183+
await website.goto(
184+
'./?storage=temp&networking=no&url=/wp-admin/plugin-install.php'
185+
);
184186
await expect(wordpress.locator('.notice.error')).toContainText(
185187
'Network access is an experimental, opt-in feature'
186188
);
@@ -190,12 +192,16 @@ test('should enable networking when requested', async ({
190192
website,
191193
wordpress,
192194
}) => {
193-
await website.goto('./?networking=yes&url=/wp-admin/plugin-install.php');
195+
await website.goto(
196+
'./?storage=temp&networking=yes&url=/wp-admin/plugin-install.php'
197+
);
194198
await expect(wordpress.locator('body')).toContainText('Install Now');
195199
});
196200

197201
test('should install the specified plugin', async ({ website, wordpress }) => {
198-
await website.goto('./?plugin=gutenberg&url=/wp-admin/plugins.php');
202+
await website.goto(
203+
'./?storage=temp&plugin=gutenberg&url=/wp-admin/plugins.php'
204+
);
199205
await expect(wordpress.locator('#deactivate-gutenberg')).toContainText(
200206
'Deactivate'
201207
);
@@ -205,23 +211,23 @@ test('should login the user in by default if no login query parameter is provide
205211
website,
206212
wordpress,
207213
}) => {
208-
await website.goto('./?url=/wp-admin/');
214+
await website.goto('./?storage=temp&url=/wp-admin/');
209215
await expect(wordpress.locator('body')).toContainText('Dashboard');
210216
});
211217

212218
test('should login the user in if the login query parameter is set to yes', async ({
213219
website,
214220
wordpress,
215221
}) => {
216-
await website.goto('./?login=yes&url=/wp-admin/');
222+
await website.goto('./?storage=temp&login=yes&url=/wp-admin/');
217223
await expect(wordpress.locator('body')).toContainText('Dashboard');
218224
});
219225

220226
test('should not login the user in if the login query parameter is set to no', async ({
221227
website,
222228
wordpress,
223229
}) => {
224-
await website.goto('./?login=no&url=/wp-admin/');
230+
await website.goto('./?storage=temp&login=no&url=/wp-admin/');
225231
await expect(wordpress.locator('input[type="submit"]')).toContainText(
226232
'Log In'
227233
);
@@ -232,7 +238,7 @@ test('should not login the user in if the login query parameter is set to no', a
232238
['/wp-admin/post.php?post=1&action=edit', 'should redirect to post editor'],
233239
].forEach(([path, description]) => {
234240
test(description, async ({ website, wordpress }) => {
235-
await website.goto(`./?url=${encodeURIComponent(path)}`);
241+
await website.goto(`./?storage=temp&url=${encodeURIComponent(path)}`);
236242
expect(
237243
await wordpress
238244
.locator('body')
@@ -251,7 +257,7 @@ test('should translate WP-admin to Spanish using the language query parameter',
251257
`It's unclear why this test fails on Safari. The root cause of the failure is unknown as the feature ` +
252258
`seems to be working in manual testing.`
253259
);
254-
await website.goto('./?language=es_ES&url=/wp-admin/');
260+
await website.goto('./?storage=temp&language=es_ES&url=/wp-admin/');
255261
await expect(wordpress.locator('body')).toContainText('Escritorio');
256262
});
257263

@@ -318,7 +324,7 @@ test('should retain encoded control characters in the URL', async ({
318324
// most wp-admin pages enforce a redirect to a sanitized (broken)
319325
// version of the URL.
320326
await website.goto(
321-
`./?url=${encodeURIComponent(
327+
`./?storage=temp&url=${encodeURIComponent(
322328
path
323329
)}&plugin=html-api-debugger#${JSON.stringify(blueprint)}`
324330
);
@@ -398,6 +404,7 @@ async function gotoPHPOnlyPlayground(
398404
) {
399405
const query = new URLSearchParams({
400406
php: '8.3',
407+
storage: 'temp',
401408
...queryParams,
402409
});
403410
const blueprint: Blueprint = {

packages/playground/website/src/components/browser-chrome/save-status-indicator.module.css

Lines changed: 94 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
display: flex;
33
align-items: center;
44
gap: 6px;
5-
padding: 4px 0;
5+
padding: 5px 8px;
66
border-radius: 4px;
77
font-size: 14px;
88
font-weight: 500;
@@ -25,30 +25,35 @@
2525
fill: #86efac;
2626
}
2727

28-
.unsaved {
29-
color: #fcd34d;
28+
.autosaved {
29+
color: #93c5fd;
3030
}
3131

32-
.unsaved svg {
33-
fill: #fcd34d;
32+
.autosaved svg {
33+
fill: #93c5fd;
3434
}
3535

36-
.saveButton {
37-
margin-left: 2px;
38-
padding: 4px 10px;
39-
background: #fcd34d;
40-
color: #1e2327;
41-
border: none;
42-
border-radius: 4px;
43-
font-size: 13px;
36+
.autosaved .label,
37+
.saving .label {
4438
font-weight: 600;
39+
}
40+
41+
.actionable {
4542
cursor: pointer;
4643
transition: background 0.15s ease;
47-
min-height: 28px;
4844
}
4945

50-
.saveButton:hover {
51-
background: #fde68a;
46+
.actionable:hover,
47+
.actionable[aria-expanded='true'] {
48+
background: rgba(255, 255, 255, 0.08);
49+
}
50+
51+
.unsaved {
52+
color: #fcd34d;
53+
}
54+
55+
.unsaved svg {
56+
fill: #fcd34d;
5257
}
5358

5459
.saving {
@@ -73,31 +78,88 @@
7378
display: inline;
7479
}
7580

76-
.spinner {
81+
.popover {
82+
z-index: 100000;
83+
}
84+
85+
.popover :global(.components-popover__content) {
86+
background: #fff;
87+
border: 1px solid #dcdcde;
88+
border-radius: 6px;
89+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);
90+
color: #1e1e1e;
91+
padding: 0;
92+
width: min(320px, calc(100vw - 24px));
93+
}
94+
95+
.popover-content {
96+
display: grid;
97+
gap: 10px;
98+
padding: 14px;
99+
}
100+
101+
.popover-title {
102+
font-size: 14px;
103+
font-weight: 600;
104+
line-height: 1.3;
105+
}
106+
107+
.popover-description {
108+
color: #50575e;
109+
font-size: 13px;
110+
line-height: 1.45;
111+
margin: 0;
112+
white-space: normal;
113+
}
114+
115+
.primary-action {
116+
align-items: center;
117+
background: #3858e9;
118+
border: 1px solid #6c84ff;
119+
border-radius: 4px;
120+
color: #fff;
121+
cursor: pointer;
122+
display: inline-flex;
123+
font-size: 13px;
124+
font-weight: 600;
125+
justify-content: center;
126+
line-height: 1.2;
127+
min-height: 34px;
128+
padding: 8px 12px;
129+
transition: background 0.15s ease;
130+
width: fit-content;
131+
}
132+
133+
.primary-action:hover,
134+
.primary-action:focus-visible {
135+
background: #4f6df0;
136+
border-color: #8fa1ff;
137+
}
138+
139+
.progress-ring {
77140
width: 16px;
78141
height: 16px;
79-
border: 2px solid rgba(147, 197, 253, 0.3);
80-
border-top-color: #93c5fd;
81142
border-radius: 50%;
82-
animation: spin 0.8s linear infinite;
143+
background: conic-gradient(
144+
currentColor var(--save-progress),
145+
rgba(255, 255, 255, 0.24) 0
146+
);
83147
flex-shrink: 0;
84-
}
85-
86-
@keyframes spin {
87-
to {
88-
transform: rotate(360deg);
89-
}
148+
mask: radial-gradient(
149+
farthest-side,
150+
transparent calc(100% - 2px),
151+
#000 calc(100% - 2px)
152+
);
153+
-webkit-mask: radial-gradient(
154+
farthest-side,
155+
transparent calc(100% - 2px),
156+
#000 calc(100% - 2px)
157+
);
90158
}
91159

92160
/* Mobile responsive styles */
93161
@media (max-width: 500px) {
94162
.indicator {
95163
font-size: 13px;
96164
}
97-
98-
.saveButton {
99-
min-width: 40px;
100-
min-height: 40px;
101-
padding: 8px 12px;
102-
}
103165
}

0 commit comments

Comments
 (0)