sendou.ink/pages/plans.tsx
Kalle 1589b84c4b
New layout (#427) closes #405
* side layout initial

* add elements to side nav

* side buttons links

* remove clog

* calendar page initial

* position sticky working

* x trends page initial

* new table

* same mode selector

* mobile friendly table

* no underline for nav links

* xsearch

* x trends page outlined

* sr initial

* relocate calendar components

* calendar fix flex

* topnav fancier look

* layout looking good edition

* relocate xtrends

* xtrends remove linecharts

* x trends new

* calender page new

* delete headbanner, new login

* remove calendar stuff from api

* rename stuff in utils

* fix user item margin

* new home page initial

* remove page concept

* no pointer xtrends

* remove xrank from app

* xtrends service

* move fa from app

* move plus

* maps tweaks

* new table for plus history

* navigational sidebar flex tweaks

* builds page

* analyzer

* user page

* free agents

* plans

* remove mx

* tweaks

* change layout to grid

* home page finalized

* mobile nav

* restrict main content width

* tweaks style

* language switcher

* container in css

* sticky nav

* use duplicate icons for now

* change mapsketch width to old

* chara tour vid

* borzoic icons
2021-04-21 17:26:50 +03:00

377 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 DraggableImageAdder from "components/plans/DraggableImageAdder";
import DraggableToolsSelector from "components/plans/DraggableToolsSelector";
import StageSelector from "components/plans/StageSelector";
import dynamic from "next/dynamic";
import { useEffect, useRef, useState } from "react";
import {
FaBomb,
FaFileDownload,
FaFileImage,
FaFileUpload,
} from "react-icons/fa";
import { stages } from "utils/lists/stages";
import MyHead from "../components/common/MyHead";
const MapSketch = dynamic(() => import("components/plans/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 = () => {
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 (
<>
<MyHead title={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 mt={4} mb={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;