mirror of
https://github.com/barthuijgen/factorio-sites.git
synced 2025-03-17 21:17:57 +02:00
Added FactorioCode component for blueprint titles
This commit is contained in:
parent
4bdf7fc28e
commit
3ab956d122
@ -1,4 +1,5 @@
|
||||
*.Dockerfile
|
||||
/credentials
|
||||
/.vscode
|
||||
/.cache
|
||||
/.cache
|
||||
/node_modules
|
@ -1,59 +0,0 @@
|
||||
import parser, { Tag } from "bbcode-to-react";
|
||||
import { FactorioIcon } from "./FactorioIcon";
|
||||
|
||||
class ImgTag extends Tag {
|
||||
toReact() {
|
||||
const content = this.getContent(true);
|
||||
const img = (this.params as Record<string, string>).img;
|
||||
const [type, item] = img.split("/");
|
||||
if (type === "item") {
|
||||
return (
|
||||
<>
|
||||
<FactorioIcon type={type} icon={item} size={20} />
|
||||
{content && <span>{content}</span>}
|
||||
</>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class ItemTag extends Tag {
|
||||
toReact() {
|
||||
const content = this.getContent(true);
|
||||
const item = (this.params as Record<string, string>).item;
|
||||
if (item) {
|
||||
return (
|
||||
<>
|
||||
<FactorioIcon type="item" icon={item} size={20} />
|
||||
{content && <span>{content}</span>}
|
||||
</>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class VirtualSignalTag extends Tag {
|
||||
toReact() {
|
||||
const signal = (this.params as Record<string, string>)["virtual-signal"];
|
||||
const content = this.getContent(true);
|
||||
if (signal) {
|
||||
return (
|
||||
<>
|
||||
<FactorioIcon type="signal" icon={signal} size={20} />
|
||||
{content && <span>{content}</span>}
|
||||
</>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
parser.registerTag("img", ImgTag as typeof Tag);
|
||||
parser.registerTag("item", ItemTag as typeof Tag);
|
||||
parser.registerTag("virtual-signal", VirtualSignalTag as typeof Tag);
|
||||
|
||||
export const BBCode: React.FC<{ code: string }> = ({ code }) => {
|
||||
return <>{parser.toReact(code)}</>;
|
||||
};
|
@ -2,7 +2,7 @@ import { css } from "@emotion/react";
|
||||
import Link from "next/link";
|
||||
import { ChildTreeBlueprintBookEnriched } from "@factorio-sites/web-utils";
|
||||
import { FactorioIcon } from "./FactorioIcon";
|
||||
import { BBCode } from "./BBCode";
|
||||
import { FactorioCode } from "./FactorioCode";
|
||||
|
||||
const componentStyles = css`
|
||||
.blueprint,
|
||||
@ -51,7 +51,7 @@ export const BookChildTree: React.FC<BookChildTreeProps> = ({
|
||||
/>
|
||||
))}
|
||||
<span className="label">
|
||||
<BBCode code={blueprint_book.name || ""} />
|
||||
<FactorioCode code={blueprint_book.name || ""} />
|
||||
</span>
|
||||
</a>
|
||||
</Link>
|
||||
@ -70,7 +70,7 @@ export const BookChildTree: React.FC<BookChildTreeProps> = ({
|
||||
/>
|
||||
))}
|
||||
<span className="label">
|
||||
<BBCode code={child.name || ""} />
|
||||
<FactorioCode code={child.name || ""} />
|
||||
</span>
|
||||
</a>
|
||||
</Link>
|
||||
|
107
apps/blueprints/src/components/FactorioCode.tsx
Normal file
107
apps/blueprints/src/components/FactorioCode.tsx
Normal file
@ -0,0 +1,107 @@
|
||||
import { Box } from "@chakra-ui/layout";
|
||||
import styled from "@emotion/styled";
|
||||
import { IconSignalTypes } from "@factorio-sites/types";
|
||||
import { ReactNode } from "react";
|
||||
import { FactorioIcon } from "./FactorioIcon";
|
||||
|
||||
const ICON_TYPES = ["entity", "item", "recipe", "fluid", "virtual-signal", "img"];
|
||||
|
||||
const ICON_REGEX = new RegExp(
|
||||
`(\\[(?<type>${ICON_TYPES.join(
|
||||
"|"
|
||||
)})=(?<icon>.*?)\\]|\\[color=(?<color>.*?)\\](?<content>.*?)\\[\\/color\\])`,
|
||||
"g"
|
||||
);
|
||||
|
||||
const RBG_COLOR_REGEX = /^\d+,\d+,\d+$/;
|
||||
const IMG_ICON_REGEX = /^\[img=(.*?)\/(.*?)\]$/;
|
||||
|
||||
interface Match {
|
||||
value: string;
|
||||
start: number;
|
||||
end: number;
|
||||
groups: Record<string, string | undefined>;
|
||||
}
|
||||
|
||||
const regexMatchAll = (string: string, regexp: RegExp): Match[] => {
|
||||
const matches = [];
|
||||
let match;
|
||||
while ((match = ICON_REGEX.exec(string)) !== null) {
|
||||
matches.push({
|
||||
value: match[0],
|
||||
start: match.index,
|
||||
end: ICON_REGEX.lastIndex,
|
||||
groups: match.groups || {},
|
||||
});
|
||||
}
|
||||
return matches;
|
||||
};
|
||||
|
||||
const parseFactorioCode = (string: string): ReactNode => {
|
||||
const iconMatches = regexMatchAll(string, ICON_REGEX);
|
||||
|
||||
if (!iconMatches.length) return <span>{string}</span>;
|
||||
|
||||
// console.log(string, iconMatches);
|
||||
|
||||
const result = [] as ReactNode[];
|
||||
let lastHandledIndex = 0;
|
||||
|
||||
iconMatches.forEach((match) => {
|
||||
if (match.start > lastHandledIndex) {
|
||||
let content = string.substr(lastHandledIndex, match.start - lastHandledIndex);
|
||||
content = content.replace(/ /g, "\u00A0");
|
||||
result.push(<span key={lastHandledIndex}>{content}</span>);
|
||||
}
|
||||
|
||||
if (match.groups.color && match.groups.content) {
|
||||
if (match.groups.color.match(RBG_COLOR_REGEX)) {
|
||||
match.groups.color = `rgb(${match.groups.color})`;
|
||||
}
|
||||
result.push(
|
||||
<span
|
||||
key={match.start}
|
||||
css={{ color: match.groups.color, display: "inline-flex", alignItems: "center" }}
|
||||
>
|
||||
{parseFactorioCode(match.groups.content)}
|
||||
</span>
|
||||
);
|
||||
} else if (match.groups.type && match.groups.icon) {
|
||||
if (match.groups.type === "img") {
|
||||
const [, type, icon] = match.value.match(IMG_ICON_REGEX) || [];
|
||||
if (type && icon) {
|
||||
match.groups.type = type;
|
||||
match.groups.icon = icon;
|
||||
}
|
||||
console.log(match);
|
||||
}
|
||||
result.push(
|
||||
<FactorioIcon
|
||||
key={match.start}
|
||||
type={match.groups.type as IconSignalTypes}
|
||||
icon={match.groups.icon as string}
|
||||
size={20}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
console.warn("[FactorioCode] Match without proper capture groups", match);
|
||||
}
|
||||
|
||||
lastHandledIndex = match.end;
|
||||
});
|
||||
|
||||
if (lastHandledIndex < string.length) {
|
||||
result.push(<span key={lastHandledIndex}>{string.substr(lastHandledIndex)}</span>);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const StyledBox = styled(Box)`
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
export const FactorioCode: React.FC<{ code: string }> = ({ code }) => {
|
||||
return <StyledBox>{parseFactorioCode(code)}</StyledBox>;
|
||||
};
|
@ -1,7 +1,7 @@
|
||||
import { IconSignalTypes } from "@factorio-sites/types";
|
||||
|
||||
interface FactorioIconProps {
|
||||
type: IconSignalTypes | "signal";
|
||||
type: IconSignalTypes | "signal" | "virtual-signal" | "entity" | "recipe";
|
||||
icon: string;
|
||||
size: number;
|
||||
}
|
||||
@ -12,15 +12,17 @@ function getUrlByType(type: FactorioIconProps["type"], icon: string) {
|
||||
return `https://storage.googleapis.com/factorio-blueprints-assets/factorio/graphics/icons/${icon}.png`;
|
||||
case "fluid":
|
||||
return `https://storage.googleapis.com/factorio-blueprints-assets/factorio/graphics/icons/fluid/${icon}.png`;
|
||||
case "virtual":
|
||||
case "virtual-signal":
|
||||
case "signal":
|
||||
return `https://storage.googleapis.com/factorio-blueprints-assets/factorio/graphics/icons/signal/${icon.replace(
|
||||
/-/,
|
||||
"_"
|
||||
)}.png`;
|
||||
case "virtual":
|
||||
return null;
|
||||
// case "virtual":
|
||||
// return null;
|
||||
default:
|
||||
console.log("icon type not found", type, icon);
|
||||
// console.log("icon type not found", type, icon);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -33,7 +35,10 @@ const ICON_RENAMES: Record<string, string> = {
|
||||
export const FactorioIcon: React.FC<FactorioIconProps> = ({ type, icon, size }) => {
|
||||
if (ICON_RENAMES[icon]) icon = ICON_RENAMES[icon];
|
||||
const url = getUrlByType(type, icon);
|
||||
if (!url) return null;
|
||||
if (!url) {
|
||||
// console.warn(`No icon for type ${type} icon ${icon}`);
|
||||
return <span css={{ color: "#ffa700" }}>[{icon}]</span>;
|
||||
}
|
||||
return (
|
||||
<div
|
||||
css={{
|
||||
|
@ -1,5 +1,4 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import BBCode from "bbcode-to-react";
|
||||
import { Grid, Box } from "@chakra-ui/react";
|
||||
import { Blueprint as IBlueprint, BlueprintPage, BlueprintStringData } from "@factorio-sites/types";
|
||||
import { chakraResponsive, parseBlueprintStringClient } from "@factorio-sites/web-utils";
|
||||
@ -14,6 +13,7 @@ import { BlueprintData } from "./BlueprintData";
|
||||
import { BlueprintInfo } from "./BlueprintInfo";
|
||||
import { BlueprintTags } from "./BlueprintTags";
|
||||
import { BlueprintEntities } from "./BlueprintEntities";
|
||||
import { FactorioCode } from "../FactorioCode";
|
||||
|
||||
const StyledBlueptintPage = styled(Grid)`
|
||||
grid-gap: 16px;
|
||||
@ -154,7 +154,7 @@ export const BlueprintSubPage: React.FC<BlueprintProps> = ({
|
||||
title={
|
||||
<span>
|
||||
Components for{" "}
|
||||
{data?.blueprint?.label ? BBCode.toReact(data.blueprint.label) : "blueprint"}
|
||||
{data?.blueprint?.label ? <FactorioCode code={data.blueprint.label} /> : "blueprint"}
|
||||
</span>
|
||||
}
|
||||
>
|
||||
|
@ -1,5 +1,4 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import BBCode from "bbcode-to-react";
|
||||
import { Box, Grid } from "@chakra-ui/react";
|
||||
import styled from "@emotion/styled";
|
||||
import {
|
||||
@ -25,23 +24,17 @@ import { BlueprintData } from "./BlueprintData";
|
||||
import { BlueprintInfo } from "./BlueprintInfo";
|
||||
import { BlueprintTags } from "./BlueprintTags";
|
||||
import { BlueprintEntities } from "./BlueprintEntities";
|
||||
import { FactorioCode } from "../FactorioCode";
|
||||
|
||||
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;
|
||||
}
|
||||
.title .text {
|
||||
white-space: nowrap;
|
||||
width: calc(100% - 120px);
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.panel {
|
||||
@ -143,10 +136,10 @@ export const BlueprintBookSubPage: React.FC<BlueprintBookSubPageProps> = ({
|
||||
<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={chakraResponsive({ mobile: "1", desktop: "1 / span 2" })}
|
||||
gridRow={chakraResponsive({ mobile: "2", desktop: "1" })}
|
||||
@ -167,7 +160,7 @@ export const BlueprintBookSubPage: React.FC<BlueprintBookSubPageProps> = ({
|
||||
gridColumn={chakraResponsive({ mobile: "1", desktop: "3 / span 2" })}
|
||||
gridRow={chakraResponsive({ mobile: "1", desktop: "1" })}
|
||||
title={
|
||||
<div className="title">
|
||||
<>
|
||||
<span>Image</span>
|
||||
<img
|
||||
src="/fbe.svg"
|
||||
@ -190,7 +183,7 @@ export const BlueprintBookSubPage: React.FC<BlueprintBookSubPageProps> = ({
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
>
|
||||
{selectedBlueprintString && <ImageEditor string={selectedBlueprintString}></ImageEditor>}
|
||||
@ -201,10 +194,10 @@ export const BlueprintBookSubPage: React.FC<BlueprintBookSubPageProps> = ({
|
||||
gridColumn={chakraResponsive({ mobile: "1", desktop: "1 / span 2" })}
|
||||
gridRow={chakraResponsive({ mobile: null, desktop: "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>
|
||||
@ -233,9 +226,11 @@ export const BlueprintBookSubPage: React.FC<BlueprintBookSubPageProps> = ({
|
||||
title={
|
||||
<span>
|
||||
Components for{" "}
|
||||
{selectedData?.blueprint?.label
|
||||
? BBCode.toReact(selectedData.blueprint.label)
|
||||
: "blueprint"}
|
||||
{selectedData?.blueprint?.label ? (
|
||||
<FactorioCode code={selectedData.blueprint.label} />
|
||||
) : (
|
||||
"blueprint"
|
||||
)}
|
||||
</span>
|
||||
}
|
||||
>
|
||||
|
@ -111,11 +111,11 @@ export const Index: NextPage<IndexProps> = ({
|
||||
<Box>
|
||||
<RadioGroup
|
||||
onChange={(value: string) => router.push(routerQueryToHref({ order: value }))}
|
||||
value={(router.query.order as string) || "date"}
|
||||
value={(router.query.order as string) || "favorites"}
|
||||
>
|
||||
<Stack>
|
||||
<Radio value="date">Last updated</Radio>
|
||||
<Radio value="favorites">Favorites</Radio>
|
||||
<Radio value="date">Last updated</Radio>
|
||||
</Stack>
|
||||
</RadioGroup>
|
||||
</Box>
|
||||
@ -203,7 +203,7 @@ export async function getServerSideProps({ query }: NextPageContext) {
|
||||
await init();
|
||||
const page = Number(query.page || "1");
|
||||
const perPage = Number(query["per-page"] || "20");
|
||||
const order = (query["order"] as string) || "date";
|
||||
const order = (query["order"] as string) || "favorites";
|
||||
const _query = query.q ? String(query.q) : undefined;
|
||||
const tags = query.tags ? String(query.tags).split(",") : undefined;
|
||||
const entities = query.entities ? String(query.entities).split(",") : undefined;
|
||||
|
@ -39,7 +39,6 @@
|
||||
"@google-cloud/storage": "5.8.1",
|
||||
"@hookstate/core": "3.0.6",
|
||||
"@prisma/client": "2.19.0",
|
||||
"bbcode-to-react": "0.2.9",
|
||||
"bcrypt": "5.0.1",
|
||||
"clsx": "1.1.1",
|
||||
"cookie": "0.4.1",
|
||||
@ -78,7 +77,6 @@
|
||||
"@nrwl/web": "11.5.1",
|
||||
"@nrwl/workspace": "11.5.1",
|
||||
"@testing-library/react": "11.2.5",
|
||||
"@types/bbcode-to-react": "0.2.0",
|
||||
"@types/bcrypt": "3.0.0",
|
||||
"@types/cookie": "0.4.0",
|
||||
"@types/jest": "26.0.20",
|
||||
|
12
yarn.lock
12
yarn.lock
@ -4071,13 +4071,6 @@
|
||||
dependencies:
|
||||
"@babel/types" "^7.3.0"
|
||||
|
||||
"@types/bbcode-to-react@0.2.0":
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/bbcode-to-react/-/bbcode-to-react-0.2.0.tgz#7aa9e5c8ba8fea5b1e4968b96413aab426b0c4b7"
|
||||
integrity sha512-qcrZwvQgwiEdZbola1LrGghV7fGxHQCM09Pa/cRVmjpD3dOkE8N/H2S9kuJf1O8wg7AM1J80USyi3pbr1Ra0uQ==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/bcrypt@3.0.0":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/bcrypt/-/bcrypt-3.0.0.tgz#851489a9065a067cb7f3c9cbe4ce9bed8bba0876"
|
||||
@ -5316,11 +5309,6 @@ batch@0.6.1:
|
||||
resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16"
|
||||
integrity sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=
|
||||
|
||||
bbcode-to-react@0.2.9:
|
||||
version "0.2.9"
|
||||
resolved "https://registry.yarnpkg.com/bbcode-to-react/-/bbcode-to-react-0.2.9.tgz#75ec5358c78e9c2a486a62274bb5f4695fb99e60"
|
||||
integrity sha512-ZuqVg44xi0nqMbZJMB/j1WxVvDnpfQYHLGFdZW8FQfW7uoCMyF48iEUVbYontcdrD5uZTcqMs3qbGeIa/bCi+g==
|
||||
|
||||
bcrypt-pbkdf@^1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e"
|
||||
|
Loading…
x
Reference in New Issue
Block a user