1
0
mirror of https://github.com/barthuijgen/factorio-sites.git synced 2025-03-17 21:17:57 +02:00

Blueprint page refactor (#51)

* blueprint page refactor

* Deduplicate code on blueprint page

Co-authored-by: Bart <barthuijgen@users.noreply.github.com>
This commit is contained in:
Bart 2021-03-17 10:33:51 +01:00 committed by GitHub
parent 653a1001e4
commit 8dc22086f9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 710 additions and 432 deletions

View File

@ -0,0 +1,174 @@
import React, { useEffect, useState } from "react";
import BBCode from "bbcode-to-react";
import { Grid, Image, Box } from "@chakra-ui/react";
import { Blueprint as IBlueprint, BlueprintPage, BlueprintStringData } from "@factorio-sites/types";
import { chakraResponsive, parseBlueprintStringClient } from "@factorio-sites/web-utils";
import { Panel } from "../../components/Panel";
import { Markdown } from "../../components/Markdown";
import { CopyButton } from "../../components/CopyButton";
import { ImageEditor } from "../../components/ImageEditor";
import styled from "@emotion/styled";
import { FavoriteButton } from "./FavoriteButton";
import { useUrl } from "../../hooks/url.hook";
import { BlueprintData } from "./BlueprintData";
import { BlueprintInfo } from "./BlueprintInfo";
import { BlueprintTags } from "./BlueprintTags";
import { BlueprintEntities } from "./BlueprintEntities";
const StyledBlueptintPage = styled(Grid)`
grid-gap: 16px;
.title {
position: relative;
width: 100%;
display: flex;
align-items: center;
.text {
white-space: nowrap;
width: calc(100% - 120px);
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
}
}
.panel {
&.image {
height: 579px;
}
&.child-tree {
overflow: hidden;
height: 579px;
position: relative;
.child-tree-wrapper {
height: 483px;
overflow: auto;
}
}
&.tags {
text-align: left;
.tag {
display: inline-block;
margin: 3px;
padding: 0 3px;
background: #313131;
border-radius: 3px;
}
}
.description {
max-height: 600px;
}
}
`;
const StyledMarkdown = styled(Markdown)`
max-height: 600px;
`;
interface BlueprintProps {
blueprint: IBlueprint;
blueprint_page: BlueprintPage;
favorite: boolean;
}
export const BlueprintSubPage: React.FC<BlueprintProps> = ({
blueprint,
blueprint_page,
favorite,
}) => {
const url = useUrl();
const [string, setString] = useState<string | null>(null);
const [data, setData] = useState<BlueprintStringData | null>(null);
useEffect(() => {
fetch(`/api/string/${blueprint.blueprint_hash}`)
.then((res) => res.text())
.then((string) => {
setString(string);
const data = parseBlueprintStringClient(string);
setData(data);
})
.catch((reason) => console.error(reason));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<StyledBlueptintPage templateColumns={chakraResponsive({ mobile: "1fr", desktop: "1fr 1fr" })}>
<Panel
className="image"
gridColumn="2"
title={
<div className="title">
<span>Image</span>
<img
src="/fbe.svg"
alt="Factorio blueprint editor"
css={{ display: "inline-block", height: "24px", marginLeft: "10px" }}
/>
<Box css={{ display: "inline-block", flexGrow: 1, textAlign: "right" }}>
{string && (
<CopyButton
primary
css={{ marginRight: "1rem" }}
label="Copy Blueprint"
content={string}
/>
)}
{blueprint.blueprint_hash && url && (
<CopyButton
label="Copy URL"
content={`${url.origin}/api/string/${blueprint.blueprint_hash}`}
/>
)}
</Box>
</div>
}
>
{string && <ImageEditor string={string}></ImageEditor>}
</Panel>
<Panel
className="description"
gridColumn="1"
gridRow="1"
title={
<div className="title">
<span className="text">{blueprint_page.title}</span>
<FavoriteButton is_favorite={favorite} blueprint_page_id={blueprint_page.id} />
</div>
}
>
<StyledMarkdown>{blueprint_page.description_markdown}</StyledMarkdown>
</Panel>
<Panel title="Info" gridColumn="1" gridRow="2">
<BlueprintInfo blueprint_page={blueprint_page} />
</Panel>
<Panel gridColumn="2" gridRow="2" title="Tags">
<BlueprintTags blueprint_page={blueprint_page} />
</Panel>
<Panel
className="entities"
gridColumn="1 / span 2"
title={
<span>
Entities for{" "}
{data?.blueprint?.label ? BBCode.toReact(data.blueprint.label) : "blueprint"}
</span>
}
>
{data && <BlueprintEntities data={data} />}
</Panel>
<Panel className="bp-strings" gridColumn="1 / span 2" title="Blueprint data">
{string && data && <BlueprintData string={string} data={data} />}
</Panel>
</StyledBlueptintPage>
);
};

View File

@ -0,0 +1,256 @@
import React, { useEffect, useState } from "react";
import BBCode from "bbcode-to-react";
import { Image, Box, Grid } from "@chakra-ui/react";
import styled from "@emotion/styled";
import {
BlueprintBook,
Blueprint,
BlueprintPage,
BlueprintStringData,
} from "@factorio-sites/types";
import {
chakraResponsive,
ChildTreeBlueprintBookEnriched,
mergeBlueprintDataAndChildTree,
parseBlueprintStringClient,
} from "@factorio-sites/web-utils";
import { Panel } from "../../components/Panel";
import { Markdown } from "../../components/Markdown";
import { BookChildTree } from "../../components/BookChildTree";
import { CopyButton } from "../../components/CopyButton";
import { ImageEditor } from "../../components/ImageEditor";
import { useUrl } from "../../hooks/url.hook";
import { FavoriteButton } from "./FavoriteButton";
import { BlueprintData } from "./BlueprintData";
import { BlueprintInfo } from "./BlueprintInfo";
import { BlueprintTags } from "./BlueprintTags";
import { BlueprintEntities } from "./BlueprintEntities";
const StyledBlueptintPage = styled(Grid)`
grid-gap: 16px;
.title {
position: relative;
width: 100%;
display: flex;
align-items: center;
.text {
white-space: nowrap;
width: calc(100% - 120px);
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
}
}
.panel {
&.image {
height: 579px;
}
&.child-tree {
overflow: hidden;
height: 579px;
position: relative;
.child-tree-wrapper {
height: 483px;
overflow: auto;
}
}
.description {
max-height: 600px;
}
}
`;
const StyledMarkdown = styled(Markdown)`
max-height: 600px;
`;
type Selected =
| { type: "blueprint"; data: Pick<Blueprint, "id" | "blueprint_hash" | "image_hash" | "label"> }
| { type: "blueprint_book"; data: Pick<BlueprintBook, "id" | "blueprint_hash" | "label"> };
interface BlueprintBookSubPageProps {
selected: Selected;
blueprint_book: BlueprintBook;
blueprint_page: BlueprintPage;
favorite: boolean;
}
export const BlueprintBookSubPage: React.FC<BlueprintBookSubPageProps> = ({
selected,
blueprint_book,
blueprint_page,
favorite,
}) => {
const url = useUrl();
const [selectedBlueprintString, setSelectedBlueprintString] = useState<string | null>(null);
const [bookChildTreeData, setBookChildTreeData] = useState<ChildTreeBlueprintBookEnriched | null>(
null
);
const [selectedData, setSelectedData] = useState<BlueprintStringData | null>(null);
const selectedHash = selected.data.blueprint_hash;
const showEntities = selected.type === "blueprint" && selectedData?.blueprint;
useEffect(() => {
fetch(`/api/string/${blueprint_book.blueprint_hash}`)
.then((res) => res.text())
.then((string) => {
const data = parseBlueprintStringClient(string);
if (data) {
setBookChildTreeData(
mergeBlueprintDataAndChildTree(data, {
id: blueprint_book.id,
name: blueprint_book.label,
type: "blueprint_book",
children: blueprint_book.child_tree,
})
);
}
})
.catch((reason) => console.error(reason));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
fetch(`/api/string/${selectedHash}`)
.then((res) => res.text())
.then((string) => {
setSelectedBlueprintString(string);
if (selected.type === "blueprint") {
const data = parseBlueprintStringClient(string);
setSelectedData(data);
} else {
setSelectedData(null);
}
})
.catch((reason) => console.error(reason));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedHash]);
const onRequestData = () => {
fetch(`/api/string/${selectedHash}`)
.then((res) => res.text())
.then((string) => {
const data = parseBlueprintStringClient(string);
setSelectedData(data);
});
};
return (
<StyledBlueptintPage
className="bp-book"
templateColumns={chakraResponsive({ mobile: "1fr", desktop: "1fr 1fr" })}
>
<Panel
className="child-tree"
title={
<div className="title">
<span className="text">{blueprint_page.title}</span>
<FavoriteButton is_favorite={favorite} blueprint_page_id={blueprint_page.id} />
</div>
}
gridColumn="1"
gridRow="1"
>
{bookChildTreeData && (
<div className="child-tree-wrapper">
<BookChildTree
blueprint_book={bookChildTreeData}
base_url={`/blueprint/${blueprint_page.id}`}
selected_id={selected.data.id}
/>
</div>
)}
</Panel>
<Panel
className="image"
gridColumn="2"
title={
<div className="title">
<span>Image</span>
<img
src="/fbe.svg"
alt="Factorio blueprint editor"
css={{ display: "inline-block", height: "24px", marginLeft: "10px" }}
/>
<Box css={{ display: "inline-block", flexGrow: 1, textAlign: "right" }}>
{selectedBlueprintString && (
<CopyButton
primary
css={{ marginRight: "1rem" }}
label="Copy Blueprint"
content={selectedBlueprintString}
/>
)}
{selected.data.blueprint_hash && url && (
<CopyButton
label="Copy URL"
content={`${url.origin}/api/string/${selected.data.blueprint_hash}`}
/>
)}
</Box>
</div>
}
>
{selectedBlueprintString && <ImageEditor string={selectedBlueprintString}></ImageEditor>}
</Panel>
<Panel
className="description"
gridColumn="1"
gridRow={"2 / span 2"}
title={
<div className="title">
<span className="text">Description </span>
<FavoriteButton is_favorite={favorite} blueprint_page_id={blueprint_page.id} />
</div>
}
>
<StyledMarkdown>{blueprint_page.description_markdown}</StyledMarkdown>
</Panel>
<Panel title="Info" gridColumn="2" gridRow="2">
<BlueprintInfo blueprint_page={blueprint_page} />
</Panel>
<Panel title="Tags" gridColumn="2" gridRow={"3"}>
<BlueprintTags blueprint_page={blueprint_page} />
</Panel>
{showEntities && (
<Panel
className="entities"
gridColumn="1 / span 2"
title={
<span>
Entities for{" "}
{selectedData?.blueprint?.label
? BBCode.toReact(selectedData.blueprint.label)
: "blueprint"}
</span>
}
>
{selectedData && <BlueprintEntities data={selectedData} />}
</Panel>
)}
<Panel
className="bp-strings"
gridColumn="1 / span 2"
title={`data for ${selected.type.replace("_", " ")} "${selected.data.label}"`}
>
{selectedBlueprintString && (
<BlueprintData
string={selectedBlueprintString}
data={selectedData}
onRequestData={onRequestData}
/>
)}
</Panel>
</StyledBlueptintPage>
);
};

View File

@ -0,0 +1,63 @@
import { Button } from "../Button";
import { Box } from "@chakra-ui/react";
import { useEffect, useState } from "react";
import { BlueprintStringData } from "@factorio-sites/types";
interface BlueprintDataProps {
string: string;
data: BlueprintStringData | null;
onRequestData?: () => void;
}
export const BlueprintData: React.FC<BlueprintDataProps> = ({ string, data, onRequestData }) => {
const [showDetails, setShowDetails] = useState<"string" | "json" | "none">("none");
useEffect(() => {
setShowDetails("none");
}, [string, data]);
return (
<Box>
<Box>
<Button
onClick={() => {
setShowDetails(showDetails === "string" ? "none" : "string");
}}
>
{showDetails === "string" ? "Hide" : "Show"} string
</Button>
<Button
css={{ marginLeft: "1rem" }}
onClick={() => {
setShowDetails(showDetails === "json" ? "none" : "json");
if (!data && onRequestData) {
onRequestData();
}
}}
>
{showDetails === "json" ? "Hide" : "Show"} json
</Button>
</Box>
<Box css={{ marginTop: "1rem" }}>
{showDetails === "string" && (
<textarea
value={string || "Loading..."}
readOnly
css={{
width: "100%",
height: "100px",
resize: "none",
color: "#fff",
backgroundColor: "#414040",
}}
/>
)}
{showDetails === "json" && (
<pre css={{ maxHeight: "500px", overflowY: "scroll" }}>
{JSON.stringify(data, null, 2)}
</pre>
)}
</Box>
</Box>
);
};

View File

@ -0,0 +1,54 @@
import { Box, Image } from "@chakra-ui/react";
import { BlueprintStringData } from "@factorio-sites/types";
import styled from "@emotion/styled";
const StyledBox = styled(Box)`
td {
border: 1px solid #909090;
}
td:not(.no-padding) {
padding: 5px 10px;
}
`;
interface BlueprintEntitiesProps {
data: BlueprintStringData;
}
export const BlueprintEntities: React.FC<BlueprintEntitiesProps> = ({ data }) => {
return (
<StyledBox>
<table>
<tbody>
{data?.blueprint?.entities &&
Object.entries(
data.blueprint.entities.reduce<Record<string, number>>((entities, entity) => {
if (entities[entity.name]) {
entities[entity.name]++;
} else {
entities[entity.name] = 1;
}
return entities;
}, {})
)
.sort((a, b) => b[1] - a[1])
.map(([entry_name, entry]) => (
<tr key={entry_name}>
<td className="no-padding">
<Image
alt={entry_name.replace(/-/g, " ")}
src={`https://factorioprints.com/icons/${entry_name}.png`}
fallbackSrc="https://storage.googleapis.com/factorio-blueprints-assets/error-icon.png"
width="32px"
height="32px"
/>
</td>
<td>{entry_name}</td>
<td>{entry}</td>
</tr>
))}
</tbody>
</table>
</StyledBox>
);
};

View File

@ -0,0 +1,66 @@
import { Box } from "@chakra-ui/react";
import { format } from "date-fns";
import Link from "next/link";
import { BlueprintPage } from "@factorio-sites/types";
import styled from "@emotion/styled";
const StyledBox = styled(Box)`
dl {
display: flex;
dt {
width: 65%;
font-weight: 600;
}
dd {
width: 35%;
text-align: right;
}
}
hr {
margin-left: -64px;
margin-right: -64px;
border: none;
height: 2px;
margin: 12px auto;
box-shadow: inset 0 1px 1px 0 #131313, inset 0 -1px 1px 0 #838383, 0 0 4px 0 #392f2e;
}
`;
interface BlueprintInfoProps {
blueprint_page: BlueprintPage;
}
export const BlueprintInfo: React.FC<BlueprintInfoProps> = ({ blueprint_page }) => {
return (
<StyledBox>
<dl>
<dt>User:</dt>
<dd>
{blueprint_page.user ? (
<Link href={`/?user=${blueprint_page.user?.id}`}>
<a>{blueprint_page.user?.username}</a>
</Link>
) : (
"-"
)}
</dd>
</dl>
<hr />
<dl>
<dt>Last updated:</dt>
<dd>{format(new Date(blueprint_page.updated_at * 1000), "DD/mm/YYYY")}</dd>
</dl>
<hr />
<dl>
<dt>Created:</dt>
<dd>{format(new Date(blueprint_page.created_at * 1000), "DD/mm/YYYY")}</dd>
</dl>
<hr />
<dl>
<dt>Favorites:</dt>
<dd>{blueprint_page.favorite_count || "0"}</dd>
</dl>
</StyledBox>
);
};

View File

@ -0,0 +1,36 @@
import { Box } from "@chakra-ui/react";
import { BlueprintPage } from "@factorio-sites/types";
import styled from "@emotion/styled";
import { TAGS_BY_KEY } from "@factorio-sites/common-utils";
const StyledBox = styled(Box)`
text-align: left;
.tag {
display: inline-block;
margin: 3px;
padding: 0 3px;
background: #313131;
border-radius: 3px;
}
`;
interface BlueprintTagsProps {
blueprint_page: BlueprintPage;
}
export const BlueprintTags: React.FC<BlueprintTagsProps> = ({ blueprint_page }) => {
return (
<StyledBox>
{blueprint_page.tags.length ? (
blueprint_page.tags.map((tag) => (
<span key={tag} className="tag">
{TAGS_BY_KEY[tag].category}: {TAGS_BY_KEY[tag].label}
</span>
))
) : (
<div>No tags have been added yet</div>
)}
</StyledBox>
);
};

View File

@ -0,0 +1,40 @@
import { useState } from "react";
import { AiFillHeart, AiOutlineHeart } from "react-icons/ai";
import { useAuth } from "../../providers/auth";
import { Button } from "../Button";
interface FavoriteButtonProps {
is_favorite: boolean;
blueprint_page_id: string;
}
export const FavoriteButton: React.FC<FavoriteButtonProps> = ({
is_favorite: isFavoriteDefault,
blueprint_page_id,
}) => {
const auth = useAuth();
const [isFavorite, setIsFavorite] = useState(isFavoriteDefault);
const onClickFavorite = async () => {
const result = await fetch("/api/user/favorite", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ blueprint_page_id }),
}).then((res) => res.json());
setIsFavorite(result.favorite);
};
if (!auth) return null;
return (
<Button
onClick={onClickFavorite}
css={{ display: "inline-flex", float: "right", fontSize: "initial" }}
>
Favorite
<span className="icon" css={{ marginLeft: "5px" }}>
{isFavorite ? <AiFillHeart /> : <AiOutlineHeart />}
</span>
</Button>
);
};

View File

@ -1,122 +1,16 @@
import React, { useEffect, useState } from "react";
import React, { useEffect } from "react";
import { NextPage } from "next";
import Link from "next/link";
import BBCode from "bbcode-to-react";
import { Image, Box, Grid } from "@chakra-ui/react";
import styled from "@emotion/styled";
import clsx from "clsx";
import {
getBlueprintBookById,
getBlueprintById,
getBlueprintPageWithUserById,
isBlueprintPageUserFavorite,
} from "@factorio-sites/database";
import {
BlueprintBook,
Blueprint,
BlueprintPage,
BlueprintStringData,
} from "@factorio-sites/types";
import { TAGS_BY_KEY, timeLogger } from "@factorio-sites/common-utils";
import {
chakraResponsive,
ChildTreeBlueprintBookEnriched,
mergeBlueprintDataAndChildTree,
parseBlueprintStringClient,
} from "@factorio-sites/web-utils";
import { Panel } from "../../components/Panel";
import { Markdown } from "../../components/Markdown";
import { BookChildTree } from "../../components/BookChildTree";
import { CopyButton } from "../../components/CopyButton";
import { ImageEditor } from "../../components/ImageEditor";
import { useAuth } from "../../providers/auth";
import { BlueprintBook, Blueprint, BlueprintPage } from "@factorio-sites/types";
import { timeLogger } from "@factorio-sites/common-utils";
import { pageHandler } from "../../utils/page-handler";
import { AiOutlineHeart, AiFillHeart } from "react-icons/ai";
import { Button } from "../../components/Button";
import { useUrl } from "../../hooks/url.hook";
const StyledBlueptintPage = styled(Grid)`
grid-gap: 16px;
.title {
position: relative;
width: 100%;
.text {
white-space: nowrap;
width: calc(100% - 120px);
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
}
}
.panel {
&.image {
height: 579px;
}
&.child-tree {
overflow: hidden;
height: 579px;
position: relative;
.child-tree-wrapper {
height: 483px;
overflow: auto;
}
}
&.info {
dl {
display: flex;
dt {
width: 65%;
font-weight: 600;
}
dd {
width: 35%;
text-align: right;
}
}
hr {
margin-left: -64px;
margin-right: -64px;
border: none;
height: 2px;
margin: 12px auto;
box-shadow: inset 0 1px 1px 0 #131313, inset 0 -1px 1px 0 #838383, 0 0 4px 0 #392f2e;
}
}
&.tags {
text-align: left;
.tag {
display: inline-block;
margin: 3px;
padding: 0 3px;
background: #313131;
border-radius: 3px;
}
}
&.entities table {
td {
border: 1px solid #909090;
}
td:not(.no-padding) {
padding: 5px 10px;
}
}
.description {
max-height: 600px;
}
}
`;
const StyledMarkdown = styled(Markdown)`
max-height: 600px;
`;
import { BlueprintSubPage } from "../../components/blueprint/Blueprint";
import { BlueprintBookSubPage } from "../../components/blueprint/BlueprintBook";
type Selected =
| { type: "blueprint"; data: Pick<Blueprint, "id" | "blueprint_hash" | "image_hash" | "label"> }
@ -137,58 +31,6 @@ export const Index: NextPage<IndexProps> = ({
blueprint_page,
favorite,
}) => {
const auth = useAuth();
const url = useUrl();
const [selectedBlueprintString, setSelectedBlueprintString] = useState<string | null>(null);
const [bookChildTreeData, setBookChildTreeData] = useState<ChildTreeBlueprintBookEnriched | null>(
null
);
const [selectedData, setSelectedData] = useState<BlueprintStringData | null>(null);
const [showDetails, setShowDetails] = useState<"string" | "json" | "none">("none");
const [isFavorite, setIsFavorite] = useState(favorite);
const selectedHash = selected.data.blueprint_hash;
const isBlueprintBook = Boolean(blueprint_book);
// const isBlueprintBookChild = isBlueprintBook && selected.type === "blueprint";
const showEntities = selected.type === "blueprint" && selectedData?.blueprint;
useEffect(() => {
const hash = blueprint_book ? blueprint_book.blueprint_hash : blueprint?.blueprint_hash;
fetch(`/api/string/${hash}`)
.then((res) => res.text())
.then((string) => {
const data = parseBlueprintStringClient(string);
if (data && blueprint_book) {
setBookChildTreeData(
mergeBlueprintDataAndChildTree(data, {
id: blueprint_book.id,
name: blueprint_book.label,
type: "blueprint_book",
children: blueprint_book.child_tree,
})
);
}
})
.catch((reason) => console.error(reason));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
fetch(`/api/string/${selectedHash}`)
.then((res) => res.text())
.then((string) => {
setShowDetails("none");
setSelectedBlueprintString(string);
if (selected.type === "blueprint") {
const data = parseBlueprintStringClient(string);
setSelectedData(data);
} else {
setSelectedData(null);
}
})
.catch((reason) => console.error(reason));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedHash]);
useEffect(() => {
console.log({
selected,
@ -199,275 +41,22 @@ export const Index: NextPage<IndexProps> = ({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const onClickFavorite = async () => {
const result = await fetch("/api/user/favorite", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ blueprint_page_id: blueprint_page.id }),
}).then((res) => res.json());
setIsFavorite(result.favorite);
};
return (
<StyledBlueptintPage
className={clsx({ "bp-book": isBlueprintBook })}
templateColumns={chakraResponsive({ mobile: "1fr", desktop: "1fr 1fr" })}
>
{isBlueprintBook && (
<Panel
className="child-tree"
title={
<div className="title">
<span className="text">{blueprint_page.title}</span>
{auth && (
<Button
onClick={onClickFavorite}
css={{ display: "inline-flex", float: "right", fontSize: "initial" }}
>
Favorite
<span className="icon" css={{ marginLeft: "5px" }}>
{isFavorite ? <AiFillHeart /> : <AiOutlineHeart />}
</span>
</Button>
)}
</div>
}
gridColumn="1"
gridRow="1"
>
{bookChildTreeData && (
<div className="child-tree-wrapper">
<BookChildTree
blueprint_book={bookChildTreeData}
base_url={`/blueprint/${blueprint_page.id}`}
selected_id={selected.data.id}
/>
</div>
)}
</Panel>
)}
<Panel
className="image"
gridColumn="2"
title={
<div className="title">
<span>Image</span>
<img
src="/fbe.svg"
alt="Factorio blueprint editor"
css={{ display: "inline-block", height: "24px", marginLeft: "10px" }}
/>
<Box css={{ display: "inline-block", float: "right" }}>
{selectedBlueprintString && (
<CopyButton
primary
css={{ marginRight: "1rem" }}
label="Copy Blueprint"
content={selectedBlueprintString}
/>
)}
{selected.data.blueprint_hash && url && (
<CopyButton
label="Copy URL"
content={`${url.origin}/api/string/${selected.data.blueprint_hash}`}
/>
)}
</Box>
</div>
}
>
{selectedBlueprintString && <ImageEditor string={selectedBlueprintString}></ImageEditor>}
</Panel>
<Panel
className="description"
gridColumn="1"
gridRow={isBlueprintBook ? "2 / span 2" : "1"}
title={
<div className="title">
<span className="text">{isBlueprintBook ? "Description" : blueprint_page.title}</span>
{auth && !isBlueprintBook && (
<Button
onClick={onClickFavorite}
css={{ display: "inline-flex", float: "right", fontSize: "initial" }}
>
Favorite
<span className="icon" css={{ marginLeft: "5px" }}>
{isFavorite ? <AiFillHeart /> : <AiOutlineHeart />}
</span>
</Button>
)}
</div>
}
// bottom={
// <div css={{ display: "flex", justifyContent: "flex-end" }}>
// {selectedBlueprintString && (
// <CopyButton
// css={{ marginRight: 16 }}
// label="Copy Blueprint"
// content={selectedBlueprintString}
// />
// )}
// {selected.data.blueprint_hash && typeof window !== "undefined" && (
// <CopyButton
// label="Copy URL"
// content={`${window.location.origin}/api/string/${selected.data.blueprint_hash}`}
// />
// )}
// </div>
// }
>
<StyledMarkdown>{blueprint_page.description_markdown}</StyledMarkdown>
</Panel>
<Panel className="info" gridColumn={isBlueprintBook ? "2" : "1"} gridRow="2" title={"Info"}>
<Box>
<dl>
<dt>User:</dt>
<dd>
{blueprint_page.user ? (
<Link href={`/?user=${blueprint_page.user?.id}`}>
<a>{blueprint_page.user?.username}</a>
</Link>
) : (
"-"
)}
</dd>
</dl>
<hr />
<dl>
<dt>Last updated:</dt>
<dd>{new Date(blueprint_page.updated_at * 1000).toLocaleDateString()}</dd>
</dl>
<hr />
<dl>
<dt>Created:</dt>
<dd>{new Date(blueprint_page.created_at * 1000).toLocaleDateString()}</dd>
</dl>
<hr />
<dl>
<dt>Favorites:</dt>
<dd>{blueprint_page.favorite_count || "0"}</dd>
</dl>
</Box>
</Panel>
<Panel className="tags" gridColumn="2" gridRow={isBlueprintBook ? "3" : "2"} title={"Tags"}>
{blueprint_page.tags.length ? (
blueprint_page.tags.map((tag) => (
<span key={tag} className="tag">
{TAGS_BY_KEY[tag].category}: {TAGS_BY_KEY[tag].label}
</span>
))
) : (
<div>No tags have been added yet</div>
)}
</Panel>
{showEntities && (
<Panel
className="entities"
gridColumn="1 / span 2"
title={
<span>
Entities for{" "}
{selectedData?.blueprint?.label
? BBCode.toReact(selectedData.blueprint.label)
: "blueprint"}
</span>
}
>
<table>
<tbody>
{selectedData?.blueprint?.entities &&
Object.entries(
selectedData.blueprint.entities.reduce<Record<string, number>>(
(entities, entity) => {
if (entities[entity.name]) {
entities[entity.name]++;
} else {
entities[entity.name] = 1;
}
return entities;
},
{}
)
)
.sort((a, b) => b[1] - a[1])
.map(([entry_name, entry]) => (
<tr key={entry_name}>
<td className="no-padding">
<Image
alt={entry_name.replace(/-/g, " ")}
src={`https://factorioprints.com/icons/${entry_name}.png`}
fallbackSrc="https://storage.googleapis.com/factorio-blueprints-assets/error-icon.png"
width="32px"
height="32px"
/>
</td>
<td>{entry_name}</td>
<td>{entry}</td>
</tr>
))}
</tbody>
</table>
</Panel>
)}
<Panel
className="bp-strings"
gridColumn="1 / span 2"
title={`data for ${selected.type.replace("_", " ")} "${selected.data.label}"`}
>
<Box>
<Button
onClick={() => {
setShowDetails(showDetails === "string" ? "none" : "string");
}}
>
{showDetails === "string" ? "Hide" : "Show"} string
</Button>
<Button
css={{ marginLeft: "1rem" }}
onClick={() => {
setShowDetails(showDetails === "json" ? "none" : "json");
if (!selectedData) {
fetch(`/api/string/${selectedHash}`)
.then((res) => res.text())
.then((string) => {
const data = parseBlueprintStringClient(string);
setSelectedData(data);
});
}
}}
>
{showDetails === "json" ? "Hide" : "Show"} json
</Button>
</Box>
<Box css={{ marginTop: "1rem" }}>
{showDetails === "string" && (
<textarea
value={selectedBlueprintString || "Loading..."}
readOnly
css={{
width: "100%",
height: "100px",
resize: "none",
color: "#fff",
backgroundColor: "#414040",
}}
/>
)}
{showDetails === "json" && (
<pre css={{ maxHeight: "500px", overflowY: "scroll" }}>
{JSON.stringify(selectedData, null, 2)}
</pre>
)}
</Box>
</Panel>
</StyledBlueptintPage>
);
if (blueprint) {
return (
<BlueprintSubPage blueprint={blueprint} blueprint_page={blueprint_page} favorite={favorite} />
);
} else if (blueprint_book) {
return (
<BlueprintBookSubPage
selected={selected}
blueprint_book={blueprint_book}
blueprint_page={blueprint_page}
favorite={favorite}
/>
);
} else {
return <div>404</div>;
}
};
export const getServerSideProps = pageHandler(async (context, { session }) => {