Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add dashboard #30

Closed
wants to merge 14 commits into from
Binary file added assets/icons/3c73ee8caf56fcc08bd11d595dca167d.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/icons/IdeaDrawnNewLogo_transparent.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions assets/icons/check.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions assets/icons/delete.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions assets/icons/down.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/icons/exclamation.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/icons/icons_151918.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/icons/icons_19028.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/icons/icons_417061.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/icons/icons_515682.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/icons/image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
92 changes: 73 additions & 19 deletions components/Canvas/Canvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ import useIndexed from "../../state/hooks/useIndexed";
import useStoreSubscription from "../../state/hooks/useStoreSubscription";
import useLayerReferences from "../../state/hooks/useLayerReferences";
import useStore from "../../state/hooks/useStore";
import * as Utils from "../../utils";
import * as Utils from "../../lib/utils";
import clsx from "clsx";

// Types
import type { FC, MouseEvent } from "react";
import type { Layer, Coordinates } from "../../types";
import type { Layer, Coordinates, CanvasFile } from "../../types";

// Styles
import "./Canvas.styles.css";
Expand All @@ -24,6 +24,7 @@ type DBLayer = {
image: Blob;
name: string;
position: number;
id: string;
};

const Canvas: FC<CanvasProps> = ({ isGrabbing }) => {
Expand Down Expand Up @@ -59,8 +60,8 @@ const Canvas: FC<CanvasProps> = ({ isGrabbing }) => {
}))
);

const { references, add, remove } = useLayerReferences();
const { get } = useIndexed();
const { references, add, getActiveLayer } = useLayerReferences();
const { get, remove } = useIndexed();

const isDrawing = useRef<boolean>(false);
const isMovingElement = useStoreSubscription((state) => state.elementMoving);
Expand Down Expand Up @@ -238,28 +239,81 @@ const Canvas: FC<CanvasProps> = ({ isGrabbing }) => {
// TODO: Improve this implementation of updating the layers from the storage.
useEffect(() => {
async function updateLayers() {
const entries = await get<[string, DBLayer][]>("layers");
const urlParams = new URLSearchParams(window.location.search);
const fileId = urlParams.get("f");
// The open query parameter is used to determine if the file
// was created from opening a file from the local computer.
const open = urlParams.get("open");

if (!fileId) {
// Simply do nothing. We want to redirect if there is no file.
// This is handled in the Page component for the editor route.
return;
}

const entries = await get<DBLayer[]>("layers", fileId);

if (!entries) {
// We're in a new file. We have one layer by default,
// so we'll render the opened file with that layer.

if (!open) {
return;
}

const file = await get<CanvasFile>("files", fileId);

if (!file) {
console.error(
"Tried to get file from temporary storage, there was no file."
);
} else {
const ref = getActiveLayer();
const canvasWidth = Number(ref.style.width.replace("px", ""));
const canvasHeight = Number(ref.style.height.replace("px", ""));
const ctx = ref.getContext("2d");
const img = new Image();

img.width = canvasWidth;
img.height = canvasHeight;

img.onload = () => {
ctx!.drawImage(img, 0, 0, canvasWidth, canvasHeight);
URL.revokeObjectURL(img.src);

// A custom event to notify that the image of the layer
// displayed in the layer list needs to be updated.

const ev = new CustomEvent("imageupdate", {
detail: {
layer: ref
}
});

document.dispatchEvent(ev);
};

img.src = URL.createObjectURL(file.file);
}
return;
}

const newEntries = await updateLayerState(entries);
updateLayerContents(newEntries);
}

function updateLayerState(entries: [string, DBLayer][]) {
return new Promise<[string, DBLayer][]>((resolve) => {
function updateLayerState(entries: DBLayer[]) {
return new Promise<DBLayer[]>((resolve) => {
const newLayers: Layer[] = [];

const sorted = entries.sort((a, b) => b[1].position - a[1].position); // Sort by position, where the highest position is the top layer.
const sorted = entries.sort((a, b) => b.position - a.position); // Sort by position, where the highest position is the top layer.

sorted.forEach((entry, i) => {
const [layerId, layer] = entry;
const { name, id } = entry;

newLayers.push({
name: layer.name,
id: layerId,
name: name,
id: id,
active: i === 0,
hidden: false
});
Expand All @@ -275,10 +329,10 @@ const Canvas: FC<CanvasProps> = ({ isGrabbing }) => {
});
}

function updateLayerContents(entries: [string, DBLayer][]) {
function updateLayerContents(entries: DBLayer[]) {
entries.forEach((entry) => {
const [, layer] = entry;
const canvas = references.current[layer.position];
const { position, image } = entry;
const canvas = references.current[position];

if (!canvas) return;

Expand All @@ -300,12 +354,12 @@ const Canvas: FC<CanvasProps> = ({ isGrabbing }) => {
document.dispatchEvent(ev);
};

img.src = URL.createObjectURL(layer.image);
img.src = URL.createObjectURL(image);
});
}

updateLayers();
}, [setLayers, get, references]);
}, [setLayers, get, references, getActiveLayer, remove]);

useEffect(() => {
const refs = references.current;
Expand Down Expand Up @@ -350,9 +404,9 @@ const Canvas: FC<CanvasProps> = ({ isGrabbing }) => {
transform
}}
ref={(element) => {
if (element !== null) {
add(element, i);
}
if (element !== null) {
add(element, i);
}
}}
id={layer.id}
width={width * dpi}
Expand Down
6 changes: 6 additions & 0 deletions components/CanvasPane/CanvasPane.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,17 @@ import type { Coordinates } from "../../types";

// Styles
import "./CanvasPane.styles.css";
import ScaleIndicator from "../ScaleIndicator/ScaleIndicator";

const MemoizedShapeElement = memo(ShapeElement);
const MemoizedCanvas = memo(Canvas);
const MemoizedDrawingToolbar = memo(DrawingToolbar);
const MemoizedScaleIndicator = memo(ScaleIndicator);

const CanvasPane: FC = () => {
const {
mode,
scale,
changeX,
changeY,
increaseScale,
Expand All @@ -40,6 +43,7 @@ const CanvasPane: FC = () => {
} = useStore(
useShallow((state) => ({
mode: state.mode,
scale: state.scale,
changeX: state.changeX,
changeY: state.changeY,
increaseScale: state.increaseScale,
Expand Down Expand Up @@ -340,6 +344,8 @@ const CanvasPane: FC = () => {
>
<MemoizedCanvas isGrabbing={isMoving} />
</div>

<MemoizedScaleIndicator scale={scale} />
</div>
);
};
Expand Down
5 changes: 2 additions & 3 deletions components/CanvasPointerMarker/CanvasPointerMarker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useState, useEffect, useRef } from "react";
import useStore from "../../state/hooks/useStore";
import useStoreSubscription from "../../state/hooks/useStoreSubscription";
import { useShallow } from "zustand/react/shallow";
import * as Utils from "../../utils";
import * as Utils from "../../lib/utils";

// Types
import type { FC, RefObject } from "react";
Expand Down Expand Up @@ -51,8 +51,7 @@ const CanvasPointerMarker: FC<CanvasPointerMarker> = ({
setIsVisible(false);
}

const { x, y, left, top, width, height } =
canvasSpace.getBoundingClientRect();
const { left, top, width, height } = canvasSpace.getBoundingClientRect();
let newX;
let newY;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Lib
import { useRef, useEffect, useState } from "react";
import * as UTILS from "../../utils";
import * as UTILS from "../../lib/utils";
import useLayerReferences from "../../state/hooks/useLayerReferences";
import useStoreSubscription from "../../state/hooks/useStoreSubscription";
import useStore from "../../state/hooks/useStore";
Expand Down
53 changes: 53 additions & 0 deletions components/Dropdown/Dropdown.styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
:root {
--description: lightgray;
}

.desc {
color: var(--description);
}

.currentFilter {
display: inline-flex;
align-items: center;
justify-content: center;
background: none;
border: none;
cursor: pointer;
padding: 0 10px;
}
.dropdownFilter {
opacity: 0;
display: flex;
align-items: flex-start;
justify-content: center;
flex-direction: column;
z-index: 888;
width: 10rem;
background: rgba(0, 0, 0, 0.9);
border: solid 1px rgba(255, 255, 255, 0.2);
border-radius: 3px;
padding-block: 10px;
padding-inline: 1rem;
padding-inline-end: 0;
position: absolute;
transition: all 0.3s ease;
pointer-events: none;
}

.filter-item {
width: fit-content;
background: none;
border: none;
padding-block: 5px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
}

.active {
opacity: 1;
transform: translateY(10px);
pointer-events: all;
}
84 changes: 84 additions & 0 deletions components/Dropdown/Dropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Lib
import check from "../../assets/icons/check.svg";
import down from "../../assets/icons/down.svg";
import { useState } from "react";

// Types
import type { FC, MouseEvent } from "react";
import { Option } from "../../types";

// Styles
import "./Dropdown.styles.css";

type DropdownProps = {
description: string;
options: Option[];
onSelect?: (option: Option) => void;
};

const Dropdown: FC<DropdownProps> = ({
description = "Default",
options = [],
onSelect
}) => {
const [selected, setSelected] = useState<Option>(options[0]);
const [open, setOpen] = useState<boolean>(false);

const handleSelect = (e: MouseEvent) => {
const option = options.find(
(option) => option.value === e.currentTarget.id
);
if (option) {
setSelected(option);
onSelect && onSelect(option);
}
setOpen(false);
};

const handleToggle = () => setOpen(!open);

return (
<div style={{ position: "relative" }}>
<span className="desc">{description}:</span>
<button
onClick={handleToggle}
className="currentFilter"
>
<span>{selected.label}</span>
<img
src={down}
alt="downBtn"
width="20px"
/>
</button>
<div
style={{
left: description.length * 7.5 + 10
}}
className={`dropdownFilter${open ? " active" : ""}`}
>
{options.map((option) => (
<button
onClick={handleSelect}
className="filter-item"
aria-valuetext="active"
id={option.value}
key={option.value}
>
{selected.value === option.value && (
<img
src={check}
width="13px"
alt=""
className="check"
/>
)}
{option.label}
</button>
))}
</div>
</div>
);
};

export default Dropdown;
Loading
Loading