diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 5e222f060..def9063c7 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -15,7 +15,7 @@ env:
# renovate: datasource=npm depName=npm
NPM_VERSION: "11"
# renovate: datasource=npm depName=@microbit-foundation/python-editor-v3-microbit registryUrl=https://npm.pkg.github.com
- THEME_VERSION: 0.4.0
+ THEME_VERSION: 0.0.0-projects.pages.111
# renovate: datasource=npm depName=@microbit-foundation/website-deploy-aws registryUrl=https://npm.pkg.github.com
DEPLOY_AWS_VERSION: "0.6.0"
# renovate: datasource=npm depName=@microbit-foundation/website-deploy-aws-config registryUrl=https://npm.pkg.github.com
@@ -74,7 +74,7 @@ jobs:
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: reports
- path: reports/
+ path: playwright-report/
retention-days: 3
- run: npm run deploy
if: github.repository_owner == 'microbit-foundation' && (env.STAGE == 'REVIEW' || success())
diff --git a/AGENTS.md b/AGENTS.md
index 040bd2da4..faba89ef1 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -5,8 +5,10 @@
This app uses react-aria-components + Panda CSS via `@microbit/ui`.
Read `../ui/docs/hints.md` before styling/theming/UI work.
The private theme package is the sibling repo
-`../python-editor-v3-microbit` (consumed via a manual `node_modules`
-symlink locally — re-create it after `npm install`).
+`../python-editor-v3-microbit`. Link it with `npm run dev:link-theme`, which
+builds it and symlinks it into `node_modules`; re-run after `npm install`.
+Home page artwork (banner, help cards) lives there and is resolved per file by
+`theme-package/images/*` imports, falling back to `src/deployment/default`.
`@microbit/ui` is consumed as the **published package**, pinned in
`package.json`. To develop against a local `../ui` checkout instead — what you
diff --git a/docs/analytics-events.md b/docs/analytics-events.md
index 0e0eae1f1..857b98e3d 100644
--- a/docs/analytics-events.md
+++ b/docs/analytics-events.md
@@ -108,26 +108,80 @@ No `destination` param: the editor only downloads. ml-trainer's
### `project_import`
User brought files in. Fires once per drop / picker selection, before the
-files are parsed, so it counts attempts.
+files are parsed, so it counts attempts. A hex always becomes a new project.
+Other files join the open project from the editor and become a new project
+from the home and projects pages.
-| Param | Values |
-| -------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
-| `source` | `drop` / `file_picker` (same values as ml-trainer) |
-| `format` | `hex` (replaces the project) / `py` (Python file added to the project) / `other` (any other single file) / `multiple` (more than one file) |
-
-### `project_reset`
-
-User chose Reset project, replacing everything with the starter program. Fires
-when the menu action is chosen, before the confirm dialog. No params.
+| Param | Values |
+| --------- | ------------------------------------------------------------------------------------------------------------ |
+| `source` | `drop` / `file_picker` (same values as ml-trainer) |
+| `format` | `hex` / `py` (a single Python file) / `other` (any other single file) / `multiple` (more than one file) |
+| `surface` | `editor` (the Files tab or a drop on the editor) / `home` (the projects row's Import button or a drop there) |
### `project_rename`
User set the project name, from the header or the name-your-project prompt on
-save. No params.
+save, with no params; or from a project card or the projects toolbar, with
+`surface`.
+
+| Param | Values |
+| --------- | --------------------------------------------------------- |
+| `surface` | `home` / `projects` (absent when renamed from the editor) |
+
+## Project management events
+
+The home and projects pages, aligned with ml-trainer's events of the same
+names so one set of custom definitions serves both.
+
+### `project_create`
+
+User named and created a new project from the home page.
+
+| Param | Values |
+| --------- | ------ |
+| `surface` | `home` |
+
+### `project_open`
+
+User opened a project from a card or its menu.
+
+| Param | Values |
+| --------- | ------------------- |
+| `surface` | `home` / `projects` |
+
+### `project_duplicate`
+
+| Param | Values |
+| --------- | ------------------- |
+| `surface` | `home` / `projects` |
+
+### `project_delete`
+
+Fires once for a single delete and once for a multi-select delete, after the
+user confirms.
+
+| Param | Values |
+| --------- | --------------------------------------------------------- |
+| `count` | int (1 for a card's menu; the selection's size otherwise) |
+| `surface` | `home` / `projects` |
+
+### `project_search`
+
+Projects page only. Fires once per intentional search, 400ms after the last
+keystroke, not per keypress. No params.
+
+### `project_sort`
+
+Projects page only. Fires when the user changes the sort field or direction.
+
+| Param | Values |
+| ----------- | ---------------------------------------------- |
+| `field` | `name` / `timestamp` (last modified or opened) |
+| `direction` | `asc` / `desc` |
### `idea_open`
-User opened an idea into the editor.
+User opened an idea from the documentation as a new project.
| Param | Values |
| ----- | -------------------------- |
@@ -234,6 +288,7 @@ reading old dashboards.
| Old name | Status |
| -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
+| `project_reset` | Removed with the Reset project action (September 2026); replacing a program means importing from the home page. |
| `boot` → `WebUSB-available` | Dropped. Replaced by the `webusb_available` user property. `session_start` auto-fires. |
| `connect` | Replaced by `device_step` (`task: connect`) and `device_success` / `device_failure` / `device_exit`. |
| `disconnect` | Renamed `device_disconnect`; widened with `reason` to also capture unexpected drops. |
diff --git a/docs/home-page.md b/docs/home-page.md
new file mode 100644
index 000000000..3539e181e
--- /dev/null
+++ b/docs/home-page.md
@@ -0,0 +1,124 @@
+# Python home page / multipe-projects support
+
+New WIP version: https://review-python-editor-v3.microbit.org/home-page/ (pending PR merge)
+
+Older prototype: https://review-python-editor-v3.microbit.org/multiple-projects-prototype/
+
+The older prototype likely has nothing further to offer except as a comparison for one open question (the way home from the editor, see below).
+
+## 1. What this is
+
+The Python Editor gains a home page and a projects page, and keeps projects in an IndexedDB database in the browser, so a user can have more than one and come back to them. The editor moves to `/project`. The pages are built from shared components in `@microbit/ui-patterns`, lifted from ml-trainer, so the two apps present projects the same way.
+
+Launch is coordinated with content, support articles and video, so the work sits on an integration branch and reaches `main` at launch or for a TBC beta period. A react-router routing foundation and the shared error page have aready shipped.
+
+## 2. Where the work is
+
+- `home-page` (python-editor-v3, origin): the integration branch, from `main` after #1326. Merge `main` into it rather than rebasing.
+- `storage-foundation` → `home-page`: PR open, under review from 12 September. `FileSystem.switchStorage`, the projects database and per-project storage, session-storage migration and fallback, the review-stage clear-and-reload page.
+- `projects-pages` → `storage-foundation`: stacked, local. Everything else in this document: routes, pages, import, editor chrome, header, notice, toasts, e2e. Pins the released `@microbit/ui` 0.5.0, `ui-carousel` 0.4.0 and `ui-patterns` 0.7.0; the `dev:link-ui` and `dev:link-theme` scripts are no longer in use here.
+- `projects-pages` (private theme package): the home page images, the `AppLogo` and `OrgLogo` header components and the footer's `copyrightHolder`. A temporary branch build, `0.0.0-projects.pages.111`, is pinned in `build.yml`; merge and publish a release before landing.
+- `shared-project-components` (ml-trainer): consumes the shared components. Bump its pins to the releases before merging.
+
+Order once the storage PR is in: publish the theme package and pin the release, review `projects-pages`, land on `home-page`. At launch merge `home-page` to `main`; the database name includes the base path, so beta starts empty.
+
+## 3. Reviewing and running it
+
+There are still a lot of scenarios that haven't been manually exercised.
+
+Easy to miss cases:
+
+- **Iframe controller mode**, which the e2e suite covers only with a synthetic host page. Check the real embeds: `python-editor-embed`'s Storybook, and micro:bit classroom against a review build. Look at load, edits reaching the host, `importproject`, a dropped hex or Python file, the replace confirmation on an edited project, the before-unload prompt, and that no home or projects page is reachable.
+- **No IndexedDB outside an iframe.** The session-storage fallback exists for this cohort, but it is not known whether the cohort is real. Test the browsers and modes that plausibly block IndexedDB (Firefox and Safari private windows, Chrome with site data blocked, a school-managed Chromebook profile if one is to hand, Safari's storage settings) and record what actually happens in each. If none blocks it in practice, the fallback can be simplified or dropped; if some do, that is the case the fallback has to be good at.
+
+Notes for reviewers:
+
+## 4. Design notes
+
+### Storage
+
+- Write-through via the existing `FSStorage` seam. `FileSystem` keeps its in-memory working copy; the persistent secondary is `IndexedDBFSStorage`, scoped to a project id, in place of session storage. Opening a project is `FileSystem.switchStorage`. There is one record of a project's content and no autosave loop.
+- Schema: `projects` (id, name, timestamp) and `files` keyed by `[projectId, name]`, storing `Uint8Array`. Stores are asserted on open; a version mismatch shows a clear-and-reload page on non-public stages, which share one database. Production and beta databases are separate by base path.
+- Writes are coalesced per file with a short delay and flushed on `pagehide` and `visibilitychange`, stamped with the edit time so "last modified" is the edit. A failed flush drops that batch, keeps the in-memory copy, logs once per session and shows ml-trainer's persistent toast: "Browser storage full" for a quota error, "Failed to save your project to browser storage" otherwise. Page actions that hit the quota show the same toast.
+- Cross-tab: a `BroadcastChannel` carries `changed` and `deleted` project ids. Tabs refresh their lists; a tab with a changed project open reloads it; one with a deleted project open lets go of it. Timestamp bumps (e.g. from open) are not broadcast.
+- No IndexedDB (private browsing, blocked storage): one implicit project in session storage as today, no project management, pages redirect to the editor. Iframe controller mode is unchanged: the host owns the project.
+- Migration: on first load, session-storage files with no current project id become a new project with the stored name, and the editor opens on them. A `#project:` link takes precedence.
+- The dirty flag is not stored in the database. The before-unload prompt applies only without the database, where closing the tab still loses work.
+- Not done, deliberately: `navigator.storage.persist()` (Firefox prompts, Safari ignores it). The home page's info tooltip says projects are stored in this browser.
+
+### Routing
+
+- `react-router` 7, `createBrowserRouter`, one root layout route with `errorElement`, `urls.ts` builders as route paths, as ml-trainer. No project id in the URL; the tab's project is in `sessionStorage`.
+- Editor at `/project` with tabs at `/project/:tab/:slug`; the old `/:tab/:slug` paths redirect; unknown paths are the shared not-found page. Iframe mode has a single-route router with the editor at the root.
+- The editor route's loader chooses the project: the tab's, else migrated session storage, else the most recent, else a new one with the starter program. So `/project` in a fresh tab goes straight to code, which ml-trainer does not do; the editor is low friction today and some users bookmark it.
+- The editor's project is chosen when its route loads, not at boot, so the pages render without one.
+
+### Pages and sharing
+
+- Shared in `@microbit/ui-patterns`: `ProjectCard` and its actions, `ProjectsToolbar`, `SearchInput`, `SortInput`, `NameProjectDialog`, the search, sort and selection hooks, and the `useProjectActions` flows. Kept per app: page composition, banner, resource cards, header, storage, import, the editor. No shared `ProjectsPage` body; revisit if paging arrives.
+- Pages live in `src/pages/`; the `Projects` session in `src/project/projects.ts` owns storage switching and the cross-tab channel. Components never touch storage. Route loaders refresh the list so there is no empty-then-populated flash.
+- Cards show the Python logo in brand colour, and no file list, since it would almost always say `main.py`. File names are still loaded for search.
+- Header: organisation logo, divider, product wordmark, from `AppLogo` and `OrgLogo` in `BrandConfig`, the same `LogoProps` shape as ml-trainer. The OSS build has a text wordmark and no organisation logo; the SVG wordmark stays private because it is set in the brand typeface. The header buttons are pinned to 48px with a 24px icon because this app's dense preset would shrink them (see Open). Page header icon buttons use the `plain` variant, no hover state, as the family does; the editor's black chrome keeps `sidebar`.
+- The projects page's back button is the white `toolbar` pill with ml-trainer's `BackArrow`, copied not promoted.
+- The beta notice is a band under the page header with ml-trainer's copy and a Feedback button; the editor keeps its short sidebar notice, minus the "More" button. English only, since it shows on non-public stages only.
+- Images: `theme-package/images/*` resolves per file, the branded package winning and `src/deployment/default/images/` standing in with ml-trainer's minimal grey placeholders. Project idea card images are the matching MICI pages' images (updated for the recent changes). `ResourceCard` fills its slide (`w="100%"`), which ml-trainer should copy so the width lives in one place.
+
+### Import
+
+- A hex is always a whole program. With the database it becomes a new project, named after the file; without it, it replaces the implicit project, asking first if there are unsaved edits. Ideas and `#project:` links behave the same way.
+- From the editor, other files join the open project under their own names. Files that would overwrite existing ones ask first, in every mode.
+- From the home and projects pages, everything becomes a new project that opens in the editor: a single script becomes `main.py` and names the project; otherwise names are kept and the starter `main.py` added if missing.
+- The editor's drop target wraps the editor routes only. The pages have their own. This fixed two bugs found on 13 September: a drop on the projects page in a fresh tab waited silently on the storage promise and landed in whatever project opened next, and a drop on the home page left the app-level overlay stuck over the editor.
+- Gone: Reset project, the Open button in the action bar, the choose-main-script dialog. The Project tab is Files, with Add files.
+
+What each input does, by surface. "Editor" is the Files tab's Add files button or a drop on the editor. The pages only exist with the projects database; without it, and in iframe mode, their routes redirect to the editor.
+
+| Input | Iframe, editor | No IndexedDB, editor | Database, editor | Database, home or projects page |
+| ---------------------------- | ------------------------------------------ | ---------------------------- | ----------------------------------------------------------- | ----------------------------------------------------------- |
+| One hex | Replaces the project; asks first if edited | Same | New project named after the file; the editor switches to it | New project; the editor opens on it |
+| Hex with other files | Error toast | Same | Same | Same |
+| `.mpy` | Error toast | Same | Same | Same |
+| One `.py` script | Added under its own name | Same | Same | New project named after the script, which becomes `main.py` |
+| `main.py` | Replaces `main.py`; asks first | Same | Same | New untitled project with that `main.py` |
+| Several files, or a module | Added; asks before overwriting any | Same | Same | New untitled project; starter `main.py` added if none |
+| Idea from the Ideas tab | Replaces the project; asks first if edited | Same | New project named after the idea | n/a |
+| `#project:` link at boot | Ignored; the host owns the project | Replaces the project at boot | New project | n/a |
+| Host `importproject` message | Replaces the project | n/a | n/a | n/a |
+
+Against `main`: the replace confirmation used to gate a hex, an idea, reset and `#project:` links when the project was dirty; with the database those make a new project instead, and without it the confirmation remains. The choose-main-script dialog used to default a lone script to `main.py` and let the user pick; that default is the open question in section 6. Same-name replacement used to be shown in that dialog before confirming; it now has its own confirmation.
+
+## 5. Copy for review
+
+All new pages strings are new copy. Changed or notable:
+
+- `homepage-banner-heading` "Python for the BBC micro:bit"; `homepage-banner-subtitle` "Write a program, try it in the simulator, then send it to your micro:bit." microbit.org's vocabulary, describing the flow to a student.
+- `project-tab` "Project" → "Files"; `add-files-action` "Add files…"; `add-files-hover` "Add Python or other files to your project. A hex file opens as a new project."; `import-file-action` "Import" (ml-trainer's id and text).
+- `load-error-mixed` → "A hex file can only be imported on its own."
+- New: `confirm-replace-files-title` "Replace existing files?" and `confirm-replace-files-body`.
+- Restored from `main` with translations: the confirm-replace strings. Copied from ml-trainer so Crowdin dedupes: `storage-error-*`, `project-storage-tooltip`.
+- Never change shipped wording inside a refactor. Strings moved into ui-patterns keep their translations only if the ui-patterns Crowdin sync runs before the apps remove theirs; the sync is manual.
+
+## 6. Open decisions and discussion
+
+- **Home page banner artwork.** The background is ml-trainer's, brought across as a stand-in. It was drawn for CreateAI and does not suit this editor. A replacement drops in as `theme-package/images/banner-background.svg`.
+- **A lone dropped script no longer becomes `main.py`.** The old dialog's default made it so; now it is added under its own name and only a file called `main.py` replaces main. Renaming is the workaround. Hex downloads have been encouraged over script downloads for some time, which may make this moot, but the case that matters is the editor embedded in micro:bit classroom, where there is no home page to import from. Options: treat a single script dropped on the editor as "replace main.py" with a confirmation, or a lighter dialog.
+- **Where the welcome dialog appears.** Its state lives in the editor's sidebar, so a user landing on the home page sees nothing until they first open a project. Inherited, not chosen. Keep it in the editor, or lift it to the root layout. A re-recorded video's script probably decides this: a video that opens on the home page wants the dialog there.
+- **"Learn more" on the banner** links to the user guide for now. Its target is undecided and may be a landing page, which means recasting the "new micro:bit Python Editor" announcement page.
+- **Home page content.** The rows carry the prototype's project ideas, lessons and help cards as a first pass. The real list is to come, along with a feedback pass on cards, banner and copy once there is a shareable build.
+- **The way home from the editor** is the sidebar logo, which links to microbit.org without the database or in iframe mode. The collapsed sidebar has no room for a separate home button; the prototype had one in place of the logos, which is the comparison worth looking at. Revisit with any header change.
+- **Dense preset versus the shared pages.** This app stacks `@microbit/ui`'s dense preset; ml-trainer does not. The pages are now near-identical UI in both apps, so the difference is stark: the same `lg` icon button is 48px there and 42px here. The header pins its sizes as a visible hack. Options: accept the difference; pin more sizes as they come up; drop dense for this app (the editor was designed dense); or un-dense the pages by overriding token variables on the page root, which nothing does yet. The shared components are token-based, so the choice applies to them too.
+- **Promotions to the ui packages**, when the shared header work happens: `BackArrow` (now byte-identical in both apps, the family's bar), the header pattern (logo, divider, wordmark, back button), the beta notice. Also the family-wide absence of hover and active states on icon buttons in the brand bar, which is a gap to fix in `@microbit/ui` rather than here.
+- **ml-trainer follow-ups:** `ResourceCard` width as above; the `shared-error-pages` branches (ml-trainer, classroom, data-microbit-org) consume ui-patterns 0.6.1 and await review outside this stream.
+
+## 7. Decisions taken, subject to review/discussion
+
+- Iframe controller mode unchanged. Editor at `/project`; revisit if server-side projects arrive. New tab at `/` lands on home; `/project` goes straight to code.
+- Database name namespaced by base path; review stages share one. `Uint8Array` in the files store.
+- No IndexedDB: as the iframe path, with session storage kept as the fallback.
+- Before-unload prompt only without the database. Dirty flag not stored.
+- Without the database, a hex or idea asks before replacing an edited project. Revised 13 September: the first version dropped this, reasoning that the before-unload prompt protected the cohort, but that only guards closing the tab, and in classroom a student's dropped hex would silently replace the teacher's starter.
+- Adding files that overwrite existing ones asks first, in every mode.
+- Shared components in `ui-patterns`; no `ui-carousel` dependency there; no shared `ProjectsPage` body.
+- Integration branch `home-page`, merged from `main`, PRs into it reviewed as normal. `projects-pages` stacked ahead of the ui releases, red CI accepted.
+- The SVG wordmark stays in the private theme package. OSS placeholders are ml-trainer's minimal ones.
+- Beta notice and header not shared yet; the "More" button is gone.
diff --git a/i18n.config.mjs b/i18n.config.mjs
index 9e514f06a..ebd7ebabb 100644
--- a/i18n.config.mjs
+++ b/i18n.config.mjs
@@ -34,7 +34,11 @@ export default defineConfig({
// in the format it was created in.
crowdinFormat: "chrome",
out: "src/messages/ui.{lang}.json",
- packages: ["@microbit/ui", "@microbit/ui-patterns"],
+ packages: [
+ "@microbit/ui",
+ "@microbit/ui-patterns",
+ "@microbit/ui-carousel",
+ ],
},
],
});
diff --git a/lang/ui.ca.json b/lang/ui.ca.json
index 3ddeec423..c723b3034 100644
--- a/lang/ui.ca.json
+++ b/lang/ui.ca.json
@@ -53,9 +53,6 @@
"cancel-action": {
"defaultMessage": "Cancel·lar"
},
- "change-files": {
- "defaultMessage": "Canviar fitxers?"
- },
"choose-main-add-file": {
"defaultMessage": "Afegeix el fitxer {name}"
},
@@ -98,24 +95,9 @@
"confirm-delete": {
"defaultMessage": "Confirma la supressió"
},
- "confirm-replace-body": {
- "defaultMessage": "Substitueix tots els fitxers amb els de l'hexadecimal?"
- },
- "confirm-replace-reset": {
- "defaultMessage": "Vols substituir tots els fitxers pel programa d'inici per defecte?"
- },
- "confirm-replace-title": {
- "defaultMessage": "Confirma la substitució del projecte"
- },
- "confirm-replace-with-idea": {
- "defaultMessage": "Vols substituir tots els fitxers per {ideaName}?"
- },
"confirm-save-action": {
"defaultMessage": "Confirma i desa"
},
- "confirm-save-hint": {
- "defaultMessage": "Cancel·la i després utilitza Desa per conservar una còpia del teu projecte."
- },
"connect-action": {
"defaultMessage": "Connecta"
},
@@ -392,18 +374,9 @@
"open-action": {
"defaultMessage": "Obert"
},
- "open-file-action": {
- "defaultMessage": "Obert"
- },
"open-file-dropped": {
"defaultMessage": "Obre el fitxer quan es deixa anar"
},
- "open-hover": {
- "defaultMessage": "Obre un fitxer hexadecimal o Python o afegeix altres fitxers"
- },
- "options": {
- "defaultMessage": "Opcions"
- },
"parameter-help": {
"defaultMessage": "Ajuda dels paràmetres"
},
@@ -473,18 +446,6 @@
"reference-tab": {
"defaultMessage": "Referència"
},
- "replace-action-label": {
- "defaultMessage": "Substitueix"
- },
- "reset-project-action": {
- "defaultMessage": "Restableix el projecte"
- },
- "reset-project-feedback": {
- "defaultMessage": "Restableix el projecte al programa d'inici predeterminat"
- },
- "reset-project-hover": {
- "defaultMessage": "Restableix el projecte al programa inicial predeterminat, descartant el teu treball"
- },
"results-count": {
"defaultMessage": "{count, plural, =0 {Sense resultats} one {# resultat} other {# resultats}}"
},
diff --git a/lang/ui.de.json b/lang/ui.de.json
index a3e21c661..9cfef4340 100644
--- a/lang/ui.de.json
+++ b/lang/ui.de.json
@@ -53,9 +53,6 @@
"cancel-action": {
"defaultMessage": "Abbrechen"
},
- "change-files": {
- "defaultMessage": "Dateien ändern?"
- },
"choose-main-add-file": {
"defaultMessage": "Datei {name} hinzufügen?"
},
@@ -98,24 +95,9 @@
"confirm-delete": {
"defaultMessage": "Löschen bestätigen"
},
- "confirm-replace-body": {
- "defaultMessage": "Alle Dateien durch die in der HEX-Datei ersetzen?"
- },
- "confirm-replace-reset": {
- "defaultMessage": "Alle Dateien durch den Standard-Startcode ersetzen?"
- },
- "confirm-replace-title": {
- "defaultMessage": "Zurücksetzen bestätigen"
- },
- "confirm-replace-with-idea": {
- "defaultMessage": "Alle Dateien durch {ideaName} ersetzen?"
- },
"confirm-save-action": {
"defaultMessage": "Bestätigen und speichern"
},
- "confirm-save-hint": {
- "defaultMessage": "Abbrechen und dann Speichern verwenden, um eine Kopie deines Projekts zu behalten."
- },
"connect-action": {
"defaultMessage": "Verbinden"
},
@@ -392,18 +374,9 @@
"open-action": {
"defaultMessage": "Öffnen"
},
- "open-file-action": {
- "defaultMessage": "Öffnen …"
- },
"open-file-dropped": {
"defaultMessage": "Datei nach Ablegen öffnen"
},
- "open-hover": {
- "defaultMessage": "Eine HEX- oder Python-Datei öffnen oder andere Dateien hinzufügen."
- },
- "options": {
- "defaultMessage": "Optionen"
- },
"parameter-help": {
"defaultMessage": "Parameter-Hilfe"
},
@@ -473,18 +446,6 @@
"reference-tab": {
"defaultMessage": "Referenz"
},
- "replace-action-label": {
- "defaultMessage": "Ersetzen"
- },
- "reset-project-action": {
- "defaultMessage": "Projekt zurücksetzen"
- },
- "reset-project-feedback": {
- "defaultMessage": "Projekt auf den Standard-Startcode zurückgesetzt"
- },
- "reset-project-hover": {
- "defaultMessage": "Setzt das Projekt auf den Standard-Startcode zurück und verwirft deine Arbeit"
- },
"results-count": {
"defaultMessage": "{count, plural, =0 {Keine Ergebnisse} one {# Ergebnis} other {# Ergebnisse}}"
},
diff --git a/lang/ui.en.json b/lang/ui.en.json
index 0b1b8a0cb..a6d6f026c 100644
--- a/lang/ui.en.json
+++ b/lang/ui.en.json
@@ -27,6 +27,18 @@
"defaultMessage": "Accessibility",
"description": "Menu item for link to accessibility site"
},
+ "accessibility-resource-title": {
+ "defaultMessage": "Accessibility",
+ "description": "Title of the help card linking to the accessibility statement"
+ },
+ "add-files-action": {
+ "defaultMessage": "Add files…",
+ "description": "Button in the Files tab to add files to the project"
+ },
+ "add-files-hover": {
+ "defaultMessage": "Add Python or other files to your project. A hex file opens as a new project.",
+ "description": "Hover text for the add files button"
+ },
"added-change": {
"defaultMessage": "Added file {changeName}",
"description": "Change made to file"
@@ -71,10 +83,6 @@
"defaultMessage": "Cancel",
"description": "Cancel button text"
},
- "change-files": {
- "defaultMessage": "Change files?",
- "description": "Header for dialog of confirmation of files changed"
- },
"choose-main-add-file": {
"defaultMessage": "Add file {name}",
"description": "Shown in load dialog to confirm actions"
@@ -135,9 +143,13 @@
"defaultMessage": "Replace all files with those in the hex?",
"description": "Confirmation message body for replacing project dialog"
},
- "confirm-replace-reset": {
- "defaultMessage": "Replace all files with the default starter code?",
- "description": "Confirmation message for the reset action"
+ "confirm-replace-files-body": {
+ "defaultMessage": "{count, plural, one {{names} is already in this project and will be replaced.} other {These files are already in this project and will be replaced: {names}}}",
+ "description": "Body of the confirmation shown when added files would overwrite files already in the project. names is a comma-separated list of file names."
+ },
+ "confirm-replace-files-title": {
+ "defaultMessage": "Replace existing files?",
+ "description": "Title of the confirmation shown when added files would overwrite files already in the project"
},
"confirm-replace-title": {
"defaultMessage": "Confirm replace project",
@@ -227,6 +239,10 @@
"defaultMessage": "Create file",
"description": "Button text for action that creates a new blank Python file in the current project"
},
+ "create-project-action": {
+ "defaultMessage": "Create",
+ "description": "Confirm button of the dialog naming a new project"
+ },
"create-python": {
"defaultMessage": "Create a new Python file in this project",
"description": "Hover and dialog title when creating a new Python file"
@@ -327,6 +343,10 @@
"defaultMessage": "Firmware update required",
"description": "Title for the firmware update dialog"
},
+ "first-lessons-with-python-resource-title": {
+ "defaultMessage": "First lessons with Python and the micro:bit",
+ "description": "Title of the teacher resource card for the first lessons unit"
+ },
"flash-action": {
"defaultMessage": "Flash",
"description": "Text for flash button"
@@ -359,6 +379,10 @@
"defaultMessage": "Help",
"description": "Help menu label"
},
+ "help-resources-row-title": {
+ "defaultMessage": "Help",
+ "description": "Heading of the home page row of help cards"
+ },
"help-support": {
"defaultMessage": "Help & support",
"description": "Menu item for link to support site"
@@ -379,6 +403,18 @@
"defaultMessage": "Simple",
"description": "Highlight code structure option"
},
+ "home-action": {
+ "defaultMessage": "Home",
+ "description": "Link on the editor logo and button on the projects page returning to the home page"
+ },
+ "homepage-banner-heading": {
+ "defaultMessage": "Python for the BBC micro:bit",
+ "description": "Heading of the banner at the top of the home page"
+ },
+ "homepage-banner-subtitle": {
+ "defaultMessage": "Write a program, try it in the simulator, then send it to your micro:bit.",
+ "description": "Text under the home page banner heading"
+ },
"ideas-tab": {
"defaultMessage": "Ideas",
"description": "Ideas tab button text. Tab shows programs to give students ideas."
@@ -387,6 +423,10 @@
"defaultMessage": "Try out these projects, modify them and get inspired",
"description": "Ideas tab description text. Tab shows programs to give students ideas."
},
+ "import-file-action": {
+ "defaultMessage": "Import",
+ "description": "Import file action"
+ },
"insert-code-action": {
"defaultMessage": "Insert code"
},
@@ -394,6 +434,10 @@
"defaultMessage": "Language",
"description": "Language option text"
},
+ "learn-more-action": {
+ "defaultMessage": "Learn more",
+ "description": "Home page banner button linking to microbit.org"
+ },
"less-action": {
"defaultMessage": "Less",
"description": "Less button text (showing less content)"
@@ -407,7 +451,7 @@
"description": "Load error message"
},
"load-error-mixed": {
- "defaultMessage": "A hex file can only be loaded on its own. It replaces all files in the project.",
+ "defaultMessage": "A hex file can only be imported on its own.",
"description": "Load error message"
},
"load-error-mpy": {
@@ -470,6 +514,10 @@
"defaultMessage": "Warning: Only main.py downloaded",
"description": "Title of dialog shown when multiple files are available for download but only the main file downloaded."
},
+ "my-projects-row-title": {
+ "defaultMessage": "My projects",
+ "description": "Heading of the home page row of the user's projects, and of the projects page"
+ },
"name-project": {
"defaultMessage": "Name your project",
"description": "Name your project header"
@@ -486,10 +534,18 @@
"defaultMessage": "We'll add the .py extension for you.",
"description": "Hint shown in the new Python file dialog"
},
+ "new-project-action": {
+ "defaultMessage": "New project",
+ "description": "Home page card creating a new project"
+ },
"next-action": {
"defaultMessage": "Next",
"description": "Next button text"
},
+ "no-projects": {
+ "defaultMessage": "No projects to display",
+ "description": "Shown on the projects page when there are no projects, or none match the search"
+ },
"not-found-checklist-one": {
"defaultMessage": "Is your micro:bit plugged in? Did you follow these steps?",
"description": "Checklist text in the no micro:bit found dialog"
@@ -522,22 +578,10 @@
"defaultMessage": "Open",
"description": "Open button text"
},
- "open-file-action": {
- "defaultMessage": "Open…",
- "description": "Open file button text"
- },
"open-file-dropped": {
"defaultMessage": "Open file when dropped",
"description": "Aria label for file drop target"
},
- "open-hover": {
- "defaultMessage": "Open a hex or Python file or add other files",
- "description": "Hover text over load button"
- },
- "options": {
- "defaultMessage": "Options",
- "description": "Label for an options menu"
- },
"parameter-help": {
"defaultMessage": "Parameter help",
"description": "Setting label to control whether pop-up documentation for function/method parameters is automatically shown."
@@ -586,6 +630,34 @@
"defaultMessage": "Project header",
"description": "Aria label for the project header area"
},
+ "project-idea-animated-animals-title": {
+ "defaultMessage": "Animated animals",
+ "description": "Title of a project idea card; matches the microbit.org project name"
+ },
+ "project-idea-beating-heart-title": {
+ "defaultMessage": "Beating heart",
+ "description": "Title of a project idea card; matches the microbit.org project name"
+ },
+ "project-idea-emotion-badge-title": {
+ "defaultMessage": "Emotion badge",
+ "description": "Title of a project idea card; matches the microbit.org project name"
+ },
+ "project-idea-flashing-emotions-title": {
+ "defaultMessage": "Flashing emotions",
+ "description": "Title of a project idea card; matches the microbit.org project name"
+ },
+ "project-idea-get-silly-title": {
+ "defaultMessage": "Get silly",
+ "description": "Title of a project idea card; matches the microbit.org project name"
+ },
+ "project-idea-heart-title": {
+ "defaultMessage": "Heart",
+ "description": "Title of a project idea card; matches the microbit.org project name"
+ },
+ "project-ideas-row-title": {
+ "defaultMessage": "Project ideas",
+ "description": "Heading of the home page row of project idea cards"
+ },
"project-name": {
"defaultMessage": "Project name",
"description": "Text used to indicate the project name"
@@ -594,14 +666,22 @@
"defaultMessage": "The project name cannot be empty",
"description": "Validation message for project name"
},
+ "project-storage-tooltip": {
+ "defaultMessage": "Your data is saved in this browser on this device. Clearing your browser's cookies or site data will delete it. It may also be automatically removed by your browser if your device is low on storage.",
+ "description": "Tooltip on the home page projects row heading explaining where projects are stored"
+ },
"project-tab": {
- "defaultMessage": "Project",
- "description": "Project tab button text"
+ "defaultMessage": "Files",
+ "description": "Files tab button text (the project's files)"
},
"project-tab-description": {
"defaultMessage": "View, create, add and edit the files in your project",
"description": "Project tab description"
},
+ "projects-page-title": {
+ "defaultMessage": "Projects",
+ "description": "Title of the page listing all projects"
+ },
"python-powered": {
"defaultMessage": "Python powered",
"description": "Python powered logo alt text"
@@ -634,18 +714,6 @@
"defaultMessage": "Replace",
"description": "Action label for replacing project dialog"
},
- "reset-project-action": {
- "defaultMessage": "Reset project",
- "description": "Action to reset the project to its default state"
- },
- "reset-project-feedback": {
- "defaultMessage": "Project reset to the default starter code",
- "description": "Confirmation message after resetting the project"
- },
- "reset-project-hover": {
- "defaultMessage": "Resets the project to the default starter code, discarding your work",
- "description": "Reset action hover text"
- },
"results-count": {
"defaultMessage": "{count, plural, =0 {No results} one {# result} other {# results}}",
"description": "Number of results from a search. Uses ICU syntax for pluralisation: https://formatjs.io/docs/core-concepts/icu-syntax/#plural-format."
@@ -1066,10 +1134,26 @@
"defaultMessage": "Start coding",
"description": "Start coding button text"
},
+ "storage-error-other": {
+ "defaultMessage": "Failed to save your project to browser storage",
+ "description": "Toast shown when an IndexedDB storage write fails for an unknown reason"
+ },
+ "storage-error-quota-description": {
+ "defaultMessage": "Your project edit may not be saved.",
+ "description": "Toast description when browser storage quota is exceeded"
+ },
+ "storage-error-quota-title": {
+ "defaultMessage": "Browser storage full",
+ "description": "Toast title when browser storage quota is exceeded"
+ },
"support": {
"defaultMessage": "Support",
"description": "Support menu option text"
},
+ "teacher-resources-row-title": {
+ "defaultMessage": "Teacher resources",
+ "description": "Heading of the home page row of teacher resource cards"
+ },
"terms-of-use": {
"defaultMessage": "Terms of use",
"description": "Terms of use menu option text"
@@ -1110,6 +1194,10 @@
"defaultMessage": "Transfer saved hex file to micro:bit",
"description": "Title for the transfer hex dialog"
},
+ "troubleshooting-resource-title": {
+ "defaultMessage": "Troubleshooting",
+ "description": "Title of the help card linking to the support site"
+ },
"try-again-action": {
"defaultMessage": "Try again",
"description": "Try again button text"
@@ -1142,6 +1230,10 @@
"defaultMessage": "User guide",
"description": "Menu item for link to user guide site"
},
+ "view-all-projects": {
+ "defaultMessage": "View all",
+ "description": "Link and card on the home page leading to the projects page"
+ },
"visit-dot-org": {
"defaultMessage": "visit microbit.org (opens in a new tab)",
"description": "alt text for logo link to .org"
diff --git a/lang/ui.es-ES.json b/lang/ui.es-ES.json
index 44badbdb0..4d353f8e2 100644
--- a/lang/ui.es-ES.json
+++ b/lang/ui.es-ES.json
@@ -53,9 +53,6 @@
"cancel-action": {
"defaultMessage": "Cancelar"
},
- "change-files": {
- "defaultMessage": "¿Cambiar archivos?"
- },
"choose-main-add-file": {
"defaultMessage": "Añadir archivo {name}"
},
@@ -98,24 +95,9 @@
"confirm-delete": {
"defaultMessage": "Confirmar eliminación"
},
- "confirm-replace-body": {
- "defaultMessage": "¿Sustituir todos los archivos por los del HEX?"
- },
- "confirm-replace-reset": {
- "defaultMessage": "¿Sustituir todos los archivos por el código de inicio predeterminado?"
- },
- "confirm-replace-title": {
- "defaultMessage": "¿Sustituir proyecto?"
- },
- "confirm-replace-with-idea": {
- "defaultMessage": "¿Sustituir todos los archivos por {ideaName}?"
- },
"confirm-save-action": {
"defaultMessage": "Confirmar y guardar"
},
- "confirm-save-hint": {
- "defaultMessage": "Cancela y utiliza Guardar para conservar una copia de tu proyecto."
- },
"connect-action": {
"defaultMessage": "Conectar"
},
@@ -392,18 +374,9 @@
"open-action": {
"defaultMessage": "Abrir"
},
- "open-file-action": {
- "defaultMessage": "Abrir…"
- },
"open-file-dropped": {
"defaultMessage": "Abrir archivo al soltarlo"
},
- "open-hover": {
- "defaultMessage": "Abre un archivo HEX o Python o añade otros archivos"
- },
- "options": {
- "defaultMessage": "Opciones"
- },
"parameter-help": {
"defaultMessage": "Ayuda con parámetros"
},
@@ -473,18 +446,6 @@
"reference-tab": {
"defaultMessage": "Referencia"
},
- "replace-action-label": {
- "defaultMessage": "Sustituir"
- },
- "reset-project-action": {
- "defaultMessage": "Restablecer proyecto"
- },
- "reset-project-feedback": {
- "defaultMessage": "Proyecto restablecido al código de inicio predeterminado"
- },
- "reset-project-hover": {
- "defaultMessage": "Restablece el proyecto al código de inicio predeterminado, descartando tu trabajo"
- },
"results-count": {
"defaultMessage": "{count, plural, =0 {Ningún resultado} one {# resultado} other {# resultados}}"
},
diff --git a/lang/ui.fr.json b/lang/ui.fr.json
index 75cf586da..457bf6bb0 100644
--- a/lang/ui.fr.json
+++ b/lang/ui.fr.json
@@ -53,9 +53,6 @@
"cancel-action": {
"defaultMessage": "Annuler"
},
- "change-files": {
- "defaultMessage": "Changer de fichiers ?"
- },
"choose-main-add-file": {
"defaultMessage": "Ajouter le fichier {name}"
},
@@ -98,24 +95,9 @@
"confirm-delete": {
"defaultMessage": "Confirmer la suppression"
},
- "confirm-replace-body": {
- "defaultMessage": "Remplacer tous les fichiers par ceux de l’hex ?"
- },
- "confirm-replace-reset": {
- "defaultMessage": "Remplacer tous les fichiers avec le code de départ par défaut ?"
- },
- "confirm-replace-title": {
- "defaultMessage": "Confirmer le remplacement du projet"
- },
- "confirm-replace-with-idea": {
- "defaultMessage": "Remplacer tous les fichiers par {ideaName} ?"
- },
"confirm-save-action": {
"defaultMessage": "Confirmer et enregistrer"
},
- "confirm-save-hint": {
- "defaultMessage": "Annulez et utilisez Enregistrer pour conserver une copie de votre projet."
- },
"connect-action": {
"defaultMessage": "Connecter"
},
@@ -392,18 +374,9 @@
"open-action": {
"defaultMessage": "Ouvrir"
},
- "open-file-action": {
- "defaultMessage": "Ouvrir…"
- },
"open-file-dropped": {
"defaultMessage": "Ouvrir le fichier lorsqu’il est déposé"
},
- "open-hover": {
- "defaultMessage": "Ouvrir un fichier hex ou Python, ou ajouter d'autres fichiers"
- },
- "options": {
- "defaultMessage": "Options"
- },
"parameter-help": {
"defaultMessage": "Aide aux paramètres"
},
@@ -473,18 +446,6 @@
"reference-tab": {
"defaultMessage": "Référence"
},
- "replace-action-label": {
- "defaultMessage": "Remplacer"
- },
- "reset-project-action": {
- "defaultMessage": "Réinitialiser le projet"
- },
- "reset-project-feedback": {
- "defaultMessage": "Le projet a été réinitialisé au code de départ par défaut"
- },
- "reset-project-hover": {
- "defaultMessage": "Réinitialise le projet au code de départ par défaut, supprimant ainsi votre travail"
- },
"results-count": {
"defaultMessage": "{count, plural, =0 {Aucun résultat} one {# résultat} other {# résultats}}"
},
diff --git a/lang/ui.ga-IE.json b/lang/ui.ga-IE.json
index cc8d2a4bf..eeae19028 100644
--- a/lang/ui.ga-IE.json
+++ b/lang/ui.ga-IE.json
@@ -53,9 +53,6 @@
"cancel-action": {
"defaultMessage": "Cealaigh"
},
- "change-files": {
- "defaultMessage": "Athraigh comhaid?"
- },
"choose-main-add-file": {
"defaultMessage": "Cuir comhad {name} leis"
},
@@ -98,24 +95,9 @@
"confirm-delete": {
"defaultMessage": "Deimhnigh scrios"
},
- "confirm-replace-body": {
- "defaultMessage": "Cuir iad siúd sa heics in ionad gach comhad?"
- },
- "confirm-replace-reset": {
- "defaultMessage": "Cuir an cód tosaithe réamhshocraithe in ionad gach comhaid?"
- },
- "confirm-replace-title": {
- "defaultMessage": "Deimhnigh ionadaigh an tionscadal"
- },
- "confirm-replace-with-idea": {
- "defaultMessage": "Cuir {ideaName} in ionad gach comhaid?"
- },
"confirm-save-action": {
"defaultMessage": "Deimhnigh agus sábháil"
},
- "confirm-save-hint": {
- "defaultMessage": "Cealaigh ansin úsáid Sábháil chun cóip de do thionscadal a choinneáil."
- },
"connect-action": {
"defaultMessage": "Ceangail"
},
@@ -392,18 +374,9 @@
"open-action": {
"defaultMessage": "Oscail"
},
- "open-file-action": {
- "defaultMessage": "Oscail…"
- },
"open-file-dropped": {
"defaultMessage": "Oscail comhad nuair a thit sé"
},
- "open-hover": {
- "defaultMessage": "Oscail comhad heicsidheachúlach nó Python nó cuir comhaid eile leis"
- },
- "options": {
- "defaultMessage": "Roghanna"
- },
"parameter-help": {
"defaultMessage": "Cabhair pharaiméadair"
},
@@ -473,18 +446,6 @@
"reference-tab": {
"defaultMessage": "Tagairt"
},
- "replace-action-label": {
- "defaultMessage": "Ionadaigh"
- },
- "reset-project-action": {
- "defaultMessage": "Athshocraigh tionscadal"
- },
- "reset-project-feedback": {
- "defaultMessage": "Athshocraigh an tionscadal go dtí an cód tosaithe réamhshocraithe"
- },
- "reset-project-hover": {
- "defaultMessage": "Athshocraigh an tionscadal chuig an gcód tosaithe réamhshocraithe, ag caitheamh do chuid oibre"
- },
"results-count": {
"defaultMessage": "{count, plural, =0 {Gan torthaí} one {# toradh} two {# thoradh} few {# torthaí} many {# torthaí} other {# torthaí}}\n"
},
diff --git a/lang/ui.ja.json b/lang/ui.ja.json
index 22d3baacd..2e0798fd7 100644
--- a/lang/ui.ja.json
+++ b/lang/ui.ja.json
@@ -53,9 +53,6 @@
"cancel-action": {
"defaultMessage": "キャンセル"
},
- "change-files": {
- "defaultMessage": "ファイルを変更しますか?"
- },
"choose-main-add-file": {
"defaultMessage": "ファイル {name} を追加"
},
@@ -98,24 +95,9 @@
"confirm-delete": {
"defaultMessage": "削除の確認"
},
- "confirm-replace-body": {
- "defaultMessage": "すべてのファイルを hex に置き換えますか?"
- },
- "confirm-replace-reset": {
- "defaultMessage": "すべてのファイルをデフォルトのスターターコードに置き換えますか?"
- },
- "confirm-replace-title": {
- "defaultMessage": "プロジェクトの置き換えを確認"
- },
- "confirm-replace-with-idea": {
- "defaultMessage": "すべてのファイルを {ideaName} に置き換えますか?"
- },
"confirm-save-action": {
"defaultMessage": "確認して保存"
},
- "confirm-save-hint": {
- "defaultMessage": "キャンセルして、保存を使ってプロジェクトのコピーを保持します。"
- },
"connect-action": {
"defaultMessage": "接続"
},
@@ -392,18 +374,9 @@
"open-action": {
"defaultMessage": "開く"
},
- "open-file-action": {
- "defaultMessage": "開く…"
- },
"open-file-dropped": {
"defaultMessage": "ドロップ時にファイルを開く"
},
- "open-hover": {
- "defaultMessage": "hex ファイルまたは Python ファイルを開くか、他のファイルを追加する"
- },
- "options": {
- "defaultMessage": "オプション"
- },
"parameter-help": {
"defaultMessage": "パラメータのヘルプ"
},
@@ -473,18 +446,6 @@
"reference-tab": {
"defaultMessage": "リファレンス"
},
- "replace-action-label": {
- "defaultMessage": "置換"
- },
- "reset-project-action": {
- "defaultMessage": "プロジェクトをリセット"
- },
- "reset-project-feedback": {
- "defaultMessage": "プロジェクトがデフォルトのスターターコードにリセットされました"
- },
- "reset-project-hover": {
- "defaultMessage": "プロジェクトをデフォルトのスターターコードにリセットし、作業を破棄します"
- },
"results-count": {
"defaultMessage": "{count, plural, =0 {結果なし} other {#件の結果}}"
},
diff --git a/lang/ui.ko.json b/lang/ui.ko.json
index ddea6d64d..155dcc315 100644
--- a/lang/ui.ko.json
+++ b/lang/ui.ko.json
@@ -53,9 +53,6 @@
"cancel-action": {
"defaultMessage": "취소"
},
- "change-files": {
- "defaultMessage": "파일을 변경하시겠습니까?"
- },
"choose-main-add-file": {
"defaultMessage": "{name} 파일 추가"
},
@@ -98,24 +95,9 @@
"confirm-delete": {
"defaultMessage": "삭제 확인"
},
- "confirm-replace-body": {
- "defaultMessage": "모든 파일을 hex 내 파일로 교체하시겠습니까?"
- },
- "confirm-replace-reset": {
- "defaultMessage": "모든 파일을 기본 스타터 코드로 교체하시겠습니까?"
- },
- "confirm-replace-title": {
- "defaultMessage": "프로젝트 교체 확인"
- },
- "confirm-replace-with-idea": {
- "defaultMessage": "모든 파일을 {ideaName}(으)로 교체하시겠습니까?"
- },
"confirm-save-action": {
"defaultMessage": "확인 및 저장"
},
- "confirm-save-hint": {
- "defaultMessage": "프로젝트의 사본을 유지하려면 취소하고 저장하세요."
- },
"connect-action": {
"defaultMessage": "연결"
},
@@ -389,18 +371,9 @@
"open-action": {
"defaultMessage": "열기"
},
- "open-file-action": {
- "defaultMessage": "열기…"
- },
"open-file-dropped": {
"defaultMessage": "드롭될 때 파일 열기"
},
- "open-hover": {
- "defaultMessage": "Hex 또는 Python 파일을 열거나 다른 파일을 추가하세요"
- },
- "options": {
- "defaultMessage": "옵션"
- },
"parameter-help": {
"defaultMessage": "매개변수 도움말"
},
@@ -470,18 +443,6 @@
"reference-tab": {
"defaultMessage": "참조"
},
- "replace-action-label": {
- "defaultMessage": "교체"
- },
- "reset-project-action": {
- "defaultMessage": "프로젝트 초기화"
- },
- "reset-project-feedback": {
- "defaultMessage": "프로젝트를 기본 스타터 코드로 초기화합니다."
- },
- "reset-project-hover": {
- "defaultMessage": "프로젝트를 기본 스타터 코드로 초기화하고 작업한 내용을 삭제합니다"
- },
"results-count": {
"defaultMessage": "{count, plural, =0 {결과 없음} one {#개의 결과} other {#개의 결과}}"
},
diff --git a/lang/ui.lol.json b/lang/ui.lol.json
index 8186e5efa..68dcaab09 100644
--- a/lang/ui.lol.json
+++ b/lang/ui.lol.json
@@ -53,9 +53,6 @@
"cancel-action": {
"defaultMessage": "crwdns331388:0crwdne331388:0"
},
- "change-files": {
- "defaultMessage": "crwdns331390:0crwdne331390:0"
- },
"choose-main-add-file": {
"defaultMessage": "crwdns331392:0{name}crwdne331392:0"
},
@@ -98,24 +95,9 @@
"confirm-delete": {
"defaultMessage": "crwdns331418:0crwdne331418:0"
},
- "confirm-replace-body": {
- "defaultMessage": "crwdns331420:0crwdne331420:0"
- },
- "confirm-replace-reset": {
- "defaultMessage": "crwdns331422:0crwdne331422:0"
- },
- "confirm-replace-title": {
- "defaultMessage": "crwdns331424:0crwdne331424:0"
- },
- "confirm-replace-with-idea": {
- "defaultMessage": "crwdns331426:0{ideaName}crwdne331426:0"
- },
"confirm-save-action": {
"defaultMessage": "crwdns331428:0crwdne331428:0"
},
- "confirm-save-hint": {
- "defaultMessage": "crwdns331430:0crwdne331430:0"
- },
"connect-action": {
"defaultMessage": "crwdns331432:0crwdne331432:0"
},
@@ -392,18 +374,9 @@
"open-action": {
"defaultMessage": "crwdns331610:0crwdne331610:0"
},
- "open-file-action": {
- "defaultMessage": "crwdns331612:0crwdne331612:0"
- },
"open-file-dropped": {
"defaultMessage": "crwdns331614:0crwdne331614:0"
},
- "open-hover": {
- "defaultMessage": "crwdns331616:0crwdne331616:0"
- },
- "options": {
- "defaultMessage": "crwdns331618:0crwdne331618:0"
- },
"parameter-help": {
"defaultMessage": "crwdns331620:0crwdne331620:0"
},
@@ -473,18 +446,6 @@
"reference-tab": {
"defaultMessage": "crwdns331662:0crwdne331662:0"
},
- "replace-action-label": {
- "defaultMessage": "crwdns331664:0crwdne331664:0"
- },
- "reset-project-action": {
- "defaultMessage": "crwdns331666:0crwdne331666:0"
- },
- "reset-project-feedback": {
- "defaultMessage": "crwdns331668:0crwdne331668:0"
- },
- "reset-project-hover": {
- "defaultMessage": "crwdns331670:0crwdne331670:0"
- },
"results-count": {
"defaultMessage": "crwdns331672:0count={count}crwdne331672:0"
},
diff --git a/lang/ui.nl.json b/lang/ui.nl.json
index 9fea59b0a..f97271039 100644
--- a/lang/ui.nl.json
+++ b/lang/ui.nl.json
@@ -53,9 +53,6 @@
"cancel-action": {
"defaultMessage": "Annuleren"
},
- "change-files": {
- "defaultMessage": "Bestanden wijzigen?"
- },
"choose-main-add-file": {
"defaultMessage": "Voeg bestand {name} toe"
},
@@ -98,24 +95,9 @@
"confirm-delete": {
"defaultMessage": "Verwijderen bevestigen"
},
- "confirm-replace-body": {
- "defaultMessage": "Alle bestanden vervangen door bestanden in de hex?"
- },
- "confirm-replace-reset": {
- "defaultMessage": "Alle bestanden vervangen door de standaard startcode?"
- },
- "confirm-replace-title": {
- "defaultMessage": "Vervanging bevestigen"
- },
- "confirm-replace-with-idea": {
- "defaultMessage": "Alle bestanden vervangen door {ideaName}?"
- },
"confirm-save-action": {
"defaultMessage": "Bevestigen en opslaan"
},
- "confirm-save-hint": {
- "defaultMessage": "Annuleer dan Opslaan om een kopie van je project te bewaren."
- },
"connect-action": {
"defaultMessage": "Verbinden "
},
@@ -392,18 +374,9 @@
"open-action": {
"defaultMessage": "Open"
},
- "open-file-action": {
- "defaultMessage": "Open…"
- },
"open-file-dropped": {
"defaultMessage": "Open bestand wanneer gedropt"
},
- "open-hover": {
- "defaultMessage": "Open een hex of Python bestand of voeg andere bestanden toe"
- },
- "options": {
- "defaultMessage": "Opties"
- },
"parameter-help": {
"defaultMessage": "Hulp parameter"
},
@@ -473,18 +446,6 @@
"reference-tab": {
"defaultMessage": "Referentie"
},
- "replace-action-label": {
- "defaultMessage": "Vervangen"
- },
- "reset-project-action": {
- "defaultMessage": "Reset project"
- },
- "reset-project-feedback": {
- "defaultMessage": "Project opnieuw instellen op de standaard startcode"
- },
- "reset-project-hover": {
- "defaultMessage": "Reset het project naar de standaard start code, waarmee je jouw werk weggooit"
- },
"results-count": {
"defaultMessage": "{count, plural, =0{# results} one{# result} other{# results}}"
},
diff --git a/lang/ui.pl.json b/lang/ui.pl.json
index 9792c7d6a..746ed6f70 100644
--- a/lang/ui.pl.json
+++ b/lang/ui.pl.json
@@ -53,9 +53,6 @@
"cancel-action": {
"defaultMessage": "Anuluj"
},
- "change-files": {
- "defaultMessage": "Zmienić pliki?"
- },
"choose-main-add-file": {
"defaultMessage": "Dodaj plik {name}"
},
@@ -98,24 +95,9 @@
"confirm-delete": {
"defaultMessage": "Potwierdź usunięcie"
},
- "confirm-replace-body": {
- "defaultMessage": "Zastąpić wszystkie pliki tymi z hex?"
- },
- "confirm-replace-reset": {
- "defaultMessage": "Zastąpić wszystkie pliki domyślnym kodem startowym?"
- },
- "confirm-replace-title": {
- "defaultMessage": "Potwierdź zastąpienie projektu"
- },
- "confirm-replace-with-idea": {
- "defaultMessage": "Zastąpić wszystkie pliki {ideaName}?"
- },
"confirm-save-action": {
"defaultMessage": "Potwierdź i zapisz"
},
- "confirm-save-hint": {
- "defaultMessage": "Anuluj następnie użycie Zapisz, aby zachować kopię projektu."
- },
"connect-action": {
"defaultMessage": "Podłącz"
},
@@ -392,18 +374,9 @@
"open-action": {
"defaultMessage": "Otwórz"
},
- "open-file-action": {
- "defaultMessage": "Otwórz…"
- },
"open-file-dropped": {
"defaultMessage": "Otwórz plik po upuszczeniu"
},
- "open-hover": {
- "defaultMessage": "Otwórz plik hex lub Pythona lub dodaj inne pliki"
- },
- "options": {
- "defaultMessage": "Opcje"
- },
"parameter-help": {
"defaultMessage": "Pomoc dot. parametru"
},
@@ -473,18 +446,6 @@
"reference-tab": {
"defaultMessage": "Referencje"
},
- "replace-action-label": {
- "defaultMessage": "Zastąp"
- },
- "reset-project-action": {
- "defaultMessage": "Resetuj projekt"
- },
- "reset-project-feedback": {
- "defaultMessage": "Reset projektu do domyślnego kodu startowego"
- },
- "reset-project-hover": {
- "defaultMessage": "Resetuje projekt do domyślnego kodu startowego, odrzucając Twoją pracę"
- },
"results-count": {
"defaultMessage": "{count, plural, =0 {Brak wyników} one {# wynik} few {# wyników} many {# wyników} other {# wyników}}"
},
diff --git a/lang/ui.zh-CN.json b/lang/ui.zh-CN.json
index 0d8dc26da..861a93fd1 100644
--- a/lang/ui.zh-CN.json
+++ b/lang/ui.zh-CN.json
@@ -53,9 +53,6 @@
"cancel-action": {
"defaultMessage": "取消"
},
- "change-files": {
- "defaultMessage": "更改文件?"
- },
"choose-main-add-file": {
"defaultMessage": "添加文件 {name}"
},
@@ -98,24 +95,9 @@
"confirm-delete": {
"defaultMessage": "确认删除"
},
- "confirm-replace-body": {
- "defaultMessage": "将所有文件替换成 hex 文件?"
- },
- "confirm-replace-reset": {
- "defaultMessage": "用默认启动代码替换所有文件?"
- },
- "confirm-replace-title": {
- "defaultMessage": "确认替换项目"
- },
- "confirm-replace-with-idea": {
- "defaultMessage": "用 {ideaName} 替换所有文件?"
- },
"confirm-save-action": {
"defaultMessage": "确认并保存"
},
- "confirm-save-hint": {
- "defaultMessage": "取消然后使用 Save (保存)来保留您的项目副本。"
- },
"connect-action": {
"defaultMessage": "连接"
},
@@ -392,18 +374,9 @@
"open-action": {
"defaultMessage": "打开"
},
- "open-file-action": {
- "defaultMessage": "打开…"
- },
"open-file-dropped": {
"defaultMessage": "放下时打开文件"
},
- "open-hover": {
- "defaultMessage": "打开一 份 hex 或 Python 文件,或添加其他文件"
- },
- "options": {
- "defaultMessage": "选项"
- },
"parameter-help": {
"defaultMessage": "参数帮助"
},
@@ -473,18 +446,6 @@
"reference-tab": {
"defaultMessage": "参考"
},
- "replace-action-label": {
- "defaultMessage": "替换"
- },
- "reset-project-action": {
- "defaultMessage": "重置项目"
- },
- "reset-project-feedback": {
- "defaultMessage": "项目重置为默认启动代码"
- },
- "reset-project-hover": {
- "defaultMessage": "将项目重置为默认启动代码,放弃您的工作"
- },
"results-count": {
"defaultMessage": "{count, plural, =0 {No results} other {# results}}"
},
diff --git a/lang/ui.zh-TW.json b/lang/ui.zh-TW.json
index 1df1e54b4..2ce53a2c1 100644
--- a/lang/ui.zh-TW.json
+++ b/lang/ui.zh-TW.json
@@ -53,9 +53,6 @@
"cancel-action": {
"defaultMessage": "取消"
},
- "change-files": {
- "defaultMessage": "是否要變更檔案?"
- },
"choose-main-add-file": {
"defaultMessage": "新增檔案 {name}"
},
@@ -98,24 +95,9 @@
"confirm-delete": {
"defaultMessage": "確認刪除"
},
- "confirm-replace-body": {
- "defaultMessage": "是否要用 hex 取代所有檔案?"
- },
- "confirm-replace-reset": {
- "defaultMessage": "是否要用預設起始程式碼來取代所有檔案?"
- },
- "confirm-replace-title": {
- "defaultMessage": "確認取代專案"
- },
- "confirm-replace-with-idea": {
- "defaultMessage": "是否要用 {ideaName} 取代所有檔案?"
- },
"confirm-save-action": {
"defaultMessage": "確認並儲存"
},
- "confirm-save-hint": {
- "defaultMessage": "取消,然後使用「儲存」來保留您的專案副本。"
- },
"connect-action": {
"defaultMessage": "連線"
},
@@ -389,18 +371,9 @@
"open-action": {
"defaultMessage": "開啟"
},
- "open-file-action": {
- "defaultMessage": "開啟..."
- },
"open-file-dropped": {
"defaultMessage": "放置時開啟檔案"
},
- "open-hover": {
- "defaultMessage": "開啟 Hex 或 Python 檔案,或是新增其他檔案"
- },
- "options": {
- "defaultMessage": "選項"
- },
"parameter-help": {
"defaultMessage": "參數說明"
},
@@ -470,18 +443,6 @@
"reference-tab": {
"defaultMessage": "參考資料"
},
- "replace-action-label": {
- "defaultMessage": "取代"
- },
- "reset-project-action": {
- "defaultMessage": "重設專案"
- },
- "reset-project-feedback": {
- "defaultMessage": "專案重設為預設起始程式碼"
- },
- "reset-project-hover": {
- "defaultMessage": "將專案重設為預設起始程式碼,放棄您的工作"
- },
"results-count": {
"defaultMessage": "{count, plural, =0 {沒有結果} one {# 個結果} other {# 個結果}}"
},
diff --git a/package-lock.json b/package-lock.json
index 195ff89cd..4722443e9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -18,8 +18,9 @@
"@microbit/microbit-connection": "^1.0.0",
"@microbit/microbit-fs": "^0.10.0",
"@microbit/micropython-microbit-stubs": "^1.0.0",
- "@microbit/ui": "^0.4.0",
- "@microbit/ui-patterns": "^0.6.1",
+ "@microbit/ui": "^0.5.0",
+ "@microbit/ui-carousel": "^0.4.0",
+ "@microbit/ui-patterns": "^0.7.0",
"@sanity/block-content-to-react": "^3.0.0",
"@sanity/image-url": "^1.0.1",
"@sentry/browser": "^10.71.0",
@@ -48,6 +49,7 @@
"react-icons": "^4.12.0",
"react-intl": "^6.6.8",
"react-router": "^7.18.3",
+ "swiper": "^14.2.0",
"vite": "^7.3.1",
"vscode-jsonrpc": "^9.0.0",
"vscode-languageserver-protocol": "^3.16.0",
@@ -3384,9 +3386,9 @@
"license": "SEE LICENSE IN LICENSE.md"
},
"node_modules/@microbit/ui": {
- "version": "0.4.2",
- "resolved": "https://registry.npmjs.org/@microbit/ui/-/ui-0.4.2.tgz",
- "integrity": "sha512-NsfDwtyxxjgwTb9OzUaThdSjax7KiPk53IFXRegTP8mCoebCu71Tipp/qgjQoHN/oG91UgJh7DbFJMVAxpo+Mg==",
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/@microbit/ui/-/ui-0.5.0.tgz",
+ "integrity": "sha512-r95JncGQHl9ERABLf3yf29taBBB6r0KewQqFkT5l9lGb6h3DHPeoYcmIXB64WvadWwhMq5afWD+orxuDyqKJ/A==",
"license": "MIT",
"peerDependencies": {
"@pandacss/dev": "^1.11.4",
@@ -3397,16 +3399,30 @@
"react-intl": "^6.6.8 || ^7.0.0"
}
},
+ "node_modules/@microbit/ui-carousel": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/@microbit/ui-carousel/-/ui-carousel-0.4.0.tgz",
+ "integrity": "sha512-oDAaqZfaSLBWeDYSi9EAlCYC8rKw9nsU6TmOm5BBGNm1OyD34M2d3QA8ukFJ4hM8RmAVg/8DJ2MhOk7UNhXMjQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@microbit/ui": "^0.5.0",
+ "@pandacss/dev": "^1.11.4",
+ "react": "^18.3.1",
+ "react-aria": "^3.50.0",
+ "react-intl": "^6.6.8 || ^7.0.0",
+ "swiper": "^14.0.0"
+ }
+ },
"node_modules/@microbit/ui-patterns": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/@microbit/ui-patterns/-/ui-patterns-0.6.1.tgz",
- "integrity": "sha512-Imnmc9Jpg2M80/3aY3HKaK3sUHjLWLQtoqzuj/IedjT8Ozsige/mCZE5g9efwYqcT4xIxpI80gvFBSWvKvigqw==",
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/@microbit/ui-patterns/-/ui-patterns-0.7.0.tgz",
+ "integrity": "sha512-IFzdKUu34iV5Pw3vFod4J1Ofrkkjh+CVw3ih4UjUj8qgUBSN1ij8TvlwPXC3E83ShKfCtSwgm1raW5qf3+SRkQ==",
"license": "MIT",
"dependencies": {
"@formatjs/intl-localematcher": "^0.8.0"
},
"peerDependencies": {
- "@microbit/ui": "^0.4.0",
+ "@microbit/ui": "^0.5.0",
"@pandacss/dev": "^1.11.4",
"react": "^18.3.1",
"react-icons": "^4.12.0 || ^5.0.0",
@@ -11667,6 +11683,25 @@
"node": ">=8"
}
},
+ "node_modules/swiper": {
+ "version": "14.2.0",
+ "resolved": "https://registry.npmjs.org/swiper/-/swiper-14.2.0.tgz",
+ "integrity": "sha512-GsL4M9Fq7Sg22OyL3SjdNj/0bxTfIxPJJHHW394Li0u5vYtExi0a09iBUc8CuLafJi5pAlH2AHUe1PocxHl45g==",
+ "funding": [
+ {
+ "type": "custom",
+ "url": "https://sponsors.nolimits4web.com"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/nolimits4web"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4.7.0"
+ }
+ },
"node_modules/symbol-tree": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
diff --git a/package.json b/package.json
index 85006632c..db321125b 100644
--- a/package.json
+++ b/package.json
@@ -22,8 +22,9 @@
"@microbit/microbit-connection": "^1.0.0",
"@microbit/microbit-fs": "^0.10.0",
"@microbit/micropython-microbit-stubs": "^1.0.0",
- "@microbit/ui": "^0.4.0",
- "@microbit/ui-patterns": "^0.6.1",
+ "@microbit/ui": "^0.5.0",
+ "@microbit/ui-carousel": "^0.4.0",
+ "@microbit/ui-patterns": "^0.7.0",
"@sanity/block-content-to-react": "^3.0.0",
"@sanity/image-url": "^1.0.1",
"@sentry/browser": "^10.71.0",
@@ -52,6 +53,7 @@
"react-icons": "^4.12.0",
"react-intl": "^6.6.8",
"react-router": "^7.18.3",
+ "swiper": "^14.2.0",
"vite": "^7.3.1",
"vscode-jsonrpc": "^9.0.0",
"vscode-languageserver-protocol": "^3.16.0",
@@ -91,7 +93,8 @@
"prebuild": "npm run generate",
"ci": "npm run typecheck && npm run test && npm run lint && npm run i18n:tidy -- --check && npm run build",
"deploy": "website-deploy-aws",
- "dev:link-ui": "rm -rf node_modules/@microbit/ui node_modules/@microbit/ui-patterns node_modules/@microbit/i18n-tools && ln -s ../../../ui/packages/ui node_modules/@microbit/ui && ln -s ../../../ui/packages/ui-patterns node_modules/@microbit/ui-patterns && ln -s ../../../ui/packages/i18n-tools node_modules/@microbit/i18n-tools && ln -sf ../@microbit/i18n-tools/bin/microbit-i18n.mjs node_modules/.bin/microbit-i18n && rm -rf styled-system node_modules/.vite && npm run panda",
+ "dev:link-ui": "rm -rf node_modules/@microbit/ui node_modules/@microbit/ui-patterns node_modules/@microbit/ui-carousel node_modules/@microbit/i18n-tools && ln -s ../../../ui/packages/ui node_modules/@microbit/ui && ln -s ../../../ui/packages/ui-patterns node_modules/@microbit/ui-patterns && ln -s ../../../ui/packages/ui-carousel node_modules/@microbit/ui-carousel && ln -s ../../../ui/packages/i18n-tools node_modules/@microbit/i18n-tools && ln -sf ../@microbit/i18n-tools/bin/microbit-i18n.mjs node_modules/.bin/microbit-i18n && rm -rf styled-system node_modules/.vite && npm run panda",
+ "dev:link-theme": "rm -rf node_modules/@microbit-foundation/python-editor-v3-microbit && mkdir -p node_modules/@microbit-foundation && ln -s ../../../python-editor-v3-microbit node_modules/@microbit-foundation/python-editor-v3-microbit && npm run build --prefix ../python-editor-v3-microbit && rm -rf styled-system node_modules/.vite && npm run panda",
"fix-licensing-headers": "node bin/fix-licensing-headers.cjs",
"generate": "npm run panda && npm run i18n:compile && npm run stubs",
"i18n:compile": "microbit-i18n compile",
diff --git a/panda.config.ts b/panda.config.ts
index f8afe2a94..3a6522ad7 100644
--- a/panda.config.ts
+++ b/panda.config.ts
@@ -52,6 +52,7 @@ export default defineConfig({
// broken non-recipe styling — check the resolved node_modules path.
"./node_modules/@microbit/ui/src/**/*.{ts,tsx}",
"./node_modules/@microbit/ui-patterns/src/**/*.{ts,tsx}",
+ "./node_modules/@microbit/ui-carousel/src/**/*.{ts,tsx}",
],
outdir: "styled-system",
});
diff --git a/src/App.tsx b/src/App.tsx
index 37a7bae50..fd2439bf4 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -5,8 +5,9 @@
*/
import { SharedUIProvider, ToastProvider } from "@microbit/ui";
import { polyfill } from "mobile-drag-drop";
-import { useEffect, useMemo } from "react";
+import { useEffect } from "react";
import "./App.css";
+import { deferred } from "./common/deferred";
import { DialogProvider } from "./common/use-dialogs";
import VisualViewPortCSSVariables from "./common/VisualViewportCSSVariables";
import { deployment, useDeployment } from "./deployment";
@@ -22,15 +23,21 @@ import SearchProvider from "./documentation/search/search-hooks";
import { ActiveEditorProvider } from "./editor/active-editor-hooks";
import { FileSystem } from "./fs/fs";
import { FileSystemProvider } from "./fs/fs-hooks";
-import { createHost } from "./fs/host";
+import { openProjectsDatabase } from "./fs/current-project";
+import { createHost, IframeHost } from "./fs/host";
+import { PendingMigration } from "./fs/migration";
+import { FSStorage } from "./fs/storage";
+import { IframeModeProvider } from "./iframe-mode-hooks";
+import { Projects } from "./project/projects";
+import { ProjectsProvider } from "./project/projects-hooks";
import { fetchMicroPython } from "./micropython/micropython";
import { LanguageServerClientProvider } from "./language-server/language-server-hooks";
import { logDeviceStatusChange } from "./logging/analytics";
import { LoggingProvider } from "./logging/logging-hooks";
import TranslationProvider from "./messages/TranslationProvider";
-import ProjectDropTarget from "./project/ProjectDropTarget";
import { RouterProvider } from "react-router/dom";
import { createRouter } from "./router";
+import StorageErrorToast from "./project/storage-error-toast";
import SessionSettingsProvider from "./settings/session-settings";
import SettingsProvider from "./settings/settings";
import BeforeUnloadDirtyCheck from "./workbench/BeforeUnloadDirtyCheck";
@@ -47,12 +54,34 @@ const device: MicrobitUSBConnection = isMockDeviceMode()
? new MockDeviceConnection()
: createUSBConnection({ logging });
-const host = createHost(logging);
+// The database opens at boot; the project the editor shows is chosen when
+// the editor route loads, so the pages need not pick one.
+const projectStorage = deferred();
+const migration = new PendingMigration(window.location.href);
+const host = createHost(logging, migration, projectStorage.promise);
+const iframeMode = host instanceof IframeHost;
const fs = new FileSystem(logging, host, fetchMicroPython);
+const projects = iframeMode
+ ? undefined
+ : new Projects(
+ fs,
+ logging,
+ openProjectsDatabase(logging),
+ projectStorage,
+ migration
+ );
+if (!projects) {
+ projectStorage.resolve(undefined);
+}
-// If this fails then we retry on access.
+// If this fails then we retry on access. Until a project is opened this
+// waits on the storage, so only in iframe mode does it run straight away.
fs.initializeInBackground();
+// Created once here: a browser router starts running its loaders as soon as
+// it exists, and React's development-mode double render would make two.
+const router = createRouter({ projects });
+
const App = () => {
useEffect(() => {
logging.setUserProperty(
@@ -75,46 +104,48 @@ const App = () => {
const deployment = useDeployment();
const { ConsentProvider } = deployment.compliance;
- const router = useMemo(() => createRouter(), []);
return (
<>
-
-
-
- {/* Inside TranslationProvider: SharedUIProvider passes the app
+
+
+
+
+ {/* Inside TranslationProvider: SharedUIProvider passes the app
locale to react-aria for its built-in strings, and the
toast region's close label and status announcements are
react-intl messages. */}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
>
);
diff --git a/src/common/FileDropTarget.tsx b/src/common/FileDropTarget.tsx
index e818f910e..3e34ea3fc 100644
--- a/src/common/FileDropTarget.tsx
+++ b/src/common/FileDropTarget.tsx
@@ -6,9 +6,9 @@
import { ReactNode, useCallback, useState } from "react";
import { RiFolderOpenLine } from "react-icons/ri";
import { useIntl } from "react-intl";
-import { Box, Center } from "styled-system/jsx";
+import { Box, BoxProps, Center } from "styled-system/jsx";
-interface FileDropTargetProps {
+interface FileDropTargetProps extends BoxProps {
children: ReactNode;
onFileDrop: (files: File[]) => void;
"data-testid"?: string;
@@ -16,11 +16,15 @@ interface FileDropTargetProps {
/**
* An area that handles multiple dropped files.
+ *
+ * The drop overlay covers this element's box, so size it to the content it
+ * wraps: the editor fills its parent, the pages grow with their content.
*/
const FileDropTarget = ({
children,
onFileDrop,
"data-testid": dataTestId,
+ ...props
}: FileDropTargetProps) => {
const [dragOver, setDragOver] = useState(false);
@@ -56,7 +60,7 @@ const FileDropTarget = ({
data-testid={dataTestId}
onDragOver={handleDragOver}
position="relative"
- height="100%"
+ {...props}
>
{dragOver && (