mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-23 19:49:50 +02:00
feat: use custom state manager
This commit is contained in:
1 parent
cf38cb3a6e
commit
87b6a36520
11 files changed
+84
-36
No files matched your search
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"name": "varuna",
|
||||||
|
"type": "module",
|
||||||
|
"main": "src/index.ts",
|
||||||
|
"dependencies": {
|
||||||
|
"react": "^18.3.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import { produce } from 'immer';
|
||||||
|
import { merge } from 'lodash-es';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
export class Varuna<Type> {
|
||||||
|
private listeners = new Set<(state: Type) => void>();
|
||||||
|
public initial: Type;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public state: Type,
|
||||||
|
persistence?: string,
|
||||||
|
) {
|
||||||
|
if (persistence) {
|
||||||
|
const dehydrated = localStorage.getItem(persistence);
|
||||||
|
|
||||||
|
if (dehydrated) {
|
||||||
|
const rehydrated = JSON.parse(dehydrated) as unknown;
|
||||||
|
this.state = merge(this.state, rehydrated);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.initial = this.state;
|
||||||
|
}
|
||||||
|
|
||||||
|
private dispatch() {
|
||||||
|
const state = this.state;
|
||||||
|
this.listeners.forEach((callback) => callback(state));
|
||||||
|
}
|
||||||
|
|
||||||
|
on<Slice>(slicer: (state: Type) => Slice, callback: (state: Slice) => void) {
|
||||||
|
const last = slicer(this.state);
|
||||||
|
|
||||||
|
function listener(state: Type) {
|
||||||
|
const next = slicer(state);
|
||||||
|
if (next !== last) callback(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.listeners.add(listener);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
this.listeners.delete(listener);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
mutate(mutator: (state: Type) => void) {
|
||||||
|
this.state = produce(this.state, mutator);
|
||||||
|
this.dispatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
use<Slice>(slicer: (state: Type) => Slice) {
|
||||||
|
const [slice, setSlice] = useState(slicer(this.state));
|
||||||
|
useEffect(() => this.on(slicer, setSlice), []);
|
||||||
|
return slice;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json"
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@
|
|||||||
"@astrolib/seo": "^1.0.0-beta.8",
|
"@astrolib/seo": "^1.0.0-beta.8",
|
||||||
"@blitzkit/core": "workspace:*",
|
"@blitzkit/core": "workspace:*",
|
||||||
"@blitzkit/i18n": "workspace:*",
|
"@blitzkit/i18n": "workspace:*",
|
||||||
|
"varuna": "workspace:*",
|
||||||
"@fontsource/inter": "^5.2.5",
|
"@fontsource/inter": "^5.2.5",
|
||||||
"@nanostores/persistent": "^0.10.2",
|
"@nanostores/persistent": "^0.10.2",
|
||||||
"@nanostores/react": "^0.7.3",
|
"@nanostores/react": "^0.7.3",
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import { ReloadIcon } from '@radix-ui/react-icons';
|
import { ReloadIcon } from '@radix-ui/react-icons';
|
||||||
import { Button, Flex } from '@radix-ui/themes';
|
import { Button, Flex } from '@radix-ui/themes';
|
||||||
import { useLocale } from '../../../hooks/useLocale';
|
import { useLocale } from '../../../hooks/useLocale';
|
||||||
import { TierList, tierListInitialState } from '../../../stores/tierList';
|
import { TierList } from '../../../stores/tierList';
|
||||||
import { Share } from './components/Share';
|
import { Share } from './components/Share';
|
||||||
|
|
||||||
export function TierListControls() {
|
export function TierListControls() {
|
||||||
const mutateTierList = TierList.useMutation();
|
|
||||||
const { strings } = useLocale();
|
const { strings } = useLocale();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -13,8 +12,8 @@ export function TierListControls() {
|
|||||||
<Button
|
<Button
|
||||||
color="red"
|
color="red"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
mutateTierList((draft) => {
|
TierList.mutate((draft) => {
|
||||||
Object.assign(draft, tierListInitialState);
|
Object.assign(draft, TierList.initial);
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ export function Header({ index }: HeaderProps) {
|
|||||||
const rowStyle = tierListRows[index];
|
const rowStyle = tierListRows[index];
|
||||||
const name = TierList.use((state) => state.rows[index].name);
|
const name = TierList.use((state) => state.rows[index].name);
|
||||||
const input = useRef<HTMLInputElement>(null);
|
const input = useRef<HTMLInputElement>(null);
|
||||||
const mutateTierList = TierList.useMutation();
|
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -67,9 +66,8 @@ export function Header({ index }: HeaderProps) {
|
|||||||
onBlur={() => {
|
onBlur={() => {
|
||||||
if (!input.current) return;
|
if (!input.current) return;
|
||||||
|
|
||||||
mutateTierList((draft) => {
|
TierList.mutate((draft) => {
|
||||||
if (!input.current) return;
|
if (!input.current) return;
|
||||||
|
|
||||||
draft.rows[index].name = input.current.value;
|
draft.rows[index].name = input.current.value;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ type TierListTileProps = {
|
|||||||
|
|
||||||
export function TierListTile(props: TierListTileProps) {
|
export function TierListTile(props: TierListTileProps) {
|
||||||
const card = useRef<HTMLDivElement>(null);
|
const card = useRef<HTMLDivElement>(null);
|
||||||
const mutateTierList = TierList.useMutation();
|
|
||||||
const lastPosition = useRef({ x: 0, y: 0 });
|
const lastPosition = useRef({ x: 0, y: 0 });
|
||||||
const handlePointerDown = useCallback(
|
const handlePointerDown = useCallback(
|
||||||
(event: ReactPointerEvent<HTMLSpanElement>) => {
|
(event: ReactPointerEvent<HTMLSpanElement>) => {
|
||||||
@@ -48,7 +47,7 @@ export function TierListTile(props: TierListTileProps) {
|
|||||||
window.addEventListener('pointermove', handlePointerMove);
|
window.addEventListener('pointermove', handlePointerMove);
|
||||||
window.addEventListener('pointerup', handlePointerUp);
|
window.addEventListener('pointerup', handlePointerUp);
|
||||||
|
|
||||||
mutateTierList((draft) => {
|
TierList.mutate((draft) => {
|
||||||
draft.dragging = true;
|
draft.dragging = true;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -104,7 +103,7 @@ export function TierListTile(props: TierListTileProps) {
|
|||||||
|
|
||||||
const tileIndex = Number(tile.dataset.tileIndex);
|
const tileIndex = Number(tile.dataset.tileIndex);
|
||||||
|
|
||||||
mutateTierList((draft) => {
|
TierList.mutate((draft) => {
|
||||||
draft.dragging = false;
|
draft.dragging = false;
|
||||||
draft.rows.forEach((row) => {
|
draft.rows.forEach((row) => {
|
||||||
row.tanks = row.tanks.filter((tankId) => tankId !== props.tank.id);
|
row.tanks = row.tanks.filter((tankId) => tankId !== props.tank.id);
|
||||||
@@ -118,7 +117,7 @@ export function TierListTile(props: TierListTileProps) {
|
|||||||
break rowLoop;
|
break rowLoop;
|
||||||
}
|
}
|
||||||
|
|
||||||
mutateTierList((draft) => {
|
TierList.mutate((draft) => {
|
||||||
draft.dragging = false;
|
draft.dragging = false;
|
||||||
draft.rows.forEach((row) => {
|
draft.rows.forEach((row) => {
|
||||||
row.tanks = row.tanks.filter((tankId) => tankId !== props.tank.id);
|
row.tanks = row.tanks.filter((tankId) => tankId !== props.tank.id);
|
||||||
@@ -133,7 +132,7 @@ export function TierListTile(props: TierListTileProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!wasPlaced) {
|
if (!wasPlaced) {
|
||||||
mutateTierList((draft) => {
|
TierList.mutate((draft) => {
|
||||||
draft.dragging = false;
|
draft.dragging = false;
|
||||||
draft.rows.forEach((row) => {
|
draft.rows.forEach((row) => {
|
||||||
row.tanks = row.tanks.filter((tankId) => tankId !== props.tank.id);
|
row.tanks = row.tanks.filter((tankId) => tankId !== props.tank.id);
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ const tankDefinitions = await awaitableTankDefinitions;
|
|||||||
|
|
||||||
export function URLManager() {
|
export function URLManager() {
|
||||||
const rows = TierList.use((state) => state.rows);
|
const rows = TierList.use((state) => state.rows);
|
||||||
const mutateTierList = TierList.useMutation();
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const params = new URLSearchParams(window.location.search);
|
const params = new URLSearchParams(window.location.search);
|
||||||
@@ -20,7 +19,7 @@ export function URLManager() {
|
|||||||
.map(Number)
|
.map(Number)
|
||||||
.filter((id) => id in tankDefinitions.tanks);
|
.filter((id) => id in tankDefinitions.tanks);
|
||||||
|
|
||||||
mutateTierList((draft) => {
|
TierList.mutate((draft) => {
|
||||||
draft.rows[index].tanks = values;
|
draft.rows[index].tanks = values;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,16 +9,13 @@ import {
|
|||||||
type LocaleAcceptorProps,
|
type LocaleAcceptorProps,
|
||||||
} from '../../../hooks/useLocale';
|
} from '../../../hooks/useLocale';
|
||||||
import { TankopediaPersistent } from '../../../stores/tankopediaPersistent';
|
import { TankopediaPersistent } from '../../../stores/tankopediaPersistent';
|
||||||
import { TierList } from '../../../stores/tierList';
|
|
||||||
|
|
||||||
export function Page({ locale }: LocaleAcceptorProps) {
|
export function Page({ locale }: LocaleAcceptorProps) {
|
||||||
return (
|
return (
|
||||||
<LocaleProvider locale={locale}>
|
<LocaleProvider locale={locale}>
|
||||||
<TierList.Provider>
|
<TankopediaPersistent.Provider>
|
||||||
<TankopediaPersistent.Provider>
|
<Content />
|
||||||
<Content />
|
</TankopediaPersistent.Provider>
|
||||||
</TankopediaPersistent.Provider>
|
|
||||||
</TierList.Provider>
|
|
||||||
</LocaleProvider>
|
</LocaleProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import { enableMapSet } from 'immer';
|
import { enableMapSet } from 'immer';
|
||||||
import { create } from 'zustand';
|
import { Varuna } from 'varuna';
|
||||||
import { subscribeWithSelector } from 'zustand/middleware';
|
|
||||||
import { tierListRows } from '../components/TierList/Table/constants';
|
import { tierListRows } from '../components/TierList/Table/constants';
|
||||||
import { createContextualStore } from '../core/zustand/createContextualStore';
|
|
||||||
|
|
||||||
export interface TierList {
|
export interface TierList {
|
||||||
dragging: boolean;
|
dragging: boolean;
|
||||||
@@ -10,19 +8,10 @@ export interface TierList {
|
|||||||
placedTanks: Set<number>;
|
placedTanks: Set<number>;
|
||||||
}
|
}
|
||||||
|
|
||||||
enableMapSet();
|
export const TierList = new Varuna<TierList>({
|
||||||
|
|
||||||
export const tierListInitialState: TierList = {
|
|
||||||
dragging: false,
|
dragging: false,
|
||||||
rows: tierListRows.map((row) => ({
|
rows: tierListRows.map((row) => ({ name: row.name, tanks: [] })),
|
||||||
name: row.name,
|
|
||||||
tanks: [],
|
|
||||||
})),
|
|
||||||
placedTanks: new Set(),
|
placedTanks: new Set(),
|
||||||
};
|
});
|
||||||
|
|
||||||
export const TierList = createContextualStore(() =>
|
enableMapSet();
|
||||||
create<TierList>()(
|
|
||||||
subscribeWithSelector<TierList>(() => tierListInitialState),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
Reference in new issue
Block a user