adds ui for collaborators
This commit is contained in:
Generated
+110
@@ -16,6 +16,8 @@
|
||||
"@radix-ui/react-dialog": "^1.0.4",
|
||||
"@radix-ui/react-icons": "^1.3.0",
|
||||
"@radix-ui/react-menubar": "^1.0.3",
|
||||
"@radix-ui/react-popover": "^1.0.6",
|
||||
"@radix-ui/react-scroll-area": "^1.0.4",
|
||||
"@radix-ui/react-slider": "^1.1.2",
|
||||
"@radix-ui/react-toggle-group": "^1.0.4",
|
||||
"@react-three/drei": "^9.80.2",
|
||||
@@ -1416,6 +1418,43 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-popover": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.0.6.tgz",
|
||||
"integrity": "sha512-cZ4defGpkZ0qTRtlIBzJLSzL6ht7ofhhW4i1+pkemjV1IKXm0wgCRnee154qlV6r9Ttunmh2TNZhMfV2bavUyA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/primitive": "1.0.1",
|
||||
"@radix-ui/react-compose-refs": "1.0.1",
|
||||
"@radix-ui/react-context": "1.0.1",
|
||||
"@radix-ui/react-dismissable-layer": "1.0.4",
|
||||
"@radix-ui/react-focus-guards": "1.0.1",
|
||||
"@radix-ui/react-focus-scope": "1.0.3",
|
||||
"@radix-ui/react-id": "1.0.1",
|
||||
"@radix-ui/react-popper": "1.1.2",
|
||||
"@radix-ui/react-portal": "1.0.3",
|
||||
"@radix-ui/react-presence": "1.0.1",
|
||||
"@radix-ui/react-primitive": "1.0.3",
|
||||
"@radix-ui/react-slot": "1.0.2",
|
||||
"@radix-ui/react-use-controllable-state": "1.0.1",
|
||||
"aria-hidden": "^1.1.1",
|
||||
"react-remove-scroll": "2.5.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-popper": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.1.2.tgz",
|
||||
@@ -1549,6 +1588,37 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-scroll-area": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.0.4.tgz",
|
||||
"integrity": "sha512-OIClwBkwPG+FKvC4OMTRaa/3cfD069nkKFFL/TQzRzaO42Ce5ivKU9VMKgT7UU6UIkjcQqKBrDOIzWtPGw6e6w==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/number": "1.0.1",
|
||||
"@radix-ui/primitive": "1.0.1",
|
||||
"@radix-ui/react-compose-refs": "1.0.1",
|
||||
"@radix-ui/react-context": "1.0.1",
|
||||
"@radix-ui/react-direction": "1.0.1",
|
||||
"@radix-ui/react-presence": "1.0.1",
|
||||
"@radix-ui/react-primitive": "1.0.3",
|
||||
"@radix-ui/react-use-callback-ref": "1.0.1",
|
||||
"@radix-ui/react-use-layout-effect": "1.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-slider": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.1.2.tgz",
|
||||
@@ -6462,6 +6532,29 @@
|
||||
"@radix-ui/react-use-controllable-state": "1.0.1"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-popover": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.0.6.tgz",
|
||||
"integrity": "sha512-cZ4defGpkZ0qTRtlIBzJLSzL6ht7ofhhW4i1+pkemjV1IKXm0wgCRnee154qlV6r9Ttunmh2TNZhMfV2bavUyA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/primitive": "1.0.1",
|
||||
"@radix-ui/react-compose-refs": "1.0.1",
|
||||
"@radix-ui/react-context": "1.0.1",
|
||||
"@radix-ui/react-dismissable-layer": "1.0.4",
|
||||
"@radix-ui/react-focus-guards": "1.0.1",
|
||||
"@radix-ui/react-focus-scope": "1.0.3",
|
||||
"@radix-ui/react-id": "1.0.1",
|
||||
"@radix-ui/react-popper": "1.1.2",
|
||||
"@radix-ui/react-portal": "1.0.3",
|
||||
"@radix-ui/react-presence": "1.0.1",
|
||||
"@radix-ui/react-primitive": "1.0.3",
|
||||
"@radix-ui/react-slot": "1.0.2",
|
||||
"@radix-ui/react-use-controllable-state": "1.0.1",
|
||||
"aria-hidden": "^1.1.1",
|
||||
"react-remove-scroll": "2.5.5"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-popper": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.1.2.tgz",
|
||||
@@ -6525,6 +6618,23 @@
|
||||
"@radix-ui/react-use-controllable-state": "1.0.1"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-scroll-area": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.0.4.tgz",
|
||||
"integrity": "sha512-OIClwBkwPG+FKvC4OMTRaa/3cfD069nkKFFL/TQzRzaO42Ce5ivKU9VMKgT7UU6UIkjcQqKBrDOIzWtPGw6e6w==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/number": "1.0.1",
|
||||
"@radix-ui/primitive": "1.0.1",
|
||||
"@radix-ui/react-compose-refs": "1.0.1",
|
||||
"@radix-ui/react-context": "1.0.1",
|
||||
"@radix-ui/react-direction": "1.0.1",
|
||||
"@radix-ui/react-presence": "1.0.1",
|
||||
"@radix-ui/react-primitive": "1.0.3",
|
||||
"@radix-ui/react-use-callback-ref": "1.0.1",
|
||||
"@radix-ui/react-use-layout-effect": "1.0.1"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-slider": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.1.2.tgz",
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
"@radix-ui/react-dialog": "^1.0.4",
|
||||
"@radix-ui/react-icons": "^1.3.0",
|
||||
"@radix-ui/react-menubar": "^1.0.3",
|
||||
"@radix-ui/react-popover": "^1.0.6",
|
||||
"@radix-ui/react-scroll-area": "^1.0.4",
|
||||
"@radix-ui/react-slider": "^1.1.2",
|
||||
"@radix-ui/react-toggle-group": "^1.0.4",
|
||||
"@react-three/drei": "^9.80.2",
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import "./App.css";
|
||||
import { useEffect } from "react";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { LegoRoom } from "./components/3D";
|
||||
import { useStore } from "./store";
|
||||
import { Loader } from "./components/UI/Loader";
|
||||
|
||||
@@ -10,6 +10,15 @@ import {
|
||||
} from "react";
|
||||
import { useFrame, useThree } from "@react-three/fiber";
|
||||
import { DoubleSide, RawShaderMaterial, Color, Vector2 } from "three";
|
||||
import { Html } from "@react-three/drei";
|
||||
import { cursorColors } from "../../../utils";
|
||||
|
||||
function shuffleArray(array) {
|
||||
for (let i = array.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[array[i], array[j]] = [array[j], array[i]]; // Swap elements
|
||||
}
|
||||
}
|
||||
|
||||
const vertexShader = `
|
||||
varying vec2 vUv;
|
||||
@@ -70,6 +79,7 @@ export const BorderPlane = forwardRef(function BorderPlane(
|
||||
const { size } = useThree();
|
||||
|
||||
const mesh = useRef();
|
||||
const group = useRef();
|
||||
|
||||
const uniforms = useMemo(() => {
|
||||
return {
|
||||
@@ -91,7 +101,7 @@ export const BorderPlane = forwardRef(function BorderPlane(
|
||||
mesh.current.material.uniforms.uSize.value.x = planeSize[0];
|
||||
mesh.current.material.uniforms.uSize.value.y = planeSize[1];
|
||||
mesh.current.material.uniforms.uBorderWidth.value =
|
||||
(Math.min(planeSize[0], planeSize[1]) / 50) * 5;
|
||||
(Math.min(planeSize[0], planeSize[1]) / 50) * 10;
|
||||
}, [planeSize]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
@@ -114,16 +124,24 @@ export const BorderPlane = forwardRef(function BorderPlane(
|
||||
// planeMaterial.uniforms.uTime.value = elapsedTime;
|
||||
// });
|
||||
|
||||
useImperativeHandle(ref, () => mesh.current);
|
||||
useImperativeHandle(ref, () => ({
|
||||
plane: mesh.current,
|
||||
container: group.current,
|
||||
}));
|
||||
|
||||
return (
|
||||
<mesh
|
||||
rotation={[-Math.PI / 2, 0, 0]}
|
||||
ref={mesh}
|
||||
material={planeMaterial}
|
||||
{...props}
|
||||
>
|
||||
<planeGeometry args={planeSize} />
|
||||
</mesh>
|
||||
<group ref={group}>
|
||||
<mesh
|
||||
rotation={[-Math.PI / 2, 0, 0]}
|
||||
ref={mesh}
|
||||
material={planeMaterial}
|
||||
{...props}
|
||||
>
|
||||
<planeGeometry args={planeSize} />
|
||||
</mesh>
|
||||
{/* <Html>
|
||||
<h1>Hello</h1>
|
||||
</Html> */}
|
||||
</group>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -4,9 +4,8 @@
|
||||
import { useStore } from "../../store";
|
||||
import { memo, useRef, useEffect } from "react";
|
||||
import { BorderPlane } from "./BorderPlane";
|
||||
import { base } from "../../utils";
|
||||
|
||||
const Other = memo(function Other({ id, otherCurors }) {
|
||||
const Other = memo(function Other({ id, color }) {
|
||||
const room = useStore((state) => state.liveblocks.room);
|
||||
|
||||
const ref = useRef();
|
||||
@@ -15,15 +14,15 @@ const Other = memo(function Other({ id, otherCurors }) {
|
||||
const unsubscribe = room.subscribe("event", ({ event }) => {
|
||||
if (event.type === id) {
|
||||
const d = event.data;
|
||||
ref.current.position.x = d.x;
|
||||
ref.current.position.z = d.z;
|
||||
ref.current.position.y = d.y - 31 / 2;
|
||||
ref.current.container.position.x = d.x;
|
||||
ref.current.container.position.z = d.z;
|
||||
ref.current.container.position.y = d.y - 31 / 2;
|
||||
|
||||
ref.current.scale.set(d.w, d.d);
|
||||
ref.current.material.uniforms.uSize.value.x = d.w;
|
||||
ref.current.material.uniforms.uSize.value.y = d.d;
|
||||
ref.current.material.uniforms.uBorderWidth.value =
|
||||
(Math.min(d.w, d.d) / 50) * 5;
|
||||
ref.current.plane.scale.set(d.w, d.d);
|
||||
ref.current.plane.material.uniforms.uSize.value.x = d.w;
|
||||
ref.current.plane.material.uniforms.uSize.value.y = d.d;
|
||||
ref.current.plane.material.uniforms.uBorderWidth.value =
|
||||
(Math.min(d.w, d.d) / 50) * 10;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -34,7 +33,7 @@ const Other = memo(function Other({ id, otherCurors }) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<BorderPlane planeSize={[25, 25]} ref={ref} color={"#ff0000"} />
|
||||
<BorderPlane planeSize={[25, 25]} ref={ref} color={color} />
|
||||
</>
|
||||
);
|
||||
});
|
||||
@@ -45,8 +44,13 @@ export const Others = () => {
|
||||
return (
|
||||
<>
|
||||
{others.map((user) => {
|
||||
console.log(user);
|
||||
return user.presence?.self ? (
|
||||
<Other key={user.presence.self.id} id={user.presence.self.id} />
|
||||
<Other
|
||||
key={user.presence.self.id}
|
||||
id={user.presence.self.id}
|
||||
color={user.presence.self.color}
|
||||
/>
|
||||
) : null;
|
||||
})}
|
||||
</>
|
||||
|
||||
@@ -3,6 +3,7 @@ import React from "react";
|
||||
import { TrashIcon, ResetIcon } from "@radix-ui/react-icons";
|
||||
import "./styles.css";
|
||||
import { useStore } from "../../../store";
|
||||
import { PopoverPeopleList } from "../PeopleList";
|
||||
// import { EDIT_MODE } from "../../../utils";
|
||||
|
||||
export const BottomBar = () => {
|
||||
@@ -16,6 +17,8 @@ export const BottomBar = () => {
|
||||
const setBricks = useStore((state) => state.setBricks);
|
||||
const setSelection = useStore((state) => state.setSelectedBricks);
|
||||
|
||||
const room = useStore((state) => state.liveblocks.room);
|
||||
|
||||
const deleteSelectedBricks = () => {
|
||||
setBricks((bricks) => {
|
||||
const newBricks = bricks.filter((brick) => {
|
||||
@@ -38,12 +41,22 @@ export const BottomBar = () => {
|
||||
|
||||
const clearBricks = useStore((state) => state.clearBricks);
|
||||
|
||||
const undo = () => {
|
||||
room.history.undo();
|
||||
setSelection({});
|
||||
};
|
||||
|
||||
const redo = () => {
|
||||
room.history.redo();
|
||||
setSelection({});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="BottomBar">
|
||||
<button className="Button violet">
|
||||
<button className="Button violet" onClick={undo}>
|
||||
<ResetIcon className="Icon" color="black" />
|
||||
</button>
|
||||
<button className="Button violet">
|
||||
<button className="Button violet" onClick={redo}>
|
||||
<ResetIcon
|
||||
style={{ transform: "scaleX(-1)" }}
|
||||
className="Icon"
|
||||
@@ -56,6 +69,7 @@ export const BottomBar = () => {
|
||||
<button onClick={clearBricks} className="Button violet">
|
||||
<TrashIcon className="Icon" color="black" />
|
||||
</button>
|
||||
<PopoverPeopleList />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,16 +1,27 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import React from "react";
|
||||
import React, { useRef } from "react";
|
||||
import { HexColorPicker } from "react-colorful";
|
||||
import "./styles.css";
|
||||
import { useStore } from "../../../../store";
|
||||
// import useDebounce from "use-debouncy";
|
||||
|
||||
export const ColorInput = () => {
|
||||
const setColor = useStore((state) => state.setColor);
|
||||
|
||||
const tId = useRef();
|
||||
|
||||
const debouncesetColor = (color) => {
|
||||
if (tId.current) clearTimeout(tId.current);
|
||||
|
||||
tId.current = setTimeout(() => {
|
||||
setColor(color);
|
||||
}, 200);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="SliderInputContainer">
|
||||
<label className="SliderLabel">Color</label>
|
||||
<HexColorPicker color="#000000" onChange={(color) => setColor(color)} />
|
||||
<HexColorPicker color="#000000" onChange={debouncesetColor} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
import "./style.css";
|
||||
import * as Popover from "@radix-ui/react-popover";
|
||||
import { PersonIcon } from "@radix-ui/react-icons";
|
||||
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
||||
import { useStore } from "../../../store";
|
||||
|
||||
const Person = ({ color = "#ff0000", name = "" }) => {
|
||||
return (
|
||||
<div className="PeopleListItem">
|
||||
<div className="ColorBadge" style={{ background: color }}></div>
|
||||
<h3 className="name">{name}</h3>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const s = {
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
};
|
||||
|
||||
const PeopleList = () => {
|
||||
const others = useStore((state) => state.liveblocks.others);
|
||||
|
||||
const NoOfPeoples = others.length;
|
||||
|
||||
return (
|
||||
<ScrollArea.Root className="PeopleList ScrollAreaRoot">
|
||||
<ScrollArea.Viewport
|
||||
className="ScrollAreaViewport"
|
||||
style={NoOfPeoples < 1 ? s : {}}
|
||||
>
|
||||
{NoOfPeoples < 1 && (
|
||||
<h3 style={{ textAlign: "center", padding: "6px", fontSize: "14px" }}>
|
||||
No one else in room
|
||||
</h3>
|
||||
)}
|
||||
{others.map((user) => {
|
||||
return user.presence?.self ? (
|
||||
<Person
|
||||
key={user.presence.self.id}
|
||||
name={"Person"}
|
||||
color={user.presence.self.color}
|
||||
/>
|
||||
) : null;
|
||||
})}
|
||||
</ScrollArea.Viewport>
|
||||
<ScrollArea.Scrollbar
|
||||
className="ScrollAreaScrollbar"
|
||||
orientation="vertical"
|
||||
>
|
||||
<ScrollArea.Thumb className="ScrollAreaThumb" />
|
||||
</ScrollArea.Scrollbar>
|
||||
<ScrollArea.Scrollbar
|
||||
className="ScrollAreaScrollbar"
|
||||
orientation="horizontal"
|
||||
>
|
||||
<ScrollArea.Thumb className="ScrollAreaThumb" />
|
||||
</ScrollArea.Scrollbar>
|
||||
<ScrollArea.Corner className="ScrollAreaCorner" />
|
||||
</ScrollArea.Root>
|
||||
);
|
||||
};
|
||||
|
||||
export const PopoverPeopleList = () => (
|
||||
<Popover.Root>
|
||||
<Popover.Trigger asChild>
|
||||
<button className="Button violet" aria-label="Connected People List">
|
||||
<PersonIcon className="Icon" color="black" />
|
||||
</button>
|
||||
</Popover.Trigger>
|
||||
<Popover.Portal>
|
||||
<Popover.Content className="PopoverContent" sideOffset={5}>
|
||||
<PeopleList />
|
||||
{/* <Popover.Close className="PopoverClose" aria-label="Close">
|
||||
<Cross2Icon />
|
||||
</Popover.Close> */}
|
||||
{/* <Popover.Arrow className="PopoverArrow" /> */}
|
||||
</Popover.Content>
|
||||
</Popover.Portal>
|
||||
</Popover.Root>
|
||||
);
|
||||
@@ -0,0 +1,104 @@
|
||||
@import "@radix-ui/colors/black-alpha.css";
|
||||
@import "@radix-ui/colors/mauve.css";
|
||||
@import "@radix-ui/colors/violet.css";
|
||||
|
||||
.ScrollAreaRoot {
|
||||
max-height: 225px;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 10px var(--black-a7);
|
||||
background-color: white;
|
||||
--scrollbar-size: 10px;
|
||||
}
|
||||
|
||||
.ScrollAreaViewport {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: inherit;
|
||||
}
|
||||
|
||||
.ScrollAreaScrollbar {
|
||||
display: flex;
|
||||
user-select: none;
|
||||
touch-action: none;
|
||||
padding: 2px;
|
||||
background-color: var(--black-a6);
|
||||
transition: background-color 160ms ease-out;
|
||||
}
|
||||
.ScrollAreaScrollbar:hover {
|
||||
background: var(--black-a8);
|
||||
}
|
||||
.ScrollAreaScrollbar[data-orientation="vertical"] {
|
||||
width: var(--scrollbar-size);
|
||||
}
|
||||
.ScrollAreaScrollbar[data-orientation="horizontal"] {
|
||||
flex-direction: column;
|
||||
height: var(--scrollbar-size);
|
||||
}
|
||||
|
||||
.ScrollAreaThumb {
|
||||
flex: 1;
|
||||
background: var(--mauve-10);
|
||||
border-radius: var(--scrollbar-size);
|
||||
position: relative;
|
||||
}
|
||||
/* increase target size for touch devices https://www.w3.org/WAI/WCAG21/Understanding/target-size.html */
|
||||
.ScrollAreaThumb::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-width: 44px;
|
||||
min-height: 44px;
|
||||
}
|
||||
|
||||
.ScrollAreaCorner {
|
||||
background: var(--black-a8);
|
||||
}
|
||||
|
||||
.PeopleList .PeopleListItem {
|
||||
line-height: 18px;
|
||||
border-top: 1px solid var(--mauve-6);
|
||||
}
|
||||
|
||||
.PeopleList {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.PeopleList .PeopleListItem {
|
||||
display: flex;
|
||||
justify-content: start;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
width: 200px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.PeopleList .PeopleListItem .ColorBadge {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
border-radius: 50%;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid black;
|
||||
}
|
||||
|
||||
.PeopleList .PeopleListItem .name {
|
||||
font-size: 14px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
width: 157px;
|
||||
}
|
||||
|
||||
.PopoverContent {
|
||||
background-color: white;
|
||||
border-radius: 4px;
|
||||
}
|
||||
+22
-3
@@ -1,5 +1,11 @@
|
||||
import { create } from "zustand";
|
||||
import { CREATE_MODE, defaultAnchor, defaultWidth, uID } from "../utils";
|
||||
import {
|
||||
CREATE_MODE,
|
||||
defaultAnchor,
|
||||
defaultWidth,
|
||||
generateSoftColors,
|
||||
uID,
|
||||
} from "../utils";
|
||||
import { createClient } from "@liveblocks/client";
|
||||
import { liveblocks } from "@liveblocks/zustand";
|
||||
|
||||
@@ -61,6 +67,7 @@ const client = createClient({
|
||||
// }));
|
||||
|
||||
const id = uID();
|
||||
const color = generateSoftColors();
|
||||
|
||||
export const useStore = create(
|
||||
liveblocks(
|
||||
@@ -107,12 +114,24 @@ export const useStore = create(
|
||||
set((state) => ({ bricks: getBricks(state.bricks) })),
|
||||
clearBricks: () => set({ bricks: [] }),
|
||||
|
||||
self: { id: id },
|
||||
self: { id: id, color: color },
|
||||
|
||||
cursorColors: {},
|
||||
setCursorColors: (newCursorColor) =>
|
||||
set((state) => ({
|
||||
cursorColors: { ...state.cursorColors, ...newCursorColor },
|
||||
})),
|
||||
|
||||
removeCursorColor: (id) =>
|
||||
set((state) => {
|
||||
delete state.cursorColors[id];
|
||||
return { cursorColors: { ...state.cursorColors } };
|
||||
}),
|
||||
}),
|
||||
{
|
||||
client,
|
||||
presenceMapping: { self: true },
|
||||
storageMapping: { bricks: true },
|
||||
storageMapping: { bricks: true, cursorColors: true },
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
@@ -30,3 +30,26 @@ export const colors = [
|
||||
"#000000",
|
||||
"#652A0C",
|
||||
];
|
||||
|
||||
export const cursorColors = [
|
||||
"#FFA6B1", // Soft Pink
|
||||
"#FFB6C1", // Light Pink
|
||||
"#FFD1A8", // Peach
|
||||
"#FFE4B5", // Moccasin
|
||||
"#FFDEAD", // Navajo White
|
||||
"#FFF0E1", // Misty Rose
|
||||
"#D8BFD8", // Thistle
|
||||
"#E6E6FA", // Lavender
|
||||
"#B0E0E6", // Powder Blue
|
||||
"#ADD8E6", // Light Blue
|
||||
"#B0C4DE", // Light Steel Blue
|
||||
"#98FB98", // Pale Green
|
||||
"#F0E68C", // Khaki
|
||||
"#FFEBD6", // Almond
|
||||
"#FFE5B4", // Mellow Apricot
|
||||
"#FFDAB9", // Peach Puff
|
||||
"#EEDD82", // Light Goldenrod
|
||||
"#D3D3D3", // Light Gray
|
||||
"#FFC0CB", // Pink
|
||||
"#FFB347", // Pastel Orange
|
||||
];
|
||||
|
||||
@@ -84,3 +84,46 @@ export function uID(length = 8) {
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function hslToRgb(h, s, l) {
|
||||
let r, g, b;
|
||||
|
||||
if (s === 0) {
|
||||
r = g = b = l; // achromatic
|
||||
} else {
|
||||
const hue2rgb = (p, q, t) => {
|
||||
if (t < 0) t += 1;
|
||||
if (t > 1) t -= 1;
|
||||
if (t < 1 / 6) return p + (q - p) * 6 * t;
|
||||
if (t < 1 / 2) return q;
|
||||
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
|
||||
return p;
|
||||
};
|
||||
|
||||
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
||||
const p = 2 * l - q;
|
||||
r = hue2rgb(p, q, h + 1 / 3);
|
||||
g = hue2rgb(p, q, h);
|
||||
b = hue2rgb(p, q, h - 1 / 3);
|
||||
}
|
||||
|
||||
return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
|
||||
}
|
||||
|
||||
function rgbToHex(r, g, b) {
|
||||
return (
|
||||
"#" +
|
||||
((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1).toUpperCase()
|
||||
);
|
||||
}
|
||||
|
||||
export function generateSoftColors() {
|
||||
const hue = Math.random();
|
||||
const saturation = Math.random() * 0.2 + 0.4; // 40% to 60%
|
||||
const lightness = Math.random() * 0.3 + 0.5; // 50% to 80%
|
||||
|
||||
const [r, g, b] = hslToRgb(hue, saturation, lightness);
|
||||
const hexColor = rgbToHex(r, g, b);
|
||||
|
||||
return hexColor;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user