aboutsummaryrefslogtreecommitdiff
path: root/src/store/store.ts
blob: e5fce0ed262f4f0848e37608300d54442fa2163c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
import { create } from "zustand";
import { availableModels } from "../data/availableModels.ts";

export interface Vector3D {
  x: number;
  y: number;
  z: number;
}

export interface HueShift {
  red: number;
  green: number;
  blue: number;
}

export interface Model3D {
  id: string;
  sourceId: string;
  name: string;
  url: string;
  position: Vector3D;
  rotation: Vector3D;
  hueShift: HueShift;
}

export interface AvailableModel {
  id: string;
  name: string;
  url: string;
}

const CAMERA_VIEWS = [
  "perspective",
  "ortho-top",
  "ortho-front",
  "ortho-side",
] as const;
export type CameraView = (typeof CAMERA_VIEWS)[number];

export function isValidCameraView(value: string): value is CameraView {
  return CAMERA_VIEWS.includes(value as CameraView);
}

export interface StoreState {
  models: Model3D[];
  selectedModelId: string | null;
  cameraView: CameraView;
  backdropUrl: string | null;
  availableModels: AvailableModel[];
  sidebarOpen: boolean;
  addModel: (sourceId: string, position: Vector3D) => void;
  removeModel: (id: string) => void;
  selectModel: (id: string | null) => void;
  updateModel: (
    id: string,
    updates: Partial<Pick<Model3D, "position" | "rotation" | "hueShift">>,
  ) => void;
  setCameraView: (view: CameraView) => void;
  setBackdrop: (url: string | null) => void;
  toggleSidebar: () => void;
  closeSidebar: () => void;
}

export const useStore = create<StoreState>((set, get) => ({
  models: [],
  selectedModelId: null,
  cameraView: "perspective",
  backdropUrl: null,
  availableModels,
  sidebarOpen: false,

  addModel: (sourceId: string, position: Vector3D) => {
    const { availableModels } = get();
    const source = availableModels.find((model) => model.id === sourceId);
    if (!source) return;

    const newModel: Model3D = {
      id: crypto.randomUUID(),
      sourceId,
      name: source.name,
      url: source.url,
      position,
      rotation: { x: 0, y: 0, z: 0 },
      hueShift: { red: 0, green: 0, blue: 0 },
    };

    set((state) => ({
      models: [...state.models, newModel],
      selectedModelId: newModel.id,
    }));
  },

  removeModel: (id: string) => {
    set((state) => ({
      models: state.models.filter((model) => model.id !== id),
      selectedModelId:
        state.selectedModelId === id ? null : state.selectedModelId,
    }));
  },

  selectModel: (id: string | null) => {
    set({ selectedModelId: id });
  },

  updateModel: (
    id: string,
    updates: Partial<Pick<Model3D, "position" | "rotation" | "hueShift">>,
  ) => {
    set((state) => ({
      models: state.models.map((model) =>
        model.id === id ? { ...model, ...updates } : model,
      ),
    }));
  },

  setCameraView: (view: CameraView) => {
    set({ cameraView: view });
  },

  setBackdrop: (url: string | null) => {
    set({ backdropUrl: url });
  },

  toggleSidebar: () => {
    set((state) => ({ sidebarOpen: !state.sidebarOpen }));
  },

  closeSidebar: () => {
    set({ sidebarOpen: false });
  },
}));