diff --git a/.dockerignore b/.dockerignore index 8e46cfc..fb9b59f 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,4 +1,5 @@ *.Dockerfile /credentials /.vscode -/.cache \ No newline at end of file +/.cache +/node_modules \ No newline at end of file diff --git a/apps/blueprints/src/components/BBCode.tsx b/apps/blueprints/src/components/BBCode.tsx deleted file mode 100644 index f737944..0000000 --- a/apps/blueprints/src/components/BBCode.tsx +++ /dev/null @@ -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).img; - const [type, item] = img.split("/"); - if (type === "item") { - return ( - <> - - {content && {content}} - - ); - } - return null; - } -} - -class ItemTag extends Tag { - toReact() { - const content = this.getContent(true); - const item = (this.params as Record).item; - if (item) { - return ( - <> - - {content && {content}} - - ); - } - return null; - } -} - -class VirtualSignalTag extends Tag { - toReact() { - const signal = (this.params as Record)["virtual-signal"]; - const content = this.getContent(true); - if (signal) { - return ( - <> - - {content && {content}} - - ); - } - 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)}; -}; diff --git a/apps/blueprints/src/components/BookChildTree.tsx b/apps/blueprints/src/components/BookChildTree.tsx index 8874ad0..f8ff466 100644 --- a/apps/blueprints/src/components/BookChildTree.tsx +++ b/apps/blueprints/src/components/BookChildTree.tsx @@ -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 = ({ /> ))} - + @@ -70,7 +70,7 @@ export const BookChildTree: React.FC = ({ /> ))} - + diff --git a/apps/blueprints/src/components/FactorioCode.tsx b/apps/blueprints/src/components/FactorioCode.tsx new file mode 100644 index 0000000..2b57ed9 --- /dev/null +++ b/apps/blueprints/src/components/FactorioCode.tsx @@ -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( + `(\\[(?${ICON_TYPES.join( + "|" + )})=(?.*?)\\]|\\[color=(?.*?)\\](?.*?)\\[\\/color\\])`, + "g" +); + +const RBG_COLOR_REGEX = /^\d+,\d+,\d+$/; +const IMG_ICON_REGEX = /^\[img=(.*?)\/(.*?)\]$/; + +interface Match { + value: string; + start: number; + end: number; + groups: Record; +} + +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 {string}; + + // 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({content}); + } + + if (match.groups.color && match.groups.content) { + if (match.groups.color.match(RBG_COLOR_REGEX)) { + match.groups.color = `rgb(${match.groups.color})`; + } + result.push( + + {parseFactorioCode(match.groups.content)} + + ); + } 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( + + ); + } else { + console.warn("[FactorioCode] Match without proper capture groups", match); + } + + lastHandledIndex = match.end; + }); + + if (lastHandledIndex < string.length) { + result.push({string.substr(lastHandledIndex)}); + } + + return result; +}; + +const StyledBox = styled(Box)` + display: inline-flex; + align-items: center; +`; + +export const FactorioCode: React.FC<{ code: string }> = ({ code }) => { + return {parseFactorioCode(code)}; +}; diff --git a/apps/blueprints/src/components/FactorioIcon.tsx b/apps/blueprints/src/components/FactorioIcon.tsx index c962706..eecb684 100644 --- a/apps/blueprints/src/components/FactorioIcon.tsx +++ b/apps/blueprints/src/components/FactorioIcon.tsx @@ -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 = { export const FactorioIcon: React.FC = ({ 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 [{icon}]; + } return (
= ({ title={ Components for{" "} - {data?.blueprint?.label ? BBCode.toReact(data.blueprint.label) : "blueprint"} + {data?.blueprint?.label ? : "blueprint"} } > diff --git a/apps/blueprints/src/components/blueprint/BlueprintBook.tsx b/apps/blueprints/src/components/blueprint/BlueprintBook.tsx index 84151e6..6c778d6 100644 --- a/apps/blueprints/src/components/blueprint/BlueprintBook.tsx +++ b/apps/blueprints/src/components/blueprint/BlueprintBook.tsx @@ -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 = ({ + <> {blueprint_page.title} -
+ } gridColumn={chakraResponsive({ mobile: "1", desktop: "1 / span 2" })} gridRow={chakraResponsive({ mobile: "2", desktop: "1" })} @@ -167,7 +160,7 @@ export const BlueprintBookSubPage: React.FC = ({ gridColumn={chakraResponsive({ mobile: "1", desktop: "3 / span 2" })} gridRow={chakraResponsive({ mobile: "1", desktop: "1" })} title={ -
+ <> Image = ({ /> )} -
+ } > {selectedBlueprintString && } @@ -201,10 +194,10 @@ export const BlueprintBookSubPage: React.FC = ({ gridColumn={chakraResponsive({ mobile: "1", desktop: "1 / span 2" })} gridRow={chakraResponsive({ mobile: null, desktop: "2 / span 2" })} title={ -
+ <> Description -
+ } > {blueprint_page.description_markdown} @@ -233,9 +226,11 @@ export const BlueprintBookSubPage: React.FC = ({ title={ Components for{" "} - {selectedData?.blueprint?.label - ? BBCode.toReact(selectedData.blueprint.label) - : "blueprint"} + {selectedData?.blueprint?.label ? ( + + ) : ( + "blueprint" + )} } > diff --git a/apps/blueprints/src/pages/index.tsx b/apps/blueprints/src/pages/index.tsx index 2a38714..5df6be5 100644 --- a/apps/blueprints/src/pages/index.tsx +++ b/apps/blueprints/src/pages/index.tsx @@ -111,11 +111,11 @@ export const Index: NextPage = ({ router.push(routerQueryToHref({ order: value }))} - value={(router.query.order as string) || "date"} + value={(router.query.order as string) || "favorites"} > - Last updated Favorites + Last updated @@ -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; diff --git a/package.json b/package.json index 6a44373..e6b4d9f 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/yarn.lock b/yarn.lock index d46b9c8..56af228 100644 --- a/yarn.lock +++ b/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"