Skip to content

Commit

Permalink
refactor types
Browse files Browse the repository at this point in the history
  • Loading branch information
Kaciras committed Jun 19, 2024
1 parent c4bf604 commit 43e8f0f
Show file tree
Hide file tree
Showing 16 changed files with 165 additions and 192 deletions.
2 changes: 1 addition & 1 deletion web/app/AnalyzePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import { Button, DownloadButton } from "../ui/index.ts";
import { AnalyzeContext, ControlsMap } from "./index";
import { ControlType } from "../form/index.ts";
import { getEncoderNames } from "../codecs/index.ts";
import { getMerger } from "../mutation.ts";
import { MetricMeta } from "../features/measurement.tsx";
import ImageView from "./ImageView.tsx";
import ChartPanel from "./ChartPanel.tsx";
import ControlPanel from "./ControlPanel.tsx";
import { AnalyzeResult } from "../features/image-worker.tsx";
import styles from "./AnalyzePage.scss";
import { getMerger } from "../hooks.ts";

interface SimplePanelProps {
visible: boolean;
Expand Down
3 changes: 1 addition & 2 deletions web/app/CompressConfigDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { Dispatch, useState } from "react";
import { MeasureOptions } from "../features/measurement.ts";
import { InputImage } from "../features/image-worker.ts";
import { useLocalStorage } from "../hooks.ts";
import { getMerger, useLocalStorage } from "../hooks.ts";
import { Button, Dialog, TabList, TabSwitch } from "../ui/index.ts";
import { getMerger } from "../mutation.ts";
import ImageInfoPanel from "./ImageInfoPanel.tsx";
import MeasurePanel, { getMeasureOptions } from "./MeasurePanel.tsx";
import EncoderPanel, { EncodingOptions, getEncodingOptions } from "./EncoderPanel.tsx";
Expand Down
6 changes: 3 additions & 3 deletions web/app/ControlPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ import clsx from "clsx";
import { SelectBox } from "../ui/index.ts";
import i18n from "../i18n.ts";
import { getEncoderNames } from "../codecs/index.ts";
import { Merger } from "../mutation.ts";
import { Merger } from "../hooks.ts";
import { ControlsMap } from "./index.ts";
import { ControlState, VariableType } from "./AnalyzePage.tsx";
import styles from "./ControlPanel.scss";

interface WrapperProps {
interface FieldWrapperProps {
type: VariableType;
targetType: VariableType;
id: string;
Expand All @@ -19,7 +19,7 @@ interface WrapperProps {
onChange: Dispatch<Partial<ControlState>>;
}

function FieldWrapper(props: WrapperProps) {
function FieldWrapper(props: FieldWrapperProps) {
const { type, id, targetType, targetId, children, onChange } = props;

function handleClick() {
Expand Down
2 changes: 1 addition & 1 deletion web/app/EncoderPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import React, { useState } from "react";
import clsx from "clsx";
import { TabPanelBase } from "../ui/TabSwitch.tsx";
import { CheckBox } from "../ui/index.ts";
import { Merger } from "../mutation.ts";
import { stopPropagation } from "../utils.ts";
import { OptionStateMap } from "../form/index.ts";
import { ENCODER_MAP, ENCODERS, ImageEncoder } from "../codecs/index.ts";
import styles from "./EncoderPanel.scss";
import OptionsForm from "../form/OptionsForm.tsx";
import { Merger } from "../hooks.ts";

export interface EncoderConfig {
enable: boolean;
Expand Down
8 changes: 4 additions & 4 deletions web/app/ImageView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Button, ColorPicker, NumberInput, PinchZoom, SwitchButton, ZoomControl
import { AnalyzeResult, InputImage } from "../features/image-worker.ts";
import theme from "../theme.module.scss";
import styles from "./ImageView.scss";
import { drawImage, usePointerMove } from "../utils.ts";
import { dragHandler, drawImage } from "../utils.ts";
import i18n from "../i18n.ts";

export enum ViewType {
Expand Down Expand Up @@ -116,7 +116,7 @@ export default function ImageView(props: ImageViewProps) {
"--brightness": `${brightnessVal}`,
};

const splitCSS = type === ViewType.Split
const splitCSS: any = type === ViewType.Split
? { "--split": `${splitPoint}px` }
: undefined;

Expand All @@ -129,7 +129,7 @@ export default function ImageView(props: ImageViewProps) {
left: clientX,
};

const onPointerDown = usePointerMove(e => {
const onPointerDown = dragHandler(e => {
const p = e.pageX;
if (p > 0 && p < window.innerWidth) {
setSplitPoint(p); // Prevent move to out of window.
Expand Down Expand Up @@ -210,7 +210,7 @@ export default function ImageView(props: ImageViewProps) {

<ZoomControl
className={styles.controls}
initValue={pinchZoomInit}
defaultValue={pinchZoomInit}
value={pinchZoom}
onChange={setPinchZoom}
/>
Expand Down
2 changes: 1 addition & 1 deletion web/app/MeasurePanel.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { ChangeEvent, ReactNode, useCallback } from "react";
import * as ssimJs from "ssim.js";
import { defaultButteraugliOptions } from "../../lib/similarity.ts";
import { deepUpdate, Mutator } from "../mutation.ts";
import { CheckBox, NumberInput } from "../ui/index.ts";
import { TabPanelBase } from "../ui/TabSwitch.tsx";
import { MeasureOptions } from "../features/measurement.ts";
import styles from "./MeasurePanel.scss";
import i18n from "../i18n.ts";
import { deepUpdate, Mutator } from "../hooks.ts";

export function getMeasureOptions(saved?: MeasureOptions) {
if (saved) {
Expand Down
2 changes: 1 addition & 1 deletion web/form/OptionsForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import clsx from "clsx";
import { BsSliders, BsType } from "react-icons/bs";
import { OptionState, OptionStateMap, OptionType } from "./index.ts";
import { Button } from "../ui/index.ts";
import { Merger } from "../mutation.ts";
import { Merger } from "../hooks.ts";
import styles from "./OptionsForm.scss";

interface OptionProps {
Expand Down
127 changes: 125 additions & 2 deletions web/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Dispatch, useEffect, useState } from "react";
import { Mutator } from "./mutation.ts";
import { Dispatch, SetStateAction, useEffect, useState } from "react";

export interface ProgressState {
value: number;
Expand Down Expand Up @@ -29,6 +28,130 @@ export function useProgress(initialMax = 1): ProgressState {
return { value, max, error, increase, reset, setError };
}

/** Alia of the setter type in React state hook */
export type Mutator<T> = Dispatch<SetStateAction<T>>;

export function deepUpdate<T>(updater: Mutator<T>, path: string, value: any) {
const parts = path.split(".");

function recurs(current: any, index: number) {
const key = parts[index];
let localValue = value;
if (index < parts.length - 1) {
localValue = recurs(current[key], index + 1);
}
return { ...current, [key]: localValue };
}

return updater(current => recurs(current, 0));
}

/**
* Enhanced mutator, designed to help mutate nested properties of an object.
*
* @example
* const [value, setValue] = useState({ a: { b: { c: 0 } } });
*
* // The code that uses mutator:
*
* const setA = (newValue) => setValue(prev => ({
* ...prev, a: newValue
* });
* const setC = (newValue) => setValue(prev => ({
* ...prev, a: {
* ...prev.a, b: {
* ...prev.b, c: newValue
* }
* }
* });
* const memoizedSetA = useCallback(setA, [setValue]);
* const memoizedSetC = useCallback(setC, [setValue]);
*
* // Use merger instead:
*
* const memoizedSetA = getMerger(setValue).sub("a");
* const memoizedSetC = memoizedSetA.sub("b").sub("c");
*/
export interface Merger<T> {

(value: SetStateAction<T>): void;

/**
* Internal usage, keep sub merger references.
*/
cache: Map<any, Merger<any>>;

/**
* Convenient function to copies properties to current value.
*/
merge(changes: Partial<T>): void;

/**
* Convenient function to change a property of current value.
*
* @param key the property name
* @param value the new value
*/
set<K extends keyof T>(key: K, value: T[K]): void;

/**
* Get the child merger for specified property, when the merger mutate its value,
* the new value will be merged into the parent object recursively.
*
* The child merger is cached, call this function on re-renders always returns the same reference.
*
* @param key the property name
* @return the merger for specified property
*/
sub<K extends keyof T>(key: K): Merger<T[K]>;
}

function derive<T>(merger: Merger<T>, key: keyof T) {

function subSetValue(action: SetStateAction<any>) {
if (typeof action !== "function") {
return merger.set(key, action);
}
merger(prev => {
const newVal = action(prev[key]);
return { ...prev, [key]: newVal };
});
}

return getMerger<T[typeof key]>(subSetValue);
}

/**
* Convert a mutator to a merger, the mutator identity must be stable and won't change on re-renders.
*/
export function getMerger<T>(mutator: Mutator<T>) {
const merger = mutator as Merger<T>;

if (merger.cache) {
return merger;
}
merger.cache = new Map();

merger.merge = changes => {
merger(prev => ({ ...prev, ...changes }));
};

merger.set = (key, value) => {
merger(prev => ({ ...prev, [key]: value }));
};

merger.sub = key => {
let cachedSetter = merger.cache.get(key);
if (!cachedSetter) {
cachedSetter = derive(merger, key);
merger.cache.set(key, cachedSetter);
}
return cachedSetter;
};

return merger;
}

type LocalStorageState<T> = [T, Mutator<T>, () => void];

export function useLocalStorage<T>(key: string, processor: (saved?: T) => T) {
Expand Down
125 changes: 0 additions & 125 deletions web/mutation.ts

This file was deleted.

Loading

0 comments on commit 43e8f0f

Please sign in to comment.