Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file removed public/wallpapers/thumbs/wallpaper1.jpg
Binary file not shown.
Binary file removed public/wallpapers/thumbs/wallpaper10.jpg
Binary file not shown.
Binary file removed public/wallpapers/thumbs/wallpaper11.jpg
Binary file not shown.
Binary file removed public/wallpapers/thumbs/wallpaper12.jpg
Binary file not shown.
Binary file removed public/wallpapers/thumbs/wallpaper13.jpg
Binary file not shown.
Binary file removed public/wallpapers/thumbs/wallpaper14.jpg
Binary file not shown.
Binary file removed public/wallpapers/thumbs/wallpaper15.jpg
Binary file not shown.
Binary file removed public/wallpapers/thumbs/wallpaper16.jpg
Binary file not shown.
Binary file removed public/wallpapers/thumbs/wallpaper17.jpg
Binary file not shown.
Binary file removed public/wallpapers/thumbs/wallpaper18.jpg
Binary file not shown.
Binary file removed public/wallpapers/thumbs/wallpaper2.jpg
Binary file not shown.
Binary file removed public/wallpapers/thumbs/wallpaper3.jpg
Binary file not shown.
Binary file removed public/wallpapers/thumbs/wallpaper4.jpg
Binary file not shown.
Binary file removed public/wallpapers/thumbs/wallpaper5.jpg
Binary file not shown.
Binary file removed public/wallpapers/thumbs/wallpaper6.jpg
Binary file not shown.
Binary file removed public/wallpapers/thumbs/wallpaper7.jpg
Binary file not shown.
Binary file removed public/wallpapers/thumbs/wallpaper8.jpg
Binary file not shown.
Binary file removed public/wallpapers/thumbs/wallpaper9.jpg
Binary file not shown.
Binary file removed public/wallpapers/wallpaper1.jpg
Binary file not shown.
Binary file removed public/wallpapers/wallpaper10.jpg
Binary file not shown.
Binary file removed public/wallpapers/wallpaper11.jpg
Binary file not shown.
Binary file removed public/wallpapers/wallpaper12.jpg
Binary file not shown.
Binary file removed public/wallpapers/wallpaper13.jpg
Binary file not shown.
Binary file removed public/wallpapers/wallpaper14.jpg
Binary file not shown.
Binary file removed public/wallpapers/wallpaper15.jpg
Binary file not shown.
Binary file removed public/wallpapers/wallpaper16.jpg
Diff not rendered.
Binary file removed public/wallpapers/wallpaper17.jpg
Diff not rendered.
Binary file removed public/wallpapers/wallpaper18.jpg
Diff not rendered.
Binary file removed public/wallpapers/wallpaper2.jpg
Diff not rendered.
Binary file removed public/wallpapers/wallpaper3.jpg
Diff not rendered.
Binary file removed public/wallpapers/wallpaper4.jpg
Diff not rendered.
Binary file removed public/wallpapers/wallpaper5.jpg
Diff not rendered.
Binary file removed public/wallpapers/wallpaper6.jpg
Diff not rendered.
Binary file removed public/wallpapers/wallpaper7.jpg
Diff not rendered.
Binary file removed public/wallpapers/wallpaper8.jpg
Diff not rendered.
Binary file removed public/wallpapers/wallpaper9.jpg
Diff not rendered.
36 changes: 24 additions & 12 deletions src/components/video-editor/projectPersistence.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest";
import { DEFAULT_CURSOR_THEME_ID } from "@/lib/cursor/cursorThemes";
import { DEFAULT_WALLPAPER } from "@/lib/wallpaper";
import { DEFAULT_WALLPAPER, WALLPAPER_PATHS } from "@/lib/wallpaper";
import {
createProjectData,
createProjectSnapshot,
Expand Down Expand Up @@ -32,7 +32,7 @@ describe("projectPersistence media compatibility", () => {
webcamVideoPath: "/tmp/webcam.webm",
},
{
wallpaper: "/wallpapers/wallpaper1.jpg",
wallpaper: "/wallpapers/brand/rm-dark-dotgrid.jpg",
shadowIntensity: 0,
showBlur: false,
motionBlurAmount: 0,
Expand Down Expand Up @@ -183,7 +183,7 @@ it("creates stable snapshots for identical project state", () => {
webcamVideoPath: "/tmp/webcam.webm",
};
const editor = normalizeProjectEditor({
wallpaper: "/wallpapers/wallpaper1.jpg",
wallpaper: "/wallpapers/brand/rm-dark-dotgrid.jpg",
shadowIntensity: 0,
showBlur: false,
motionBlurAmount: 0,
Expand Down Expand Up @@ -216,35 +216,47 @@ it("detects unsaved changes from differing snapshots", () => {
describe("wallpaper legacy normalization", () => {
it("rewrites pre-fix packaged paths (resources/assets/wallpapers/…)", () => {
const normalized = normalizeProjectEditor({
wallpaper: "file:///opt/Openscreen/resources/assets/wallpapers/wallpaper5.jpg",
wallpaper: "file:///opt/Openscreen/resources/assets/wallpapers/brand/rm-grid.jpg",
});
expect(normalized.wallpaper).toBe("/wallpapers/wallpaper5.jpg");
expect(normalized.wallpaper).toBe("/wallpapers/brand/rm-grid.jpg");
});

it("rewrites new packaged layout (resources/wallpapers/…)", () => {
const normalized = normalizeProjectEditor({
wallpaper: "file:///opt/Openscreen/resources/wallpapers/wallpaper3.jpg",
wallpaper: "file:///opt/Openscreen/resources/wallpapers/brand/rm-framed.jpg",
});
expect(normalized.wallpaper).toBe("/wallpapers/wallpaper3.jpg");
expect(normalized.wallpaper).toBe("/wallpapers/brand/rm-framed.jpg");
});

it("rewrites unpackaged dev layout (public/wallpapers/…)", () => {
const normalized = normalizeProjectEditor({
wallpaper: "file:///home/user/project/public/wallpapers/wallpaper1.jpg",
wallpaper: "file:///home/user/project/public/wallpapers/brand/rm-dark-dotgrid.jpg",
});
expect(normalized.wallpaper).toBe("/wallpapers/wallpaper1.jpg");
expect(normalized.wallpaper).toBe("/wallpapers/brand/rm-dark-dotgrid.jpg");
});

it("rewrites Windows-style file URLs with drive letter", () => {
const normalized = normalizeProjectEditor({
wallpaper: "file:///C:/Users/me/openscreen/resources/wallpapers/wallpaper2.jpg",
wallpaper: "file:///C:/Users/me/openscreen/resources/wallpapers/brand/rm-grid.jpg",
});
expect(normalized.wallpaper).toBe("/wallpapers/wallpaper2.jpg");
expect(normalized.wallpaper).toBe("/wallpapers/brand/rm-grid.jpg");
});

it("leaves canonical relative paths untouched", () => {
const normalized = normalizeProjectEditor({ wallpaper: "/wallpapers/brand/rm-grid.jpg" });
expect(normalized.wallpaper).toBe("/wallpapers/brand/rm-grid.jpg");
});

/*
* And a project naming a wallpaper that is no longer bundled falls back.
*
* The eighteen stock gradients were removed, so an older project can name one
* that is not there any more. Left as-is it would be a broken image behind
* somebody's recording; DEFAULT_WALLPAPER is a brand board.
*/
it("falls back when a project names a wallpaper that is gone", () => {
const normalized = normalizeProjectEditor({ wallpaper: "/wallpapers/wallpaper2.jpg" });
expect(normalized.wallpaper).toBe("/wallpapers/wallpaper2.jpg");
expect(normalized.wallpaper).toBe(WALLPAPER_PATHS[0]);
});

it("leaves data URIs untouched", () => {
Expand Down
44 changes: 36 additions & 8 deletions src/components/video-editor/projectPersistence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,19 +49,47 @@ import {

const VALID_BLUR_SHAPES = new Set(["rectangle", "oval", "freehand"] as const);

// Old projects persisted machine-specific file:// URLs for bundled wallpapers.
// Match only the known install layouts (packaged resources/[assets/]wallpapers,
// dev public/wallpapers) so a user's own file under some "wallpapers" folder isn't
// silently replaced.
/*
* Old projects persisted machine-specific file:// URLs for bundled wallpapers.
*
* Only the known install layouts are matched — packaged
* `resources/[assets/]wallpapers`, dev `public/wallpapers` — so a user's own file
* under some folder called "wallpapers" is not silently replaced.
*
* The tail allows one directory, because the bundled set moved into
* `wallpapers/brand/`. It used to be `wallpaper\d+\.jpg` and nothing else, which
* matched the stock run and no brand board: a packaged project whose wallpaper was
* one of ours came back as an unrewritten file:// URL pointing at the machine it
* was saved on. Now that the brand set is the ONLY set, that was every packaged
* project. Still one segment and still `.jpg`, so it cannot walk anywhere.
*/
const LEGACY_FILE_WALLPAPER_RE =
/^file:\/\/.*?\/(?:resources\/(?:assets\/)?|public\/)wallpapers\/(wallpaper\d+\.jpg)$/i;
/^file:\/\/.*?\/(?:resources\/(?:assets\/)?|public\/)wallpapers\/((?:[\w-]+\/)?[\w-]+\.jpg)$/i;
const CANONICAL_WALLPAPERS = new Set(WALLPAPER_PATHS);

function normalizeWallpaperValue(value: string): string {
const match = LEGACY_FILE_WALLPAPER_RE.exec(value);
if (!match) return value;
const canonical = `/wallpapers/${match[1]}`;
return CANONICAL_WALLPAPERS.has(canonical) ? canonical : DEFAULT_WALLPAPER;
if (match) {
const canonical = `/wallpapers/${match[1]}`;
return CANONICAL_WALLPAPERS.has(canonical) ? canonical : DEFAULT_WALLPAPER;
}
/*
* A bundled path that is no longer bundled also falls back.
*
* Only file:// URLs used to be checked against the canonical set, so an
* already-relative `/wallpapers/…` was passed through whatever it named. That was
* harmless while the bundled set only ever grew. It stopped being harmless when
* the eighteen stock gradients were removed: every project that used one now
* names a file that is not there, and passing it through puts a broken image
* behind somebody's recording instead of a board.
*
* Scoped to `/wallpapers/` — a data: URI or a user's own file:// path is theirs
* and is left exactly as it is, which the tests above pin.
*/
if (value.startsWith("/wallpapers/") && !CANONICAL_WALLPAPERS.has(value)) {
return DEFAULT_WALLPAPER;
}
return value;
}

export const PROJECT_VERSION = 2;
Expand Down
11 changes: 6 additions & 5 deletions src/lib/wallpaper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,18 @@ import {
DEFAULT_WALLPAPER,
resolveImageWallpaperUrl,
UnsafeImagePrefixError,
WALLPAPER_COUNT,
WALLPAPER_PATHS,
WALLPAPER_THUMB_PATHS,
wallpaperLabel,
} from "./wallpaper";

describe("WALLPAPER_PATHS", () => {
// The list is the brand set followed by the stock run, not a count on its own —
// WALLPAPER_COUNT still describes the stock half and nothing else.
it("is the brand set plus the stock set", () => {
expect(WALLPAPER_PATHS).toHaveLength(BRAND_WALLPAPERS.length + WALLPAPER_COUNT);
// The brand set and nothing else. This is a branded build, and a picker offering
// eighteen stock gradients beside the boards invites a video that is off-brand by
// one click.
it("is the brand set, and only the brand set", () => {
expect(WALLPAPER_PATHS).toHaveLength(BRAND_WALLPAPERS.length);
expect(WALLPAPER_PATHS.every((p) => p.startsWith("/wallpapers/brand/"))).toBe(true);
});

it("puts the brand set first", () => {
Expand Down
46 changes: 17 additions & 29 deletions src/lib/wallpaper.ts
Original file line number Diff line number Diff line change
@@ -1,48 +1,36 @@
import { getAssetPath } from "@/lib/assetPath";
import { BRAND_WALLPAPERS } from "@/lib/brandWallpapers";

/** The stock set, which is still a count and a naming convention. */
export const WALLPAPER_COUNT = 18;

const STOCK_PATHS: readonly string[] = Array.from(
{ length: WALLPAPER_COUNT },
(_, i) => `/wallpapers/wallpaper${i + 1}.jpg`,
);

/*
* Brand wallpapers first, then the stock set.
* The brand boards, and only those.
*
* The list used to be derived entirely from WALLPAPER_COUNT, so a wallpaper that was
* not called `wallpaperN.jpg` did not exist as far as the picker was concerned — which
* is why the RoleModel set never appeared in it however many times the branding was
* redone. They live under /wallpapers/brand/ with their own names rather than being
* renumbered into the stock run: 19 upward would put them last, lose the names, and
* collide the moment upstream adds one.
* The list used to be derived from a count and a naming convention — eighteen
* `wallpaperN.jpg` — so a wallpaper not called that did not exist as far as the
* picker was concerned, which is why the RoleModel set never appeared in it however
* many times the branding was redone. Then it was both sets, ours first. It is now
* ours alone: this is a branded build, and a picker offering eighteen stock
* gradients alongside the boards invites a video that is off-brand by one click.
*
* First on purpose. These are the default look for this build, and DEFAULT_WALLPAPER
* is still index 0 — which now means the brand board rather than a stock gradient.
* They live under /wallpapers/brand/ with their own names rather than being
* renumbered into the old run, which would lose the names and collide the moment
* upstream adds one.
*
* projectPersistence keys its canonical set off WALLPAPER_PATHS, so being in this
* array is also what stops a saved project's brand wallpaper being replaced by the
* default on load.
* projectPersistence keys its canonical set off this array, so a saved project that
* still names a stock gradient now falls back to DEFAULT_WALLPAPER — index 0, which
* is a brand board. That is the intended migration and the only visible effect on
* existing projects.
*/
export const WALLPAPER_PATHS: readonly string[] = [
...BRAND_WALLPAPERS.map((w) => w.path),
...STOCK_PATHS,
];
export const WALLPAPER_PATHS: readonly string[] = BRAND_WALLPAPERS.map((w) => w.path);

// Small (240x240, ~3-8KB) pre-generated copies used ONLY for the picker grid's swatches — the
// full-res originals (up to 4+MB, up to 7680px) are what `settings.wallpaper` still stores and
// what actually gets rendered/exported. Without this the grid was decoding all 18 originals
// (~20MB combined) simultaneously just to paint a few dozen px each (reported: picker felt slow
// to load).
export const WALLPAPER_THUMB_PATHS: readonly string[] = [
...BRAND_WALLPAPERS.map((w) => w.thumb),
...Array.from({ length: WALLPAPER_COUNT }, (_, i) => `/wallpapers/thumbs/wallpaper${i + 1}.jpg`),
];
export const WALLPAPER_THUMB_PATHS: readonly string[] = BRAND_WALLPAPERS.map((w) => w.thumb);

/**
* A label for a bundled wallpaper, or null for the stock set, which has none.
* A label for a bundled wallpaper, or null for anything not in the set.
*
* The picker shows a bare grid of swatches; ours have names worth reading and a
* `title` is the cheapest place to put them.
Expand Down
Loading