├── .eslintrc.cjs
├── .gitignore
├── README.md
├── index.html
├── package.json
├── pnpm-lock.yaml
├── postcss.config.js
├── public
├── font
│ └── Inter_Bold.json
├── img
│ ├── favicon.png
│ ├── no-image.png
│ └── none-texture.jpg
└── svg
│ └── spotlight-svgrepo-com.svg
├── src
├── components
│ ├── App.tsx
│ ├── form
│ │ ├── InputMaterial.tsx
│ │ ├── InputModel.tsx
│ │ ├── InputSound.tsx
│ │ ├── InputTexture.tsx
│ │ ├── PropertiesForm.tsx
│ │ └── SelectMaterial.tsx
│ ├── general
│ │ ├── Actions.tsx
│ │ ├── CameraControls.tsx
│ │ ├── ContextMenu.tsx
│ │ ├── EditorControls.tsx
│ │ ├── Element.tsx
│ │ ├── ElementsRender.tsx
│ │ ├── FileIcon.tsx
│ │ ├── FolderIcon.tsx
│ │ ├── GlbHelper.tsx
│ │ ├── Helpers.tsx
│ │ ├── Icon.tsx
│ │ ├── Layout.tsx
│ │ ├── MeshMaterial.tsx
│ │ └── Notifications.tsx
│ ├── modal
│ │ ├── ModalCommands.tsx
│ │ ├── ModalExport.tsx
│ │ ├── ModalHub.tsx
│ │ ├── ModalNewDocument.tsx
│ │ ├── ModalNewFile.tsx
│ │ ├── ModalSearch.tsx
│ │ ├── ModalSettings.tsx
│ │ └── ModalTerms.tsx
│ ├── properties
│ │ └── DocumentProps.tsx
│ ├── resources
│ │ ├── AudioPicker.tsx
│ │ ├── CodePicker.tsx
│ │ ├── EnvironmentPicker.tsx
│ │ ├── ImagePicker.tsx
│ │ ├── MaterialPicker.tsx
│ │ ├── Resources.tsx
│ │ └── TexturePicker.tsx
│ ├── screens
│ │ ├── CodeEditor.tsx
│ │ ├── Graph.tsx
│ │ └── Mesa.tsx
│ ├── toolbar
│ │ ├── ToolbarBottom.tsx
│ │ ├── ToolbarLeft.tsx
│ │ ├── ToolbarRight.tsx
│ │ └── ToolbarTop.tsx
│ ├── ui
│ │ ├── accordion.tsx
│ │ ├── alert.tsx
│ │ ├── badge.tsx
│ │ ├── button.tsx
│ │ ├── checkbox.tsx
│ │ ├── command.tsx
│ │ ├── dialog.tsx
│ │ ├── dropdown-menu.tsx
│ │ ├── input.tsx
│ │ ├── label.tsx
│ │ ├── popover.tsx
│ │ ├── scroll-area.tsx
│ │ ├── select.tsx
│ │ ├── slider.tsx
│ │ ├── switch.tsx
│ │ ├── tabs.tsx
│ │ ├── textarea.tsx
│ │ └── tooltip.tsx
│ └── window
│ │ ├── History.tsx
│ │ ├── Layers.tsx
│ │ ├── LogContainer.tsx
│ │ ├── MediaMaker.tsx
│ │ ├── ProjectFiles.tsx
│ │ └── Properties.tsx
├── constants
│ ├── blueprint.json
│ ├── commands.json
│ ├── document.json
│ ├── environment.json
│ ├── general.json
│ ├── index.ts
│ ├── layout.json
│ ├── light.json
│ ├── material.json
│ ├── materials.json
│ ├── models.json
│ ├── object.json
│ ├── settings.json
│ ├── sounds.json
│ ├── textures.json
│ ├── toolbar.json
│ └── tree_theme.json
├── elements
│ ├── general
│ │ ├── Browser
│ │ │ ├── Controls.tsx
│ │ │ ├── index.tsx
│ │ │ └── props.ts
│ │ ├── Environment
│ │ │ ├── Controls.tsx
│ │ │ ├── index.tsx
│ │ │ └── props.ts
│ │ ├── Glb
│ │ │ ├── Controls.tsx
│ │ │ ├── index.tsx
│ │ │ └── props.ts
│ │ ├── Image
│ │ │ ├── Controls.tsx
│ │ │ ├── index.tsx
│ │ │ └── props.ts
│ │ ├── PositionalAudio
│ │ │ ├── Controls.tsx
│ │ │ ├── index.tsx
│ │ │ └── props.ts
│ │ ├── Sky
│ │ │ ├── Controls.tsx
│ │ │ ├── index.tsx
│ │ │ └── props.ts
│ │ ├── Stars
│ │ │ ├── Controls.tsx
│ │ │ ├── index.tsx
│ │ │ └── props.ts
│ │ ├── Svg
│ │ │ ├── Controls.tsx
│ │ │ ├── index.tsx
│ │ │ └── props.ts
│ │ ├── Text
│ │ │ ├── Controls.tsx
│ │ │ ├── index.tsx
│ │ │ └── props.ts
│ │ └── Text3D
│ │ │ ├── Controls.tsx
│ │ │ ├── index.tsx
│ │ │ └── props.ts
│ ├── geometry
│ │ ├── BoxGeometry
│ │ │ ├── Controls.tsx
│ │ │ ├── index.tsx
│ │ │ └── props.ts
│ │ ├── CapsuleGeometry
│ │ │ ├── Controls.tsx
│ │ │ ├── index.tsx
│ │ │ └── props.ts
│ │ ├── CircleGeometry
│ │ │ ├── Controls.tsx
│ │ │ ├── index.tsx
│ │ │ └── props.ts
│ │ ├── ConeGeometry
│ │ │ ├── Controls.tsx
│ │ │ ├── index.tsx
│ │ │ └── props.ts
│ │ ├── CylinderGeometry
│ │ │ ├── Controls.tsx
│ │ │ ├── index.tsx
│ │ │ └── props.ts
│ │ ├── DodecahedronGeometry
│ │ │ ├── Controls.tsx
│ │ │ ├── index.tsx
│ │ │ └── props.ts
│ │ ├── ExtrudeGeometry
│ │ │ ├── Controls.tsx
│ │ │ ├── index.tsx
│ │ │ └── props.ts
│ │ ├── IcosahedronGeometry
│ │ │ ├── Controls.tsx
│ │ │ ├── index.tsx
│ │ │ └── props.ts
│ │ ├── LatheGeometry
│ │ │ ├── Controls.tsx
│ │ │ ├── index.tsx
│ │ │ └── props.ts
│ │ ├── OctahedronGeometry
│ │ │ ├── Controls.tsx
│ │ │ ├── index.tsx
│ │ │ └── props.ts
│ │ ├── PlaneGeometry
│ │ │ ├── Controls.tsx
│ │ │ ├── index.tsx
│ │ │ └── props.ts
│ │ ├── RingGeometry
│ │ │ ├── Controls.tsx
│ │ │ ├── index.tsx
│ │ │ └── props.ts
│ │ ├── SphereGeometry
│ │ │ ├── Controls.tsx
│ │ │ ├── index.tsx
│ │ │ └── props.ts
│ │ ├── TetrahedronGeometry
│ │ │ ├── Controls.tsx
│ │ │ ├── index.tsx
│ │ │ └── props.ts
│ │ ├── TorusGeometry
│ │ │ ├── Controls.tsx
│ │ │ ├── index.tsx
│ │ │ └── props.ts
│ │ ├── TorusKnotGeometry
│ │ │ ├── Controls.tsx
│ │ │ ├── index.tsx
│ │ │ └── props.ts
│ │ └── TubeGeometry
│ │ │ ├── Controls.tsx
│ │ │ ├── index.tsx
│ │ │ └── props.ts
│ ├── index.tsx
│ └── light
│ │ ├── AmbientLight
│ │ ├── Controls.tsx
│ │ ├── index.tsx
│ │ └── props.ts
│ │ ├── DirectionalLight
│ │ ├── Controls.tsx
│ │ ├── index.tsx
│ │ └── props.ts
│ │ ├── HemisphereLight
│ │ ├── Controls.tsx
│ │ ├── index.tsx
│ │ └── props.ts
│ │ ├── PointLight
│ │ ├── Controls.tsx
│ │ ├── index.tsx
│ │ └── props.ts
│ │ └── SpotLight
│ │ ├── Controls.tsx
│ │ ├── index.tsx
│ │ └── props.ts
├── hooks
│ ├── useBlueprint.ts
│ ├── useCodeEditor.ts
│ ├── useComponentFunctions.ts
│ ├── useElement.ts
│ ├── useKeyboardListener.ts
│ ├── useModal.ts
│ ├── usePlayer.ts
│ ├── useProjectFiles.ts
│ ├── useProviderControls.ts
│ ├── useScrollToElement.ts
│ ├── useSdk.ts
│ ├── useStorageProvider.ts
│ └── useToolbar.ts
├── lib
│ └── utils.ts
├── main.tsx
├── matrix
│ ├── element.ts
│ └── material.ts
├── store
│ ├── commands.ts
│ ├── document.ts
│ ├── elements.ts
│ ├── files.ts
│ ├── graph.ts
│ ├── history.ts
│ ├── layout.ts
│ └── settings.ts
├── styles
│ ├── SidebarCode.css
│ ├── globals.css
│ ├── index.css
│ └── layout.css
├── types.ts
├── utils
│ ├── clearLocalStorage.ts
│ ├── getEditorLang.ts
│ ├── getFunctionByName.ts
│ ├── getObjectDiff.ts
│ ├── getOpenedFiles.ts
│ ├── mountNewElement.ts
│ ├── mountToolbarActions.ts
│ ├── neverUsed.ts
│ └── processElements.ts
└── vite-env.d.ts
├── tailwind.config.js
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts
/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: { browser: true, es2020: true },
3 | extends: [
4 | 'eslint:recommended',
5 | 'plugin:@typescript-eslint/recommended',
6 | 'plugin:react-hooks/recommended',
7 | ],
8 | parser: '@typescript-eslint/parser',
9 | parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
10 | plugins: ['react-refresh'],
11 | rules: {
12 | 'react-refresh/only-export-components': 'warn',
13 | },
14 | }
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | ThreeD Studio - Truly beautiful experiences on the web
8 |
9 |
10 |
11 |
12 | reset-document
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "builder",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "tsc && vite build",
9 | "lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
10 | "preview": "vite preview"
11 | },
12 | "dependencies": {
13 | "@mantine/hooks": "^6.0.11",
14 | "@monaco-editor/react": "^4.5.1",
15 | "@radix-ui/react-accordion": "^1.1.2",
16 | "@radix-ui/react-checkbox": "^1.0.4",
17 | "@radix-ui/react-dialog": "^1.0.4",
18 | "@radix-ui/react-dropdown-menu": "^2.0.5",
19 | "@radix-ui/react-label": "^2.0.2",
20 | "@radix-ui/react-popover": "^1.0.6",
21 | "@radix-ui/react-scroll-area": "^1.0.4",
22 | "@radix-ui/react-select": "^1.2.2",
23 | "@radix-ui/react-slider": "^1.1.2",
24 | "@radix-ui/react-slot": "^1.0.2",
25 | "@radix-ui/react-switch": "^1.0.3",
26 | "@radix-ui/react-tabs": "^1.0.4",
27 | "@radix-ui/react-tooltip": "^1.0.6",
28 | "@react-three/drei": "^9.68.3",
29 | "@react-three/fiber": "^8.13.0",
30 | "@react-three/postprocessing": "^2.11.4",
31 | "@tabler/icons-react": "^2.23.0",
32 | "@types/three": "^0.152.0",
33 | "@uiw/react-textarea-code-editor": "^2.1.6",
34 | "animate.css": "^4.1.1",
35 | "class-variance-authority": "^0.6.0",
36 | "clsx": "^1.2.1",
37 | "cmdk": "^0.2.0",
38 | "console-feed": "^3.5.0",
39 | "leva": "^0.9.35",
40 | "lucide-react": "^0.241.0",
41 | "nanoid": "^4.0.2",
42 | "r3f-perf": "^7.1.2",
43 | "react": "^18.2.0",
44 | "react-accessible-treeview": "^2.5.4",
45 | "react-cmdk": "^1.3.9",
46 | "react-color": "^2.19.3",
47 | "react-dom": "^18.2.0",
48 | "react-headless-pagination": "^1.1.4",
49 | "react-hot-toast": "^2.4.1",
50 | "react-json-tree": "^0.18.0",
51 | "reactflow": "^11.7.2",
52 | "tailwind-merge": "^1.13.1",
53 | "tailwindcss-animate": "^1.0.6",
54 | "three": "^0.152.2",
55 | "utility-types": "^3.10.0",
56 | "zustand": "^4.3.8"
57 | },
58 | "devDependencies": {
59 | "@types/react": "^18.0.28",
60 | "@types/react-color": "^3.0.6",
61 | "@types/react-dom": "^18.0.11",
62 | "@types/uuid": "^9.0.1",
63 | "@typescript-eslint/eslint-plugin": "^5.57.1",
64 | "@typescript-eslint/parser": "^5.57.1",
65 | "@vitejs/plugin-react": "^4.0.0",
66 | "autoprefixer": "^10.4.14",
67 | "eslint": "^8.38.0",
68 | "eslint-plugin-react-hooks": "^4.6.0",
69 | "eslint-plugin-react-refresh": "^0.3.4",
70 | "icons-react@latest": "link:@types/@tabler/icons-react@latest",
71 | "postcss": "^8.4.24",
72 | "tailwindcss": "^3.3.2",
73 | "typescript": "^5.0.2",
74 | "vite": "^4.3.2",
75 | "vite-tsconfig-paths": "^4.2.0"
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/public/img/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-threed/studio/a69dcfe3d2ef67c439caf96c8de0bd1ba3e3da5d/public/img/favicon.png
--------------------------------------------------------------------------------
/public/img/no-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-threed/studio/a69dcfe3d2ef67c439caf96c8de0bd1ba3e3da5d/public/img/no-image.png
--------------------------------------------------------------------------------
/public/img/none-texture.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-threed/studio/a69dcfe3d2ef67c439caf96c8de0bd1ba3e3da5d/public/img/none-texture.jpg
--------------------------------------------------------------------------------
/src/components/form/InputMaterial.tsx:
--------------------------------------------------------------------------------
1 | export default function InputMaterial() {
2 | return (
3 |
4 | lorem
5 |
6 | )
7 | }
--------------------------------------------------------------------------------
/src/components/form/InputModel.tsx:
--------------------------------------------------------------------------------
1 | export default function InputModel() {
2 | return (
3 |
4 | lorem
5 |
6 | )
7 | }
--------------------------------------------------------------------------------
/src/components/form/InputSound.tsx:
--------------------------------------------------------------------------------
1 | import { IconFileMusic } from "@tabler/icons-react"
2 | import CONSTANTS from "../../constants"
3 | import { useElementsStore } from "../../store/elements"
4 | import { Button } from "@/components/ui/button"
5 | import {
6 | Popover,
7 | PopoverContent,
8 | PopoverTrigger,
9 | } from "@/components/ui/popover"
10 |
11 | import { ScrollArea } from "@/components/ui/scroll-area"
12 |
13 | export default function InputSound() {
14 | const { current, updateCurrent } = useElementsStore()
15 |
16 | function handleChange(value:string) {
17 | updateCurrent({
18 | soundUrl: value
19 | })
20 | }
21 |
22 | return (
23 | <>
24 |
25 |
26 |
32 |
33 |
34 |
35 |
36 |
37 | {CONSTANTS.sounds.map((item:string) => (
38 |
39 |
handleChange(item)}
42 | >
43 |
44 |
45 | {item?.replaceAll('-', ' ')}
46 |
47 |
48 |
49 | ))}
50 |
51 |
52 |
53 |
54 |
55 | >
56 | )
57 | }
--------------------------------------------------------------------------------
/src/components/form/InputTexture.tsx:
--------------------------------------------------------------------------------
1 | import CONSTANTS from "../../constants"
2 | import { useElementsStore } from "../../store/elements"
3 | import { Button } from "@/components/ui/button"
4 |
5 | import {
6 | Popover,
7 | PopoverContent,
8 | PopoverTrigger,
9 | } from "@/components/ui/popover"
10 |
11 | import { ScrollArea } from "@/components/ui/scroll-area"
12 |
13 | export default function InputTexture() {
14 | const { current, updateElement } = useElementsStore()
15 |
16 | const handleChange = (value:string) => {
17 | updateElement({
18 | id: current.id,
19 | texture: value
20 | })
21 | }
22 |
23 | return (
24 | <>
25 |
26 |
27 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
handleChange('none')}
38 | style={{
39 | borderColor: current.texture !== 'none' ?'transparent':'white'
40 | }}>
41 |
none
42 |
43 |
44 | {CONSTANTS.textures.map((item:string) => (
45 |
46 |
handleChange(item)}
48 | style={{
49 | backgroundImage: `url(${CONSTANTS.cdn}/textures/wood_board/${item}.jpg)`,
50 | borderColor: current.texture !== item ?'transparent':'white'
51 | }}>
52 |
{item}
53 |
54 |
55 | ))}
56 |
57 |
58 |
59 |
60 |
61 | >
62 | )
63 | }
--------------------------------------------------------------------------------
/src/components/form/SelectMaterial.tsx:
--------------------------------------------------------------------------------
1 | import PropertiesForm from "./PropertiesForm"
2 | import CONSTANTS from "../../constants"
3 | import { useElementsStore } from "../../store/elements"
4 | import { Button } from "@/components/ui/button"
5 |
6 | import {
7 | Popover,
8 | PopoverContent,
9 | PopoverTrigger,
10 | } from "@/components/ui/popover"
11 |
12 | import {
13 | Select,
14 | SelectContent,
15 | SelectGroup,
16 | SelectItem,
17 | SelectTrigger,
18 | SelectValue,
19 | } from "@/components/ui/select"
20 |
21 | import { ScrollArea } from "@/components/ui/scroll-area"
22 | import { MatetialType } from "@/types"
23 |
24 | const options = Object
25 | .entries(CONSTANTS.materials)
26 | .filter((item) => JSON.stringify(item[1]) !== '{}')
27 | .map((item, index) => ({
28 | value: item[0],
29 | label: item[0],
30 | disabled: JSON.stringify(item[1]) === '{}',
31 | group: index>7? 'Basic' : 'Advanced'
32 | }))
33 |
34 | export default function SelectMaterial() {
35 | const { current, updateElement } = useElementsStore()
36 |
37 | const handleChange = (value:string) => {
38 | updateElement({
39 | id: current.id,
40 | material_type: value,
41 | material: CONSTANTS.materials[value]
42 | })
43 | }
44 |
45 | const handleChangeMaterial = (value: MatetialType) => {
46 | updateElement({
47 | id: current.id,
48 | material: value
49 | })
50 | }
51 |
52 | return (
53 | <>
54 |
55 |
56 |
59 |
60 |
61 |
62 |
63 |
type:
64 |
79 |
80 | handleChangeMaterial(e)}
83 | selects={{
84 | wireframeLinejoin:[
85 | "round",
86 | "bevel",
87 | "miter"
88 | ],
89 | wireframeLinecap: [
90 | "butt",
91 | "round",
92 | "square"
93 | ]
94 | }}
95 | />
96 |
97 |
98 |
99 | >
100 | )
101 | }
--------------------------------------------------------------------------------
/src/components/general/CameraControls.tsx:
--------------------------------------------------------------------------------
1 | import { MapControls, OrbitControls } from '@react-three/drei'
2 | import { useDocumentStore } from '../../store/document'
3 |
4 | export default function CameraControls() {
5 | const { document } = useDocumentStore()
6 |
7 | return (
8 | <>
9 |
10 |
11 | >
12 | )
13 | }
14 |
--------------------------------------------------------------------------------
/src/components/general/ContextMenu.tsx:
--------------------------------------------------------------------------------
1 | import { useMouse } from '@mantine/hooks';
2 | import { IconPhoto, IconMessageCircle } from '@tabler/icons-react';
3 | import { useElementsStore } from '../../store/elements';
4 | import { ElementType } from '../../types';
5 |
6 | import {
7 | DropdownMenu,
8 | DropdownMenuContent,
9 | DropdownMenuItem,
10 | DropdownMenuTrigger
11 | } from "@/components/ui/dropdown-menu"
12 |
13 | export default function ContextMenu() {
14 | const { x, y } = useMouse()
15 | const { elements, contextElementId, updateElements, setContextElementId, setCurrent } = useElementsStore()
16 |
17 | function handleClose() {
18 | setContextElementId(null)
19 | }
20 |
21 | if(!contextElementId) {
22 | return
23 | }
24 |
25 | const remove = () => {
26 | updateElements([
27 | ...elements.filter((item: ElementType) => item.id !== contextElementId)
28 | ])
29 | setContextElementId(null)
30 | setCurrent(null)
31 | }
32 |
33 | return (
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | Duplicate element
43 |
44 |
45 |
46 | Remove element
47 |
48 |
49 |
50 | );
51 | }
--------------------------------------------------------------------------------
/src/components/general/EditorControls.tsx:
--------------------------------------------------------------------------------
1 | import { TransformControls } from '@react-three/drei'
2 |
3 | import usePlayer from '../../hooks/usePlayer'
4 | import { useSettingsStore } from '../../store/settings'
5 | import { useElementsStore } from '../../store/elements'
6 |
7 | export default function EditorControls() {
8 | const { isPlayerScreen } = usePlayer()
9 | const { settings } = useSettingsStore()
10 | const { current, updateElement } = useElementsStore()
11 |
12 | const transformControlsObject = !settings.tool.selectGroup
13 | ? current?.object
14 | : current?.eventObject
15 |
16 | const isActive = (current?.object && !isPlayerScreen)
17 |
18 | if(!isActive) {
19 | return null
20 | }
21 |
22 | return (
23 | {
27 | updateElement({
28 | id: current.id,
29 | position: Object.entries(transformControlsObject.position).map((item) =>item[1]),
30 | // rotation: Object.entries({x, y, z}).map((item) =>item[1]),
31 | scale: Object.entries(transformControlsObject.scale).map((item) =>item[1])
32 | })
33 | }}
34 | />
35 | )
36 | }
37 |
--------------------------------------------------------------------------------
/src/components/general/Element.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef } from 'react'
2 |
3 | import element from '../../matrix/element'
4 | import useBlueprint from '../../hooks/useBlueprint'
5 | import usePlayer from '../../hooks/usePlayer'
6 | import { useElementsStore } from '../../store/elements'
7 | import { ElementType } from '@/types'
8 |
9 | type ElementProps = {
10 | item: ElementType
11 | onHover: React.Dispatch>
12 | }
13 |
14 | export default function Element({ item, onHover }: ElementProps) {
15 | const ref = useRef(null)
16 | const { run } = useBlueprint()
17 | const { isPlayerScreen } = usePlayer()
18 | const { setCurrent, setContextElementId } = useElementsStore()
19 |
20 | useEffect(() => {
21 | run('onMount', item.id)
22 | }, [])
23 |
24 | const handleElementClick = (item: ElementType) => {
25 | const hasClick = item.editable
26 | if(hasClick) {
27 | if (!isPlayerScreen) {
28 | setCurrent(item)
29 | } else {
30 | run('onClick', item.id)
31 | }
32 | }
33 | }
34 |
35 | const {
36 | eventObject,
37 | object,
38 | id,
39 | type,
40 | editable,
41 | position,
42 | rotation,
43 | scale,
44 | ...restOfProps
45 | } = item
46 |
47 | const Component = element[item.type]
48 |
49 | return (
50 | {
56 | stopPropagation();
57 | setContextElementId(item.id)
58 | }}
59 | onClick={({ stopPropagation }) => {
60 | stopPropagation();
61 | handleElementClick({
62 | ...item,
63 | eventObject: ref?.current,
64 | object: ref?.current
65 | });
66 | }}
67 | ref={ref}
68 | >
69 |
70 |
71 | )
72 | }
73 |
--------------------------------------------------------------------------------
/src/components/general/ElementsRender.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react'
2 |
3 | import { EffectComposer, Outline } from '@react-three/postprocessing'
4 | import Element from '../general/Element'
5 | import { useElementsStore } from '../../store/elements'
6 | import { ElementType } from '../../types'
7 |
8 | export default function ElementsRender() {
9 | const { elements } = useElementsStore()
10 |
11 | const [hovered, onHover] = useState(null)
12 | const selected = hovered ? [hovered] : undefined
13 |
14 | return (
15 | <>
16 | {elements.map((item: ElementType) => (
17 |
22 | ))}
23 |
24 |
29 |
30 | >
31 | )
32 | }
33 |
--------------------------------------------------------------------------------
/src/components/general/FileIcon.tsx:
--------------------------------------------------------------------------------
1 | import { IconBrandCss3, IconFile, IconCode, IconPackage } from "@tabler/icons-react";
2 |
3 | type FileIconProps = {
4 | filename: string
5 | }
6 |
7 | export default function FileIcon({ filename }: FileIconProps) {
8 | const extension = filename.slice(filename.lastIndexOf(".") + 1);
9 | switch (extension) {
10 | case "js":
11 | return ;
12 | case "css":
13 | return ;
14 | case "json":
15 | return ;
16 | case "npmignore":
17 | return ;
18 | default:
19 | return null;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/components/general/FolderIcon.tsx:
--------------------------------------------------------------------------------
1 | import { IconFolderCode, IconFolderFilled } from "@tabler/icons-react";
2 |
3 | type FolderIconProps = {
4 | isOpen: boolean
5 | }
6 |
7 | export default function FolderIcon({ isOpen }: FolderIconProps) {
8 | return isOpen ? (
9 |
10 | ) : (
11 |
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/src/components/general/GlbHelper.tsx:
--------------------------------------------------------------------------------
1 | import { useDocumentStore } from "../../store/document"
2 |
3 | export function GlbHelper() {
4 | const { document } = useDocumentStore()
5 |
6 | if(!document.helpers) {
7 | return null
8 | }
9 |
10 | return (
11 |
12 | )
13 | }
--------------------------------------------------------------------------------
/src/components/general/Helpers.tsx:
--------------------------------------------------------------------------------
1 | import { Perf } from 'r3f-perf'
2 |
3 | import usePlayer from '../../hooks/usePlayer'
4 | import { useSettingsStore } from '../../store/settings'
5 | import { useDocumentStore } from '../../store/document'
6 |
7 | export default function Helpers() {
8 | const { isPlayerScreen } = usePlayer()
9 | const { settings } = useSettingsStore()
10 | const { document } = useDocumentStore()
11 |
12 | if(isPlayerScreen) {
13 | return null
14 | }
15 |
16 | return (
17 |
18 | <>
19 | {document.axes && }
20 | {document.helpers && }
21 | >
22 | {settings.performanceMonitor.enabled && (
23 |
24 | )}
25 |
26 | )
27 | }
28 |
--------------------------------------------------------------------------------
/src/components/general/Icon.tsx:
--------------------------------------------------------------------------------
1 | import { IconType } from '@/types'
2 | import * as icons from '@tabler/icons-react'
3 |
4 | type IconProps = {
5 | name: string
6 | label?: string
7 | }
8 |
9 | export default function Icon({ name }: IconProps) {
10 | const listIcons: IconType = icons
11 | const BaseIcon = listIcons[name] || icons.IconHelpCircle
12 | return (
13 |
14 | )
15 | }
--------------------------------------------------------------------------------
/src/components/general/MeshMaterial.tsx:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three'
2 |
3 | import materialz from "../../matrix/material"
4 | import { useTexture } from '@react-three/drei'
5 | import CONSTANTS from '../../constants'
6 | import { MaterialType } from '@/types'
7 |
8 | type materialName = 'meshDistortMaterial'
9 | | 'meshReflectorMaterial'
10 | | 'meshRefractionMaterial'
11 | | 'meshTransmissionMaterial'
12 | | 'meshWobbleMaterial'
13 | | 'meshDiscardMaterial'
14 | | 'pointMaterial'
15 | | 'shaderMaterial'
16 | | 'lineBasicMaterial'
17 | | 'lineDashedMaterial'
18 | | 'material'
19 | | 'meshBasicMaterial'
20 | | 'meshDepthMaterial'
21 | | 'meshDistanceMaterial'
22 | | 'meshLambertMaterial'
23 | | 'meshMatcapMaterial'
24 | | 'meshNormalMaterial'
25 | | 'meshPhongMaterial'
26 | | 'meshPhysicalMaterial'
27 | | 'meshStandardMaterial'
28 | | 'meshToonMaterial'
29 | | 'pointsMaterial'
30 | | 'rawShaderMaterial'
31 | | 'shadowMaterial'
32 | | 'spriteMaterial'
33 |
34 | type MeshMaterialProps = {
35 | name: materialName
36 | material: MaterialType
37 | texture?: string
38 | }
39 |
40 | export default function MeshMaterial({
41 | name, material, texture
42 | }: MeshMaterialProps) {
43 | const hasTexture = texture !== 'none'
44 | const textureUrl = !hasTexture ? '/img/none-texture.jpg' : `${CONSTANTS.cdn}/textures/wood_board/${texture}.jpg`
45 | const map = useTexture(textureUrl)
46 |
47 | const Comp = materialz[name]
48 | return (
49 | <>
50 |
51 | >
52 | )
53 | }
54 |
--------------------------------------------------------------------------------
/src/components/general/Notifications.tsx:
--------------------------------------------------------------------------------
1 | import { IconCheck, IconLoader, IconX } from '@tabler/icons-react'
2 | import toast, { useToaster } from 'react-hot-toast/headless';
3 | import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
4 |
5 | export default function Notifications() {
6 | const { toasts, handlers } = useToaster();
7 | const { startPause, endPause } = handlers;
8 |
9 | return (
10 |
11 | {toasts
12 | .filter((current) => current.visible)
13 | .map((current) => (
14 |
15 |
16 | {current.type === 'loading' ? (
) : null}
17 | {current.type === 'success' ? (
) : null}
18 | {current.type === 'error' ? (
) : null}
19 |
20 |
21 | {String(current?.message).split('\n')[0]}
22 |
23 | {String(current?.message).split('\n')[1] && (
24 |
25 | {String(current?.message).split('\n')[1]}
26 |
27 | )}
28 |
29 |
30 | {current.type !== 'loading' && (
31 |
34 | )}
35 |
36 |
37 | ))}
38 |
39 | );
40 | }
--------------------------------------------------------------------------------
/src/components/modal/ModalCommands.tsx:
--------------------------------------------------------------------------------
1 | import { useCommandsStore } from '../../store/commands'
2 | import useModal from '../../hooks/useModal'
3 |
4 | import {
5 | Dialog,
6 | DialogContent,
7 | DialogHeader,
8 | DialogTitle,
9 | } from "@/components/ui/dialog"
10 |
11 | export default function ModalCommands() {
12 | const { isOpenedCommandsModal, closeCommandsModal } = useModal()
13 | const { commands } = useCommandsStore()
14 |
15 | return (
16 |
43 | )
44 | }
--------------------------------------------------------------------------------
/src/components/modal/ModalExport.tsx:
--------------------------------------------------------------------------------
1 | import useModal from "../../hooks/useModal"
2 | import { JSONTree } from 'react-json-tree'
3 | import CONSTANTS from "../../constants"
4 | import { useDocumentStore } from "../../store/document"
5 |
6 | import {
7 | Dialog,
8 | DialogContent,
9 | DialogHeader,
10 | DialogTitle,
11 | } from "@/components/ui/dialog"
12 | import { ScrollArea } from "@/components/ui/scroll-area"
13 | import { Button } from "@/components/ui/button"
14 | import { useFilesStore } from "@/store/files"
15 | import { useGraphStore } from "@/store/graph"
16 | import { useLayoutStore } from "@/store/layout"
17 | import { useHistoryStore } from "@/store/history"
18 | import { useSettingsStore } from "@/store/settings"
19 | import { useCommandsStore } from "@/store/commands"
20 | import { useElementsStore } from "@/store/elements"
21 |
22 | export default function ModalExport() {
23 | const { isOpenedExportModal, closeExportModal } = useModal()
24 | const { document: documentStore } = useDocumentStore()
25 | const { files } = useFilesStore()
26 | const { graph, graphLock } = useGraphStore()
27 | const { layout, footer } = useLayoutStore()
28 | const { history } = useHistoryStore()
29 | const { loadingView, settings } = useSettingsStore()
30 | const { commands } = useCommandsStore()
31 | const { elements, current, contextElementId } = useElementsStore()
32 |
33 | const data = {
34 | document: documentStore,
35 | files,
36 | graph,
37 | graphLock,
38 | layout,
39 | footer,
40 | history,
41 | loadingView,
42 | settings,
43 | commands,
44 | elements,
45 | current,
46 | contextElementId,
47 | }
48 |
49 | function download(){
50 | const element = document.createElement("a");
51 | const textFile = new Blob([JSON.stringify(data, null, 2)]);
52 | element.href = URL.createObjectURL(textFile);
53 | element.download = `${documentStore.title}-${Date.now()}-threed-studio.json`;
54 | document.body.appendChild(element);
55 | element.click();
56 | }
57 |
58 | return (
59 |
75 | )
76 | }
77 |
--------------------------------------------------------------------------------
/src/components/modal/ModalNewFile.tsx:
--------------------------------------------------------------------------------
1 | import { FormEvent, useEffect, useRef } from "react";
2 | import useModal from "../../hooks/useModal";
3 | import { nanoid } from 'nanoid';
4 | import { useLayoutStore } from "../../store/layout";
5 | import { useFilesStore } from "../../store/files";
6 |
7 | import {
8 | Dialog,
9 | DialogContent,
10 | DialogHeader,
11 | DialogTitle,
12 | } from "@/components/ui/dialog"
13 | import { Button } from "@/components/ui/button"
14 | import { Input } from "@/components/ui/input"
15 |
16 | export default function ModalNewFile() {
17 | const { isOpeneNewFileModal, closeNewFileModal } = useModal()
18 | const { setModalOpen } = useLayoutStore()
19 | const { updateFiles } = useFilesStore()
20 |
21 | const titleRef = useRef(null)
22 |
23 | useEffect(() => {
24 | setTimeout(() => {
25 | if(isOpeneNewFileModal && titleRef.current) {
26 | titleRef.current.focus()
27 | }
28 | }, 100)
29 | }, [isOpeneNewFileModal])
30 |
31 | function handleSubmit(event: FormEvent) {
32 | event.preventDefault()
33 | updateFiles([
34 | {
35 | id: nanoid(6),
36 | name: titleRef.current?.value.split('.')[0]+'.js',
37 | opened: true,
38 | selected: true,
39 | content: `// Welcome to ThreeD Studio Code!
40 | // Use sdk.help() to see more details.
41 |
42 | function onMount() {
43 | sdk.notifications.show({
44 | title: 'file started ID: ' + sdk.component.fileId,
45 | text: 'haha'
46 | })
47 | }
48 |
49 | function onClick() {
50 | sdk.notifications.show({
51 | title: 'event type: ' + sdk.component.type,
52 | text: 'haha'
53 | })
54 | }
55 |
56 | `
57 | }
58 | ])
59 |
60 | setModalOpen({ name: 'newFile', value: false })
61 |
62 | }
63 |
64 | return (
65 |
81 | )
82 | }
--------------------------------------------------------------------------------
/src/components/modal/ModalSearch.tsx:
--------------------------------------------------------------------------------
1 | import useModal from "@/hooks/useModal";
2 |
3 | import {
4 | CommandDialog,
5 | CommandEmpty,
6 | CommandGroup,
7 | CommandInput,
8 | CommandItem,
9 | CommandList,
10 | CommandSeparator,
11 | } from "@/components/ui/command"
12 |
13 | const ModalSearch = () => {
14 | const { isOpeneSearchModal, closeSearchModal, openSearchModal } = useModal()
15 |
16 | return (
17 |
18 |
19 |
20 | No results found.
21 |
22 | Create element
23 | Search Model
24 |
25 |
26 |
27 | Logout
28 | Settings
29 |
30 |
31 |
32 | );
33 | };
34 |
35 | export default ModalSearch;
--------------------------------------------------------------------------------
/src/components/properties/DocumentProps.tsx:
--------------------------------------------------------------------------------
1 | import { useDocumentStore } from '../../store/document';
2 | import { useControls, folder } from "leva";
3 |
4 | export default function DocumentProps() {
5 | const { document, updateDocument, updateDocumentOrbitalControls,
6 | updateDocumentCanvas } = useDocumentStore()
7 | const doc: {
8 | [key: string]: any
9 | } = document
10 |
11 | const mountObject = (arrlv: string[]) => arrlv.reduce(
12 | (obj, item) => Object.assign(obj, {
13 | [item]: {
14 | value: doc[item],
15 | onChange: (e: any) => updateDocument({ [item]: e })
16 | }
17 | }), {});
18 |
19 | const mountOrbitalControlsObject = (arrlv: string[]) => arrlv.reduce(
20 | (obj, item) => Object.assign(obj, {
21 | [item]: {
22 | value: doc.orbitalControls[item],
23 | onChange: (e: any) => updateDocumentOrbitalControls({ [item]: e })
24 | }
25 | }), {});
26 |
27 | const mountCanvasObject = (arrlv: string[]) => arrlv.reduce(
28 | (obj, item) => Object.assign(obj, {
29 | [item]: {
30 | value: doc.canvas[item],
31 | onChange: (e: any) => updateDocumentCanvas({ [item]: e })
32 | }
33 | }), {});
34 |
35 | useControls({
36 | Document: folder(mountObject([
37 | 'title',
38 | 'description',
39 | 'helpers',
40 | 'axes',
41 | ])),
42 | OrbitalControls: folder(mountOrbitalControlsObject([
43 | 'enableDamping',
44 | 'enablePan',
45 | 'enableRotate',
46 | 'enableZoom',
47 | 'reverseOrbit',
48 | 'makeDefault',
49 | 'enabled',
50 | 'minPolarAngle',
51 | 'screenSpacePanning',
52 | 'autoRotate',
53 | 'regress',
54 | ])),
55 | Canvas: folder({
56 | ...mountCanvasObject([
57 | 'shadows',
58 | 'frameloop',
59 | 'orthographic',
60 | 'dpr',
61 | 'legacy',
62 | 'linear',
63 | 'eventPrefix',
64 | 'flat',
65 | ]),
66 | // gl: {},
67 | // scene: {},
68 | // raycaster: {},
69 | // Camera: folder({
70 | // fov: document.canvas.camera.fov,
71 | // near: document.canvas.camera.near,
72 | // far: document.canvas.camera.far,
73 | // position: document.canvas.camera.position,
74 | // }),
75 | // Resize: folder({
76 | // scroll: document.canvas.resize.scroll,
77 | // Debounce: folder({
78 | // scroll: document.canvas.resize.debounce.scroll,
79 | // resize: document.canvas.resize.debounce.resize
80 | // })
81 | // }),
82 | }),
83 | })
84 |
85 | return
86 | }
87 |
--------------------------------------------------------------------------------
/src/components/resources/AudioPicker.tsx:
--------------------------------------------------------------------------------
1 | import CONSTANTS from "@/constants"
2 | import { useElementsStore } from "@/store/elements"
3 | import { IconMusic } from "@tabler/icons-react"
4 |
5 | export default function AudioPicker() {
6 | const { current, updateElement } = useElementsStore()
7 |
8 | const handleChange = (value:string) => {
9 | updateElement({
10 | id: current.id,
11 | soundUrl: value
12 | })
13 | }
14 |
15 | if(!current || !current.soundUrl) {
16 | return null
17 | }
18 |
19 | return (
20 |
21 |
22 | {CONSTANTS.sounds.map((item:string) => (
23 |
24 |
handleChange(item)}
26 | style={{
27 | borderColor: current.soundUrl !== item ?'#1d283a':'white'
28 | }}>
29 |
30 |
31 |
32 |
33 |
{item}
34 |
35 |
36 | ))}
37 |
38 |
39 | )
40 | }
--------------------------------------------------------------------------------
/src/components/resources/CodePicker.tsx:
--------------------------------------------------------------------------------
1 | import { useElementsStore } from "@/store/elements"
2 | import { useFilesStore } from "@/store/files"
3 | import { IconFile, IconFileOff } from "@tabler/icons-react"
4 |
5 | export default function CodePicker() {
6 | const { current, updateElement } = useElementsStore()
7 | const { files } = useFilesStore()
8 |
9 | const handleChange = (value:string) => {
10 | updateElement({
11 | id: current.id,
12 | fileId: value
13 | })
14 | }
15 |
16 | if(!current) {
17 | return null
18 | }
19 |
20 | return (
21 |
22 |
23 |
24 |
handleChange('')}
26 | style={{
27 | borderColor: current.fileId !== '' ?'#1d283a':'white'
28 | }}>
29 |
30 |
31 |
32 |
none
33 |
34 |
35 | {files.map((item) => (
36 |
37 |
handleChange(item.id)}
39 | style={{
40 | borderColor: current.fileId !== item.id ?'#1d283a':'white'
41 | }}>
42 |
43 |
44 |
45 |
46 |
{item.name}
47 |
48 |
49 | ))}
50 |
51 |
52 | )
53 | }
--------------------------------------------------------------------------------
/src/components/resources/EnvironmentPicker.tsx:
--------------------------------------------------------------------------------
1 | import CONSTANTS from "@/constants"
2 | import { useElementsStore } from "@/store/elements"
3 |
4 | export default function EnvironmentPicker() {
5 | const { current, updateElement } = useElementsStore()
6 |
7 | const handleChange = (value:string) => {
8 | updateElement({
9 | id: current.id,
10 | environment: value
11 | })
12 | }
13 |
14 | if(!current || !current.environment) {
15 | return null
16 | }
17 |
18 | return (
19 |
20 |
21 | {CONSTANTS.environment.map((item:string) => (
22 |
23 |
handleChange(item)}
25 | style={{
26 | borderColor: current.environment !== item ?'#1d283a':'white'
27 | }}>
28 |

29 |
30 |
{item}
31 |
32 |
33 | ))}
34 |
35 |
36 | )
37 | }
--------------------------------------------------------------------------------
/src/components/resources/ImagePicker.tsx:
--------------------------------------------------------------------------------
1 | import CONSTANTS from "@/constants"
2 | import { useElementsStore } from "@/store/elements"
3 |
4 | const images = [
5 | 'react-threejs.png',
6 | 'threejs-react.png',
7 | ]
8 |
9 | export default function ImagePicker() {
10 | const { current, updateElement } = useElementsStore()
11 |
12 | const handleChange = (value:string) => {
13 | updateElement({
14 | id: current.id,
15 | file: value
16 | })
17 | }
18 |
19 | if(!current || !current.file) {
20 | return null
21 | }
22 |
23 | return (
24 |
25 |
26 | {images.map((item:string) => (
27 |
28 |
handleChange(item)}
30 | style={{
31 | borderColor: current.file !== item ?'#1d283a':'white'
32 | }}>
33 |

34 |
35 |
{item}
36 |
37 |
38 | ))}
39 |
40 |
41 | )
42 | }
--------------------------------------------------------------------------------
/src/components/resources/MaterialPicker.tsx:
--------------------------------------------------------------------------------
1 | import CONSTANTS from "@/constants"
2 | import { useElementsStore } from "@/store/elements"
3 |
4 | export default function MaterialPicker() {
5 | const { current, updateElement } = useElementsStore()
6 |
7 | const handleChange = (value:string) => {
8 | updateElement({
9 | id: current.id,
10 | material_type: value
11 | })
12 | }
13 |
14 | const isDisable = (value: string) => JSON.stringify(CONSTANTS.materials[value]) === '{}'
15 |
16 | if(!current || !current.material_type) {
17 | return null
18 | }
19 |
20 | return (
21 |
22 |
23 | {Object.keys(CONSTANTS.materials).filter((itm) => !isDisable(itm)).map((item:string) => (
24 |
25 |
handleChange(item)}
27 | style={{
28 | borderColor: current.material_type !== item ?'#1d283a':'white'
29 | }}>
30 |

31 |
{item}
32 |
33 |
34 | ))}
35 |
36 |
37 | )
38 | }
--------------------------------------------------------------------------------
/src/components/resources/Resources.tsx:
--------------------------------------------------------------------------------
1 | import MaterialPicker from "./MaterialPicker";
2 | import TexturePicker from "./TexturePicker";
3 | import CodePicker from "./CodePicker";
4 | import EnvironmentPicker from "./EnvironmentPicker";
5 | import ImagePicker from "./ImagePicker";
6 | import AudioPicker from "./AudioPicker";
7 | import { useSettingsStore } from "@/store/settings";
8 | import { Button } from "../ui/button";
9 | import { IconArrowLeft } from "@tabler/icons-react";
10 |
11 | export default function Resources() {
12 | const {settings, setPicker} = useSettingsStore()
13 |
14 | if(settings.picker === '') {
15 | return null
16 | }
17 |
18 | return (
19 |
20 |
21 |
24 |
{settings.picker}
25 |
26 | {(settings.picker === 'material') &&
}
27 | {(settings.picker === 'texture') &&
}
28 | {(settings.picker === 'code') &&
}
29 | {(settings.picker === 'environment') &&
}
30 | {(settings.picker === 'image') &&
}
31 | {(settings.picker === 'audio') &&
}
32 |
33 | )
34 | }
--------------------------------------------------------------------------------
/src/components/resources/TexturePicker.tsx:
--------------------------------------------------------------------------------
1 | import CONSTANTS from "@/constants"
2 | import { useElementsStore } from "@/store/elements"
3 |
4 | export default function TexturePicker() {
5 | const { current, updateElement } = useElementsStore()
6 |
7 | const handleChange = (value:string) => {
8 | updateElement({
9 | id: current.id,
10 | texture: value
11 | })
12 | }
13 |
14 | if(!current || !current.texture) {
15 | return null
16 | }
17 |
18 | return (
19 |
20 |
21 |
22 |
handleChange('none')}
24 | style={{
25 | borderColor: current.texture !== 'none' ?'#1d283a':'white'
26 | }}>
27 |
28 |
none
29 |
30 |
31 | {CONSTANTS.textures.map((item:string) => (
32 |
33 |
handleChange(item)}
35 | style={{
36 | borderColor: current.texture !== item ?'#1d283a':'white'
37 | }}>
38 |

39 |
40 |
{item}
41 |
42 |
43 | ))}
44 |
45 |
46 | )
47 | }
--------------------------------------------------------------------------------
/src/components/screens/CodeEditor.tsx:
--------------------------------------------------------------------------------
1 | import { Editor, useMonaco } from '@monaco-editor/react'
2 | import { IconX } from '@tabler/icons-react';
3 | import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
4 |
5 | import getEditorLang from '../../utils/getEditorLang'
6 | import useCodeEditor from '../../hooks/useCodeEditor'
7 | import { useCommandsStore } from '../../store/commands'
8 | import { Button } from '@/components/ui/button'
9 |
10 | function EmptyScreen() {
11 | const { commands } = useCommandsStore()
12 |
13 | return (
14 |
15 | {commands.map((item, index) => (
16 |
17 |
{item.label}
18 |
19 | {item.data.map((subitem, subindex) => (
20 |
21 | {subindex === 0 ? '' : ' + '}
22 | {subitem}
23 |
24 | ))}
25 |
26 |
27 | ))}
28 |
29 | )
30 | }
31 |
32 | export default function CodeEditor() {
33 | const {
34 | openedObjects,
35 | closeFile,
36 | updateCode,
37 | openFile
38 | } = useCodeEditor()
39 |
40 | const monaco = useMonaco()
41 | monaco?.editor.defineTheme('my-theme', {
42 | base: 'vs-dark',
43 | inherit: true,
44 | rules: [],
45 | colors: {
46 | 'editor.background': '#000000',
47 | 'minimap.background': '#060606',
48 | },
49 | });
50 |
51 |
52 | if(openedObjects.length < 1) {
53 | return
54 | }
55 |
56 | return (
57 |
58 |
item.selected)[0]?.id} className="h-full bg-transparent">
59 |
60 | {openedObjects.map((item) => (
61 | item.selected)[0]?.id ? 'border' : ''}`}>
62 | <>
63 | openFile(item.id)}>{item.name}
64 |
71 | >
72 |
73 | ))}
74 |
75 | {openedObjects.map((item) => (
76 |
77 | updateCode(item.id, e)}
82 | />
83 |
84 | ))}
85 |
86 |
87 | )
88 | }
--------------------------------------------------------------------------------
/src/components/screens/Mesa.tsx:
--------------------------------------------------------------------------------
1 | import { Canvas } from '@react-three/fiber'
2 |
3 | import EditorControls from '../general/EditorControls'
4 | import ElementsRender from '../general/ElementsRender'
5 | import CameraControls from '../general/CameraControls'
6 | import Helpers from '../general/Helpers'
7 | import { useDocumentStore } from '../../store/document'
8 | import { useElementsStore } from '../../store/elements'
9 | import { useSettingsStore } from '@/store/settings'
10 |
11 | export default function Mesa() {
12 | const { document } = useDocumentStore()
13 | const { setCurrent } = useElementsStore()
14 | const { loadingView } = useSettingsStore()
15 |
16 | const canvas = document.canvas
17 | const onPointerMissed = () => setCurrent(null)
18 |
19 | return (
20 |
28 | )
29 | }
30 |
--------------------------------------------------------------------------------
/src/components/toolbar/ToolbarBottom.tsx:
--------------------------------------------------------------------------------
1 | import Actions from '../general/Actions'
2 | import useToolbar from '../../hooks/useToolbar'
3 | import mountToolbarActions from '../../utils/mountToolbarActions'
4 | import CONSTANTS from '../../constants'
5 | import { useElementsStore } from '../../store/elements'
6 | import { useLayoutStore } from '../../store/layout'
7 | import { Badge } from "@/components/ui/badge"
8 | export default function ToolbarBottom() {
9 | const { showHorizontalBottom } = useToolbar()
10 | const { current } = useElementsStore()
11 | const { footer, setFooter } = useLayoutStore()
12 |
13 | const toolbarHorizontalBottom = mountToolbarActions(
14 | CONSTANTS.toolbar.bottom,
15 | {
16 | log: { onClick: () => setFooter(footer === 'log' ? null : 'log') },
17 | previus_change: { onClick: null },
18 | next_change: { onClick: null },
19 | studio: { onClick: () => setFooter(footer === 'studio' ? null : 'studio') },
20 | })
21 |
22 | if(!showHorizontalBottom) {
23 | return
24 | }
25 |
26 | return (
27 | <>
28 |
29 | <>
30 |
31 |
32 |
33 |
34 | {current
35 | ? (
36 | <>
37 |
38 | {current.id}
39 |
40 |
41 | {current.type}
42 |
43 | >
44 | )
45 | : 'No item selected'}
46 |
47 |
48 | >
49 |
50 | >
51 | );
52 | }
53 |
--------------------------------------------------------------------------------
/src/components/toolbar/ToolbarRight.tsx:
--------------------------------------------------------------------------------
1 | import Actions from '../general/Actions'
2 | import Properties from '../window/Properties'
3 | import Layers from '../window/Layers'
4 | import History from '../window/History'
5 | import useToolbar from '../../hooks/useToolbar'
6 | import { useState } from 'react'
7 | import mountToolbarActions from '../../utils/mountToolbarActions'
8 | import CONSTANTS from '../../constants'
9 |
10 | import {
11 | Accordion,
12 | AccordionContent,
13 | AccordionItem,
14 | AccordionTrigger,
15 | } from "@/components/ui/accordion"
16 |
17 |
18 | import Icon from '../general/Icon'
19 |
20 | export default function ToolbarRight() {
21 | const [opened, setOpen] = useState('0')
22 |
23 | const {
24 | toggleOpenRight,
25 | showVerticalRight,
26 | isRightOpen
27 | } = useToolbar()
28 |
29 | const setOpenNumber = (value: number) => {
30 | toggleOpenRight();
31 | setOpen(String(value));
32 | }
33 |
34 | const toolbarVerticalRight = mountToolbarActions(
35 | CONSTANTS.toolbar.right,
36 | {
37 | properties: {
38 | onClick: () => setOpenNumber(0),
39 | component:
40 | },
41 | layers: {
42 | onClick: () => setOpenNumber(1),
43 | component:
44 | },
45 | history: {
46 | onClick: () => setOpenNumber(2),
47 | component:
48 | },
49 | }
50 | )
51 |
52 | if(!showVerticalRight) {
53 | return
54 | }
55 |
56 | return (
57 |
58 | {!isRightOpen ? (
59 |
60 | ) : (
61 |
62 |
63 |
64 | {toolbarVerticalRight.map((item, index:number) => (
65 |
66 | {
69 | if(String(index)===opened) {
70 | toggleOpenRight()
71 | } else {
72 | setOpen(String(index))
73 | }
74 | }}
75 | >
76 |
77 |
78 | {item.label}
79 |
80 |
81 |
82 |
83 | {item.component}
84 |
85 |
86 |
87 | ))}
88 |
89 |
90 | )}
91 |
92 | )
93 | }
94 |
--------------------------------------------------------------------------------
/src/components/ui/accordion.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as AccordionPrimitive from "@radix-ui/react-accordion"
5 | import { ChevronDown } from "lucide-react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const Accordion = AccordionPrimitive.Root
10 |
11 | const AccordionItem = React.forwardRef<
12 | React.ElementRef,
13 | React.ComponentPropsWithoutRef
14 | >(({ className, ...props }, ref) => (
15 |
20 | ))
21 | AccordionItem.displayName = "AccordionItem"
22 |
23 | const AccordionTrigger = React.forwardRef<
24 | React.ElementRef,
25 | React.ComponentPropsWithoutRef
26 | >(({ className, children, ...props }, ref) => (
27 |
28 | svg]:rotate-180",
32 | className
33 | )}
34 | {...props}
35 | >
36 | {children}
37 |
38 |
39 |
40 | ))
41 | AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
42 |
43 | const AccordionContent = React.forwardRef<
44 | React.ElementRef,
45 | React.ComponentPropsWithoutRef
46 | >(({ className, children, ...props }, ref) => (
47 |
55 | {children}
56 |
57 | ))
58 | AccordionContent.displayName = AccordionPrimitive.Content.displayName
59 |
60 | export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
61 |
--------------------------------------------------------------------------------
/src/components/ui/alert.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { cva, type VariantProps } from "class-variance-authority"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const alertVariants = cva(
7 | "relative w-full rounded-lg border p-4 [&>svg]:absolute [&>svg]:text-foreground [&>svg]:left-4 [&>svg]:top-4 [&>svg+div]:translate-y-[-3px] [&:has(svg)]:pl-11",
8 | {
9 | variants: {
10 | variant: {
11 | default: "bg-background text-foreground",
12 | destructive:
13 | "text-destructive border-destructive/50 dark:border-destructive [&>svg]:text-destructive text-destructive",
14 | },
15 | },
16 | defaultVariants: {
17 | variant: "default",
18 | },
19 | }
20 | )
21 |
22 | const Alert = React.forwardRef<
23 | HTMLDivElement,
24 | React.HTMLAttributes & VariantProps
25 | >(({ className, variant, ...props }, ref) => (
26 |
32 | ))
33 | Alert.displayName = "Alert"
34 |
35 | const AlertTitle = React.forwardRef<
36 | HTMLParagraphElement,
37 | React.HTMLAttributes
38 | >(({ className, ...props }, ref) => (
39 |
44 | ))
45 | AlertTitle.displayName = "AlertTitle"
46 |
47 | const AlertDescription = React.forwardRef<
48 | HTMLParagraphElement,
49 | React.HTMLAttributes
50 | >(({ className, ...props }, ref) => (
51 |
56 | ))
57 | AlertDescription.displayName = "AlertDescription"
58 |
59 | export { Alert, AlertTitle, AlertDescription }
60 |
--------------------------------------------------------------------------------
/src/components/ui/badge.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { cva, type VariantProps } from "class-variance-authority"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const badgeVariants = cva(
7 | "inline-flex items-center border rounded-full px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
8 | {
9 | variants: {
10 | variant: {
11 | default:
12 | "bg-primary hover:bg-primary/80 border-transparent text-primary-foreground",
13 | secondary:
14 | "bg-secondary hover:bg-secondary/80 border-transparent text-secondary-foreground",
15 | destructive:
16 | "bg-destructive hover:bg-destructive/80 border-transparent text-destructive-foreground",
17 | outline: "text-foreground",
18 | },
19 | },
20 | defaultVariants: {
21 | variant: "default",
22 | },
23 | }
24 | )
25 |
26 | export interface BadgeProps
27 | extends React.HTMLAttributes,
28 | VariantProps {}
29 |
30 | function Badge({ className, variant, ...props }: BadgeProps) {
31 | return (
32 |
33 | )
34 | }
35 |
36 | export { Badge, badgeVariants }
37 |
--------------------------------------------------------------------------------
/src/components/ui/button.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { Slot } from "@radix-ui/react-slot"
3 | import { cva, type VariantProps } from "class-variance-authority"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const buttonVariants = cva(
8 | "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background",
9 | {
10 | variants: {
11 | variant: {
12 | default: "bg-primary text-primary-foreground hover:bg-primary/90",
13 | destructive:
14 | "bg-destructive text-destructive-foreground hover:bg-destructive/90",
15 | outline:
16 | "border border-input hover:bg-accent hover:text-accent-foreground",
17 | secondary:
18 | "bg-secondary text-secondary-foreground hover:bg-secondary/80",
19 | ghost: "hover:bg-accent hover:text-accent-foreground",
20 | link: "underline-offset-4 hover:underline text-primary",
21 | },
22 | size: {
23 | default: "h-10 py-2 px-4",
24 | sm: "h-9 px-3 rounded-md",
25 | lg: "h-11 px-8 rounded-md",
26 | },
27 | },
28 | defaultVariants: {
29 | variant: "default",
30 | size: "default",
31 | },
32 | }
33 | )
34 |
35 | export interface ButtonProps
36 | extends React.ButtonHTMLAttributes,
37 | VariantProps {
38 | asChild?: boolean
39 | }
40 |
41 | const Button = React.forwardRef(
42 | ({ className, variant, size, asChild = false, ...props }, ref) => {
43 | const Comp = asChild ? Slot : "button"
44 | return (
45 |
50 | )
51 | }
52 | )
53 | Button.displayName = "Button"
54 |
55 | export { Button, buttonVariants }
56 |
--------------------------------------------------------------------------------
/src/components/ui/checkbox.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
5 | import { Check } from "lucide-react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const Checkbox = React.forwardRef<
10 | React.ElementRef,
11 | React.ComponentPropsWithoutRef
12 | >(({ className, ...props }, ref) => (
13 |
21 |
24 |
25 |
26 |
27 | ))
28 | Checkbox.displayName = CheckboxPrimitive.Root.displayName
29 |
30 | export { Checkbox }
31 |
--------------------------------------------------------------------------------
/src/components/ui/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | export type InputProps = React.InputHTMLAttributes
6 |
7 | const Input = React.forwardRef(
8 | ({ className, type, ...props }, ref) => {
9 | return (
10 |
19 | )
20 | }
21 | )
22 | Input.displayName = "Input"
23 |
24 | export { Input }
25 |
--------------------------------------------------------------------------------
/src/components/ui/label.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as LabelPrimitive from "@radix-ui/react-label"
5 | import { cva, type VariantProps } from "class-variance-authority"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const labelVariants = cva(
10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
11 | )
12 |
13 | const Label = React.forwardRef<
14 | React.ElementRef,
15 | React.ComponentPropsWithoutRef &
16 | VariantProps
17 | >(({ className, ...props }, ref) => (
18 |
23 | ))
24 | Label.displayName = LabelPrimitive.Root.displayName
25 |
26 | export { Label }
27 |
--------------------------------------------------------------------------------
/src/components/ui/popover.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as PopoverPrimitive from "@radix-ui/react-popover"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Popover = PopoverPrimitive.Root
9 |
10 | const PopoverTrigger = PopoverPrimitive.Trigger
11 |
12 | const PopoverContent = React.forwardRef<
13 | React.ElementRef,
14 | React.ComponentPropsWithoutRef
15 | >(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
16 |
17 |
27 |
28 | ))
29 | PopoverContent.displayName = PopoverPrimitive.Content.displayName
30 |
31 | export { Popover, PopoverTrigger, PopoverContent }
32 |
--------------------------------------------------------------------------------
/src/components/ui/scroll-area.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const ScrollArea = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, children, ...props }, ref) => (
12 |
17 |
18 | {children}
19 |
20 |
21 |
22 |
23 | ))
24 | ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
25 |
26 | const ScrollBar = React.forwardRef<
27 | React.ElementRef,
28 | React.ComponentPropsWithoutRef
29 | >(({ className, orientation = "vertical", ...props }, ref) => (
30 |
43 |
44 |
45 | ))
46 | ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
47 |
48 | export { ScrollArea, ScrollBar }
49 |
--------------------------------------------------------------------------------
/src/components/ui/slider.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SliderPrimitive from "@radix-ui/react-slider"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Slider = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, ...props }, ref) => (
12 |
20 |
21 |
22 |
23 |
24 |
25 | ))
26 | Slider.displayName = SliderPrimitive.Root.displayName
27 |
28 | export { Slider }
29 |
--------------------------------------------------------------------------------
/src/components/ui/switch.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SwitchPrimitives from "@radix-ui/react-switch"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Switch = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, ...props }, ref) => (
12 |
20 |
25 |
26 | ))
27 | Switch.displayName = SwitchPrimitives.Root.displayName
28 |
29 | export { Switch }
30 |
--------------------------------------------------------------------------------
/src/components/ui/tabs.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as TabsPrimitive from "@radix-ui/react-tabs"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Tabs = TabsPrimitive.Root
9 |
10 | const TabsList = React.forwardRef<
11 | React.ElementRef,
12 | React.ComponentPropsWithoutRef
13 | >(({ className, ...props }, ref) => (
14 |
22 | ))
23 | TabsList.displayName = TabsPrimitive.List.displayName
24 |
25 | const TabsTrigger = React.forwardRef<
26 | React.ElementRef,
27 | React.ComponentPropsWithoutRef
28 | >(({ className, ...props }, ref) => (
29 |
37 | ))
38 | TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
39 |
40 | const TabsContent = React.forwardRef<
41 | React.ElementRef,
42 | React.ComponentPropsWithoutRef
43 | >(({ className, ...props }, ref) => (
44 |
52 | ))
53 | TabsContent.displayName = TabsPrimitive.Content.displayName
54 |
55 | export { Tabs, TabsList, TabsTrigger, TabsContent }
56 |
--------------------------------------------------------------------------------
/src/components/ui/textarea.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | export interface TextareaProps
6 | extends React.TextareaHTMLAttributes {}
7 |
8 | const Textarea = React.forwardRef(
9 | ({ className, ...props }, ref) => {
10 | return (
11 |
19 | )
20 | }
21 | )
22 | Textarea.displayName = "Textarea"
23 |
24 | export { Textarea }
25 |
--------------------------------------------------------------------------------
/src/components/ui/tooltip.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as TooltipPrimitive from "@radix-ui/react-tooltip"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const TooltipProvider = TooltipPrimitive.Provider
9 |
10 | const Tooltip = TooltipPrimitive.Root
11 |
12 | const TooltipTrigger = TooltipPrimitive.Trigger
13 |
14 | const TooltipContent = React.forwardRef<
15 | React.ElementRef,
16 | React.ComponentPropsWithoutRef
17 | >(({ className, sideOffset = 4, ...props }, ref) => (
18 |
27 | ))
28 | TooltipContent.displayName = TooltipPrimitive.Content.displayName
29 |
30 | export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
31 |
--------------------------------------------------------------------------------
/src/components/window/History.tsx:
--------------------------------------------------------------------------------
1 | import { useHistoryStore } from "../../store/history";
2 | import CodeEditor from '@uiw/react-textarea-code-editor';
3 |
4 | // import getObjectDiff, { applyDiffChanges, removeDiffChange } from "../../utils/getObjectDiff";
5 |
6 | // const difference = getObjectDiff(lhs, rhs);
7 | // const withChange = applyDiffChanges(lhs, difference);
8 | // const withoutChange = removeDiffChange(lhs, difference);
9 |
10 | export default function History() {
11 | const { history } = useHistoryStore()
12 |
13 | if(!history.length) {
14 | return (
15 |
16 |
17 | No change identified
18 |
19 |
20 | )
21 | }
22 | return (
23 |
24 |
30 |
31 | )
32 | }
--------------------------------------------------------------------------------
/src/components/window/Layers.tsx:
--------------------------------------------------------------------------------
1 | import { IconCopyOff } from '@tabler/icons-react'
2 | import { useElementsStore } from '../../store/elements'
3 | import elementsBase from '@/elements'
4 |
5 | import {
6 | Tooltip,
7 | TooltipContent,
8 | TooltipTrigger,
9 | } from "@/components/ui/tooltip"
10 | import Icon from '../general/Icon'
11 | import { Button } from '@/components/ui/button'
12 |
13 | function TooltipCopy() {
14 | return (
15 |
16 |
17 |
18 |
19 |
20 | Unique element cannot be duplicated
21 |
22 |
23 | )
24 | }
25 |
26 | export default function Layers() {
27 | const { elements, current, setCurrent } = useElementsStore()
28 |
29 | return (
30 | <>
31 | {elements.map((element, index: number) => (
32 |
55 | ))}
56 | >
57 | )
58 | }
--------------------------------------------------------------------------------
/src/components/window/LogContainer.tsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react'
2 | import { Console, Hook, Unhook } from 'console-feed'
3 | import { GenericType } from '@/types'
4 |
5 | export default function LogContainer() {
6 | const [logs, setLogs]: GenericType = useState([])
7 |
8 | useEffect((): () => void => {
9 | const hookedConsole = Hook(
10 | window.console,
11 | (log) => setLogs((currLogs: GenericType) => [...currLogs, log]),
12 | false
13 | )
14 | console.log('Welcome to log section')
15 | return () => Unhook(hookedConsole)
16 | }, [])
17 |
18 | return (
19 |
20 |
21 |
22 | )
23 | }
24 |
--------------------------------------------------------------------------------
/src/components/window/MediaMaker.tsx:
--------------------------------------------------------------------------------
1 | import { useHotkeys, useMouse } from "@mantine/hooks";
2 | import { useEffect, useState } from "react";
3 | import { useInterval } from "@mantine/hooks";
4 |
5 | const TIMES = 24
6 |
7 | export default function MediaMaker() {
8 | const { ref, x } = useMouse();
9 |
10 | const [seconds, setSeconds] = useState(0);
11 | const interval = useInterval(() => setSeconds((s) => s + 1), 10);
12 |
13 | useEffect(() => {
14 | if(seconds > 60*TIMES) {
15 | setSeconds(0)
16 | }
17 | }, [seconds])
18 |
19 |
20 | useHotkeys([
21 | ['Space', (event) => {
22 | event.preventDefault();
23 | interval.toggle()
24 | }],
25 | ]);
26 |
27 | return (
28 | setSeconds(x)}
32 | >
33 |
34 | {Array.from(Array(TIMES).keys()).map((item) => (
35 |
36 | {item+1}s
37 |
38 | ))}
39 |
40 |
46 | {seconds}
47 |
48 | )
49 | }
--------------------------------------------------------------------------------
/src/components/window/ProjectFiles.tsx:
--------------------------------------------------------------------------------
1 | import TreeView, { flattenTree } from "react-accessible-treeview";
2 | import { IconPlus } from "@tabler/icons-react";
3 | import useModal from "../../hooks/useModal";
4 | import useProjectFiles from "../../hooks/useProjectFiles";
5 | import FolderIcon from "../general/FolderIcon";
6 | import FileIcon from "../general/FileIcon";
7 | import "../../styles/SidebarCode.css";
8 | import { useDocumentStore } from "../../store/document";
9 | import { useFilesStore } from "../../store/files";
10 |
11 | import { Button } from "@/components/ui/button";
12 |
13 | export default function ProjectFiles() {
14 | const { openNewFileModal } = useModal()
15 | const { openFile } = useProjectFiles()
16 | const { document } = useDocumentStore()
17 | const { files } = useFilesStore()
18 |
19 | const data = flattenTree({
20 | name: "",
21 | children: [
22 | {
23 | name: document.title,
24 | children: files,
25 | },
26 | ]
27 | });
28 |
29 | const selectedFile = files.find(({selected}) => selected)
30 |
31 | return (
32 |
33 |
34 |
35 |
e.element.name.includes('.') && openFile(String(e.element.id))}
41 | nodeRenderer={({
42 | element,
43 | isBranch,
44 | isExpanded,
45 | getNodeProps,
46 | level,
47 | }) => (
48 |
49 | {isBranch || !element.name.includes('.') ? (
50 |
51 | ) : (
52 |
53 | )}
54 |
55 |
56 | {element.name}
57 |
58 |
59 | )}
60 | />
61 |
62 |
63 |
66 |
67 |
68 | );
69 | }
70 |
--------------------------------------------------------------------------------
/src/components/window/Properties.tsx:
--------------------------------------------------------------------------------
1 | // import PropertiesForm from '@/components/form/PropertiesForm';
2 | // import { useDocumentStore } from '@/store/document';
3 | // import React from 'react';
4 | import { useElementsStore } from '@/store/elements';
5 | // import { ElementType } from '@/types';
6 | // import { Leva, useControls } from "leva";
7 | import DocumentProps from '@/components/properties/DocumentProps';
8 | import { elementControls } from '@/elements';
9 | import Resources from '../resources/Resources';
10 | import { useSettingsStore } from '@/store/settings';
11 | import { useEffect } from 'react';
12 | import { Leva } from 'leva';
13 |
14 | function CurrentProperties() {
15 | const { current } = useElementsStore()
16 | const CurrentPropsComponent = elementControls[current.type]
17 | return
18 | }
19 |
20 | export default function Properties() {
21 | // const { document } = useDocumentStore()
22 | const { current } = useElementsStore()
23 | const {settings, setPicker} = useSettingsStore()
24 |
25 | useEffect(() => {
26 | if(!current) {
27 | setPicker('')
28 | }
29 | }, [current])
30 |
31 | if(!current) {
32 | return (
33 | <>
34 |
35 |
36 |
37 |
38 | >
39 | )
40 | // return (
41 | //
45 | // )
46 | }
47 |
48 | if(settings.picker !== '') {
49 | return (
50 |
51 | )
52 | }
53 |
54 | return (
55 | <>
56 |
57 |
58 |
59 |
60 | >
61 | )
62 |
63 | // const {
64 | // eventObject,
65 | // object,
66 | // id,
67 | // type,
68 | // editable,
69 | // ...restOfCurrent
70 | // } = current
71 |
72 | // return (
73 | //
102 | // )
103 | }
--------------------------------------------------------------------------------
/src/constants/commands.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "label": "Show All Commands",
4 | "data": ["⇧","⌘","P"]
5 | },
6 | {
7 | "label": "Access the Files",
8 | "data": ["⌘","P"]
9 | },
10 | {
11 | "label": "Find in Archives",
12 | "data": ["⇧","⌘","F"]
13 | },
14 | {
15 | "label": "Start Debugging",
16 | "data": ["F5"]
17 | },
18 | {
19 | "label": "Enable/Disable Terminal",
20 | "data": ["ˆ","`"]
21 | }
22 | ]
23 |
--------------------------------------------------------------------------------
/src/constants/document.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "",
3 | "description": "",
4 | "helpers": true,
5 | "axes": false,
6 | "customModels": [],
7 | "orbitalControls": {
8 | "enableDamping": true,
9 | "enablePan": true,
10 | "enableRotate": true,
11 | "enableZoom": true,
12 | "reverseOrbit": false,
13 | "makeDefault": true,
14 | "enabled": true,
15 | "minPolarAngle": 0,
16 | "screenSpacePanning": false,
17 | "autoRotate": false,
18 | "regress": false
19 | },
20 | "canvas": {
21 | "gl": {},
22 | "camera": {
23 | "fov": 75,
24 | "near": 0.1,
25 | "far": 1000,
26 | "position": [8, 8.5, 8]
27 | },
28 | "scene": {},
29 | "shadows": false,
30 | "raycaster": {},
31 | "frameloop": "always",
32 | "resize": {
33 | "scroll": true,
34 | "debounce": {
35 | "scroll": 50,
36 | "resize": 0
37 | }
38 | },
39 | "orthographic": false,
40 | "dpr": [1, 2],
41 | "legacy": false,
42 | "linear": false,
43 | "eventPrefix": "offset",
44 | "flat": false
45 | }
46 | }
--------------------------------------------------------------------------------
/src/constants/environment.json:
--------------------------------------------------------------------------------
1 |
2 | [
3 | "apartment",
4 | "bridge",
5 | "city",
6 | "dawn",
7 | "esplanade",
8 | "forest",
9 | "hall",
10 | "lab",
11 | "lobby",
12 | "night",
13 | "park",
14 | "sky",
15 | "studio",
16 | "sunrise",
17 | "sunset",
18 | "venice",
19 | "warehouse",
20 | "workshop"
21 | ]
--------------------------------------------------------------------------------
/src/constants/general.json:
--------------------------------------------------------------------------------
1 | {
2 | "cdn": "https://cdn-threed.vercel.app"
3 | }
--------------------------------------------------------------------------------
/src/constants/layout.json:
--------------------------------------------------------------------------------
1 | {
2 | "modal": {
3 | "open": {
4 | "settings": false,
5 | "hub": false,
6 | "newDocument": false,
7 | "export": false,
8 | "commands": false,
9 | "terms": false,
10 | "codeEditor": false,
11 | "newFile": false,
12 | "search": false
13 | }
14 | },
15 | "toolbar": {
16 | "top": {
17 | "visible": true,
18 | "expanded": false
19 | },
20 | "right": {
21 | "visible": true,
22 | "expanded": false
23 | },
24 | "bottom": {
25 | "visible": true,
26 | "expanded": false
27 | },
28 | "left": {
29 | "visible": true,
30 | "expanded": true
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/constants/light.json:
--------------------------------------------------------------------------------
1 | {
2 | "color": "#ffffff",
3 | "intensity": 1.0,
4 | "isLight": true
5 | }
--------------------------------------------------------------------------------
/src/constants/material.json:
--------------------------------------------------------------------------------
1 | {
2 | "material_type": "meshNormalMaterial",
3 | "material": "meshNormalMaterial"
4 | }
--------------------------------------------------------------------------------
/src/constants/materials.json:
--------------------------------------------------------------------------------
1 | {
2 | "meshNormalMaterial": {
3 | "color": "#ffffff"
4 | },
5 | "meshDistortMaterial": {},
6 | "meshReflectorMaterial": {
7 | "blur": [0, 0],
8 | "mixBlur": 0,
9 | "mixStrength": 1,
10 | "mixContrast": 1,
11 | "resolution": 256,
12 | "mirror": 0,
13 | "depthScale": 0,
14 | "minDepthThreshold": 0.9,
15 | "maxDepthThreshold": 1,
16 | "depthToBlurRatioBias": 0.25,
17 | "distortion": 1,
18 | "distortionMap": null,
19 | "debug": 0,
20 | "reflectorOffset": 0.2,
21 | "roughness": 0,
22 | "color": "#fafafa",
23 | "metalness": 0.5
24 | },
25 | "meshRefractionMaterial": {},
26 | "meshTransmissionMaterial": {},
27 | "meshWobbleMaterial": {},
28 | "meshDiscardMaterial": {},
29 | "pointMaterial": {},
30 | "shaderMaterial": {},
31 | "lineBasicMaterial": {},
32 | "lineDashedMaterial": {},
33 | "material": {},
34 | "meshBasicMaterial": {
35 | "aoMapIntensity": 1,
36 | "color": "#ffffff",
37 | "fog": true,
38 | "lightMapIntensity": 1,
39 | "reflectivity": 1,
40 | "refractionRatio": 0.98,
41 | "wireframe": false,
42 | "wireframeLinecap": "round",
43 | "wireframeLinejoin": "round",
44 | "wireframeLinewidth": 1
45 | },
46 | "meshDepthMaterial": {},
47 | "meshDistanceMaterial": {},
48 | "meshLambertMaterial": {},
49 | "meshMatcapMaterial": {},
50 | "meshPhongMaterial": {},
51 | "meshPhysicalMaterial": {},
52 | "meshStandardMaterial": {
53 | "color": "#ffffff"
54 | },
55 | "meshToonMaterial": {},
56 | "pointsMaterial": {},
57 | "rawShaderMaterial": {},
58 | "shadowMaterial": {},
59 | "spriteMaterial": {}
60 | }
61 |
--------------------------------------------------------------------------------
/src/constants/object.json:
--------------------------------------------------------------------------------
1 | {
2 | "castShadow": false,
3 | "frustumCulled": true,
4 | "isObject3D": true,
5 | "matrixAutoUpdate": true,
6 | "matrixWorldAutoUpdate": true,
7 | "matrixWorldNeedsUpdate": false,
8 | "name": "",
9 | "position": [0,0,0],
10 | "receiveShadow": false,
11 | "renderOrder": 0,
12 | "rotation": [0,0,0],
13 | "scale": [1,1,1],
14 | "up": [0,0,0],
15 | "visible": true
16 | }
--------------------------------------------------------------------------------
/src/constants/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "theme": "dark",
3 | "main": "view",
4 | "picker": "",
5 | "autoSave": true,
6 | "minimalist": true,
7 | "performanceMonitor": {
8 | "enabled": false,
9 | "logsPerSecond": 10,
10 | "antialias": true,
11 | "overClock": false,
12 | "deepAnalyze": false,
13 | "showGraph": true,
14 | "minimal": false,
15 | "customData": {
16 | "value": 0,
17 | "name": "",
18 | "round": 2,
19 | "info": ""
20 | },
21 | "matrixUpdate": false,
22 | "chart": {
23 | "hz": 60,
24 | "length": 120
25 | },
26 | "colorBlind": false,
27 | "className": "stats_panel",
28 | "style": {},
29 | "position": "bottom-left"
30 | },
31 | "tool": {
32 | "move": "translate",
33 | "selectGroup": true
34 | }
35 | }
--------------------------------------------------------------------------------
/src/constants/sounds.json:
--------------------------------------------------------------------------------
1 | [
2 | "arcade-retro-jump-223.wav",
3 | "arcade-video-game-bonus-2044.wav",
4 | "bonus-earned-in-video-game-2058.wav",
5 | "bonus-extra-in-a-video-game-2064.wav",
6 | "game-bonus-reached-2065.wav",
7 | "game-level-completed-2059.wav",
8 | "game-level-music-689.wav"
9 | ]
--------------------------------------------------------------------------------
/src/constants/textures.json:
--------------------------------------------------------------------------------
1 | [
2 | "1_wood_board",
3 | "2_wood_board",
4 | "3_wood_board",
5 | "4_wood_board",
6 | "5_wood_board",
7 | "6_wood_board",
8 | "7_wood_board"
9 | ]
--------------------------------------------------------------------------------
/src/constants/tree_theme.json:
--------------------------------------------------------------------------------
1 | {
2 | "scheme": "threeD",
3 | "author": "threed-team",
4 | "base00": "#000",
5 | "base01": "#fafafa",
6 | "base02": "#fafafa",
7 | "base03": "#fafafa",
8 | "base04": "#fafafa",
9 | "base05": "#a59f85",
10 | "base06": "#a59f85",
11 | "base07": "#a59f85",
12 | "base08": "#fafafa",
13 | "base09": "#fafafa",
14 | "base0A": "#fafafa",
15 | "base0B": "#fafafa",
16 | "base0C": "#fafafa",
17 | "base0D": "#a59f85",
18 | "base0E": "#fafafa",
19 | "base0F": "#fafafa"
20 | }
21 |
--------------------------------------------------------------------------------
/src/elements/general/Browser/Controls.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import { useControls, folder } from "leva";
3 |
4 | import useProviderControls from '@/hooks/useProviderControls';
5 | import { useElementsStore } from '@/store/elements';
6 | import props from './props';
7 |
8 | export default function Controls() {
9 | const { mountObject, normalizeControls, updateControl} = useProviderControls()
10 | const { current } = useElementsStore()
11 |
12 | const [_, set] = useControls(() => (
13 | normalizeControls(props.inject, {
14 | 'Browser Props': folder(mountObject([
15 | 'transform',
16 | 'url',
17 | 'width',
18 | 'height',
19 | ]))
20 | })
21 | ), [current])
22 |
23 | useEffect(() => {
24 | if(current.id) {
25 | updateControl(set, props.inject, {
26 | transform: current.transform,
27 | url: current.url,
28 | width: current.width,
29 | height: current.height,
30 | })
31 | }
32 | }, [current])
33 |
34 | return
35 | }
--------------------------------------------------------------------------------
/src/elements/general/Browser/index.tsx:
--------------------------------------------------------------------------------
1 | import { Html } from "@react-three/drei";
2 | import useToolbar from "@/hooks/useToolbar";
3 | import { ElementProps } from "@/types";
4 |
5 | interface BrowserProps extends ElementProps {
6 | transform: boolean
7 | url: string
8 | width: number
9 | height: number
10 | }
11 |
12 | export default function Browser({
13 | transform,
14 | url,
15 | width,
16 | height,
17 | ...props
18 | }: BrowserProps) {
19 | const {showHorizontalTop} = useToolbar()
20 | return (
21 |
22 | {!showHorizontalTop ? (
23 |
24 |
29 |
30 | ) : (
31 | <>
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | >
41 | )}
42 |
43 | )
44 | }
--------------------------------------------------------------------------------
/src/elements/general/Browser/props.ts:
--------------------------------------------------------------------------------
1 | const props = {
2 | editable: true,
3 | icon: 'IconBrowser',
4 | inject: ["OBJECT"],
5 | scale: [0.1,0.1,0.1],
6 | transform: true,
7 | url: "http://google.com",
8 | width: 1980,
9 | height: 1080
10 | }
11 |
12 | export default props
13 |
--------------------------------------------------------------------------------
/src/elements/general/Environment/Controls.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import { useControls, folder } from "leva";
3 |
4 | import { useElementsStore } from '@/store/elements';
5 | import useProviderControls from '@/hooks/useProviderControls';
6 | import CONSTANTS from '@/constants';
7 | import props from './props';
8 |
9 | export default function Controls() {
10 | const { current, updateElement } = useElementsStore()
11 | const { mountObject, normalizeControls, updateControl } = useProviderControls()
12 |
13 | const [_, set] = useControls(() => (
14 | normalizeControls(props.inject, {
15 | 'Environment Props': folder({
16 | preset: {
17 | options: CONSTANTS.environment,
18 | value: current.environment,
19 | onChange: (e) => updateElement({
20 | id: current.id,
21 | environment: e
22 | })
23 | },
24 | ...mountObject([
25 | 'blur',
26 | ])
27 | }),
28 | })
29 | ), [current])
30 |
31 | useEffect(() => {
32 | if(current.id) {
33 | updateControl(set, props.inject, {
34 | preset: current.environment,
35 | blur: current.blur
36 | })
37 | }
38 | }, [current])
39 |
40 | return
41 | }
--------------------------------------------------------------------------------
/src/elements/general/Environment/index.tsx:
--------------------------------------------------------------------------------
1 | import { Environment as EnvironmentDrei } from "@react-three/drei";
2 | import { ElementProps } from "@/types";
3 | import CONSTANTS from "@/constants";
4 |
5 | export default function Environment(props: ElementProps) {
6 | return (
7 |
8 | )
9 | }
10 |
--------------------------------------------------------------------------------
/src/elements/general/Environment/props.ts:
--------------------------------------------------------------------------------
1 | const props = {
2 | editable: false,
3 | icon: 'IconWorld',
4 | inject: ['OBJECT'],
5 | environment: "sunset",
6 | blur: 0,
7 | }
8 |
9 | export default props
10 |
--------------------------------------------------------------------------------
/src/elements/general/Glb/Controls.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import { useControls, folder } from "leva";
3 |
4 | import useProviderControls from '@/hooks/useProviderControls';
5 | import { useElementsStore } from '@/store/elements';
6 | import props from './props';
7 |
8 | export default function Controls() {
9 | const { normalizeControls, updateControl } = useProviderControls()
10 | const { current } = useElementsStore()
11 |
12 | const [_, set] = useControls(() => (
13 | normalizeControls(props.inject, {
14 | 'Glb Props': folder({
15 | id: current.id
16 | })
17 | })
18 | ), [current])
19 |
20 | useEffect(() => {
21 | if(current.id) {
22 | updateControl(set, props.inject, {
23 | id: current.id
24 | })
25 | }
26 | }, [current])
27 |
28 | return
29 | }
30 |
--------------------------------------------------------------------------------
/src/elements/general/Glb/index.tsx:
--------------------------------------------------------------------------------
1 | import { Center, useGLTF } from "@react-three/drei"
2 | import MeshMaterial from '@/components/general/MeshMaterial'
3 | import useComponentFunctions from "@/hooks/useComponentFunctions"
4 | import CONSTANTS from "@/constants"
5 | import { ElementProps, useGLTFType } from "@/types"
6 |
7 | export default function Glb(props: ElementProps) {
8 | const {handleRun, onPointerOver} = useComponentFunctions(props)
9 |
10 | const modelUrl = props.file.includes('http') ? props.file : `${CONSTANTS.cdn}/models/${props.file}/${props.file}.glb`
11 |
12 | const { nodes }:useGLTFType = useGLTF(modelUrl)
13 | const list = Object.entries(nodes)
14 |
15 | return (
16 |
17 |
18 | {list.map((item:useGLTFType[]) => (
19 |
20 |
25 |
26 | ))}
27 |
28 |
29 | )
30 | }
31 |
--------------------------------------------------------------------------------
/src/elements/general/Glb/props.ts:
--------------------------------------------------------------------------------
1 | const props = {
2 | editable: true,
3 | icon: 'IconSquare',
4 | inject: ['OBJECT', 'MATERIAL'],
5 | file: '180_twist',
6 | receiveShadow: true,
7 | castShadow: true
8 | }
9 |
10 | export default props
11 |
--------------------------------------------------------------------------------
/src/elements/general/Image/Controls.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import { useControls, folder, button } from "leva";
3 |
4 | import useProviderControls from '@/hooks/useProviderControls';
5 | import { useSettingsStore } from '@/store/settings';
6 | import { useElementsStore } from '@/store/elements';
7 | import props from './props';
8 |
9 | export default function Controls() {
10 | const { mountObject, normalizeControls, updateControl } = useProviderControls()
11 | const { setPicker } = useSettingsStore()
12 | const { current } = useElementsStore()
13 |
14 | const [_, set] = useControls(() => (
15 | normalizeControls(props.inject, {
16 | 'Image File': folder({
17 | file: {
18 | editable: false,
19 | value: `(${current.file})`
20 | },
21 | 'Select File': button(() => setPicker('image')),
22 | }),
23 | 'Image Props': folder(
24 | mountObject([
25 | 'color',
26 | 'zoom',
27 | 'grayscale',
28 | 'toneMapped',
29 | 'transparent',
30 | 'opacity',
31 | ])
32 | )
33 | })
34 | ), [current])
35 |
36 | useEffect(() => {
37 | if(current.id) {
38 | updateControl(set, props.inject, {
39 | file: current.file,
40 | color: current.color,
41 | zoom: current.zoom,
42 | grayscale: current.grayscale,
43 | toneMapped: current.toneMapped,
44 | transparent: current.transparent,
45 | opacity: current.opacity,
46 | })
47 | }
48 | }, [current])
49 |
50 | return
51 | }
--------------------------------------------------------------------------------
/src/elements/general/Image/index.tsx:
--------------------------------------------------------------------------------
1 | import { Image as ImageDrai } from "@react-three/drei";
2 | import CONSTANTS from "@/constants";
3 | import { ElementProps } from "@/types";
4 |
5 | interface ImageProps extends ElementProps {
6 | file: string
7 | }
8 |
9 | export default function Image({file, ...props}: ImageProps) {
10 | return (
11 |
12 | )
13 | }
--------------------------------------------------------------------------------
/src/elements/general/Image/props.ts:
--------------------------------------------------------------------------------
1 | const props = {
2 | editable: true,
3 | icon: 'IconPhoto',
4 | inject: ["OBJECT"],
5 | file: "react-threejs.png",
6 | segments: 1,
7 | color: '#fff',
8 | zoom: 1,
9 | grayscale: 0,
10 | toneMapped: false,
11 | transparent: true,
12 | opacity: 1,
13 | }
14 |
15 | export default props
16 |
--------------------------------------------------------------------------------
/src/elements/general/PositionalAudio/Controls.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import { useControls, folder } from "leva";
3 |
4 | import useProviderControls from '@/hooks/useProviderControls';
5 | import { useElementsStore } from '@/store/elements';
6 | import props from './props';
7 |
8 | export default function Controls() {
9 | const { mountObject, normalizeControls, updateControl } = useProviderControls()
10 | const { current } = useElementsStore()
11 |
12 | const [_, set] = useControls(() => (
13 | normalizeControls(props.inject, {
14 | 'Position Audio Props': folder(
15 | mountObject([
16 | 'soundUrl',
17 | 'distance',
18 | 'autoplay',
19 | 'loop',
20 | ])
21 | )
22 | })
23 | ), [current])
24 |
25 | useEffect(() => {
26 | if(current.id) {
27 | updateControl(set, props.inject, {
28 | soundUrl: current.soundUrl,
29 | distance: current.distance,
30 | autoplay: current.autoplay,
31 | loop: current.loop,
32 | })
33 | }
34 | }, [current])
35 |
36 | return
37 | }
--------------------------------------------------------------------------------
/src/elements/general/PositionalAudio/index.tsx:
--------------------------------------------------------------------------------
1 | import { PositionalAudio as PositionalAudioDrei } from "@react-three/drei"
2 | import { Suspense } from "react"
3 | import CONSTANTS from "@/constants"
4 | import { ElementProps, useGLTFType } from "@/types"
5 |
6 | import { Center, useGLTF } from "@react-three/drei"
7 |
8 | interface PositionalAudioProps extends ElementProps {
9 | soundUrl: string
10 | distance: number
11 | autoplay: boolean
12 | loop: boolean
13 | }
14 |
15 | export default function PositionalAudio({
16 | soundUrl,
17 | distance,
18 | autoplay,
19 | loop,
20 | ...props
21 | }: PositionalAudioProps) {
22 | const { nodes }:useGLTFType = useGLTF(`${CONSTANTS.cdn}/models/computer_speaker/computer_speaker.glb`)
23 | const list = Object.entries(nodes)
24 |
25 | return (
26 |
27 |
28 | {list.map((item:useGLTFType[]) => (
29 |
30 |
31 |
32 | ))}
33 |
34 |
35 |
42 |
43 |
44 | )
45 | }
46 |
47 |
--------------------------------------------------------------------------------
/src/elements/general/PositionalAudio/props.ts:
--------------------------------------------------------------------------------
1 | const props = {
2 | editable: true,
3 | icon: 'IconMusic',
4 | inject: ["OBJECT"],
5 | scale: [0.3,0.3,0.3],
6 | soundUrl: "arcade-retro-jump-223.wav",
7 | distance: 1,
8 | autoplay: true,
9 | loop: true
10 | }
11 |
12 | export default props
13 |
--------------------------------------------------------------------------------
/src/elements/general/Sky/Controls.tsx:
--------------------------------------------------------------------------------
1 | import { useControls, folder } from "leva";
2 | import { useEffect } from 'react';
3 |
4 | import useProviderControls from '@/hooks/useProviderControls';
5 | import { useElementsStore } from '@/store/elements';
6 | import props from './props';
7 |
8 | export default function Controls() {
9 | const { mountObject, normalizeControls, updateControl } = useProviderControls()
10 | const { current } = useElementsStore()
11 |
12 | const [_, set] = useControls(() => (
13 | normalizeControls(props.inject, {
14 | 'Sky Props': folder(mountObject([
15 | 'distance',
16 | // 'sunPosition',
17 | 'inclination',
18 | 'azimuth',
19 | 'mieCoefficient',
20 | 'mieDirectionalG',
21 | 'rayleigh',
22 | 'turbidity',
23 | ]))
24 | })
25 | ), [current])
26 |
27 | useEffect(() => {
28 | if(current.id) {
29 | updateControl(set, props.inject, {
30 | distance: current.distance,
31 | inclination: current.inclination,
32 | azimuth: current.azimuth,
33 | mieCoefficient: current.mieCoefficient,
34 | mieDirectionalG: current.mieDirectionalG,
35 | rayleigh: current.rayleigh,
36 | turbidity: current.turbidity,
37 | })
38 | }
39 | }, [current])
40 |
41 | return
42 | }
--------------------------------------------------------------------------------
/src/elements/general/Sky/index.tsx:
--------------------------------------------------------------------------------
1 | import { Sky as SkyDrei } from "@react-three/drei";
2 | import { ElementProps } from "@/types";
3 |
4 | export default function Sky(props: ElementProps) {
5 | return (
6 |
7 | )
8 | }
9 |
--------------------------------------------------------------------------------
/src/elements/general/Sky/props.ts:
--------------------------------------------------------------------------------
1 | const props = {
2 | editable: false,
3 | icon: 'IconSun',
4 | inject: ["OBJECT"],
5 | distance: 1000,
6 | // sunPosition: [ ],
7 | inclination: 0.6,
8 | azimuth: 0.1,
9 | mieCoefficient: 0.005,
10 | mieDirectionalG: 0.8,
11 | rayleigh: 0.5,
12 | turbidity: 10,
13 |
14 | }
15 |
16 | export default props
17 |
--------------------------------------------------------------------------------
/src/elements/general/Stars/Controls.tsx:
--------------------------------------------------------------------------------
1 | import { useControls, folder } from "leva";
2 | import { useEffect } from 'react';
3 |
4 | import useProviderControls from '@/hooks/useProviderControls';
5 | import { useElementsStore } from '@/store/elements';
6 | import props from './props';
7 |
8 | export default function Controls() {
9 | const { mountObject, normalizeControls, updateControl } = useProviderControls()
10 | const { current } = useElementsStore()
11 |
12 | const [_, set] = useControls(() => (
13 | normalizeControls(props.inject, {
14 | 'Stars Props': folder(mountObject([
15 | 'radius',
16 | 'depth',
17 | 'count',
18 | 'factor',
19 | 'saturation',
20 | 'fade',
21 | 'speed',
22 | ]))
23 | })
24 | ), [current])
25 |
26 | useEffect(() => {
27 | if(current.id) {
28 | updateControl(set, props.inject, {
29 | radius: current.radius,
30 | depth: current.depth,
31 | count: current.count,
32 | factor: current.factor,
33 | saturation: current.saturation,
34 | fade: current.fade,
35 | speed: current.speed,
36 | })
37 | }
38 | }, [current])
39 |
40 | return
41 | }
--------------------------------------------------------------------------------
/src/elements/general/Stars/index.tsx:
--------------------------------------------------------------------------------
1 | import { Stars as StarsDrei } from "@react-three/drei";
2 | import { ElementProps } from "@/types";
3 |
4 | export default function Stars(props: ElementProps) {
5 | return (
6 |
7 | )
8 | }
9 |
--------------------------------------------------------------------------------
/src/elements/general/Stars/props.ts:
--------------------------------------------------------------------------------
1 | const props = {
2 | editable: false,
3 | icon: 'IconSparkles',
4 | inject: ["OBJECT"],
5 | radius: 100,
6 | depth: 50,
7 | count: 5000,
8 | factor: 4,
9 | saturation: 0,
10 | fade: false,
11 | speed: 1,
12 | }
13 |
14 | export default props
15 |
--------------------------------------------------------------------------------
/src/elements/general/Svg/Controls.tsx:
--------------------------------------------------------------------------------
1 | import { useControls, folder } from "leva";
2 | import { useEffect } from 'react';
3 |
4 | import useProviderControls from '@/hooks/useProviderControls';
5 | import { useElementsStore } from '@/store/elements';
6 | import props from './props';
7 |
8 | export default function Controls() {
9 | const { mountObject, normalizeControls, updateControl } = useProviderControls()
10 | const { current } = useElementsStore()
11 |
12 | const [_, set] = useControls(() => (
13 | normalizeControls(props.inject, {
14 | 'Sgv Props': folder(mountObject([
15 | 'src',
16 | 'skipFill',
17 | 'skipStrokes',
18 | ]))
19 | })
20 | ), [current])
21 |
22 | useEffect(() => {
23 | if(current.id) {
24 | updateControl(set, props.inject, {
25 | src: current.src,
26 | skipFill: current.skipFill,
27 | skipStrokes: current.skipStrokes,
28 | })
29 | }
30 | }, [current])
31 |
32 | return
33 | }
--------------------------------------------------------------------------------
/src/elements/general/Svg/index.tsx:
--------------------------------------------------------------------------------
1 | import { Svg as SvgDrei } from "@react-three/drei";
2 | import { useFrame } from "@react-three/fiber";
3 | import { useRef } from "react";
4 | import { ElementProps, MeshRefHelper } from "@/types";
5 |
6 | export default function Svg(props: ElementProps) {
7 | const squareRef = useRef();
8 |
9 | useFrame(({ camera }) => {
10 | const { x, y, z } = camera.position;
11 | squareRef.current.lookAt(x, y, z);
12 | });
13 |
14 | return (
15 |
16 |
32 |
33 |
34 |
35 |
36 |
37 | )
38 | }
39 |
--------------------------------------------------------------------------------
/src/elements/general/Svg/props.ts:
--------------------------------------------------------------------------------
1 | const props = {
2 | editable: true,
3 | icon: 'IconFileVector',
4 | inject: ["OBJECT"],
5 | src: '',
6 | skipFill: false,
7 | skipStrokes: false,
8 | }
9 |
10 | export default props
11 |
--------------------------------------------------------------------------------
/src/elements/general/Text/Controls.tsx:
--------------------------------------------------------------------------------
1 | import { useControls, folder } from "leva";
2 | import { useEffect } from 'react';
3 |
4 | import useProviderControls from '@/hooks/useProviderControls';
5 | import { useElementsStore } from '@/store/elements';
6 | import props from './props';
7 |
8 | export default function Controls() {
9 | const { mountObject, normalizeControls, updateControl } = useProviderControls()
10 | const { current } = useElementsStore()
11 |
12 | const [_, set] = useControls(() => (
13 | normalizeControls(props.inject, {
14 | 'Text Props': folder(mountObject([
15 | 'text',
16 | 'anchorX',
17 | 'anchorY',
18 | ]))
19 | })
20 | ), [current])
21 |
22 | useEffect(() => {
23 | if(current.id) {
24 | updateControl(set, props.inject, {
25 | text: current.text,
26 | anchorX: current.anchorX,
27 | anchorY: current.anchorY,
28 | })
29 | }
30 | }, [current])
31 |
32 | return
33 | }
--------------------------------------------------------------------------------
/src/elements/general/Text/index.tsx:
--------------------------------------------------------------------------------
1 | import { Text as TextDrei } from "@react-three/drei";
2 | import MeshMaterial from "@/components/general/MeshMaterial";
3 | import { ElementProps } from "@/types";
4 |
5 | export default function Text(props: ElementProps) {
6 | const {material_type, text, material, ...restProps} = props
7 | return (
8 |
9 | {text}
10 |
11 |
12 | )
13 | }
14 |
--------------------------------------------------------------------------------
/src/elements/general/Text/props.ts:
--------------------------------------------------------------------------------
1 | const props = {
2 | editable: true,
3 | icon: 'IconTextRecognition',
4 | inject: ["OBJECT", "MATERIAL"],
5 | text: "hello world",
6 | anchorX: "center",
7 | anchorY: "middle"
8 | }
9 |
10 | export default props
11 |
--------------------------------------------------------------------------------
/src/elements/general/Text3D/Controls.tsx:
--------------------------------------------------------------------------------
1 | import { useControls, folder } from "leva";
2 | import { useEffect } from 'react';
3 |
4 | import useProviderControls from '@/hooks/useProviderControls';
5 | import { useElementsStore } from '@/store/elements';
6 | import props from './props';
7 |
8 | export default function Controls() {
9 | const { mountObject, normalizeControls, updateControl } = useProviderControls()
10 | const { current } = useElementsStore()
11 |
12 | const [_, set] = useControls(() => (
13 | normalizeControls(props.inject, {
14 | 'Text 3D Props': folder(mountObject([
15 | 'text',
16 | 'curveSegments',
17 | 'bevelEnabled',
18 | 'bevelSize',
19 | 'bevelThickness',
20 | 'height',
21 | 'lineHeight',
22 | 'letterSpacing',
23 | 'size',
24 | 'font',
25 | ]))
26 | })
27 | ), [current])
28 |
29 | useEffect(() => {
30 | if(current.id) {
31 | updateControl(set, props.inject, {
32 | text: current.text,
33 | curveSegments: current.curveSegments,
34 | bevelEnabled: current.bevelEnabled,
35 | bevelSize: current.bevelSize,
36 | bevelThickness: current.bevelThickness,
37 | height: current.height,
38 | lineHeight: current.lineHeight,
39 | letterSpacing: current.letterSpacing,
40 | size: current.size,
41 | font: current.font,
42 | })
43 | }
44 | }, [current])
45 |
46 | return
47 | }
--------------------------------------------------------------------------------
/src/elements/general/Text3D/index.tsx:
--------------------------------------------------------------------------------
1 | import { Center, Text3D as Text3Drei } from "@react-three/drei";
2 | import MeshMaterial from "@/components/general/MeshMaterial";
3 | import { ElementProps } from "@/types";
4 |
5 | export default function Text3D({...props}: ElementProps) {
6 | const {material_type, text, material, ...restProps} = props
7 | return (
8 |
9 |
10 | {text}
11 |
12 |
13 |
14 | )
15 | }
16 |
--------------------------------------------------------------------------------
/src/elements/general/Text3D/props.ts:
--------------------------------------------------------------------------------
1 | const props = {
2 | editable: true,
3 | icon: 'IconTypography',
4 | inject: ["OBJECT", "MATERIAL"],
5 | text: "hello world",
6 | curveSegments: 32,
7 | bevelEnabled: true,
8 | bevelSize: 0.04,
9 | bevelThickness: 0.1,
10 | height: 0.5,
11 | lineHeight: 0.5,
12 | letterSpacing: -0.06,
13 | size: 1.5,
14 | font: "/font/Inter_Bold.json"
15 | }
16 |
17 | export default props
18 |
--------------------------------------------------------------------------------
/src/elements/geometry/BoxGeometry/Controls.tsx:
--------------------------------------------------------------------------------
1 | import { useControls, folder } from "leva";
2 | import { useEffect } from 'react';
3 |
4 | import useProviderControls from '@/hooks/useProviderControls';
5 | import { useElementsStore } from '@/store/elements';
6 | import props from './props';
7 |
8 | export default function Controls() {
9 | const { mountObject, normalizeControls, updateControl } = useProviderControls()
10 | const { current } = useElementsStore()
11 |
12 | const [_, set] = useControls(() => (
13 | normalizeControls(props.inject, {
14 | 'Box Props': folder(
15 | mountObject([
16 | 'width',
17 | 'height',
18 | 'depth',
19 | 'widthSegments',
20 | 'heightSegments',
21 | ])
22 | ),
23 | })
24 | ), [current])
25 |
26 | useEffect(() => {
27 | if(current.id) {
28 | updateControl(set, props.inject, {
29 | width: current.width,
30 | height: current.height,
31 | depth: current.depth,
32 | widthSegments: current.widthSegments,
33 | heightSegments: current.heightSegments,
34 | })
35 | }
36 | }, [current])
37 |
38 | return
39 | }
--------------------------------------------------------------------------------
/src/elements/geometry/BoxGeometry/index.tsx:
--------------------------------------------------------------------------------
1 | import useComponentFunctions from "@/hooks/useComponentFunctions"
2 | import { ElementProps } from "@/types"
3 | import MeshMaterial from "@/components/general/MeshMaterial"
4 |
5 | export default function BoxGeometry(props: ElementProps) {
6 | const {handleRun, onPointerOver} = useComponentFunctions(props)
7 |
8 | const {
9 | width,
10 | height,
11 | depth,
12 | widthSegments,
13 | heightSegments,
14 | material,
15 | ...restProps
16 | } = props
17 |
18 | const fullProps = {
19 | ...restProps,
20 | onClick: handleRun,
21 | onPointerOver: onPointerOver,
22 | }
23 |
24 | return (
25 |
26 |
33 |
34 |
35 | )
36 | }
37 |
--------------------------------------------------------------------------------
/src/elements/geometry/BoxGeometry/props.ts:
--------------------------------------------------------------------------------
1 | const props = {
2 | editable: true,
3 | icon: 'IconBox',
4 | inject: ["OBJECT", "MATERIAL"],
5 | width: 1,
6 | height: 1,
7 | depth: 1,
8 | widthSegments: 1,
9 | heightSegments: 1,
10 | }
11 |
12 | export default props
13 |
--------------------------------------------------------------------------------
/src/elements/geometry/CapsuleGeometry/Controls.tsx:
--------------------------------------------------------------------------------
1 | import { useControls, folder } from "leva";
2 | import { useEffect } from 'react';
3 |
4 | import useProviderControls from '@/hooks/useProviderControls';
5 | import { useElementsStore } from '@/store/elements';
6 | import props from './props';
7 |
8 | export default function Controls() {
9 | const { mountObject, normalizeControls, updateControl } = useProviderControls()
10 | const { current } = useElementsStore()
11 |
12 | const [_, set] = useControls(() => (
13 | normalizeControls(props.inject, {
14 | 'Capsule Props': folder(mountObject([
15 | 'radius',
16 | 'length',
17 | 'capSegments',
18 | 'radialSegments',
19 | ])),
20 | })
21 | ), [current])
22 |
23 | useEffect(() => {
24 | if(current.id) {
25 | updateControl(set, props.inject, {
26 | radius: current.radius,
27 | length: current.length,
28 | capSegments: current.capSegments,
29 | radialSegments: current.radialSegments,
30 | })
31 | }
32 | }, [current])
33 |
34 | return
35 | }
--------------------------------------------------------------------------------
/src/elements/geometry/CapsuleGeometry/index.tsx:
--------------------------------------------------------------------------------
1 | import useComponentFunctions from "@/hooks/useComponentFunctions"
2 | import { ElementProps } from "@/types"
3 | import MeshMaterial from "@/components/general/MeshMaterial"
4 |
5 | export default function CapsuleGeometry(props: ElementProps) {
6 | const {handleRun, onPointerOver} = useComponentFunctions(props)
7 |
8 | const {
9 | radius,
10 | length,
11 | capSegments,
12 | radialSegments,
13 | material,
14 | ...restProps
15 | } = props
16 |
17 | const fullProps = {
18 | ...restProps,
19 | onClick: handleRun,
20 | onPointerOver: onPointerOver,
21 | }
22 |
23 | return (
24 |
25 |
31 |
32 |
33 | )
34 | }
35 |
--------------------------------------------------------------------------------
/src/elements/geometry/CapsuleGeometry/props.ts:
--------------------------------------------------------------------------------
1 | const props = {
2 | editable: true,
3 | icon: 'IconCapsule',
4 | inject: ["OBJECT", "MATERIAL"],
5 | radius: 1,
6 | length: 1,
7 | capSegments: 4,
8 | radialSegments: 32,
9 | }
10 |
11 | export default props
12 |
--------------------------------------------------------------------------------
/src/elements/geometry/CircleGeometry/Controls.tsx:
--------------------------------------------------------------------------------
1 | import { useControls, folder } from "leva";
2 | import { useEffect } from 'react';
3 |
4 | import useProviderControls from '@/hooks/useProviderControls';
5 | import { useElementsStore } from '@/store/elements';
6 | import props from './props';
7 |
8 | export default function Controls() {
9 | const { mountObject, normalizeControls, updateControl } = useProviderControls()
10 | const { current } = useElementsStore()
11 |
12 | const [_, set] = useControls(() => (
13 | normalizeControls(props.inject, {
14 | 'Circle Props': folder(mountObject([
15 | 'radius',
16 | 'segments',
17 | 'thetaStart',
18 | 'thetaLength',
19 | ])),
20 | })
21 | ), [current])
22 |
23 | useEffect(() => {
24 | if(current.id) {
25 | updateControl(set, props.inject, {
26 | radius: current.radius,
27 | segments: current.segments,
28 | thetaStart: current.thetaStart,
29 | thetaLength: current.thetaLength,
30 | })
31 | }
32 | }, [current])
33 |
34 | return
35 | }
--------------------------------------------------------------------------------
/src/elements/geometry/CircleGeometry/index.tsx:
--------------------------------------------------------------------------------
1 | import useComponentFunctions from "@/hooks/useComponentFunctions"
2 | import { ElementProps } from "@/types"
3 | import MeshMaterial from "@/components/general/MeshMaterial"
4 |
5 | export default function CircleGeometry(props: ElementProps) {
6 | const {handleRun, onPointerOver} = useComponentFunctions(props)
7 |
8 | const {
9 | radius,
10 | segments,
11 | thetaStart,
12 | thetaLength,
13 | material,
14 | ...restProps
15 | } = props
16 |
17 | const fullProps = {
18 | ...restProps,
19 | onClick: handleRun,
20 | onPointerOver: onPointerOver,
21 | }
22 |
23 | return (
24 |
25 |
31 |
32 |
33 | )
34 | }
35 |
--------------------------------------------------------------------------------
/src/elements/geometry/CircleGeometry/props.ts:
--------------------------------------------------------------------------------
1 | const props = {
2 | editable: true,
3 | icon: 'IconCircleFilled',
4 | inject: ["OBJECT", "MATERIAL"],
5 | radius: 1,
6 | segments: 32,
7 | thetaStart: 1,
8 | thetaLength: 7,
9 | }
10 |
11 | export default props
12 |
--------------------------------------------------------------------------------
/src/elements/geometry/ConeGeometry/Controls.tsx:
--------------------------------------------------------------------------------
1 | import { useControls, folder } from "leva";
2 | import { useEffect } from 'react';
3 |
4 | import useProviderControls from '@/hooks/useProviderControls';
5 | import { useElementsStore } from '@/store/elements';
6 | import props from './props';
7 |
8 | export default function Controls() {
9 | const { mountObject, normalizeControls, updateControl } = useProviderControls()
10 | const { current } = useElementsStore()
11 |
12 | const [_, set] = useControls(() => (
13 | normalizeControls(props.inject, {
14 | 'Cone Props': folder(mountObject([
15 | 'radius',
16 | 'height',
17 | 'radialSegments',
18 | 'heightSegments',
19 | 'openEnded',
20 | 'thetaStart',
21 | 'thetaLength',
22 | ])),
23 | })
24 | ), [current])
25 |
26 | useEffect(() => {
27 | if(current.id) {
28 | updateControl(set, props.inject, {
29 | radius: current.radius,
30 | height: current.height,
31 | radialSegments: current.radialSegments,
32 | heightSegments: current.heightSegments,
33 | openEnded: current.openEnded,
34 | thetaStart: current.thetaStart,
35 | thetaLength: current.thetaLength,
36 | })
37 | }
38 | }, [current])
39 |
40 | return
41 | }
--------------------------------------------------------------------------------
/src/elements/geometry/ConeGeometry/index.tsx:
--------------------------------------------------------------------------------
1 | import useComponentFunctions from "@/hooks/useComponentFunctions"
2 | import { ElementProps } from "@/types"
3 | import MeshMaterial from "@/components/general/MeshMaterial"
4 |
5 | export default function ConeGeometry(props: ElementProps) {
6 | const {handleRun, onPointerOver} = useComponentFunctions(props)
7 |
8 | const {
9 | radius,
10 | height,
11 | radialSegments,
12 | heightSegments,
13 | openEnded,
14 | thetaStart,
15 | thetaLength,
16 | material,
17 | ...restProps
18 | } = props
19 |
20 | const fullProps = {
21 | ...restProps,
22 | onClick: handleRun,
23 | onPointerOver: onPointerOver,
24 | }
25 |
26 | return (
27 |
28 |
37 |
38 |
39 | )
40 | }
41 |
--------------------------------------------------------------------------------
/src/elements/geometry/ConeGeometry/props.ts:
--------------------------------------------------------------------------------
1 | const props = {
2 | editable: true,
3 | icon: 'IconCone',
4 | inject: ["OBJECT", "MATERIAL"],
5 | radius: 1,
6 | height: 5,
7 | radialSegments: 32,
8 | heightSegments: 1,
9 | openEnded: false,
10 | thetaStart: 0,
11 | thetaLength: 7,
12 | }
13 |
14 | export default props
15 |
--------------------------------------------------------------------------------
/src/elements/geometry/CylinderGeometry/Controls.tsx:
--------------------------------------------------------------------------------
1 | import { useControls, folder } from "leva";
2 | import { useEffect } from 'react';
3 |
4 | import useProviderControls from '@/hooks/useProviderControls';
5 | import { useElementsStore } from '@/store/elements';
6 | import props from './props';
7 |
8 | export default function Controls() {
9 | const { mountObject, normalizeControls, updateControl } = useProviderControls()
10 | const { current } = useElementsStore()
11 |
12 | const [_, set] = useControls(() => (
13 | normalizeControls(props.inject, {
14 | 'Cylinder Props': folder(mountObject([
15 | 'radiusTop',
16 | 'radiusBottom',
17 | 'height',
18 | 'radialSegments',
19 | 'heightSegments',
20 | 'openEnded',
21 | 'thetaStart',
22 | 'thetaLength',
23 | ])),
24 | })
25 | ), [current])
26 |
27 | useEffect(() => {
28 | if(current.id) {
29 | updateControl(set, props.inject, {
30 | radiusTop: current.radiusTop,
31 | radiusBottom: current.radiusBottom,
32 | height: current.height,
33 | radialSegments: current.radialSegments,
34 | heightSegments: current.heightSegments,
35 | openEnded: current.openEnded,
36 | thetaStart: current.thetaStart,
37 | thetaLength: current.thetaLength,
38 | })
39 | }
40 | }, [current])
41 |
42 | return
43 | }
--------------------------------------------------------------------------------
/src/elements/geometry/CylinderGeometry/index.tsx:
--------------------------------------------------------------------------------
1 | import useComponentFunctions from "@/hooks/useComponentFunctions"
2 | import { ElementProps } from "@/types"
3 | import MeshMaterial from "@/components/general/MeshMaterial"
4 |
5 | export default function CylinderGeometry(props: ElementProps) {
6 | const {handleRun, onPointerOver} = useComponentFunctions(props)
7 |
8 | const {
9 | radiusTop,
10 | radiusBottom,
11 | height,
12 | radialSegments,
13 | heightSegments,
14 | openEnded,
15 | thetaStart,
16 | thetaLength,
17 | material,
18 | ...restProps
19 | } = props
20 |
21 | const fullProps = {
22 | ...restProps,
23 | onClick: handleRun,
24 | onPointerOver: onPointerOver,
25 | }
26 |
27 | return (
28 |
29 |
39 |
40 |
41 | )
42 | }
43 |
--------------------------------------------------------------------------------
/src/elements/geometry/CylinderGeometry/props.ts:
--------------------------------------------------------------------------------
1 | const props = {
2 | editable: true,
3 | icon: 'IconCylinder',
4 | inject: ["OBJECT", "MATERIAL"],
5 | radiusTop: 1,
6 | radiusBottom: 1,
7 | height: 5,
8 | radialSegments: 32,
9 | heightSegments: 1,
10 | openEnded: false,
11 | thetaStart: 0,
12 | thetaLength: 7,
13 | }
14 |
15 | export default props
16 |
--------------------------------------------------------------------------------
/src/elements/geometry/DodecahedronGeometry/Controls.tsx:
--------------------------------------------------------------------------------
1 | import { useControls, folder } from "leva";
2 | import { useEffect } from 'react';
3 |
4 | import useProviderControls from '@/hooks/useProviderControls';
5 | import { useElementsStore } from '@/store/elements';
6 | import props from './props';
7 |
8 | export default function Controls() {
9 | const { mountObject, normalizeControls, updateControl } = useProviderControls()
10 | const { current } = useElementsStore()
11 |
12 | const [_, set] = useControls(() => (
13 | normalizeControls(props.inject, {
14 | 'Dodecahedron Props': folder(mountObject([
15 | 'radius',
16 | 'detail',
17 | ])),
18 | })
19 | ), [current])
20 |
21 | useEffect(() => {
22 | if(current.id) {
23 | updateControl(set, props.inject, {
24 | radius: current.radius,
25 | detail: current.detail,
26 | })
27 | }
28 | }, [current])
29 |
30 | return
31 | }
--------------------------------------------------------------------------------
/src/elements/geometry/DodecahedronGeometry/index.tsx:
--------------------------------------------------------------------------------
1 | import useComponentFunctions from "@/hooks/useComponentFunctions"
2 | import { ElementProps } from "@/types"
3 | import MeshMaterial from "@/components/general/MeshMaterial"
4 |
5 | export default function DodecahedronGeometry(props: ElementProps) {
6 | const {handleRun, onPointerOver} = useComponentFunctions(props)
7 |
8 | const {
9 | radius,
10 | detail,
11 | material,
12 | ...restProps
13 | } = props
14 |
15 | const fullProps = {
16 | ...restProps,
17 | onClick: handleRun,
18 | onPointerOver: onPointerOver,
19 | }
20 |
21 | return (
22 |
23 |
27 |
28 |
29 | )
30 | }
31 |
--------------------------------------------------------------------------------
/src/elements/geometry/DodecahedronGeometry/props.ts:
--------------------------------------------------------------------------------
1 | const props = {
2 | editable: true,
3 | icon: '',
4 | inject: ["OBJECT", "MATERIAL"],
5 | radius: 1,
6 | detail: 0,
7 | }
8 |
9 | export default props
10 |
--------------------------------------------------------------------------------
/src/elements/geometry/ExtrudeGeometry/Controls.tsx:
--------------------------------------------------------------------------------
1 | import { useControls, folder } from "leva";
2 | import { useEffect } from 'react';
3 |
4 | import useProviderControls from '@/hooks/useProviderControls';
5 | import { useElementsStore } from '@/store/elements';
6 | import props from './props';
7 |
8 | export default function Controls() {
9 | const { mountObject, normalizeControls, updateControl } = useProviderControls()
10 | const { current } = useElementsStore()
11 |
12 | const [_, set] = useControls(() => (
13 | normalizeControls(props.inject, {
14 | 'Extrude Props': folder(mountObject([
15 | 'args',
16 | ])),
17 | })
18 | ), [current])
19 |
20 | useEffect(() => {
21 | if(current.id) {
22 | updateControl(set, props.inject, {
23 | args: current.args,
24 | })
25 | }
26 | }, [current])
27 |
28 | return
29 | }
--------------------------------------------------------------------------------
/src/elements/geometry/ExtrudeGeometry/index.tsx:
--------------------------------------------------------------------------------
1 | import useComponentFunctions from "@/hooks/useComponentFunctions"
2 | import { ElementProps } from "@/types"
3 | import MeshMaterial from "@/components/general/MeshMaterial"
4 |
5 | export default function ExtrudeGeometry(props: ElementProps) {
6 | const {handleRun, onPointerOver} = useComponentFunctions(props)
7 |
8 | const {args, material, ...restProps} = props
9 | const fullProps = {
10 | ...restProps,
11 | onClick: handleRun,
12 | onPointerOver: onPointerOver,
13 | }
14 |
15 | return (
16 |
17 |
18 |
19 |
20 | )
21 | }
22 |
--------------------------------------------------------------------------------
/src/elements/geometry/ExtrudeGeometry/props.ts:
--------------------------------------------------------------------------------
1 | const props = {
2 | editable: true,
3 | icon: '',
4 | inject: ["OBJECT", "MATERIAL"],
5 | args: [1, 1, 1]
6 | }
7 |
8 | export default props
9 |
--------------------------------------------------------------------------------
/src/elements/geometry/IcosahedronGeometry/Controls.tsx:
--------------------------------------------------------------------------------
1 | import { useControls, folder } from "leva";
2 | import { useEffect } from 'react';
3 |
4 | import useProviderControls from '@/hooks/useProviderControls';
5 | import { useElementsStore } from '@/store/elements';
6 | import props from './props';
7 |
8 | export default function Controls() {
9 | const { mountObject, normalizeControls, updateControl } = useProviderControls()
10 | const { current } = useElementsStore()
11 |
12 | const [_, set] = useControls(() => (
13 | normalizeControls(props.inject, {
14 | 'Icosahedron Props': folder(mountObject([
15 | 'radius',
16 | 'detail',
17 | ])),
18 | })
19 | ), [current])
20 |
21 | useEffect(() => {
22 | if(current.id) {
23 | updateControl(set, props.inject, {
24 | radius: current.radius,
25 | detail: current.detail,
26 | })
27 | }
28 | }, [current])
29 |
30 | return
31 | }
--------------------------------------------------------------------------------
/src/elements/geometry/IcosahedronGeometry/index.tsx:
--------------------------------------------------------------------------------
1 | import useComponentFunctions from "@/hooks/useComponentFunctions"
2 | import { ElementProps } from "@/types"
3 | import MeshMaterial from "@/components/general/MeshMaterial"
4 |
5 | export default function IcosahedronGeometry(props: ElementProps) {
6 | const {handleRun, onPointerOver} = useComponentFunctions(props)
7 |
8 | const {
9 | radius,
10 | detail,
11 | material,
12 | ...restProps
13 | } = props
14 |
15 | const fullProps = {
16 | ...restProps,
17 | onClick: handleRun,
18 | onPointerOver: onPointerOver,
19 | }
20 |
21 | return (
22 |
23 |
27 |
28 |
29 | )
30 | }
31 |
--------------------------------------------------------------------------------
/src/elements/geometry/IcosahedronGeometry/props.ts:
--------------------------------------------------------------------------------
1 | const props = {
2 | editable: true,
3 | icon: '',
4 | inject: ["OBJECT", "MATERIAL"],
5 | radius: 1,
6 | detail: 0,
7 | }
8 |
9 | export default props
10 |
--------------------------------------------------------------------------------
/src/elements/geometry/LatheGeometry/Controls.tsx:
--------------------------------------------------------------------------------
1 | import { useControls, folder } from "leva";
2 | import { useEffect } from 'react';
3 |
4 | import useProviderControls from '@/hooks/useProviderControls';
5 | import { useElementsStore } from '@/store/elements';
6 | import props from './props';
7 |
8 | export default function Controls() {
9 | const { mountObject, normalizeControls, updateControl } = useProviderControls()
10 | const { current } = useElementsStore()
11 |
12 | const [_, set] = useControls(() => (
13 | normalizeControls(props.inject, {
14 | 'Lathe Props': folder(mountObject([
15 | 'args',
16 | ])),
17 | })
18 | ), [current])
19 |
20 | useEffect(() => {
21 | if(current.id) {
22 | updateControl(set, props.inject, {
23 | args: current.args,
24 | })
25 | }
26 | }, [current])
27 |
28 | return
29 | }
--------------------------------------------------------------------------------
/src/elements/geometry/LatheGeometry/index.tsx:
--------------------------------------------------------------------------------
1 | import useComponentFunctions from "@/hooks/useComponentFunctions"
2 | import { ElementProps } from "@/types"
3 | import MeshMaterial from "@/components/general/MeshMaterial"
4 |
5 | export default function LatheGeometry(props: ElementProps) {
6 | const {handleRun, onPointerOver} = useComponentFunctions(props)
7 |
8 | const {args, material, ...restProps} = props
9 | const fullProps = {
10 | ...restProps,
11 | onClick: handleRun,
12 | onPointerOver: onPointerOver,
13 | }
14 |
15 | return (
16 |
17 |
18 |
19 |
20 | )
21 | }
22 |
--------------------------------------------------------------------------------
/src/elements/geometry/LatheGeometry/props.ts:
--------------------------------------------------------------------------------
1 | const props = {
2 | editable: true,
3 | icon: '',
4 | inject: ["OBJECT", "MATERIAL"],
5 | args: [[[1, 1]], 1, 1, 1]
6 | }
7 |
8 | export default props
9 |
--------------------------------------------------------------------------------
/src/elements/geometry/OctahedronGeometry/Controls.tsx:
--------------------------------------------------------------------------------
1 | import { useControls, folder } from "leva";
2 | import { useEffect } from 'react';
3 |
4 | import useProviderControls from '@/hooks/useProviderControls';
5 | import { useElementsStore } from '@/store/elements';
6 | import props from './props';
7 |
8 | export default function Controls() {
9 | const { mountObject, normalizeControls, updateControl } = useProviderControls()
10 | const { current } = useElementsStore()
11 |
12 | const [_, set] = useControls(() => (
13 | normalizeControls(props.inject, {
14 | 'Octahedron Props': folder(mountObject([
15 | 'radius',
16 | 'detail',
17 | ])),
18 | })
19 | ), [current])
20 |
21 | useEffect(() => {
22 | if(current.id) {
23 | updateControl(set, props.inject, {
24 | radius: current.radius,
25 | detail: current.detail,
26 | })
27 | }
28 | }, [current])
29 |
30 | return
31 | }
--------------------------------------------------------------------------------
/src/elements/geometry/OctahedronGeometry/index.tsx:
--------------------------------------------------------------------------------
1 | import useComponentFunctions from "@/hooks/useComponentFunctions"
2 | import { ElementProps } from "@/types"
3 | import MeshMaterial from "@/components/general/MeshMaterial"
4 |
5 | export default function OctahedronGeometry(props: ElementProps) {
6 | const {handleRun, onPointerOver} = useComponentFunctions(props)
7 |
8 | const {
9 | radius,
10 | detail,
11 | material,
12 | ...restProps
13 | } = props
14 |
15 | const fullProps = {
16 | ...restProps,
17 | onClick: handleRun,
18 | onPointerOver: onPointerOver,
19 | }
20 |
21 | return (
22 |
23 |
27 |
28 |
29 | )
30 | }
31 |
--------------------------------------------------------------------------------
/src/elements/geometry/OctahedronGeometry/props.ts:
--------------------------------------------------------------------------------
1 | const props = {
2 | editable: true,
3 | icon: 'IconOctahedron',
4 | inject: ["OBJECT", "MATERIAL"],
5 | radius: 1,
6 | detail: 0,
7 | }
8 |
9 | export default props
10 |
--------------------------------------------------------------------------------
/src/elements/geometry/PlaneGeometry/Controls.tsx:
--------------------------------------------------------------------------------
1 | import { useControls, folder } from "leva";
2 | import { useEffect } from 'react';
3 |
4 | import useProviderControls from '@/hooks/useProviderControls';
5 | import { useElementsStore } from '@/store/elements';
6 | import props from './props';
7 |
8 | export default function Controls() {
9 | const { mountObject, normalizeControls, updateControl } = useProviderControls()
10 | const { current } = useElementsStore()
11 |
12 | const [_, set] = useControls(() => (
13 | normalizeControls(props.inject, {
14 | 'Plane Props': folder(mountObject([
15 | 'width',
16 | 'height',
17 | 'widthSegments',
18 | 'heightSegments',
19 | ])),
20 | })
21 | ), [current])
22 |
23 | useEffect(() => {
24 | if(current.id) {
25 | updateControl(set, props.inject, {
26 | width: current.width,
27 | height: current.height,
28 | widthSegments: current.widthSegments,
29 | heightSegments: current.heightSegments,
30 | })
31 | }
32 | }, [current])
33 |
34 | return
35 | }
--------------------------------------------------------------------------------
/src/elements/geometry/PlaneGeometry/index.tsx:
--------------------------------------------------------------------------------
1 | import useComponentFunctions from "@/hooks/useComponentFunctions"
2 | import { ElementProps } from "@/types"
3 | import MeshMaterial from "@/components/general/MeshMaterial"
4 |
5 | export default function PlaneGeometry(props: ElementProps) {
6 | const {handleRun, onPointerOver} = useComponentFunctions(props)
7 |
8 | const {
9 | width,
10 | height,
11 | widthSegments,
12 | heightSegments,
13 | material,
14 | ...restProps
15 | } = props
16 |
17 | const fullProps = {
18 | ...restProps,
19 | onClick: handleRun,
20 | onPointerOver: onPointerOver,
21 | }
22 |
23 | return (
24 |
25 |
31 |
32 |
33 | )
34 | }
35 |
--------------------------------------------------------------------------------
/src/elements/geometry/PlaneGeometry/props.ts:
--------------------------------------------------------------------------------
1 | const props = {
2 | editable: true,
3 | icon: 'IconSquare',
4 | inject: ["OBJECT", "MATERIAL"],
5 | width: 1,
6 | height: 1,
7 | widthSegments: 1,
8 | heightSegments: 1,
9 | }
10 |
11 | export default props
12 |
--------------------------------------------------------------------------------
/src/elements/geometry/RingGeometry/Controls.tsx:
--------------------------------------------------------------------------------
1 | import { useControls, folder } from "leva";
2 | import { useEffect } from 'react';
3 |
4 | import useProviderControls from '@/hooks/useProviderControls';
5 | import { useElementsStore } from '@/store/elements';
6 | import props from './props';
7 |
8 | export default function Controls() {
9 | const { mountObject, normalizeControls, updateControl } = useProviderControls()
10 | const { current } = useElementsStore()
11 |
12 | const [_, set] = useControls(() => (
13 | normalizeControls(props.inject, {
14 | 'Ring Props': folder(mountObject([
15 | 'innerRadius',
16 | 'outerRadius',
17 | 'thetaSegments',
18 | 'phiSegments',
19 | 'thetaStart',
20 | 'thetaLength',
21 | ])),
22 | })
23 | ), [current])
24 |
25 | useEffect(() => {
26 | if(current.id) {
27 | updateControl(set, props.inject, {
28 | innerRadius: current.innerRadius,
29 | outerRadius: current.outerRadius,
30 | thetaSegments: current.thetaSegments,
31 | phiSegments: current.phiSegments,
32 | thetaStart: current.thetaStart,
33 | thetaLength: current.thetaLength,
34 | })
35 | }
36 | }, [current])
37 |
38 | return
39 | }
--------------------------------------------------------------------------------
/src/elements/geometry/RingGeometry/index.tsx:
--------------------------------------------------------------------------------
1 | import useComponentFunctions from "@/hooks/useComponentFunctions"
2 | import { ElementProps } from "@/types"
3 | import MeshMaterial from "@/components/general/MeshMaterial"
4 |
5 | export default function RingGeometry(props: ElementProps) {
6 | const {handleRun, onPointerOver} = useComponentFunctions(props)
7 |
8 | const {
9 | innerRadius,
10 | outerRadius,
11 | thetaSegments,
12 | phiSegments,
13 | thetaStart,
14 | thetaLength,
15 | material,
16 | ...restProps
17 | } = props
18 |
19 | const fullProps = {
20 | ...restProps,
21 | onClick: handleRun,
22 | onPointerOver: onPointerOver,
23 | }
24 |
25 | return (
26 |
27 |
35 |
36 |
37 | )
38 | }
39 |
--------------------------------------------------------------------------------
/src/elements/geometry/RingGeometry/props.ts:
--------------------------------------------------------------------------------
1 | const props = {
2 | editable: true,
3 | icon: 'IconLifebuoy',
4 | inject: ["OBJECT", "MATERIAL"],
5 | innerRadius: 1,
6 | outerRadius: 5,
7 | thetaSegments: 32,
8 | phiSegments: 8,
9 | thetaStart: 0,
10 | thetaLength: 7,
11 | }
12 |
13 | export default props
14 |
--------------------------------------------------------------------------------
/src/elements/geometry/SphereGeometry/Controls.tsx:
--------------------------------------------------------------------------------
1 | import { useControls, folder } from "leva";
2 | import { useEffect } from 'react';
3 |
4 | import useProviderControls from '@/hooks/useProviderControls';
5 | import { useElementsStore } from '@/store/elements';
6 | import props from './props';
7 |
8 | export default function Controls() {
9 | const { mountObject, normalizeControls, updateControl } = useProviderControls()
10 | const { current } = useElementsStore()
11 |
12 | const [_, set] = useControls(() => (
13 | normalizeControls(props.inject, {
14 | 'Sphere Props': folder(mountObject([
15 | 'radius',
16 | 'widthSegments',
17 | 'heightSegments',
18 | 'phiStart',
19 | 'phiLength',
20 | 'thetaStart',
21 | 'thetaLength',
22 | ])),
23 | })
24 | ), [current])
25 |
26 | useEffect(() => {
27 | if(current.id) {
28 | updateControl(set, props.inject, {
29 | radius: current.radius,
30 | widthSegments: current.widthSegments,
31 | heightSegments: current.heightSegments,
32 | phiStart: current.phiStart,
33 | phiLength: current.phiLength,
34 | thetaStart: current.thetaStart,
35 | thetaLength: current.thetaLength,
36 | })
37 | }
38 | }, [current])
39 |
40 | return
41 | }
--------------------------------------------------------------------------------
/src/elements/geometry/SphereGeometry/index.tsx:
--------------------------------------------------------------------------------
1 | import useComponentFunctions from "@/hooks/useComponentFunctions"
2 | import { ElementProps } from "@/types"
3 | import MeshMaterial from "@/components/general/MeshMaterial"
4 |
5 | export default function SphereGeometry(props: ElementProps) {
6 | const {handleRun, onPointerOver} = useComponentFunctions(props)
7 |
8 | const {
9 | radius,
10 | widthSegments,
11 | heightSegments,
12 | phiStart,
13 | phiLength,
14 | thetaStart,
15 | thetaLength,
16 | material,
17 | ...restProps
18 | } = props
19 |
20 | const fullProps = {
21 | ...restProps,
22 | onClick: handleRun,
23 | onPointerOver: onPointerOver,
24 | }
25 |
26 | return (
27 |
28 |
37 |
38 |
39 | )
40 | }
41 |
--------------------------------------------------------------------------------
/src/elements/geometry/SphereGeometry/props.ts:
--------------------------------------------------------------------------------
1 | const props = {
2 | editable: true,
3 | icon: 'IconInnerShadowRight',
4 | inject: ["OBJECT", "MATERIAL"],
5 | radius: 1,
6 | widthSegments: 32,
7 | heightSegments: 16,
8 | phiStart: 0,
9 | phiLength: 6.283185307179586,
10 | thetaStart: 0,
11 | thetaLength: 3.141592653589793,
12 | }
13 |
14 | export default props
15 |
--------------------------------------------------------------------------------
/src/elements/geometry/TetrahedronGeometry/Controls.tsx:
--------------------------------------------------------------------------------
1 | import { useControls, folder } from "leva";
2 | import { useEffect } from 'react';
3 |
4 | import useProviderControls from '@/hooks/useProviderControls';
5 | import { useElementsStore } from '@/store/elements';
6 | import props from './props';
7 |
8 | export default function Controls() {
9 | const { mountObject, normalizeControls, updateControl } = useProviderControls()
10 | const { current } = useElementsStore()
11 |
12 | const [_, set] = useControls(() => (
13 | normalizeControls(props.inject, {
14 | 'Tetrahedron Props': folder(mountObject([
15 | 'radius',
16 | 'detail',
17 | ])),
18 | })
19 | ), [current])
20 |
21 | useEffect(() => {
22 | if(current.id) {
23 | updateControl(set, props.inject, {
24 | radius: current.radius,
25 | detail: current.detail,
26 | })
27 | }
28 | }, [current])
29 |
30 | return
31 | }
--------------------------------------------------------------------------------
/src/elements/geometry/TetrahedronGeometry/index.tsx:
--------------------------------------------------------------------------------
1 | import useComponentFunctions from "@/hooks/useComponentFunctions"
2 | import { ElementProps } from "@/types"
3 | import MeshMaterial from "@/components/general/MeshMaterial"
4 |
5 | export default function TetrahedronGeometry(props: ElementProps) {
6 | const {handleRun, onPointerOver} = useComponentFunctions(props)
7 |
8 | const {
9 | radius,
10 | detail,
11 | material,
12 | ...restProps
13 | } = props
14 |
15 | const fullProps = {
16 | ...restProps,
17 | onClick: handleRun,
18 | onPointerOver: onPointerOver,
19 | }
20 |
21 | return (
22 |
23 |
27 |
28 |
29 | )
30 | }
31 |
--------------------------------------------------------------------------------
/src/elements/geometry/TetrahedronGeometry/props.ts:
--------------------------------------------------------------------------------
1 | const props = {
2 | editable: true,
3 | icon: '',
4 | inject: ["OBJECT", "MATERIAL"],
5 | radius: 1,
6 | detail: 0,
7 | }
8 |
9 | export default props
10 |
--------------------------------------------------------------------------------
/src/elements/geometry/TorusGeometry/Controls.tsx:
--------------------------------------------------------------------------------
1 | import { useControls, folder } from "leva";
2 | import { useEffect } from 'react';
3 |
4 | import useProviderControls from '@/hooks/useProviderControls';
5 | import { useElementsStore } from '@/store/elements';
6 | import props from './props';
7 |
8 | export default function Controls() {
9 | const { mountObject, normalizeControls, updateControl } = useProviderControls()
10 | const { current } = useElementsStore()
11 |
12 | const [_, set] = useControls(() => (
13 | normalizeControls(props.inject, {
14 | 'Torus Props': folder(mountObject([
15 | 'radius',
16 | 'tube',
17 | 'radialSegments',
18 | 'tubularSegments',
19 | 'arc',
20 | ])),
21 | })
22 | ), [current])
23 |
24 | useEffect(() => {
25 | if(current.id) {
26 | updateControl(set, props.inject, {
27 | radius: current.radius,
28 | tube: current.tube,
29 | radialSegments: current.radialSegments,
30 | tubularSegments: current.tubularSegments,
31 | arc: current.arc,
32 | })
33 | }
34 | }, [current])
35 |
36 | return
37 | }
--------------------------------------------------------------------------------
/src/elements/geometry/TorusGeometry/index.tsx:
--------------------------------------------------------------------------------
1 | import useComponentFunctions from "@/hooks/useComponentFunctions"
2 | import { ElementProps } from "@/types"
3 | import MeshMaterial from "@/components/general/MeshMaterial"
4 |
5 | export default function TorusGeometry(props: ElementProps) {
6 | const {handleRun, onPointerOver} = useComponentFunctions(props)
7 |
8 | const {
9 | radius,
10 | tube,
11 | radialSegments,
12 | tubularSegments,
13 | arc,
14 | material,
15 | ...restProps
16 | } = props
17 |
18 | const fullProps = {
19 | ...restProps,
20 | onClick: handleRun,
21 | onPointerOver: onPointerOver,
22 | }
23 |
24 | return (
25 |
26 |
33 |
34 |
35 | )
36 | }
37 |
--------------------------------------------------------------------------------
/src/elements/geometry/TorusGeometry/props.ts:
--------------------------------------------------------------------------------
1 | const props = {
2 | editable: true,
3 | icon: '',
4 | inject: ["OBJECT", "MATERIAL"],
5 | radius: 10,
6 | tube: 3,
7 | radialSegments: 16,
8 | tubularSegments: 100,
9 | arc: 7,
10 | }
11 |
12 | export default props
13 |
--------------------------------------------------------------------------------
/src/elements/geometry/TorusKnotGeometry/Controls.tsx:
--------------------------------------------------------------------------------
1 | import { useControls, folder } from "leva";
2 | import { useEffect } from 'react';
3 |
4 | import useProviderControls from '@/hooks/useProviderControls';
5 | import { useElementsStore } from '@/store/elements';
6 | import props from './props';
7 |
8 | export default function Controls() {
9 | const { mountObject, normalizeControls, updateControl } = useProviderControls()
10 | const { current } = useElementsStore()
11 |
12 | const [_, set] = useControls(() => (
13 | normalizeControls(props.inject, {
14 | 'TorusKnot Props': folder(mountObject([
15 | 'radius',
16 | 'tube',
17 | 'radialSegments',
18 | 'tubularSegments',
19 | 'p',
20 | 'q',
21 | ])),
22 | })
23 | ), [current])
24 |
25 | useEffect(() => {
26 | if(current.id) {
27 | updateControl(set, props.inject, {
28 | radius: current.radius,
29 | tube: current.tube,
30 | radialSegments: current.radialSegments,
31 | tubularSegments: current.tubularSegments,
32 | p: current.p,
33 | q: current.q,
34 | })
35 | }
36 | }, [current])
37 |
38 | return
39 | }
--------------------------------------------------------------------------------
/src/elements/geometry/TorusKnotGeometry/index.tsx:
--------------------------------------------------------------------------------
1 | import useComponentFunctions from "@/hooks/useComponentFunctions"
2 | import { ElementProps } from "@/types"
3 | import MeshMaterial from "@/components/general/MeshMaterial"
4 |
5 | export default function TorusKnotGeometry(props: ElementProps) {
6 | const {handleRun, onPointerOver} = useComponentFunctions(props)
7 |
8 | const {
9 | radius,
10 | tube,
11 | radialSegments,
12 | tubularSegments,
13 | p,
14 | q,
15 | material,
16 | ...restProps
17 | } = props
18 |
19 | const fullProps = {
20 | ...restProps,
21 | onClick: handleRun,
22 | onPointerOver: onPointerOver,
23 | }
24 |
25 | return (
26 |
27 |
35 |
36 |
37 | )
38 | }
39 |
--------------------------------------------------------------------------------
/src/elements/geometry/TorusKnotGeometry/props.ts:
--------------------------------------------------------------------------------
1 | const props = {
2 | editable: true,
3 | icon: '',
4 | inject: ["OBJECT", "MATERIAL"],
5 | radius: 10,
6 | tube: 3,
7 | radialSegments: 64,
8 | tubularSegments: 8,
9 | p: 2,
10 | q: 3,
11 | }
12 |
13 | export default props
14 |
--------------------------------------------------------------------------------
/src/elements/geometry/TubeGeometry/Controls.tsx:
--------------------------------------------------------------------------------
1 | import { useControls, folder } from "leva";
2 | import { useEffect } from 'react';
3 |
4 | import useProviderControls from '@/hooks/useProviderControls';
5 | import { useElementsStore } from '@/store/elements';
6 | import props from './props';
7 |
8 | export default function Controls() {
9 | const { mountObject, normalizeControls, updateControl } = useProviderControls()
10 | const { current } = useElementsStore()
11 |
12 | const [_, set] = useControls(() => (
13 | normalizeControls(props.inject, {
14 | 'Tube Props': folder(mountObject([
15 | 'args',
16 | ])),
17 | })
18 | ), [current])
19 |
20 | useEffect(() => {
21 | if(current.id) {
22 | updateControl(set, props.inject, {
23 | args: current.args,
24 | })
25 | }
26 | }, [current])
27 |
28 | return
29 | }
--------------------------------------------------------------------------------
/src/elements/geometry/TubeGeometry/index.tsx:
--------------------------------------------------------------------------------
1 | import useComponentFunctions from "@/hooks/useComponentFunctions"
2 | import { ElementProps } from "@/types"
3 | import MeshMaterial from "@/components/general/MeshMaterial"
4 |
5 | export default function TubeGeometry(props: ElementProps) {
6 | const {handleRun, onPointerOver} = useComponentFunctions(props)
7 |
8 | const {args, material, ...restProps} = props
9 | const fullProps = {
10 | ...restProps,
11 | onClick: handleRun,
12 | onPointerOver: onPointerOver,
13 | }
14 |
15 | return (
16 |
17 |
18 |
19 |
20 | )
21 | }
22 |
--------------------------------------------------------------------------------
/src/elements/geometry/TubeGeometry/props.ts:
--------------------------------------------------------------------------------
1 | const props = {
2 | editable: true,
3 | icon: 'IcBox',
4 | inject: ["OBJECT", "MATERIAL"],
5 | args: [10, 20, 2, 8, false]
6 | }
7 |
8 | export default props
9 |
--------------------------------------------------------------------------------
/src/elements/light/AmbientLight/Controls.tsx:
--------------------------------------------------------------------------------
1 | import { useControls, folder } from "leva";
2 | import { useEffect } from 'react';
3 |
4 | import useProviderControls from '@/hooks/useProviderControls';
5 | import { useElementsStore } from '@/store/elements';
6 | import props from './props';
7 |
8 | export default function Controls() {
9 | const { mountObject, normalizeControls, updateControl } = useProviderControls()
10 | const { current } = useElementsStore()
11 |
12 | const [_, set] = useControls(() => (
13 | normalizeControls(props.inject, {
14 | 'Ambient Light Props': folder(mountObject([])),
15 | })
16 | ), [current])
17 |
18 | useEffect(() => {
19 | if(current.id) {
20 | updateControl(set, props.inject, {})
21 | }
22 | }, [current])
23 |
24 | return
25 | }
--------------------------------------------------------------------------------
/src/elements/light/AmbientLight/index.tsx:
--------------------------------------------------------------------------------
1 | import { Center } from "@react-three/drei"
2 | import { useRef } from "react"
3 | import { Vector3 } from "three"
4 | import { ElementProps } from "@/types"
5 |
6 | type AmbienteLightPropsType = {
7 | position: Vector3 | undefined,
8 | props: ElementProps
9 | }
10 |
11 | export default function AmbientLight({
12 | position,
13 | ...props
14 | }: AmbienteLightPropsType) {
15 | const rLight = useRef(null)
16 | return (
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | )
27 | }
28 |
--------------------------------------------------------------------------------
/src/elements/light/AmbientLight/props.ts:
--------------------------------------------------------------------------------
1 | const props = {
2 | editable: true,
3 | icon: 'IconBulb',
4 | inject: ["OBJECT", "LIGHT"],
5 | }
6 |
7 | export default props
8 |
--------------------------------------------------------------------------------
/src/elements/light/DirectionalLight/Controls.tsx:
--------------------------------------------------------------------------------
1 | import { useControls, folder } from "leva";
2 | import { useEffect } from 'react';
3 |
4 | import useProviderControls from '@/hooks/useProviderControls';
5 | import { useElementsStore } from '@/store/elements';
6 | import props from './props';
7 |
8 | export default function Controls() {
9 | const { mountObject, normalizeControls, updateControl } = useProviderControls()
10 | const { current } = useElementsStore()
11 |
12 | const [_, set] = useControls(() => (
13 | normalizeControls(props.inject, {
14 | 'Directional Light Props': folder(mountObject([
15 | 'shadow',
16 | 'target',
17 | ])),
18 | })
19 | ), [current])
20 |
21 | useEffect(() => {
22 | if(current.id) {
23 | updateControl(set, props.inject, {
24 | shadow: current.shadow,
25 | target: current.target,
26 | })
27 | }
28 | }, [current])
29 |
30 | return
31 | }
--------------------------------------------------------------------------------
/src/elements/light/DirectionalLight/index.tsx:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three'
2 | import { Center, useHelper } from "@react-three/drei"
3 | import { useRef } from "react"
4 | import usePlayer from '@/hooks/usePlayer'
5 | import { ElementProps, MeshRefHelper } from '@/types'
6 | import { neverUsed } from '@/utils/neverUsed'
7 |
8 | interface DirectionalLightProps extends ElementProps {
9 | target: number
10 | }
11 |
12 | export default function DirectionalLight({
13 | target,
14 | ...props
15 | }: DirectionalLightProps) {
16 | const rLight = useRef(null)
17 | const { isPlayerScreen } = usePlayer()
18 |
19 | neverUsed(target)
20 | useHelper(!isPlayerScreen && rLight, THREE.DirectionalLightHelper);
21 |
22 | return (
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | )
33 | }
34 |
--------------------------------------------------------------------------------
/src/elements/light/DirectionalLight/props.ts:
--------------------------------------------------------------------------------
1 | const props = {
2 | editable: true,
3 | icon: 'IconLamp',
4 | inject: ["OBJECT", "LIGHT"],
5 | shadow: 0,
6 | target: 0
7 | }
8 |
9 | export default props
10 |
--------------------------------------------------------------------------------
/src/elements/light/HemisphereLight/Controls.tsx:
--------------------------------------------------------------------------------
1 | import { useControls, folder } from "leva";
2 | import { useEffect } from 'react';
3 |
4 | import useProviderControls from '@/hooks/useProviderControls';
5 | import { useElementsStore } from '@/store/elements';
6 | import props from './props';
7 |
8 | export default function Controls() {
9 | const { mountObject, normalizeControls, updateControl } = useProviderControls()
10 | const { current } = useElementsStore()
11 |
12 | const [_, set] = useControls(() => (
13 | normalizeControls(props.inject, {
14 | 'Hemisphere Light Props': folder(mountObject([
15 | 'groundColor',
16 | 'skyColor',
17 | ])),
18 | })
19 | ), [current])
20 |
21 | useEffect(() => {
22 | if(current.id) {
23 | updateControl(set, props.inject, {
24 | groundColor: current.groundColor,
25 | skyColor: current.skyColor,
26 | })
27 | }
28 | }, [current])
29 |
30 | return
31 | }
--------------------------------------------------------------------------------
/src/elements/light/HemisphereLight/index.tsx:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three'
2 | import { Center, useHelper } from "@react-three/drei"
3 | import { useRef } from "react"
4 | import { ElementProps, MeshRefHelper } from '@/types';
5 |
6 | interface HemisphereLightProps extends ElementProps {
7 | position: THREE.Vector3
8 | }
9 |
10 | export default function HemisphereLight({
11 | position,
12 | ...props
13 | }: HemisphereLightProps) {
14 | const rLight = useRef(null)
15 | useHelper(rLight, THREE.HemisphereLightHelper, 5);
16 |
17 | return (
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | )
28 | }
29 |
--------------------------------------------------------------------------------
/src/elements/light/HemisphereLight/props.ts:
--------------------------------------------------------------------------------
1 | const props = {
2 | editable: true,
3 | icon: 'IconFlame',
4 | inject: ["OBJECT", "LIGHT"],
5 | groundColor: "#ffffff",
6 | skyColor: "#ffffff",
7 | }
8 |
9 | export default props
10 |
--------------------------------------------------------------------------------
/src/elements/light/PointLight/Controls.tsx:
--------------------------------------------------------------------------------
1 | import { useControls, folder } from "leva";
2 | import { useEffect } from 'react';
3 |
4 | import useProviderControls from '@/hooks/useProviderControls';
5 | import { useElementsStore } from '@/store/elements';
6 | import props from './props';
7 |
8 | export default function Controls() {
9 | const { mountObject, normalizeControls, updateControl } = useProviderControls()
10 | const { current } = useElementsStore()
11 |
12 | const [_, set] = useControls(() => (
13 | normalizeControls(props.inject, {
14 | 'Point Light Props': folder(mountObject([
15 | 'decay',
16 | 'distance',
17 | 'power',
18 | 'shadow',
19 | ])),
20 | })
21 | ), [current])
22 |
23 | useEffect(() => {
24 | if(current.id) {
25 | updateControl(set, props.inject, {
26 | decay: current.decay,
27 | distance: current.distance,
28 | power: current.power,
29 | shadow: current.shadow,
30 | })
31 | }
32 | }, [current])
33 |
34 | return
35 | }
--------------------------------------------------------------------------------
/src/elements/light/PointLight/index.tsx:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three'
2 | import { Center, useHelper } from "@react-three/drei"
3 | import { useRef } from "react"
4 | import { ElementProps, MeshRefHelper } from '@/types'
5 | import { useDocumentStore } from '@/store/document'
6 |
7 | export default function PointLight(props: ElementProps) {
8 | const rLight = useRef(null)
9 | const { document } = useDocumentStore()
10 |
11 | useHelper(document.helpers && rLight, THREE.PointLightHelper);
12 |
13 | return (
14 |
15 |
16 | {!document.helpers && (
17 |
18 |
19 |
20 |
21 | )}
22 |
23 |
24 |
25 | )
26 | }
27 |
--------------------------------------------------------------------------------
/src/elements/light/PointLight/props.ts:
--------------------------------------------------------------------------------
1 | const props = {
2 | editable: true,
3 | icon: 'IconLamp2',
4 | inject: ["OBJECT", "LIGHT"],
5 | decay: 2,
6 | distance: 0,
7 | power: null,
8 | shadow: null
9 | }
10 |
11 | export default props
12 |
--------------------------------------------------------------------------------
/src/elements/light/SpotLight/Controls.tsx:
--------------------------------------------------------------------------------
1 | import { useControls, folder } from "leva";
2 | import { useEffect } from 'react';
3 |
4 | import useProviderControls from '@/hooks/useProviderControls';
5 | import { useElementsStore } from '@/store/elements';
6 | import props from './props';
7 |
8 | export default function Controls() {
9 | const { mountObject, normalizeControls, updateControl } = useProviderControls()
10 | const { current } = useElementsStore()
11 |
12 | const [_, set] = useControls(() => (
13 | normalizeControls(props.inject, {
14 | 'Spot Light Props': folder(mountObject([
15 | 'angle',
16 | 'decay',
17 | 'distance',
18 | 'penumbra',
19 | 'power',
20 | 'shadow',
21 | 'target',
22 | 'map',
23 | ])),
24 | })
25 | ), [current])
26 |
27 | useEffect(() => {
28 | if(current.id) {
29 | updateControl(set, props.inject, {
30 | angle: current.angle,
31 | decay: current.decay,
32 | distance: current.distance,
33 | penumbra: current.penumbra,
34 | power: current.power,
35 | shadow: current.shadow,
36 | target: current.target,
37 | map: current.map,
38 | })
39 | }
40 | }, [current])
41 |
42 | return
43 | }
--------------------------------------------------------------------------------
/src/elements/light/SpotLight/index.tsx:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three'
2 | import { Center, useHelper } from "@react-three/drei"
3 | import { useRef } from "react"
4 | import usePlayer from '@/hooks/usePlayer'
5 | import { ElementProps, MeshRefHelper } from '@/types'
6 |
7 | interface SpotLightProps extends ElementProps {
8 | target: number
9 | }
10 |
11 | export default function SpotLight({
12 | target,
13 | ...props
14 | }: SpotLightProps) {
15 | const rLight = useRef(null)
16 | const { isPlayerScreen } = usePlayer()
17 |
18 | useHelper(!isPlayerScreen && rLight, THREE.SpotLightHelper);
19 |
20 | return (
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | )
31 | }
32 |
--------------------------------------------------------------------------------
/src/elements/light/SpotLight/props.ts:
--------------------------------------------------------------------------------
1 | const props = {
2 | editable: true,
3 | icon: 'IconLighter',
4 | inject: ["OBJECT", "LIGHT"],
5 | angle: 0.2,
6 | decay: 0,
7 | distance: 10,
8 | penumbra: 0,
9 | power: 1,
10 | shadow: 0,
11 | target: 0,
12 | map: 0
13 | }
14 |
15 | export default props
16 |
--------------------------------------------------------------------------------
/src/hooks/useCodeEditor.ts:
--------------------------------------------------------------------------------
1 | import getOpenedFiles from '../utils/getOpenedFiles'
2 | import { useFilesStore } from '../store/files'
3 |
4 | export default function useCodeEditor() {
5 | const { files, setFiles } = useFilesStore()
6 |
7 | const openedObjects = getOpenedFiles(files) || [];
8 |
9 | function closeFile(id: string) {
10 | setFiles([
11 | ...files.map((item) => {
12 | if(item.id===id){
13 | return {
14 | ...item,
15 | opened: false,
16 | selected: false
17 | }
18 | }
19 | return item
20 | })
21 | ])
22 | }
23 |
24 | function updateCode(id: string, code: string | undefined) {
25 | setFiles([
26 | ...files.map((item) => {
27 | if(item.id===id){
28 | return {
29 | ...item,
30 | content: code
31 | }
32 | }
33 | return item
34 | })
35 | ])
36 | }
37 |
38 | function openFile(id: string) {
39 | setFiles([
40 | ...files.map((item) => {
41 | if(item.id===id){
42 | return {
43 | ...item,
44 | opened: true,
45 | selected: true,
46 | }
47 | }
48 | return {
49 | ...item,
50 | selected: false
51 | }
52 | })
53 | ])
54 | }
55 |
56 | return {
57 | openedObjects,
58 | closeFile,
59 | updateCode,
60 | openFile
61 | }
62 | }
--------------------------------------------------------------------------------
/src/hooks/useComponentFunctions.ts:
--------------------------------------------------------------------------------
1 | import { useEffect } from "react";
2 | import useSdk from "../hooks/useSdk";
3 | import usePlayer from "./usePlayer";
4 | import { useFilesStore } from "../store/files";
5 | import { ThreeEvent } from "@react-three/fiber";
6 | import { ElementType } from "@/types";
7 |
8 | export default function useComponentFunctions(props: ElementType) {
9 | const { isPlayerScreen } = usePlayer()
10 | const { files } = useFilesStore()
11 | const sdk = useSdk()
12 |
13 | function runFunction(e = props, functionName: string) {
14 | if(props?.fileId) {
15 | const he = files.find(({ id }) => id === props?.fileId)
16 | if (he?.content) {
17 | sdk.run(e, he.content, functionName);
18 | }
19 | }
20 | }
21 |
22 | function onMount() {
23 | runFunction(props, 'onMount')
24 | }
25 |
26 | useEffect(() => {
27 | if(isPlayerScreen) {
28 | onMount()
29 | }
30 | }, [])
31 |
32 | function handleRun(e: ThreeEvent) {
33 | runFunction(e, 'onClick')
34 | }
35 |
36 | function onPointerOver(e: ThreeEvent) {
37 | runFunction(e, 'onPointerOver')
38 | }
39 |
40 | return {
41 | runFunction,
42 | handleRun,
43 | onPointerOver
44 | }
45 | }
--------------------------------------------------------------------------------
/src/hooks/useElement.ts:
--------------------------------------------------------------------------------
1 | import mountNewElement from '../utils/mountNewElement'
2 | import { useElementsStore } from '../store/elements'
3 | import { ElementType } from '@/types'
4 |
5 | export function useElement() {
6 | const {
7 | elements,
8 | createGlbElement,
9 | createElement,
10 | createElements
11 | } = useElementsStore()
12 |
13 | function customCreateGlbElement(props: ElementType) {
14 | createGlbElement(mountNewElement('Glb', props))
15 | }
16 |
17 | function customCreateElement({ type }: ElementType) {
18 | createElement(mountNewElement(type, {}))
19 | }
20 |
21 | function customCreateElements(data: ElementType[]) {
22 | createElements(
23 | data.map((item: string) => mountNewElement(item, {}))
24 | )
25 | }
26 |
27 | return {
28 | elements,
29 | createGlbElement: customCreateGlbElement,
30 | createElement: customCreateElement,
31 | createElements: customCreateElements
32 | }
33 | }
--------------------------------------------------------------------------------
/src/hooks/useKeyboardListener.ts:
--------------------------------------------------------------------------------
1 | import { useHotkeys } from '@mantine/hooks'
2 | import { nanoid } from 'nanoid'
3 | import { toast } from 'react-hot-toast';
4 |
5 | import { useElementsStore } from '../store/elements'
6 | import useModal from './useModal'
7 |
8 | export default function useKeyboardListener() {
9 | const { current, createElement, removeElement } = useElementsStore()
10 | const { openSearchModal } = useModal()
11 |
12 | function saveDocument(event: KeyboardEvent) {
13 | event.preventDefault();
14 | const titleValue = "All saved" + current?.id
15 | const messageValue = `It's not required, autosave is already active!`
16 | toast(`${titleValue}\n${messageValue}`)
17 | }
18 |
19 | function cloneElement(event: KeyboardEvent) {
20 | event.preventDefault();
21 | createElement({
22 | ...current,
23 | id: nanoid(6),
24 | object: null,
25 | eventObject: null,
26 | fileId: ''
27 | })
28 | }
29 |
30 | function mountAndRemoveElement(event: KeyboardEvent) {
31 | event.preventDefault();
32 | if(current) {
33 | removeElement(current.id);
34 | }
35 | }
36 |
37 | useHotkeys([
38 | ['mod+K', openSearchModal],
39 | ['mod+S', saveDocument],
40 | ['mod+D', cloneElement],
41 | ['Backspace', mountAndRemoveElement],
42 | ]);
43 |
44 | return null
45 | }
46 |
--------------------------------------------------------------------------------
/src/hooks/useModal.ts:
--------------------------------------------------------------------------------
1 | import { useLayoutStore } from '../store/layout'
2 |
3 | export default function useModal() {
4 | const { layout, setModalOpen } = useLayoutStore()
5 |
6 | const openSettingsModal = () => setModalOpen({ name: 'settings', value: true })
7 | const closeSettingsModal = () => setModalOpen({ name: 'settings', value: false })
8 |
9 | const openHubModal = () => setModalOpen({ name: 'hub', value: true })
10 | const closeHubModal = () => setModalOpen({ name: 'hub', value: false })
11 |
12 | const openNewDocumentModal = () => setModalOpen({ name: 'newDocument', value: true })
13 | const closeNewDocumentModal = () => setModalOpen({ name: 'newDocument', value: false })
14 |
15 | const openExportModal = () => setModalOpen({ name: 'export', value: true })
16 | const closeExportModal = () => setModalOpen({ name: 'export', value: false })
17 |
18 | const openCommandsModal = () => setModalOpen({ name: 'commands', value: true })
19 | const closeCommandsModal = () => setModalOpen({ name: 'commands', value: false })
20 |
21 | const openTermsModal = () => setModalOpen({ name: 'terms', value: true })
22 | const closeTermsModal = () => setModalOpen({ name: 'terms', value: false })
23 |
24 | const openNewFileModal = () => setModalOpen({ name: 'newFile', value: true })
25 | const closeNewFileModal = () => setModalOpen({ name: 'newFile', value: false })
26 |
27 | const openSearchModal = () => setModalOpen({ name: 'search', value: true })
28 | const closeSearchModal = () => setModalOpen({ name: 'search', value: false })
29 |
30 | return ({
31 | isOpenedSettingsModal: layout.modal.open.settings,
32 | isOpenedHubModal: layout.modal.open.hub,
33 | isOpenedNewDocumentModal: layout.modal.open.newDocument,
34 | isOpenedExportModal: layout.modal.open.export,
35 | isOpenedCommandsModal: layout.modal.open.commands,
36 | isOpenedTermsModal: layout.modal.open.terms,
37 | isOpeneNewFileModal: layout.modal.open.newFile,
38 | isOpeneSearchModal: layout.modal.open.search,
39 |
40 | openSettingsModal,
41 | openHubModal,
42 | openNewDocumentModal,
43 | openExportModal,
44 | openCommandsModal,
45 | openTermsModal,
46 | openNewFileModal,
47 | openSearchModal,
48 |
49 | closeSettingsModal,
50 | closeHubModal,
51 | closeNewDocumentModal,
52 | closeExportModal,
53 | closeCommandsModal,
54 | closeTermsModal,
55 | closeNewFileModal,
56 | closeSearchModal,
57 | })
58 | }
--------------------------------------------------------------------------------
/src/hooks/usePlayer.ts:
--------------------------------------------------------------------------------
1 | const PLAYER_URI = '?play=all'
2 |
3 | export default function usePlayer() {
4 | const isPlayerScreen = window.location.href.includes(PLAYER_URI)
5 |
6 | const goPlayerScreen = () => window.open('/' + PLAYER_URI, '_blank');
7 |
8 | return {
9 | isPlayerScreen,
10 | goPlayerScreen
11 | }
12 | }
--------------------------------------------------------------------------------
/src/hooks/useProjectFiles.ts:
--------------------------------------------------------------------------------
1 | import { useFilesStore } from "../store/files"
2 |
3 | export default function useProjectFiles() {
4 | const { files, setFiles } = useFilesStore()
5 |
6 | function openFile(id: string) {
7 | setFiles([
8 | ...files.map((item) => {
9 | if(item.id===id){
10 | return {
11 | ...item,
12 | opened: true,
13 | selected: true,
14 | }
15 | }
16 | return {
17 | ...item,
18 | selected: false
19 | }
20 | })
21 | ])
22 | }
23 |
24 | return {
25 | openFile
26 | }
27 | }
--------------------------------------------------------------------------------
/src/hooks/useScrollToElement.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef } from 'react';
2 |
3 | const useScrollToElement = (elementId: string) => {
4 | const targetRef = useRef(null);
5 |
6 | useEffect(() => {
7 | const targetElement = document.getElementById(elementId);
8 | targetRef.current = targetElement;
9 |
10 | const handleScroll = () => {
11 | if (targetRef.current) {
12 | const { top } = targetRef.current.getBoundingClientRect();
13 | if (top <= window.innerHeight) {
14 | console.log('Element scrolled into view!');
15 | }
16 | }
17 | };
18 |
19 | window.addEventListener('scroll', handleScroll);
20 |
21 | return () => {
22 | window.removeEventListener('scroll', handleScroll);
23 | };
24 | }, [elementId]);
25 |
26 | return targetRef;
27 | };
28 |
29 | export default useScrollToElement;
--------------------------------------------------------------------------------
/src/hooks/useSdk.ts:
--------------------------------------------------------------------------------
1 | import { toast } from 'react-hot-toast/headless';
2 |
3 | import usePlayer from './usePlayer';
4 | import getFunctionByName from '../utils/getFunctionByName';
5 |
6 | function runFunctionByName(code: string, functionName: string) {
7 | if(code.includes(`function ${functionName}(`)) {
8 | return functionName + '(sdk);'
9 | }
10 | }
11 |
12 | type NotificationContent = {
13 | title: string
14 | text: string
15 | }
16 |
17 | export default function useSdk() {
18 | const { isPlayerScreen } = usePlayer()
19 |
20 | function run(
21 | component = null,
22 | code = '',
23 | functionName = 'main'
24 | ) {
25 | if(!isPlayerScreen) {
26 | return null
27 | }
28 |
29 | const onStart = () => {
30 | toast('Welcome to ThreeD SDK.')
31 | }
32 |
33 | const onHelp = () => {
34 | const titleValue = 'Need a help?'
35 | const messageValue = 'Access docs.threed.world to more derails.'
36 | toast(`${titleValue}\n${messageValue}`)
37 | }
38 |
39 | const sdk = {
40 | util: () => null,
41 | start: onStart,
42 | help: onHelp,
43 | component,
44 | notifications: {
45 | show: ({title, text}: NotificationContent) => toast(`${title}\n${text}`)
46 | }
47 | }
48 |
49 | sdk.util()
50 |
51 | const codex = `
52 | ${getFunctionByName(code, functionName)}
53 | ${runFunctionByName(code, functionName)}
54 | `
55 |
56 | eval(codex)
57 | }
58 |
59 | return {
60 | run
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/hooks/useStorageProvider.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react'
2 | import clearLocalStorage from '../utils/clearLocalStorage'
3 | // import getObjectDiff from '../../utils/getObjectDiff'
4 |
5 | export default function useStorageProvider() {
6 | const [cleaning, setClear] = useState(false)
7 |
8 | useEffect(() => {
9 | if(window.location.href.includes('?reset=document')) {
10 | setClear(true)
11 | clearLocalStorage()
12 | setTimeout(() => {
13 | window.location.href = '/'
14 | }, 1000);
15 | }
16 | }, [])
17 |
18 | const isLoading = cleaning
19 |
20 | return {
21 | isLoading
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { ClassValue, clsx } from "clsx"
2 | import { twMerge } from "tailwind-merge"
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs))
6 | }
7 |
--------------------------------------------------------------------------------
/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom/client'
3 |
4 | import App from './components/App.tsx'
5 | import '@/styles/globals.css'
6 | import './styles/index.css'
7 |
8 | ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
9 |
10 |
13 | ,
14 | )
15 |
--------------------------------------------------------------------------------
/src/matrix/element.ts:
--------------------------------------------------------------------------------
1 | import { elementThree } from "@/elements"
2 |
3 | const element = elementThree
4 |
5 | export default element
--------------------------------------------------------------------------------
/src/matrix/material.ts:
--------------------------------------------------------------------------------
1 | import {
2 | MeshReflectorMaterial,
3 | MeshWobbleMaterial,
4 | MeshDistortMaterial,
5 | MeshRefractionMaterial,
6 | MeshTransmissionMaterial,
7 | MeshDiscardMaterial,
8 | PointMaterial,
9 | shaderMaterial,
10 | } from "@react-three/drei"
11 |
12 | const material = {
13 | meshDistortMaterial: MeshDistortMaterial,
14 | meshReflectorMaterial: MeshReflectorMaterial,
15 | meshRefractionMaterial: MeshRefractionMaterial,
16 | meshTransmissionMaterial: MeshTransmissionMaterial,
17 | meshWobbleMaterial: MeshWobbleMaterial,
18 | meshDiscardMaterial: MeshDiscardMaterial,
19 | pointMaterial: PointMaterial,
20 | shaderMaterial: shaderMaterial,
21 |
22 | lineBasicMaterial: 'lineBasicMaterial',
23 | lineDashedMaterial: 'lineDashedMaterial',
24 | material: 'material',
25 | meshBasicMaterial: 'meshBasicMaterial',
26 | meshDepthMaterial: 'meshDepthMaterial',
27 | meshDistanceMaterial: 'meshDistanceMaterial',
28 | meshLambertMaterial: 'meshLambertMaterial',
29 | meshMatcapMaterial: 'meshMatcapMaterial',
30 | meshNormalMaterial: 'meshNormalMaterial',
31 | meshPhongMaterial: 'meshPhongMaterial',
32 | meshPhysicalMaterial: 'meshPhysicalMaterial',
33 | meshStandardMaterial: 'meshStandardMaterial',
34 | meshToonMaterial: 'meshToonMaterial',
35 | pointsMaterial: 'pointsMaterial',
36 | rawShaderMaterial: 'rawShaderMaterial',
37 | shadowMaterial: 'shadowMaterial',
38 | spriteMaterial: 'spriteMaterial'
39 | }
40 |
41 | export default material
42 |
--------------------------------------------------------------------------------
/src/store/commands.ts:
--------------------------------------------------------------------------------
1 | import { create } from 'zustand'
2 | import { persist } from 'zustand/middleware'
3 |
4 | import CONSTANTS from '../constants'
5 |
6 | type Command = {
7 | label: string
8 | data: string[]
9 | }
10 |
11 | interface MyState {
12 | commands: Command[]
13 | setCommands: (commands: Command[]) => void
14 | updateCommands: (commands: Command[]) => void
15 | }
16 |
17 | export const useCommandsStore = create()(
18 | persist(
19 | (set, get) => ({
20 | commands: CONSTANTS.commands,
21 | setCommands: (commands) => set({ commands }),
22 | updateCommands: (commands) => set({
23 | commands: { ...get().commands, ...commands }
24 | })
25 | }),
26 | {
27 | name: '@threed-commands',
28 | partialize: (state) => ({ commands: state.commands }),
29 | }
30 | )
31 | )
--------------------------------------------------------------------------------
/src/store/document.ts:
--------------------------------------------------------------------------------
1 | import { create } from 'zustand'
2 | import { persist } from 'zustand/middleware'
3 |
4 | import CONSTANTS, { DocumentType } from '../constants'
5 | import { GenericType } from '@/types'
6 |
7 | interface MyState {
8 | document: DocumentType
9 | setDocument: (document: DocumentType) => void
10 | updateDocument: (document: GenericType) => void
11 | updateDocumentOrbitalControls: (orbitalControls: GenericType) => void
12 | updateDocumentCanvas: (canvas: GenericType) => void
13 | }
14 |
15 | export const useDocumentStore = create()(
16 | persist(
17 | (set, get) => ({
18 | document: CONSTANTS.document,
19 | setDocument: (document) => set({ document }),
20 | updateDocument: (document) => set({
21 | document: { ...get().document, ...document }
22 | }),
23 | updateDocumentOrbitalControls: (orbitalControls) => set({
24 | document: { ...get().document, orbitalControls: { ...get().document.orbitalControls, ...orbitalControls } }
25 | }),
26 | updateDocumentCanvas: (canvas) => set({
27 | document: { ...get().document, canvas: { ...get().document.canvas, ...canvas } }
28 | })
29 | }),
30 | {
31 | name: '@threed-document',
32 | partialize: (state) => ({ document: state.document }),
33 | }
34 | )
35 | )
--------------------------------------------------------------------------------
/src/store/elements.ts:
--------------------------------------------------------------------------------
1 | import { ElementType } from '@/types'
2 | import { create } from 'zustand'
3 | import { persist } from 'zustand/middleware'
4 |
5 | interface MyState {
6 | elements: ElementType[]
7 | current: ElementType
8 | contextElementId: string | null
9 | setCurrent: (current: ElementType) => void
10 | updateCurrent: (current: ElementType) => void
11 | setContextElementId: (contextElementId: string | null) => void
12 | setElements: (elements: ElementType[]) => void
13 | updateElements: (elements: ElementType[]) => void
14 | createGlbElement: (element: ElementType) => void
15 | createElement: (element: ElementType) => void
16 | createElements: (elements: ElementType[]) => void
17 | updateElement: (element: ElementType) => void
18 | removeElement: (id: string) => void
19 | }
20 |
21 | export const useElementsStore = create()(
22 | persist(
23 | (set, get) => ({
24 | elements: [],
25 | current: null,
26 | contextElementId: null,
27 | setCurrent: (current) => set({ current }),
28 | updateCurrent: (current) => set({ ...get().current, current }),
29 | setContextElementId: (contextElementId) => set({ contextElementId }),
30 | setElements: (elements) => set({ elements }),
31 | updateElements: (elements) => set({
32 | elements: [ ...get().elements, ...elements ]
33 | }),
34 | createGlbElement: (element) => set({
35 | elements: [ ...get().elements, element ],
36 | }),
37 | createElement: (element) => set({
38 | elements: [ ...get().elements, element ]
39 | }),
40 | createElements: (elements) => set({
41 | elements: [ ...get().elements, ...elements ]
42 | }),
43 | updateElement: (element) => set({
44 | current: {
45 | ...get().current || {},
46 | ...get().elements.find(({id}) => id===element?.id),
47 | ...element
48 | },
49 | elements: [
50 | ...get().elements.map((item) => {
51 | if(item?.id === element?.id) {
52 | return {
53 | ...item,
54 | ...element
55 | }
56 | }
57 | return item
58 | })
59 | ]
60 | }),
61 | removeElement: (id) => set({
62 | current: null,
63 | elements: [
64 | ...get().elements.filter((item) => item.id !== id)
65 | ]
66 | })
67 | }),
68 | {
69 | name: '@threed-elements',
70 | partialize: (state) => ({ elements: state.elements }),
71 | }
72 | )
73 | )
74 |
75 |
--------------------------------------------------------------------------------
/src/store/files.ts:
--------------------------------------------------------------------------------
1 | import { create } from 'zustand'
2 | import { persist } from 'zustand/middleware'
3 | import { File } from '../types'
4 |
5 | interface MyState {
6 | files: File[]
7 | setFiles: (files: File[]) => void
8 | updateFiles: (files: File[]) => void
9 | addFile: (file: File) => void
10 | }
11 |
12 | export const useFilesStore = create()(
13 | persist(
14 | (set, get) => ({
15 | files: [],
16 | setFiles: (files) => set({ files }),
17 | updateFiles: (files) => set({
18 | files: [ ...get().files, ...files ]
19 | }),
20 | addFile: (file) => set({
21 | files: [ ...get().files, file ]
22 | })
23 | }),
24 | {
25 | name: '@threed-files',
26 | partialize: (state) => ({ files: state.files }),
27 | }
28 | )
29 | )
--------------------------------------------------------------------------------
/src/store/graph.ts:
--------------------------------------------------------------------------------
1 | import { create } from 'zustand'
2 | import { persist } from 'zustand/middleware'
3 | import { Graph } from '@/types'
4 |
5 | interface MyState {
6 | graph: Graph
7 | graphLock: boolean,
8 | setGraph: (graph: Graph) => void
9 | updateGraph: (graph: Graph) => void
10 | setGraphLock: (graphLock: boolean) => void
11 | }
12 |
13 | export const useGraphStore = create()(
14 | persist(
15 | (set, get) => ({
16 | graph: null,
17 | graphLock: false,
18 | setGraph: (graph) => set({ graph }),
19 | updateGraph: (graph) => set({
20 | graph: { ...get().graph, ...graph }
21 | }),
22 | setGraphLock: (graphLock) => set({ graphLock }),
23 | }),
24 | {
25 | name: '@threed-graph',
26 | partialize: (state) => ({ graph: state.graph }),
27 | }
28 | )
29 | )
--------------------------------------------------------------------------------
/src/store/history.ts:
--------------------------------------------------------------------------------
1 | import { create } from 'zustand'
2 | import { persist } from 'zustand/middleware'
3 |
4 | type History = {
5 | id: string
6 | content: string
7 | }
8 |
9 | interface MyState {
10 | history: History[]
11 | setHistory: (history: History[]) => void
12 | updateHistory: (history: History[]) => void
13 | }
14 |
15 | export const useHistoryStore = create()(
16 | persist(
17 | (set, get) => ({
18 | history: [],
19 | setHistory: (history) => set({ history }),
20 | updateHistory: (history) => set({
21 | history: [ ...get().history, ...history ]
22 | })
23 | }),
24 | {
25 | name: '@threed-history',
26 | partialize: (state) => ({ history: state.history }),
27 | }
28 | )
29 | )
--------------------------------------------------------------------------------
/src/store/layout.ts:
--------------------------------------------------------------------------------
1 | import { create } from 'zustand'
2 | import { persist } from 'zustand/middleware'
3 |
4 | import CONSTANTS, { LayoutType } from '../constants'
5 | import { GenericType } from '@/types'
6 |
7 | interface ItemType {
8 | name: string
9 | value: GenericType
10 | }
11 |
12 | interface MyState {
13 | layout: LayoutType
14 | footer: string | null
15 | setLayout: (layout: LayoutType) => void
16 | setFooter: (footer: string | null) => void
17 | updateLayout: (layout: LayoutType) => void
18 | setModalOpen: (modal: ItemType) => void
19 | setToolbarOpen: (toolbar: ItemType) => void
20 | setToolbarShow: (toolbar: ItemType) => void
21 | }
22 |
23 | export const useLayoutStore = create()(
24 | persist(
25 | (set, get) => ({
26 | layout: CONSTANTS.layout,
27 | footer: '',
28 | setLayout: (layout) => set({ layout }),
29 | setFooter: (footer) => set({ footer }),
30 | updateLayout: (layout) => set({
31 | layout: { ...get().layout, ...layout }
32 | }),
33 | setModalOpen: (modal) => set({
34 | layout: {
35 | ...get().layout,
36 | modal: {
37 | ...get().layout.modal,
38 | open: {
39 | ...get().layout.modal.open,
40 | [modal.name]: modal.value
41 | }
42 | }
43 | }
44 | }),
45 | setToolbarOpen: (toolbar) => set({
46 | layout: {
47 | ...get().layout,
48 | toolbar: {
49 | ...get().layout.toolbar,
50 | [toolbar.name]: {
51 | ...get().layout.toolbar[toolbar.name],
52 | expanded: toolbar.value
53 | }
54 | }
55 | }
56 | }),
57 | setToolbarShow: (toolbar) => set({
58 | layout: {
59 | ...get().layout,
60 | toolbar: {
61 | ...get().layout.toolbar,
62 | [toolbar.name]: {
63 | ...get().layout.toolbar[toolbar.name],
64 | visible: toolbar.value
65 | }
66 | }
67 | }
68 | })
69 | }),
70 | {
71 | name: '@threed-layout',
72 | partialize: (state) => ({ layout: state.layout }),
73 | }
74 | )
75 | )
76 |
--------------------------------------------------------------------------------
/src/store/settings.ts:
--------------------------------------------------------------------------------
1 | import { create } from 'zustand'
2 | import { persist } from 'zustand/middleware'
3 |
4 | import CONSTANTS, { SettingsType } from '../constants'
5 |
6 | interface MyState {
7 | loadingView: boolean
8 | settings: SettingsType
9 | setLoadingView: (loadingView: boolean) => void
10 | setSettings: (settings: SettingsType) => void
11 | setTheme: (theme: string) => void
12 | setPicker: (picker: string) => void
13 | setView: (main: string) => void
14 | setToolMove: (move: string) => void
15 | }
16 |
17 | export const useSettingsStore = create()(
18 | persist(
19 | (set, get) => ({
20 | loadingView: false,
21 | settings: CONSTANTS.settings,
22 | setLoadingView: (loadingView) => set({ loadingView }),
23 | setSettings: (settings) => set({ settings }),
24 | setTheme: (theme) => set({ settings: { ...get().settings, theme } }),
25 | setPicker: (picker) => set({ settings: { ...get().settings, picker } }),
26 | setView: (main) => set({ settings: { ...get().settings, main } }),
27 | setToolMove: (move: string) => {
28 | set({
29 | settings: {
30 | ...get().settings,
31 | tool: {
32 | ...get().settings.tool,
33 | move
34 | }
35 | }
36 | })
37 | }
38 | }),
39 | {
40 | name: '@threed-settings',
41 | partialize: (state) => ({ settings: state.settings }),
42 | }
43 | )
44 | )
--------------------------------------------------------------------------------
/src/styles/SidebarCode.css:
--------------------------------------------------------------------------------
1 | .directory {
2 | user-select: none;
3 | border-radius: 0.4em;
4 | position: relative;
5 | width: 100%;
6 | }
7 |
8 | li.tree-branch-wrapper {
9 | width: 100%;
10 | }
11 | .directory .tree,
12 | .directory .tree-node,
13 | .directory .tree-node__branch {
14 | display: flex !important;
15 | }
16 | .directory .tree,
17 | .directory .tree-node,
18 | .directory .tree-node-group {
19 | list-style: none;
20 | margin: 0;
21 | padding: 0;
22 |
23 | }
24 |
25 | .directory .tree-branch-wrapper,
26 | .directory .tree-node__leaf {
27 | outline: none;
28 | outline: none;
29 | }
30 |
31 | .directory .tree-node {
32 | cursor: pointer;
33 | border-radius: 3px;
34 | margin-bottom: 2px;
35 | border: 1px solid transparent;
36 | }
37 |
38 | .directory .tree-node:hover {
39 | border: 1px solid #0e1629;
40 |
41 | }
42 |
43 | .directory .tree .tree-node--focused {
44 | border: 1px solid #0e1629;
45 |
46 | }
47 |
48 | .directory .tree .tree-node--selected {
49 | background: #0e1629;
50 | }
51 |
52 | .directory .tree-node__branch {
53 | display: block;
54 | }
55 |
56 | .directory .icon {
57 | vertical-align: middle;
58 | padding-right: 5px;
59 | margin-left: 10px;
60 | }
--------------------------------------------------------------------------------
/src/styles/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @layer base {
6 | :root {
7 | --background: 0 0% 100%;
8 | --foreground: 222.2 47.4% 11.2%;
9 |
10 | --muted: 210 40% 96.1%;
11 | --muted-foreground: 215.4 16.3% 46.9%;
12 |
13 | --popover: 0 0% 100%;
14 | --popover-foreground: 222.2 47.4% 11.2%;
15 |
16 | --border: 214.3 31.8% 91.4%;
17 | --input: 214.3 31.8% 91.4%;
18 |
19 | --card: 0 0% 100%;
20 | --card-foreground: 222.2 47.4% 11.2%;
21 |
22 | --primary: 222.2 47.4% 11.2%;
23 | --primary-foreground: 210 40% 98%;
24 |
25 | --secondary: 210 40% 96.1%;
26 | --secondary-foreground: 222.2 47.4% 11.2%;
27 |
28 | --accent: 210 40% 96.1%;
29 | --accent-foreground: 222.2 47.4% 11.2%;
30 |
31 | --destructive: 0 100% 50%;
32 | --destructive-foreground: 210 40% 98%;
33 |
34 | --ring: 215 20.2% 65.1%;
35 |
36 | --radius: 0.5rem;
37 | }
38 |
39 | .dark {
40 | --background: 224 71% 4%;
41 | --foreground: 213 31% 91%;
42 |
43 | --muted: 223 47% 11%;
44 | --muted-foreground: 215.4 16.3% 56.9%;
45 |
46 | --accent: 216 34% 17%;
47 | --accent-foreground: 210 40% 98%;
48 |
49 | --popover: 224 71% 4%;
50 | --popover-foreground: 215 20.2% 65.1%;
51 |
52 | --border: 216 34% 17%;
53 | --input: 216 34% 17%;
54 |
55 | --card: 224 71% 4%;
56 | --card-foreground: 213 31% 91%;
57 |
58 | --primary: 210 40% 98%;
59 | --primary-foreground: 222.2 47.4% 1.2%;
60 |
61 | --secondary: 222.2 47.4% 11.2%;
62 | --secondary-foreground: 210 40% 98%;
63 |
64 | --destructive: 0 63% 31%;
65 | --destructive-foreground: 210 40% 98%;
66 |
67 | --ring: 216 34% 17%;
68 |
69 | --radius: 0.5rem;
70 | }
71 | }
72 |
73 | @layer base {
74 | * {
75 | @apply border-border;
76 | }
77 | body {
78 | @apply bg-background text-foreground;
79 | font-feature-settings: "rlig" 1, "calt" 1;
80 | }
81 | }
--------------------------------------------------------------------------------
/src/styles/layout.css:
--------------------------------------------------------------------------------
1 | .sectionHorizontal {
2 | height:40px;
3 | overflow: hidden;
4 | }
5 |
6 | .sectionVertical {
7 | width: 40px;
8 | display: inline-block;
9 | overflow: hidden;
10 | }
11 |
12 | .sectionVertical.opened {
13 | width: 250px;
14 | }
15 |
16 | .sectionVertical.opened.big {
17 | width: 350px;
18 | }
19 |
20 | .sectionWrap {
21 | width: 100vw;
22 | height: 100vh;
23 | background-color: #222;
24 | overflow: hidden;
25 | }
26 |
27 | .sectionContent {
28 | overflow: hidden;
29 | margin-top: -1px;
30 | }
31 |
32 | .sectionMiddle {
33 | background-color: black;
34 | display: inline-block;
35 | overflow: hidden;
36 | /* border: solid 0.5px #222; */
37 | }
38 |
39 | .playerScreen {
40 | width: 100vw;
41 | height: 100vh;
42 | background-color: black;
43 | }
--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------
1 | export type GenericType = any
2 |
3 | export type ObjectType = {
4 | [key: string]: GenericType
5 | }
6 |
7 | export type InputEvent = GenericType
8 | export type BasePropertiesFormSelects = GenericType
9 | export type InputValueType = GenericType
10 | export type ReactThreeElement = GenericType
11 | export type ElementProps = GenericType
12 | export type MeshRefHelper = GenericType
13 | export type useGLTFType = GenericType
14 | export type ElementType = GenericType
15 | export type Graph = GenericType
16 | export type ActionItemType = GenericType
17 | export type MatetialType = GenericType
18 | export type PropertiesFormSelectOption = GenericType
19 | export type IconType = GenericType
20 | export type MaterialType = GenericType
21 | export type JsonMaterialsType = GenericType
22 |
23 | export interface File {
24 | id: string
25 | name: string
26 | content?: string
27 | opened?: boolean
28 | selected?: boolean
29 | children?: File[]
30 | }
31 |
--------------------------------------------------------------------------------
/src/utils/clearLocalStorage.ts:
--------------------------------------------------------------------------------
1 | export default function clearLocalStorage() {
2 | localStorage.removeItem('@threed-graph')
3 | localStorage.removeItem('@threed-commands')
4 | localStorage.removeItem('@threed-document')
5 | localStorage.removeItem('@threed-files')
6 | localStorage.removeItem('@threed-settings')
7 | localStorage.removeItem('@threed-layout')
8 | localStorage.removeItem('@threed-history')
9 | localStorage.removeItem('@threed-elements')
10 | }
--------------------------------------------------------------------------------
/src/utils/getEditorLang.ts:
--------------------------------------------------------------------------------
1 | type getEditorLangProps = {
2 | name: string
3 | }
4 |
5 | const getEditorLang = ({ name }: getEditorLangProps): string => {
6 | const extension = name.slice(name.lastIndexOf(".") + 1);
7 | switch (extension) {
8 | case "js":
9 | return 'javascript';
10 | case "css":
11 | return 'css';
12 | case "json":
13 | return 'json';
14 | case "npmignore":
15 | return 'markdown';
16 | default:
17 | return 'markdown';
18 | }
19 | };
20 |
21 | export default getEditorLang
22 |
--------------------------------------------------------------------------------
/src/utils/getFunctionByName.ts:
--------------------------------------------------------------------------------
1 | export default function getFunctionByName(text: string, func = 'main') {
2 | const lines = text.split('\n');
3 | let startIndex = -1;
4 | let endIndex = -1;
5 |
6 | for (let i = 0; i < lines.length; i++) {
7 | if (lines[i].trim().startsWith(`function ${func}`)) {
8 | startIndex = i;
9 | }
10 |
11 | if (startIndex !== -1 && lines[i].trim() === '}') {
12 | endIndex = i;
13 | break;
14 | }
15 | }
16 |
17 | if (startIndex !== -1 && endIndex !== -1) {
18 | const oneFuncCode = lines.slice(startIndex, endIndex + 1).join('\n');
19 | return oneFuncCode;
20 | } else {
21 | return null;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/utils/getObjectDiff.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-var */
2 | /* eslint-disable no-prototype-builtins */
3 | import { ObjectType } from "@/types";
4 |
5 | export default function getObjectDiff(lhs:ObjectType, rhs:ObjectType) {
6 | function compareObjects(lhs:ObjectType, rhs:ObjectType) {
7 | const diff: ObjectType = {};
8 |
9 | for (var key in lhs) {
10 | if (rhs?.hasOwnProperty(key)) {
11 | if (typeof lhs[key] === 'object' && typeof rhs[key] === 'object') {
12 | const subDiff = compareObjects(lhs[key], rhs[key]);
13 | if (Object.keys(subDiff).length > 0) {
14 | diff[key] = subDiff;
15 | }
16 | } else if (Array.isArray(lhs[key]) && Array.isArray(rhs[key])) {
17 | if (lhs[key].length !== rhs[key].length) {
18 | diff[key] = rhs[key];
19 | } else {
20 | for (let i = 0; i < lhs[key].length; i++) {
21 | if (typeof lhs[key][i] === 'object' && typeof rhs[key][i] === 'object') {
22 | const subDiffArray = compareObjects(lhs[key][i], rhs[key][i]);
23 | if (Object.keys(subDiffArray).length > 0) {
24 | if (!diff[key]) {
25 | diff[key] = [];
26 | }
27 | diff[key][i] = subDiffArray;
28 | }
29 | } else if (lhs[key][i] !== rhs[key][i]) {
30 | if (!diff[key]) {
31 | diff[key] = [];
32 | }
33 | diff[key][i] = rhs[key][i];
34 | }
35 | }
36 | }
37 | } else if (lhs[key] !== rhs[key]) {
38 | diff[key] = rhs[key];
39 | }
40 | } else {
41 | diff[key] = rhs[key];
42 | }
43 | }
44 |
45 | for (var key in rhs) {
46 | if (!lhs?.hasOwnProperty(key)) {
47 | diff[key] = rhs[key];
48 | }
49 | }
50 |
51 | return diff;
52 | }
53 |
54 | return compareObjects(lhs, rhs);
55 | }
56 |
57 | export const applyDiffChanges = (objOriginal:ObjectType, changes:ObjectType) => {
58 | const updatedObject = { ...objOriginal };
59 |
60 | for (const key in changes) {
61 | if (updatedObject?.hasOwnProperty(key) && typeof changes[key] === 'object' && typeof updatedObject[key] === 'object') {
62 | updatedObject[key] = applyDiffChanges(updatedObject[key], changes[key]);
63 | } else {
64 | updatedObject[key] = changes[key];
65 | }
66 | }
67 |
68 | return updatedObject;
69 | };
70 |
71 | export const removeDiffChange = (objFinal:ObjectType, changes:ObjectType) => {
72 | const removedObject = { ...objFinal };
73 |
74 | for (const key in changes) {
75 | if (removedObject?.hasOwnProperty(key) && typeof changes[key] === 'object' && typeof removedObject[key] === 'object') {
76 | removedObject[key] = removeDiffChange(removedObject[key], changes[key]);
77 | if (Object.keys(removedObject[key]).length === 0) {
78 | delete removedObject[key];
79 | }
80 | }
81 | }
82 |
83 | return removedObject;
84 | };
85 |
86 |
--------------------------------------------------------------------------------
/src/utils/getOpenedFiles.ts:
--------------------------------------------------------------------------------
1 | import { File } from "../types";
2 |
3 | export default function getOpenedFiles(files: File[]): File[] {
4 | const result: File[] = [];
5 |
6 | const traverse = (files: File[]): void => {
7 | for (const file of files) {
8 | if (file?.opened) {
9 | result.push(file);
10 | }
11 | if (file?.children) {
12 | traverse(file?.children);
13 | }
14 | }
15 | };
16 |
17 | traverse(files);
18 |
19 | return result;
20 | }
21 |
--------------------------------------------------------------------------------
/src/utils/mountNewElement.ts:
--------------------------------------------------------------------------------
1 | import { nanoid } from 'nanoid'
2 | import { ElementType } from '@/types'
3 | import { elementsFull } from '@/elements'
4 |
5 | export default function mountNewElement(type: string, props: ElementType) {
6 | return {
7 | ...elementsFull[type],
8 | id: nanoid(6),
9 | type: type,
10 | fileId: '',
11 | ...props
12 | }
13 | }
--------------------------------------------------------------------------------
/src/utils/mountToolbarActions.ts:
--------------------------------------------------------------------------------
1 | import { GenericType } from "@/types"
2 |
3 | type OnClickType = () => void
4 |
5 | type ActionItem = {
6 | [name: string]: {
7 | onClick: OnClickType | null
8 | component?: React.ReactElement
9 | icon?: string
10 | active?: boolean
11 | }
12 | }
13 |
14 | export default function mountToolbarActions(
15 | toolbar: GenericType[],
16 | actions: ActionItem
17 | ) {
18 | return toolbar.map((item) => ({
19 | ...item,
20 | ...actions[item.id],
21 | options: !item.options?.length ? null :item.options?.map((subItem: GenericType) => ({
22 | ...subItem,
23 | ...actions[subItem.id]
24 | }))
25 | }))
26 | }
--------------------------------------------------------------------------------
/src/utils/neverUsed.ts:
--------------------------------------------------------------------------------
1 | import { GenericType } from "@/types"
2 |
3 | function noAction(value: GenericType) {
4 | return value
5 | }
6 |
7 | export function neverUsed(value: GenericType) {
8 | noAction(value)
9 | }
--------------------------------------------------------------------------------
/src/utils/processElements.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-prototype-builtins */
2 | import { ObjectType } from "@/types";
3 |
4 | export default function processElements(elements: ObjectType, injectedVariables: ObjectType) {
5 | const processedElements: ObjectType = {};
6 |
7 | for (const key in elements) {
8 | if (elements.hasOwnProperty(key)) {
9 | const element = elements[key];
10 | let injectedProps = {};
11 |
12 | if (element.inject) {
13 | for (const injectKey of element.inject) {
14 | if (injectedVariables.hasOwnProperty(injectKey)) {
15 | injectedProps = {
16 | ...injectedProps,
17 | ...injectedVariables[injectKey],
18 | };
19 | }
20 | }
21 | }
22 |
23 | processedElements[key] = {
24 | ...element,
25 | ...injectedProps,
26 | };
27 |
28 | delete processedElements[key].inject;
29 | }
30 | }
31 |
32 | return processedElements;
33 | }
34 |
--------------------------------------------------------------------------------
/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | const { fontFamily } = require("tailwindcss/defaultTheme")
2 |
3 | /** @type {import('tailwindcss').Config} */
4 | module.exports = {
5 | darkMode: ["class"],
6 | content: ['index.html', 'src/**/*.{js,jsx,ts,tsx}'],
7 | theme: {
8 | container: {
9 | center: true,
10 | padding: "2rem",
11 | screens: {
12 | "2xl": "1400px",
13 | },
14 | },
15 | extend: {
16 | colors: {
17 | border: "hsl(var(--border))",
18 | input: "hsl(var(--input))",
19 | ring: "hsl(var(--ring))",
20 | background: "hsl(var(--background))",
21 | foreground: "hsl(var(--foreground))",
22 | primary: {
23 | DEFAULT: "hsl(var(--primary))",
24 | foreground: "hsl(var(--primary-foreground))",
25 | },
26 | secondary: {
27 | DEFAULT: "hsl(var(--secondary))",
28 | foreground: "hsl(var(--secondary-foreground))",
29 | },
30 | destructive: {
31 | DEFAULT: "hsl(var(--destructive))",
32 | foreground: "hsl(var(--destructive-foreground))",
33 | },
34 | muted: {
35 | DEFAULT: "hsl(var(--muted))",
36 | foreground: "hsl(var(--muted-foreground))",
37 | },
38 | accent: {
39 | DEFAULT: "hsl(var(--accent))",
40 | foreground: "hsl(var(--accent-foreground))",
41 | },
42 | popover: {
43 | DEFAULT: "hsl(var(--popover))",
44 | foreground: "hsl(var(--popover-foreground))",
45 | },
46 | card: {
47 | DEFAULT: "hsl(var(--card))",
48 | foreground: "hsl(var(--card-foreground))",
49 | },
50 | },
51 | borderRadius: {
52 | lg: `var(--radius)`,
53 | md: `calc(var(--radius) - 2px)`,
54 | sm: "calc(var(--radius) - 4px)",
55 | },
56 | fontFamily: {
57 | sans: ["var(--font-sans)", ...fontFamily.sans],
58 | },
59 | keyframes: {
60 | "accordion-down": {
61 | from: { height: 0 },
62 | to: { height: "var(--radix-accordion-content-height)" },
63 | },
64 | "accordion-up": {
65 | from: { height: "var(--radix-accordion-content-height)" },
66 | to: { height: 0 },
67 | },
68 | },
69 | animation: {
70 | "accordion-down": "accordion-down 0.2s ease-out",
71 | "accordion-up": "accordion-up 0.2s ease-out",
72 | },
73 | },
74 | },
75 | plugins: [require("tailwindcss-animate")],
76 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 |
4 | "target": "ESNext",
5 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
6 | "module": "ESNext",
7 | "skipLibCheck": true,
8 |
9 | /* Bundler mode */
10 | "moduleResolution": "bundler",
11 | "allowImportingTsExtensions": true,
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "noEmit": true,
15 | "jsx": "react-jsx",
16 |
17 | /* Linting */
18 | "strict": true,
19 | "noUnusedLocals": true,
20 | "noUnusedParameters": true,
21 | "noFallthroughCasesInSwitch": true,
22 |
23 | /* shadcn-ui */
24 | "baseUrl": ".",
25 | "paths": {
26 | "@/*": ["./src/*"]
27 | }
28 | },
29 | "include": ["src"],
30 | "references": [{ "path": "./tsconfig.node.json" }]
31 | }
32 |
--------------------------------------------------------------------------------
/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "skipLibCheck": true,
5 | "module": "ESNext",
6 | "moduleResolution": "bundler",
7 | "allowSyntheticDefaultImports": true
8 | },
9 | "include": ["vite.config.ts"]
10 | }
11 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 | import tsconfigPaths from 'vite-tsconfig-paths'
4 |
5 | // https://vitejs.dev/config/
6 | export default defineConfig({
7 | plugins: [
8 | tsconfigPaths(),
9 | react({
10 | exclude: /\.stories\.(t|j)sx?$/,
11 | babel: {
12 | plugins: []
13 | }
14 | })
15 | ],
16 | })
17 |
--------------------------------------------------------------------------------