sendou.ink/components/plans/index.tsx
Kalle (Sendou) 625afad4b2 update deps
2020-11-16 23:14:39 +02:00

378 lines
9.9 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Button, ButtonGroup, Flex } from "@chakra-ui/react";
import { t, Trans } from "@lingui/macro";
import Breadcrumbs from "components/common/Breadcrumbs";
import { stages } from "lib/lists/stages";
import { NextPage } from "next";
import dynamic from "next/dynamic";
import { useEffect, useRef, useState } from "react";
import {
FaBomb,
FaFileDownload,
FaFileImage,
FaFileUpload,
} from "react-icons/fa";
import DraggableImageAdder from "./DraggableImageAdder";
import DraggableToolsSelector from "./DraggableToolsSelector";
import StageSelector from "./StageSelector";
const MapSketch = dynamic(() => import("./MapSketch"), {
ssr: false,
});
export type Tool = "pencil" | "line" | "rectangle" | "circle" | "select";
export interface PlannerMapBg {
view?: "M" | "R";
stage: string;
mode?: "SZ" | "TC" | "RM" | "CB";
tide?: "low" | "mid" | "high";
}
const stageToCode = new Map<string, string>([
["Ancho-V Games", "AG"],
["Arowana Mall", "AM"],
["Blackbelly Skatepark", "BS"],
["Camp Triggerfish", "CT"],
["Goby Arena", "GA"],
["Humpback Pump Track", "HP"],
["Inkblot Art Academy", "IA"],
["Kelp Dome", "KD"],
["Musselforge Fitness", "MF"],
["MakoMart", "MK"],
["Manta Maria", "MM"],
["Moray Towers", "MT"],
["New Albacore Hotel", "NA"],
["Port Mackerel", "PM"],
["Piranha Pit", "PP"],
["Snapper Canal", "SC"],
["Shellendorf Institute", "SI"],
["Starfish Mainstage", "SM"],
["Skipper Pavilion", "SP"],
["Sturgeon Shipyard", "SS"],
["The Reef", "TR"],
["Wahoo World", "WH"],
["Walleye Warehouse", "WW"],
]);
const plannerMapBgToImage = (bg: PlannerMapBg) => {
if (!bg.tide)
return `images/plannerMaps/${bg.view} ${stageToCode.get(bg.stage)} ${
bg.mode
}.png`;
return `images/plannerMaps/${bg.stage}-${bg.tide}.png`;
};
const defaultValue = {
shadowWidth: 0,
shadowOffset: 0,
enableRemoveSelected: false,
fillWithColor: false,
fillWithBackgroundColor: false,
drawings: [],
canUndo: false,
canRedo: false,
controlledSize: false,
sketchWidth: 600,
sketchHeight: 600,
stretched: true,
stretchedX: false,
stretchedY: false,
originX: "left",
originY: "top",
expandTools: false,
expandControls: false,
expandColors: false,
expandBack: false,
expandImages: false,
expandControlled: false,
enableCopyPaste: false,
backgroundImage: {
type: "image",
version: "2.4.3",
originX: "left",
originY: "top",
left: 0,
top: 0,
width: 1127,
height: 634,
fill: "rgb(0,0,0)",
stroke: null,
strokeWidth: 0,
strokeDashArray: null,
strokeLineCap: "butt",
strokeLineJoin: "miter",
strokeMiterLimit: 4,
scaleX: 1,
scaleY: 1,
angle: 0,
flipX: false,
flipY: false,
opacity: 1,
shadow: null,
visible: true,
clipTo: null,
backgroundColor: "",
fillRule: "nonzero",
paintFirst: "fill",
globalCompositeOperation: "source-over",
transformMatrix: null,
skewX: 0,
skewY: 0,
crossOrigin: "",
cropX: 0,
cropY: 0,
src: "/images/plannerMaps/M%20TR%20SZ.png",
filters: [],
},
};
const MapPlannerPage: NextPage = () => {
const fileInput = useRef<HTMLInputElement | null>(null);
const sketch = useRef<any>(null);
const [tool, setTool] = useState<Tool>("pencil");
const [color, setColor] = useState("#f44336");
const [canUndo, setCanUndo] = useState(false);
const [canRedo, setCanRedo] = useState(false);
const [bg, setBg] = useState<PlannerMapBg>({
view: "M",
stage: "The Reef",
mode: "SZ",
});
const [controlledValue, setControlledValue] = useState(defaultValue);
const addImageToSketch = (imgSrc: string) => {
sketch.current.addImg(imgSrc);
setTool("select");
};
const addTextToSketch = () => {
sketch.current.addText("Double-click to edit", {
fill: color,
fontFamily: "lato",
stroke: "#000000",
strokeWidth: 3,
paintFirst: "stroke",
});
setTool("select");
};
const undo = () => {
sketch.current.undo();
setCanUndo(sketch.current.canUndo());
setCanRedo(sketch.current.canRedo());
};
const redo = () => {
sketch.current.redo();
setCanUndo(sketch.current.canUndo());
setCanRedo(sketch.current.canRedo());
};
const removeSelected = () => {
sketch.current.removeSelected();
};
const onSketchChange = () => {
if (!sketch.current) return;
let prev = canUndo;
let now = sketch.current.canUndo();
if (prev !== now) {
setCanUndo(now);
}
};
const getDateFormatted = () => {
const today = new Date();
const date =
today.getFullYear() +
"-" +
(today.getMonth() + 1) +
"-" +
today.getDate();
const time =
today.getHours() + ":" + today.getMinutes() + ":" + today.getSeconds();
return date + " " + time;
};
const download = (dataUrl: string, extension: string) => {
if (!bg) return;
let a = document.createElement("a");
document.body.appendChild(a);
a.style.display = "none";
a.href = dataUrl;
a.download = `${bg.stage} plans ${getDateFormatted()}.${extension}`;
a.click();
window.URL.revokeObjectURL(dataUrl);
};
const handleUpload = () => {
if (!fileInput.current) {
return;
}
fileInput.current.click();
};
const files = fileInput.current?.files;
useEffect(() => {
if (!fileInput.current?.files?.length) return;
const fileObj = fileInput.current.files[0];
const reader = new FileReader();
reader.onload = function (event) {
const jsonObj = JSON.parse(event.target!.result as any);
setControlledValue(jsonObj);
const imgSrc = jsonObj.backgroundImage.src;
const searchFolder = "plannerMaps";
const imgName = imgSrc
.slice(imgSrc.lastIndexOf(searchFolder) + searchFolder.length + 1, -4)
.replace(/%20/g, " ");
const salmonRunMaps = [
"Spawning Grounds",
"Marooner's Bay",
"Lost Outpost",
"Salmonid Smokeyard",
"Ruins of Ark Polaris",
];
let isSalmonRunMap = false;
for (const map of salmonRunMaps) {
if (imgName.startsWith(map)) {
isSalmonRunMap = true;
const imageNameParts = imgName.split("-");
if (imageNameParts.length > 1) {
const tide = imageNameParts[1];
setBg({ tide: tide, stage: map });
}
}
}
if (!isSalmonRunMap) {
const imageNameParts = imgName.split(" ");
if (imageNameParts.length > 2) {
const view = imageNameParts[0];
const mapCode = imageNameParts[1];
const mode = imageNameParts[2];
let mapName = "";
stageToCode.forEach((value, key) => {
if (value === mapCode) {
mapName = key;
}
});
setBg({ view, stage: mapName, mode });
}
}
};
reader.readAsText(fileObj);
}, [files]);
useEffect(() => {
if (!sketch.current) return;
setCanUndo(false);
sketch.current.setBackgroundFromDataUrl(plannerMapBgToImage(bg));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [bg]);
return (
<>
<Breadcrumbs pages={[{ name: t`Map Planner` }]} />
<DraggableToolsSelector
tool={tool}
setTool={setTool}
redo={redo}
redoIsDisabled={!canRedo}
undo={undo}
undoIsDisabled={!canUndo}
removeSelected={removeSelected}
addText={addTextToSketch}
color={color}
setColor={(newColor) => setColor(newColor)}
/>
<DraggableImageAdder
addImageToSketch={(imgSrc) => addImageToSketch(imgSrc)}
/>
<MapSketch
sketch={sketch}
controlledValue={controlledValue}
color={color}
onSketchChange={onSketchChange}
tool={tool}
/>
<Flex my={2} justifyContent="space-between">
<Button
onClick={() => {
sketch.current.clear();
setBg({ ...bg });
}}
leftIcon={<FaBomb />}
colorScheme="red"
size="sm"
variant="outline"
>
<Trans>Clear drawings</Trans>
</Button>
<ButtonGroup variant="outline" size="sm" isAttached>
<Button
onClick={() => download(sketch.current.toDataURL(), "png")}
leftIcon={<FaFileImage />}
>
<Trans>Download as .png</Trans>
</Button>
<Button
onClick={() =>
download(
"data:text/json;charset=utf-8," +
encodeURIComponent(JSON.stringify(sketch.current.toJSON())),
"json"
)
}
leftIcon={<FaFileDownload />}
>
<Trans>Download as .json</Trans>
</Button>
<Button onClick={() => handleUpload()} leftIcon={<FaFileUpload />}>
<Trans>Load from .json</Trans>
</Button>
</ButtonGroup>
</Flex>
<StageSelector
handleChange={(e) => {
const newStage = e.target.value;
if (newStage === "") {
return;
}
const newIsSalmonRunStage = !stages.includes(newStage as any);
const oldIsSalmonRunStage = !stages.includes(bg.stage as any);
if (newIsSalmonRunStage === oldIsSalmonRunStage) {
setBg({ ...bg, stage: e.target.value });
return;
}
if (newIsSalmonRunStage) {
setBg({ stage: e.target.value, tide: "mid" });
return;
}
setBg({ stage: e.target.value, mode: "SZ", view: "M" });
}}
currentBackground={bg}
changeMode={(mode) => setBg({ ...bg, mode })}
changeTide={(tide: "low" | "mid" | "high") => setBg({ ...bg, tide })}
changeView={(view: "M" | "R") => setBg({ ...bg, view })}
/>
<input
type="file"
accept=".json"
ref={fileInput}
style={{ display: "none" }}
onChange={() => setBg({ ...bg })}
/>
</>
);
};
export default MapPlannerPage;