migrate to soapstone

This commit is contained in:
tresabhi committed 2026-09-27 17:04:15 -05:00
1 parent e5b44d4eb8
commit 127c55ec93
25 files changed
+61 -188

No files matched your search

-11
View File
@@ -1,11 +0,0 @@
{
"name": "varuna",
"type": "module",
"main": "src/index.ts",
"dependencies": {
"@types/lodash-es": "^4.17.12",
"immer": "^10.1.3",
"lodash-es": "^4.17.21",
"react": "^19.1.1"
}
}
-125
View File
@@ -1,125 +0,0 @@
import { produce } from "immer";
import { debounce, merge } from "lodash-es";
import { useEffect, useRef, useState } from "react";
export class Varuna<Type, Arguments = void> {
private listeners = new Set<(state: Type) => void>();
private initialized = false;
private _initial?: Type;
private _state?: Type;
constructor(
private creator: Type | ((...args: Arguments[]) => Type),
private persistence?: string
) {
if (typeof creator !== "function") this.initialize(creator);
}
private initialize(initial: Type) {
let data = initial;
if (this.persistence && typeof localStorage !== "undefined") {
const dehydrated = localStorage.getItem(this.persistence);
if (dehydrated) {
const rehydrated = JSON.parse(dehydrated) as unknown;
data = merge(data, rehydrated);
}
window.addEventListener("beforeunload", this.store.bind(this));
}
this.initialized = true;
this._initial = data;
this._state = data;
}
useInitialization(...args: Arguments[]) {
const initializedThisMount = useRef(false);
if (initializedThisMount.current) return;
if (typeof this.creator !== "function") {
throw new Error("Provider must be used with a function creator");
}
this.initialize((this.creator as (...args: Arguments[]) => Type)(...args));
initializedThisMount.current = true;
}
private dispatch() {
const state = this.state;
this.listeners.forEach((callback) => callback(state));
}
on<Slice>(slicer: (state: Type) => Slice, callback: (state: Slice) => void) {
let last = slicer(this.state);
function listener(state: Type) {
const next = slicer(state);
if (next !== last) {
callback(next);
last = next;
}
}
this.listeners.add(listener);
return () => {
this.listeners.delete(listener);
};
}
mutate(mutator: (state: Type) => void) {
this.set(produce(this.state, mutator));
}
private assertInitialized() {
if (!this.initialized) {
throw new Error("Varuna store used before initialization");
}
}
get state() {
this.assertInitialized();
return this._state as Type;
}
set(state: Type) {
this.assertInitialized();
this._state = state;
this.dispatch();
this.storeDeferred();
}
get initial() {
this.assertInitialized();
return this._initial as Type;
}
use<Slice = Type>(
slicer: (state: Type) => Slice = (state) => state as unknown as Slice
) {
return this.useDeferred(slicer, slicer(this.state));
}
useDeferred<Slice = Type>(slicer: (state: Type) => Slice, initial: Slice) {
const instance = this;
const [slice, setSlice] = useState(initial);
useEffect(() => {
setSlice(slicer(instance.state));
return instance.on(slicer, setSlice);
}, []);
return slice;
}
store() {
localStorage.setItem(this.persistence!, JSON.stringify(this.state));
}
storeDeferred = debounce(this.store.bind(this), 500);
}
-3
View File
@@ -1,3 +0,0 @@
{
"extends": "../../tsconfig.json"
}
+1 -1
View File
@@ -45,12 +45,12 @@
"react-dom": "^19.2.4",
"react-error-boundary": "^6.1.3",
"react-promise-suspense": "^0.3.4",
"soapstone": "^1.0.0",
"three": "^0.180.0",
"three-stdlib": "^2.36.0",
"transliteration": "^2.6.1",
"typescript": "^5.9.3",
"urlon": "^3.1.0",
"varuna": "workspace:*",
"vite": "^7.1.9"
}
}
+2 -2
View File
@@ -1,4 +1,4 @@
import { Varuna } from "varuna";
import { Soapstone } from "soapstone";
export interface WargamingLogin {
token: string;
@@ -14,7 +14,7 @@ export interface App {
};
}
export const App = new Varuna<App>(
export const App = new Soapstone<App>(
{
developerMode: false,
policiesAgreementIndex: -1,
@@ -1,4 +1,4 @@
import { Varuna } from "varuna";
import { Soapstone } from "soapstone";
import type { DuelMember } from "./duel";
export interface CompareMember extends DuelMember {
@@ -15,9 +15,9 @@ export interface CompareEphemeral {
};
}
export const CompareEphemeral = new Varuna<
export const CompareEphemeral = new Soapstone<
CompareEphemeral,
Record<string, number>
[Record<string, number>]
>((crewSkills) => ({
crewSkills,
equalize: false,
@@ -1,4 +1,4 @@
import { Varuna } from "varuna";
import { Soapstone } from "soapstone";
export type DeltaMode = "none" | "percentage" | "absolute";
@@ -6,7 +6,7 @@ export interface ComparePersistent {
deltaMode: DeltaMode;
}
export const ComparePersistent = new Varuna<ComparePersistent>(
export const ComparePersistent = new Soapstone<ComparePersistent>(
{ deltaMode: "none" },
"compare-2"
);
@@ -1,9 +1,9 @@
import { type IndividualTankStats } from "@blitzkit/core";
import { Varuna } from "varuna";
import { Soapstone } from "soapstone";
type EmbedBreakdown = Record<number, IndividualTankStats[]>;
export const EmbedBreakdown = new Varuna<EmbedBreakdown>(
export const EmbedBreakdown = new Soapstone<EmbedBreakdown>(
{},
"embed-breakdown-2"
);
@@ -1,5 +1,5 @@
import * as radixColors from "@radix-ui/colors";
import { Varuna } from "varuna";
import { Soapstone } from "soapstone";
import {
type EmbedConfigItemType,
EmbedItemType,
@@ -103,6 +103,6 @@ export type EmbedConfigItem = (
export type EmbedStateStore = Record<string, EmbedConfigItem["default"]>;
export const EmbedState = new Varuna<EmbedStateStore, EmbedStateStore>(
export const EmbedState = new Soapstone<EmbedStateStore, [EmbedStateStore]>(
(data) => data
);
+2 -2
View File
@@ -1,7 +1,7 @@
import { Varuna } from "varuna";
import { Soapstone } from "soapstone";
interface Gallery {
search?: string;
}
export const Gallery = new Varuna<Gallery>({});
export const Gallery = new Soapstone<Gallery>({});
+2 -2
View File
@@ -1,6 +1,6 @@
import type { TankDefinition } from "@blitzkit/core";
import { times } from "lodash-es";
import { Varuna } from "varuna";
import { Soapstone } from "soapstone";
export enum GuessState {
Correct,
@@ -18,7 +18,7 @@ export interface Guess {
tiers: number[];
}
export const Guess = new Varuna<Guess, TankDefinition>((tank) => ({
export const Guess = new Soapstone<Guess, [TankDefinition]>((tank) => ({
tank,
guessState: GuessState.NotGuessed,
totalGuesses: 0,
+2 -2
View File
@@ -3,7 +3,7 @@ import type {
TankDefinition,
TurretDefinition,
} from "@blitzkit/core";
import { Varuna } from "varuna";
import { Soapstone } from "soapstone";
export interface Mixer {
hull: TankDefinition;
@@ -18,4 +18,4 @@ export interface Mixer {
};
}
export const Mixer = new Varuna<Mixer, Mixer>((data) => data);
export const Mixer = new Soapstone<Mixer, [Mixer]>((data) => data);
+2 -2
View File
@@ -1,5 +1,5 @@
import type { Samples } from "@blitzkit/core";
import { Varuna } from "varuna";
import { Soapstone } from "soapstone";
export interface Performance {
playerCountPeriod: PlayerCountPeriod;
@@ -7,6 +7,6 @@ export interface Performance {
export type PlayerCountPeriod = keyof Samples;
export const Performance = new Varuna<Performance>({
export const Performance = new Soapstone<Performance>({
playerCountPeriod: "total",
});
+2 -2
View File
@@ -1,4 +1,4 @@
import { Varuna } from "varuna";
import { Soapstone } from "soapstone";
export interface PlaylistEntry {
id: number;
@@ -11,4 +11,4 @@ export interface Playlist {
list?: PlaylistEntry[];
}
export const Playlist = new Varuna<Playlist>({}, "playlist");
export const Playlist = new Soapstone<Playlist>({}, "playlist");
+2 -2
View File
@@ -3,7 +3,7 @@ import type {
IndividualTankStats,
Region,
} from "@blitzkit/core";
import { Varuna } from "varuna";
import { Soapstone } from "soapstone";
interface SessionBase {
columns: CompositeStatsKey[];
@@ -25,7 +25,7 @@ interface SessionNotTracking extends SessionBase {
type Session = SessionTracking | SessionNotTracking;
export const Session = new Varuna<Session>(
export const Session = new Soapstone<Session>(
{
columns: [
"cumulative_battles",
+2 -2
View File
@@ -5,7 +5,7 @@ import type {
TankClass,
TankType,
} from "@blitzkit/protos";
import { Varuna } from "varuna";
import { Soapstone } from "soapstone";
export interface TankFilters {
tiers: number[];
@@ -26,7 +26,7 @@ export interface TankFilters {
showUnowned: boolean;
}
export const TankFilters = new Varuna<TankFilters>({
export const TankFilters = new Soapstone<TankFilters>({
tiers: [],
nations: [],
classes: [],
@@ -1,5 +1,5 @@
import type en from "@blitzkit/i18n/strings/en.json";
import { Varuna } from "varuna";
import { Soapstone } from "soapstone";
export type TankPerformanceSortType =
keyof typeof en.website.tools.performance.table.stats;
@@ -9,7 +9,7 @@ export interface TankPerformanceSort {
direction: -1 | 1;
}
export const TankPerformanceSort = new Varuna<TankPerformanceSort>({
export const TankPerformanceSort = new Soapstone<TankPerformanceSort>({
type: "winrate",
direction: -1,
});
+2 -2
View File
@@ -5,7 +5,7 @@ import type {
TankDefinition,
} from "@blitzkit/protos";
import type { Vector3 } from "three";
import { Varuna } from "varuna";
import { Soapstone } from "soapstone";
import { api } from "../api/dynamic";
import type { ArmorType } from "../components/SpacedArmorScene";
import type { ExternalModuleVariant } from "../components/SpacedArmorSceneComponent";
@@ -117,7 +117,7 @@ interface Tankopedia {
const skills = await api.skills();
const models = await api.models();
export const Tankopedia = new Varuna<Tankopedia, TankDefinition>((tank) => ({
export const Tankopedia = new Soapstone<Tankopedia, [TankDefinition]>((tank) => ({
disturbed: false,
revealed: false,
@@ -1,5 +1,5 @@
import type en from "@blitzkit/i18n/strings/en.json";
import { Varuna } from "varuna";
import { Soapstone } from "soapstone";
export type TankopediaSortBy = keyof typeof en.website.common.tank_search.sort;
export type TankopediaSortDirection = "ascending" | "descending";
@@ -23,7 +23,7 @@ export interface TankopediaPersistent {
};
}
export const TankopediaPersistent = new Varuna<TankopediaPersistent>(
export const TankopediaPersistent = new Soapstone<TankopediaPersistent>(
{
hideTankModelUnderArmor: false,
wireframe: false,
@@ -1,4 +1,4 @@
import { Varuna } from 'varuna';
import { Soapstone } from 'soapstone';
import {
type TankopediaSortBy,
type TankopediaSortDirection,
@@ -9,7 +9,7 @@ export interface TankSort {
direction: TankopediaSortDirection;
}
export const TankSort = new Varuna<TankSort>({
export const TankSort = new Soapstone<TankSort>({
by: 'meta.none',
direction: 'descending',
});
+2 -2
View File
@@ -1,5 +1,5 @@
import { enableMapSet } from "immer";
import { Varuna } from "varuna";
import { Soapstone } from "soapstone";
import { tierListRows } from "../components/TierList/Table/constants";
export interface TierList {
@@ -8,7 +8,7 @@ export interface TierList {
placedTanks: Set<number>;
}
export const TierList = new Varuna<TierList>({
export const TierList = new Soapstone<TierList>({
dragging: false,
rows: tierListRows.map((row) => ({ name: row.name, tanks: [] })),
placedTanks: new Set(),