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

Added color picker #25

Merged
merged 1 commit into from
Aug 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"i18next": "^21.8.9",
"lodash": "^4.17.21",
"react": "^18.1.0",
"react-colorful": "^5.6.1",
"react-dom": "^18.1.0",
"react-hook-form": "^7.32.0",
"react-i18next": "^11.17.1",
Expand Down
34 changes: 34 additions & 0 deletions frontend/src/utils/ColorUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// see https://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-r
function rgbStringToHex(rgb: string) {
let result = /^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/i.exec(rgb);
return result ? rgbToHex(parseInt(result[1]), parseInt(result[2]), parseInt(result[3])) : null;
}

function rgbToHex(r: number, g: number, b: number) {
return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
}

function hexToRgb(hex: string) {
// Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
let shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
hex = hex.replace(shorthandRegex, function (m, r, g, b) {
return r + r + g + g + b + b;
});

let result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result
? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16),
}
: null;
}

const ColorUtils = {
rgbStringToHex,
rgbToHex,
hexToRgb,
};

export default ColorUtils;
54 changes: 43 additions & 11 deletions frontend/src/views/annotation/CodeExplorer/CodeCreationDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
import { Button, Dialog, DialogActions, DialogContent, DialogTitle, MenuItem, Stack, TextField } from "@mui/material";
import {
Box,
Button,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
MenuItem,
Stack,
TextField,
} from "@mui/material";
import React, { useEffect, useState } from "react";
import AddBoxIcon from "@mui/icons-material/AddBox";
import { useQueryClient } from "@tanstack/react-query";
Expand All @@ -9,6 +19,7 @@ import CodeHooks from "../../../api/CodeHooks";
import { QueryKey } from "../../../api/QueryKey";
import { ErrorMessage } from "@hookform/error-message";
import { LoadingButton } from "@mui/lab";
import { HexColorPicker } from "react-colorful";

interface CodeDialogProps {
projectId: number;
Expand All @@ -23,17 +34,21 @@ export default function CodeCreationDialog({ projectId, userId, codes }: CodeDia
handleSubmit,
formState: { errors },
reset,
setValue,
} = useForm();

// state
// local state
const [open, setOpen] = useState(false); // state of the dialog, either open or closed
const [selectedParent, setSelectedParent] = useState(-1);
const [color, setColor] = useState("#000000");

// initialize state properly
useEffect(() => {
setSelectedParent(-1);
setColor("#000000");
reset();
}, [reset]);
setValue("color", "#000000");
}, [setValue, reset]);

// mutations
const queryClient = useQueryClient();
Expand All @@ -52,7 +67,11 @@ export default function CodeCreationDialog({ projectId, userId, codes }: CodeDia
text: `Added code ${data.name}`,
severity: "success",
});
reset(); // reset form

// reset
reset();
setColor("#000000");
setValue("color", "#000000");
},
});

Expand Down Expand Up @@ -106,13 +125,26 @@ export default function CodeCreationDialog({ projectId, userId, codes }: CodeDia
error={Boolean(errors.name)}
helperText={<ErrorMessage errors={errors} name="name" />}
/>
<TextField
label="Color"
fullWidth
variant="standard"
{...register("color", { required: "Color is required" })}
error={Boolean(errors.color)}
helperText={<ErrorMessage errors={errors} name="color" />}
<Stack direction="row">
<TextField
label="Color"
fullWidth
variant="standard"
{...register("color", { required: "Color is required" })}
onChange={(e) => setColor(e.target.value)}
error={Boolean(errors.color)}
helperText={<ErrorMessage errors={errors} name="color" />}
InputLabelProps={{ shrink: true }}
/>
<Box sx={{ width: 48, height: 48, backgroundColor: color, ml: 1, flexShrink: 0 }} />
</Stack>
<HexColorPicker
style={{ width: "100%" }}
color={color}
onChange={(newColor) => {
setValue("color", newColor); // set value of text input
setColor(newColor); // set value of color picker (and box)
}}
/>
<TextField
multiline
Expand Down
40 changes: 29 additions & 11 deletions frontend/src/views/annotation/CodeExplorer/CodeEditDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Dialog, DialogActions, DialogContent, DialogTitle, MenuItem, Stack, TextField } from "@mui/material";
import { Box, Dialog, DialogActions, DialogContent, DialogTitle, MenuItem, Stack, TextField } from "@mui/material";
import React, { useCallback, useEffect, useState } from "react";
import { useQueryClient } from "@tanstack/react-query";
import SnackbarAPI from "../../../features/snackbar/SnackbarAPI";
Expand All @@ -9,6 +9,8 @@ import CodeHooks from "../../../api/CodeHooks";
import { QueryKey } from "../../../api/QueryKey";
import { ErrorMessage } from "@hookform/error-message";
import { LoadingButton } from "@mui/lab";
import { HexColorPicker } from "react-colorful";
import ColorUtils from "../../../utils/ColorUtils";

interface CodeEditDialogProps {
codes: CodeRead[];
Expand All @@ -21,12 +23,14 @@ function CodeEditDialog({ codes }: CodeEditDialogProps) {
handleSubmit,
formState: { errors },
reset,
setValue,
} = useForm();

// state
// local state
const [code, setCode] = useState<CodeRead | null>(null);
const [open, setOpen] = useState(false);
const [selectedParent, setSelectedParent] = useState(-1);
const [color, setColor] = useState("#000000");

// listen to event
// create a (memoized) function that stays the same across re-renders
Expand All @@ -45,12 +49,14 @@ function CodeEditDialog({ codes }: CodeEditDialogProps) {
// initialize form when code changes
useEffect(() => {
if (code) {
const c = ColorUtils.rgbStringToHex(code.color) || code.color;
reset({
name: code.name,
description: code.description,
color: code.color,
color: c,
});
setSelectedParent(!code.parent_code_id ? -1 : code.parent_code_id);
setColor(c);
}
}, [code, reset]);

Expand Down Expand Up @@ -115,7 +121,6 @@ function CodeEditDialog({ codes }: CodeEditDialogProps) {
</MenuItem>
))}
</TextField>

<TextField
label="Name"
fullWidth
Expand All @@ -124,13 +129,26 @@ function CodeEditDialog({ codes }: CodeEditDialogProps) {
error={Boolean(errors.name)}
helperText={<ErrorMessage errors={errors} name="name" />}
/>
<TextField
label="Color"
fullWidth
variant="standard"
{...register("color", { required: "Color is required" })}
error={Boolean(errors.color)}
helperText={<ErrorMessage errors={errors} name="color" />}
<Stack direction="row">
<TextField
label="Color"
fullWidth
variant="standard"
{...register("color", { required: "Color is required" })}
onChange={(e) => setColor(e.target.value)}
error={Boolean(errors.color)}
helperText={<ErrorMessage errors={errors} name="color" />}
InputLabelProps={{ shrink: true }}
/>
<Box sx={{ width: 48, height: 48, backgroundColor: color, ml: 1, flexShrink: 0 }} />
</Stack>
<HexColorPicker
style={{ width: "100%" }}
color={color}
onChange={(newColor) => {
setValue("color", newColor); // set value of text input
setColor(newColor); // set value of color picker (and box)
}}
/>
<TextField
multiline
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { forwardRef, useImperativeHandle, useState } from "react";
import {
Box,
Button,
Dialog,
DialogActions,
Expand All @@ -20,6 +21,7 @@ import SnackbarAPI from "../../../features/snackbar/SnackbarAPI";
import { QueryKey } from "../../../api/QueryKey";
import { CodeRead } from "../../../api/openapi";
import { useAppSelector } from "../../../plugins/ReduxHooks";
import { HexColorPicker } from "react-colorful";

interface CodeCreationDialogProps {
onCreateSuccess: (code: CodeRead) => void;
Expand All @@ -41,6 +43,7 @@ const CodeCreationDialog = forwardRef<CodeCreationDialogHandle, CodeCreationDial

// local state
const [isCodeCreateDialogOpen, setIsCodeCreateDialogOpen] = useState(false);
const [color, setColor] = useState("#000000");

// react form
const {
Expand Down Expand Up @@ -80,12 +83,15 @@ const CodeCreationDialog = forwardRef<CodeCreationDialogHandle, CodeCreationDial

// methods
const openCodeCreateDialog = (name?: string) => {
// reset
reset();
setValue("name", name ? name : "");
setValue("color", "#000000");
setColor("#000000");
setIsCodeCreateDialogOpen(true);
};

const closeCodeCreateDialog = () => {
reset();
setIsCodeCreateDialogOpen(false);
};

Expand Down Expand Up @@ -134,13 +140,26 @@ const CodeCreationDialog = forwardRef<CodeCreationDialogHandle, CodeCreationDial
error={Boolean(errors.name)}
helperText={<ErrorMessage errors={errors} name="name" />}
/>
<TextField
label="Color"
fullWidth
variant="standard"
{...register("color", { required: "Color is required" })}
error={Boolean(errors.color)}
helperText={<ErrorMessage errors={errors} name="color" />}
<Stack direction="row">
<TextField
label="Color"
fullWidth
variant="standard"
{...register("color", { required: "Color is required" })}
onChange={(e) => setColor(e.target.value)}
error={Boolean(errors.color)}
helperText={<ErrorMessage errors={errors} name="color" />}
InputLabelProps={{ shrink: true }}
/>
<Box sx={{ width: 48, height: 48, backgroundColor: color, ml: 1, flexShrink: 0 }} />
</Stack>
<HexColorPicker
style={{ width: "100%" }}
color={color}
onChange={(newColor) => {
setValue("color", newColor); // set value of text input
setColor(newColor); // set value of color picker (and box)
}}
/>
<TextField
multiline
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/views/search/DocumentViewer/ImageViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ function ImageViewer({ sdoc, adoc, showEntities }: ImageViewerProps) {
return (
<>
{annotations.isError && <span>{annotations.error.message}</span>}
<svg ref={svgRef} onClick={() => console.log("TEST")} width="100%" height="100%">
<svg ref={svgRef} width="100%" height="100%" style={{ cursor: "move" }}>
<g ref={gRef}>
<image href={sdoc.content} />
<g ref={bboxRef}></g>
Expand Down
34 changes: 33 additions & 1 deletion frontend/src/views/search/Tags/TagEdit/TagEditDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Dialog, DialogActions, DialogContent, DialogTitle, Stack, TextField } from "@mui/material";
import { Box, Dialog, DialogActions, DialogContent, DialogTitle, Stack, TextField } from "@mui/material";
import React, { useCallback, useEffect, useState } from "react";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import SnackbarAPI from "../../../../features/snackbar/SnackbarAPI";
Expand All @@ -9,6 +9,8 @@ import TagHooks from "../../../../api/TagHooks";
import { QueryKey } from "../../../../api/QueryKey";
import { ErrorMessage } from "@hookform/error-message";
import { LoadingButton } from "@mui/lab";
import { HexColorPicker } from "react-colorful";
import ColorUtils from "../../../../utils/ColorUtils";

/**
* A dialog that allows to update a DocumentTag.
Expand All @@ -23,11 +25,13 @@ function TagEditDialog() {
handleSubmit,
formState: { errors },
reset,
setValue,
} = useForm();

// state
const [tagId, setTagId] = useState<number | undefined>(undefined);
const [open, setOpen] = useState(false);
const [color, setColor] = useState("#000000");

// query
const tag = useQuery<DocumentTagRead, Error>(
Expand All @@ -54,10 +58,13 @@ function TagEditDialog() {
// initialize form when tag changes
useEffect(() => {
if (tag.data) {
const c = ColorUtils.rgbStringToHex(tag.data.description) || tag.data.description;
reset({
title: tag.data.title,
description: tag.data.description,
color: c,
});
setColor(c);
}
}, [tag.data, reset]);

Expand Down Expand Up @@ -113,6 +120,31 @@ function TagEditDialog() {
helperText={<>{errors?.title ? errors.title.message : ""}</>}
disabled={!tag.isSuccess}
/>
<Stack direction="row">
<TextField
label="Color"
fullWidth
variant="standard"
{...register("color", { required: "Color is required" })}
onChange={(e) => {
setColor(e.target.value);
setValue("description", e.target.value); // todo: remove this hack once tag has color attribute
}}
error={Boolean(errors.color)}
helperText={<ErrorMessage errors={errors} name="color" />}
InputLabelProps={{ shrink: true }}
/>
<Box sx={{ width: 48, height: 48, backgroundColor: color, ml: 1, flexShrink: 0 }} />
</Stack>
<HexColorPicker
style={{ width: "100%" }}
color={color}
onChange={(newColor) => {
setValue("color", newColor); // set value of text input
setColor(newColor); // set value of color picker (and box)
setValue("description", newColor); // todo: remove this hack once tag has color attribute
}}
/>
<TextField
multiline
minRows={5}
Expand Down