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
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "beatmapper",
"version": "0.5.1",
"version": "0.5.2",
"type": "module",
"private": true,
"packageManager": "pnpm@11.5.2",
Expand Down Expand Up @@ -41,10 +41,11 @@
"@zag-js/file-utils": "^1.37.0",
"@zag-js/react": "^1.37.0",
"@zag-js/utils": "^1.37.0",
"bsmap": "^2.3.5",
"bsmap": "^3.0.0",
"date-fns": "^4.1.0",
"fflate": "^0.8.2",
"file-saver": "^2.0.5",
"history-adapter": "^2.1.1",
"idb": "^8.0.3",
"lucide-react": "^1.0.1",
"postprocessing": "^6.39.0",
Expand All @@ -71,6 +72,7 @@
"@tanstack/router-plugin": "^1.167.4",
"@tanstack/virtual-file-routes": "^1.161.7",
"@types/file-saver": "^2.0.7",
"@types/mdx": "^2.0.14",
"@types/node": "^24.12.0",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
Expand Down
13 changes: 13 additions & 0 deletions patches/bsmap@3.0.0.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
diff --git a/esm/beatmap/mapping/converter.js b/esm/beatmap/mapping/converter.js
index 0cdd9261759ec36f0a3820c84b0209f0b7fdec9c..e821dacf2b71dc1f94fb1884e4f59185ac522511 100644
--- a/esm/beatmap/mapping/converter.js
+++ b/esm/beatmap/mapping/converter.js
@@ -51,7 +51,7 @@ export function convertBeatmap(type, targetVersion, data, sourceVersion) {
throw new Error(`Unsupported ${type} beatmap version ${String(targetVersion)}.`);
}
if (sourceVersion !== undefined &&
- !isSupportedMajorVersion(type, sourceVersion)) {
+ !isSupportedMajorVersion(type === "lightshow" ? "difficulty" : type, sourceVersion)) {
throw new Error(`Unsupported ${type} source beatmap version ${String(sourceVersion)}.`);
}
logger?.tInfo(tag('convertBeatmap'), `Converting wrapper contents for ${type} to version ${targetVersion}`);
39 changes: 34 additions & 5 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion pnpm-workspace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,8 @@ allowBuilds:
minimumReleaseAgeExclude:
- bsmap

patchedDependencies:
bsmap@3.0.0: patches/bsmap@3.0.0.patch

publicHoistPattern:
- "*workbox*"
- "*workbox*"
31 changes: 13 additions & 18 deletions src/setup.ts → src/_setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,16 @@ import { createToaster } from "@ark-ui/react/toast";
import { typeByExtension } from "@std/media-types/type-by-extension";
import { extname } from "@std/path/extname";
import { toPascalCase } from "@std/text/to-pascal-case";
import { createBeatmap, loadDifficulty, loadInfo, setupLogger } from "bsmap";
import { createStorage, type Driver, type StorageValue } from "unstorage";
import { createBeatmap, loadDifficulty, loadInfo } from "bsmap";
import { type CreateStorageOptions, createStorage, type StorageValue } from "unstorage";

import { BeatmapFilestore } from "./services/file.service";
import { createDriver, type LegacyStorageSchema } from "./services/storage.service";
import { createAppStore } from "./store/setup";
import { createAppStore } from "./store/_setup";
import type { App } from "./types";
import { createLazySingleton } from "./utils";
import { createSingleton } from "./utils";

if (import.meta.env.DEV) {
setupLogger();
}

const appFileDriver = createDriver<LegacyStorageSchema & { entries: { key: string; value: StorageValue } }>({
export const createAppFileStorageDriver = createDriver<LegacyStorageSchema & { entries: { key: string; value: StorageValue } }>({
name: "beat-mapper-files",
version: 4,
async upgrade(idb, _current, next, tx) {
Expand Down Expand Up @@ -75,17 +71,16 @@ const appFileDriver = createDriver<LegacyStorageSchema & { entries: { key: strin
},
});

export const { get: getAppBeatmapFilestore, setup: setupAppBeatmapFilestore } = createLazySingleton((driver?: Driver) => {
export const AppAudioContext = createSingleton((options: Omit<AudioContextOptions, "latencyHint">) => {
return new AudioContext({ ...options, latencyHint: "playback" });
});

export const AppFilestore = createSingleton((options: CreateStorageOptions) => {
return new BeatmapFilestore({
storage: createStorage({
driver: driver ?? appFileDriver({ name: "entries" }),
}),
storage: createStorage(options),
});
});

export const { get: getAppToaster, setup: setupAppToaster } = createLazySingleton((toaster?: ReturnType<typeof createToaster>): NonNullable<typeof toaster> | null => {
if (toaster !== undefined) return toaster ?? null;
return createToaster({ placement: "bottom-end", overlap: true, max: 8 });
});
export const AppToaster = createSingleton(createToaster);

export const { get: getAppStore, setup: setupAppStore } = createLazySingleton(createAppStore);
export const AppStore = createSingleton(createAppStore);
5 changes: 3 additions & 2 deletions src/components/app/constants.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { createListCollection } from "@ark-ui/react/collection";
import type { FileMimeType } from "@zag-js/file-utils";
import { CharacteristicName, CharacteristicRename, DifficultyName, DifficultyRename, EnvironmentName, EnvironmentRename, is360Environment, isV2Environment, isV3Environment } from "bsmap";
import { CharacteristicName, CharacteristicRename, DifficultyName, DifficultyRename, EnvironmentName, is360Environment, isV2Environment, isV3Environment } from "bsmap";
import { EnvironmentRename } from "bsmap/environment";
import { nonEmpty, null_, number, object, pipe, regex, string, union } from "valibot";

import { createPromptFactory } from "$/components/ui/compositions";
Expand Down Expand Up @@ -67,7 +68,7 @@ export const createQuickSelectPrompt = createPromptFactory({
validate: object({
range: pipe(
string(),
regex(/^\d+(-\d+)?$/, (issue) => `Invalid format: Expected <number> or <number>-<number> but received "${issue.input}"`),
regex(/^[+-]?\d+(?:\.\d+)?(-\d+(?:\.\d+)?)?$/, (issue) => `Invalid format: Expected a range ("3-6") or delta ("+3", "-3") but received "${issue.input}"`),
),
}),
});
Expand Down
7 changes: 3 additions & 4 deletions src/components/app/forms/create-beatmap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type { PropsWithChildren, ReactNode } from "react";
import { object } from "valibot";

import { createBeatmapCharacteristicListCollection, createBeatmapDifficultyListCollection } from "$/components/app/constants";
import { useSetupContext } from "$/components/context";
import { useToaster } from "$/components/context";
import { useAppForm } from "$/components/ui/compositions";
import { type createAppBeatmap, resolveBeatmapId } from "$/helpers/song.helpers";
import { useAppSelector } from "$/store/hooks";
Expand All @@ -30,8 +30,7 @@ function CreateBeatmapForm({ dialog, onSubmit, children }: Assign<PropsWithChild
const { view } = useRouteContext({ from: "/_/edit/$sid/$bid/_" });

const navigate = useNavigate();

const { toaster } = useSetupContext();
const toaster = useToaster();

const username = useAppSelector(selectUsername);
const beatmaps = useAppSelector((state) => selectAllBeatmaps(state, sid));
Expand All @@ -54,7 +53,7 @@ function CreateBeatmapForm({ dialog, onSubmit, children }: Assign<PropsWithChild
if (dialog) dialog.setOpen(false);
navigate({ to: `/edit/$sid/$bid/${view}`, params: { sid: sid.toString(), bid: beatmapId } });
} catch (error) {
toaster?.error({ description: `Could not create beatmap: ${error instanceof Error ? error.message : "See console for more info."}` });
toaster.error({ description: `Could not create beatmap: ${error instanceof Error ? error.message : "See console for more info."}` });
return console.error(error);
}
},
Expand Down
6 changes: 3 additions & 3 deletions src/components/app/forms/create-map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useState } from "react";
import { array, endsWith, file, type GenericSchema, gtValue, length, maxLength, minLength, minValue, number, object, pipe, string, transform } from "valibot";

import { CHARACTERISTIC_COLLECTION, COVER_ART_FILE_ACCEPT_TYPE, DIFFICULTY_COLLECTION, ENVIRONMENT_COLLECTION, SONG_FILE_ACCEPT_TYPE } from "$/components/app/constants";
import { useSetupContext } from "$/components/context";
import { useToaster } from "$/components/context";
import { Show } from "$/components/ui/atoms";
import { Audio, Switch, useAppForm } from "$/components/ui/compositions";
import { createPlaceholderImageFile, remuxImageToSquare } from "$/helpers/file.helpers";
Expand Down Expand Up @@ -42,7 +42,7 @@ function CreateMapForm({ dialog }: Props) {
const username = useAppSelector(selectUsername);
const volume = useAppSelector(selectSongVolume);

const { toaster } = useSetupContext();
const toaster = useToaster();

const Form = useAppForm({
defaultValues: {
Expand Down Expand Up @@ -104,7 +104,7 @@ function CreateMapForm({ dialog }: Props) {

if (dialog) dialog.setOpen(false);
} catch (error) {
toaster?.error({ description: `Could not create map: ${error instanceof Error ? error.message : "See console for more information."}` });
toaster.error({ description: `Could not create map: ${error instanceof Error ? error.message : "See console for more information."}` });
return console.error(error);
}
},
Expand Down
6 changes: 3 additions & 3 deletions src/components/app/forms/export-map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { InferBeatmapVersion } from "bsmap";
import type { ComponentProps } from "react";
import { boolean, null_, object, picklist, union } from "valibot";

import { useSetupContext } from "$/components/context";
import { useToaster } from "$/components/context";
import { Heading, useAppForm } from "$/components/ui/compositions";
import type { SubmitButton } from "$/components/ui/compositions/button";
import type { ExportMapArchiveOptions } from "$/services/packaging.service";
Expand All @@ -20,7 +20,7 @@ interface Props {
onSubmit: (ctx: ExportMapArchiveOptions) => void;
}
function ExportMapForm({ onSubmit, ...rest }: Assign<ComponentProps<typeof SubmitButton>, Props>) {
const { toaster } = useSetupContext();
const toaster = useToaster();

const { collection: serialVersionCollection } = useListCollection({
initialItems: ["4", "3", "2", "1"],
Expand All @@ -46,7 +46,7 @@ function ExportMapForm({ onSubmit, ...rest }: Assign<ComponentProps<typeof Submi
saveOptions: { format: value.minify ? 0 : 2, optimize: { purgeZeros: value.purgeZeros } },
});
} catch (error) {
toaster?.error({ description: `Could not export map: ${error instanceof Error ? error.message : "See console for more info."}` });
toaster.error({ description: `Could not export map: ${error instanceof Error ? error.message : "See console for more info."}` });
return console.error(error);
}
},
Expand Down
6 changes: 3 additions & 3 deletions src/components/app/forms/import-map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { type ComponentProps, useCallback } from "react";
import { Fragment } from "react/jsx-runtime";

import { MAP_ARCHIVE_FILE_ACCEPT_TYPE } from "$/components/app/constants";
import { useSetupContext } from "$/components/context";
import { useToaster } from "$/components/context";
import { FileUpload, List } from "$/components/ui/compositions";
import { Stack, Text } from "$:styled-system/jsx";

Expand All @@ -14,7 +14,7 @@ interface Props {
onAccept: (files: File[]) => void;
}
function ImportMapForm({ dialog, onAccept, ...rest }: Assign<ComponentProps<typeof FileUpload>, Props>) {
const { toaster } = useSetupContext();
const toaster = useToaster();

const handleFileChange = useCallback(
(details: FileUploadFileChangeDetails) => {
Expand All @@ -23,7 +23,7 @@ function ImportMapForm({ dialog, onAccept, ...rest }: Assign<ComponentProps<type
try {
onAccept(details.acceptedFiles);
} catch (error) {
toaster?.error({ description: `Could not import map: ${error instanceof Error ? error.message : "See console for more info."}` });
toaster.error({ description: `Could not import map: ${error instanceof Error ? error.message : "See console for more info."}` });
return console.error(error);
}
},
Expand Down
12 changes: 4 additions & 8 deletions src/components/app/forms/settings/audio.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { createListCollection } from "@ark-ui/react/collection";

import { Field, FieldInput, FieldSelectGroup } from "$/components/ui/compositions";
import { updateProcessingDelay, updateTickType } from "$/store/actions";
import { Field, FieldSelectGroup } from "$/components/ui/compositions";
import { updateTickType } from "$/store/actions";
import { useAppDispatch, useAppSelector } from "$/store/hooks";
import { selectAudioProcessingDelay, selectTickType } from "$/store/selectors";
import { selectTickType } from "$/store/selectors";
import { Stack, Wrap } from "$:styled-system/jsx";

const TICK_MAP = ["woodblock", "switch"];
Expand All @@ -14,17 +14,13 @@ const NOTE_TICK_COLLECTION = createListCollection({

function AppAudioSettings() {
const dispatch = useAppDispatch();
const processingDelay = useAppSelector(selectAudioProcessingDelay);
const tickType = useAppSelector(selectTickType);

return (
<Stack gap={4}>
<Wrap gap={2}>
<Field label="Note tick type" helperText="Change the sound effect played when simulating a hitsound during playback.">
<FieldSelectGroup collection={NOTE_TICK_COLLECTION} value={TICK_MAP[tickType]} onValueChange={(details) => dispatch(updateTickType({ value: TICK_MAP.indexOf(details.valueAsString) }))} />
</Field>
<Field label="Processing delay" helperText="Tweak the amount of time, in milliseconds, that the audio should be offset by, for it to seem synchronized. Slower machines should experiment with larger numbers.">
<FieldInput type="number" value={processingDelay} onValueChange={(details) => dispatch(updateProcessingDelay({ value: details.valueAsNumber }))} />
<FieldSelectGroup collection={NOTE_TICK_COLLECTION} value={TICK_MAP[tickType]} onValueChange={(details) => dispatch(updateTickType(TICK_MAP.indexOf(details.valueAsString)))} />
</Field>
</Wrap>
</Stack>
Expand Down
2 changes: 1 addition & 1 deletion src/components/app/forms/settings/controls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ function AppControlsSettings() {
<Stack gap={4}>
<Wrap gap={2}>
<Field label="Obstacle Placement Mode" helperText="Determines the behavior of how obstacles are placed along the grid. [Learn more](/docs/manual/notes#obstacle-placement-modes)">
<RadioGroup collection={OBSTACLE_PLACEMENT_MODE_COLLECTION} value={obstaclePlacementMode} onValueChange={(x) => dispatch(updateObstaclePlacementMode({ value: x.value as ObstaclePlacementMode }))} />
<RadioGroup collection={OBSTACLE_PLACEMENT_MODE_COLLECTION} value={obstaclePlacementMode} onValueChange={(x) => dispatch(updateObstaclePlacementMode(x.value as ObstaclePlacementMode))} />
</Field>
</Wrap>
</Stack>
Expand Down
4 changes: 2 additions & 2 deletions src/components/app/forms/settings/graphics.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ function AppGraphicsSettings() {
<Stack gap={4}>
<Wrap gap={2}>
<Field cosmetic label="Render Scale" helperText="Reduces the number of components for computationally intensive objects within the view.">
<Slider name="render-scale" stretch value={[renderScale]} step={0.05} min={0} max={1} onValueChange={(details) => dispatch(updateRenderScale({ value: details.value[0] }))} />
<Slider name="render-scale" stretch value={[renderScale]} step={0.05} min={0} max={1} onValueChange={(details) => dispatch(updateRenderScale(details.value[0]))} />
</Field>
<Field cosmetic label="Enable Bloom Effect" helperText="Enables/disables the bloom post-processing effect for lighting events.">
<Switch name="enable-bloom" checked={isBloomEnabled} onCheckedChange={(details) => dispatch(updateBloomEnabled({ checked: details.checked }))} />
<Switch name="enable-bloom" checked={isBloomEnabled} onCheckedChange={(details) => dispatch(updateBloomEnabled(details.checked))} />
</Field>
</Wrap>
</Stack>
Expand Down
Loading
Loading