mirror of
https://github.com/barthuijgen/factorio-sites.git
synced 2025-01-23 11:45:21 +02:00
Upgrade packages, added tags
This commit is contained in:
parent
364b849310
commit
7df5776fab
@ -6,15 +6,15 @@ interface Tag {
|
||||
}
|
||||
|
||||
interface SelectProps {
|
||||
options: string[];
|
||||
options: Array<string | { value: string; label: string }>;
|
||||
value: string[];
|
||||
onChange: (tags: string[]) => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const Select: React.FC<SelectProps> = ({ options, value, onChange, className }) => {
|
||||
const TAGS = options.map((key) => {
|
||||
return { value: key, label: key.replace(/[_-]/g, " ") };
|
||||
const TAGS = options.map((tag) => {
|
||||
return typeof tag === "string" ? { value: tag, label: tag.replace(/[_-]/g, " ") } : tag;
|
||||
});
|
||||
|
||||
return (
|
||||
|
@ -15,7 +15,7 @@ const handler = apiHandler(async (req, res, { session }) => {
|
||||
return res.status(401).json({ status: "Not authenticated" });
|
||||
}
|
||||
|
||||
const { title, description, string } = req.body;
|
||||
const { title, description, string, tags } = req.body;
|
||||
|
||||
const parsed = await parseBlueprintString(string).catch(() => null);
|
||||
|
||||
@ -35,7 +35,7 @@ const handler = apiHandler(async (req, res, { session }) => {
|
||||
const info = {
|
||||
user_id: session.user.id,
|
||||
title,
|
||||
tags: [],
|
||||
tags: Array.isArray(tags) ? tags : [],
|
||||
description_markdown: description,
|
||||
};
|
||||
|
||||
|
@ -20,8 +20,10 @@ import {
|
||||
RadioGroup,
|
||||
Stack,
|
||||
Radio,
|
||||
Checkbox,
|
||||
} from "@chakra-ui/react";
|
||||
import { MdSearch } from "react-icons/md";
|
||||
import { TAGS } from "@factorio-sites/common-utils";
|
||||
|
||||
interface IndexProps {
|
||||
totalItems: number;
|
||||
@ -46,12 +48,17 @@ export const Index: NextPage<IndexProps> = ({
|
||||
}, [router.query.q]);
|
||||
|
||||
if (!data) return null;
|
||||
console.log(JSON.stringify(router.query, null, 2));
|
||||
|
||||
const entityOptions = Object.keys(data.entities).filter(
|
||||
(key) => !key.startsWith("factorio-logo") && !key.startsWith("crash-site")
|
||||
);
|
||||
const recipeOptions = Object.keys(data.recipes);
|
||||
const itemOptions = Object.keys(data.items).filter((key) => key.includes("module"));
|
||||
const tagsOptions = TAGS.map((tag) => ({
|
||||
label: `${tag.category}: ${tag.label}`,
|
||||
value: tag.value,
|
||||
}));
|
||||
|
||||
return (
|
||||
<SimpleGrid columns={1} margin="0.7rem">
|
||||
@ -100,6 +107,29 @@ export const Index: NextPage<IndexProps> = ({
|
||||
</RadioGroup>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box css={{ marginTop: "1rem" }}>
|
||||
<Text css={{ marginRight: "1rem" }}>Search mode</Text>
|
||||
<Box css={{ marginRight: "1rem" }}>
|
||||
<RadioGroup
|
||||
onChange={(value: string) => router.push(routerQueryToHref({ mode: value }))}
|
||||
value={(router.query.mode as string) || "and"}
|
||||
>
|
||||
<Stack>
|
||||
<Radio value="and">AND</Radio>
|
||||
<Radio value="or">OR</Radio>
|
||||
</Stack>
|
||||
</RadioGroup>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box css={{ marginTop: "1rem" }}>
|
||||
<Text css={{ marginRight: "1rem" }}>Tags</Text>
|
||||
<Select
|
||||
options={tagsOptions}
|
||||
value={queryValueAsArray(router.query.tags)}
|
||||
onChange={(tags) => router.push(routerQueryToHref({ tags }))}
|
||||
css={{ width: "200px", marginRight: "1rem" }}
|
||||
/>
|
||||
</Box>
|
||||
<Box css={{ marginTop: "1rem" }}>
|
||||
<Text css={{ marginRight: "1rem" }}>Entities</Text>
|
||||
<Select
|
||||
@ -127,6 +157,16 @@ export const Index: NextPage<IndexProps> = ({
|
||||
css={{ width: "200px", marginRight: "1rem" }}
|
||||
/>
|
||||
</Box>
|
||||
<Box css={{ marginTop: "1rem", display: "flex", alignItems: "center" }}>
|
||||
<Text css={{ marginRight: "1rem", display: "inline-block" }}>Snaps to grid</Text>
|
||||
<Checkbox
|
||||
value="true"
|
||||
onChange={(ev) =>
|
||||
router.push(routerQueryToHref({ absolute_snapping: String(ev.target.checked) }))
|
||||
}
|
||||
isChecked={router.query.absolute_snapping === "true"}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box>
|
||||
<Box css={{ display: "flex", flexWrap: "wrap", minHeight: "400px" }}>
|
||||
@ -154,17 +194,23 @@ export async function getServerSideProps({ query }: NextPageContext) {
|
||||
const items = query.items ? String(query.items).split(",") : undefined;
|
||||
const recipes = query.recipes ? String(query.recipes).split(",") : undefined;
|
||||
const user = query.user ? String(query.user) : undefined;
|
||||
const absolute_snapping = query.absolute_snapping
|
||||
? String(query.absolute_snapping) === "true"
|
||||
: false;
|
||||
const mode = String(query.mode).toUpperCase() === "OR" ? "OR" : "AND";
|
||||
|
||||
const { count, rows } = await searchBlueprintPages({
|
||||
page,
|
||||
perPage,
|
||||
query: query.q as string,
|
||||
order,
|
||||
mode,
|
||||
tags,
|
||||
entities,
|
||||
items,
|
||||
recipes,
|
||||
user,
|
||||
absolute_snapping,
|
||||
});
|
||||
|
||||
return {
|
||||
|
@ -15,6 +15,7 @@ import {
|
||||
import { Formik, Field, FieldProps } from "formik";
|
||||
import { css } from "@emotion/react";
|
||||
import { chakraResponsive } from "@factorio-sites/web-utils";
|
||||
import { TAGS } from "@factorio-sites/common-utils";
|
||||
import { Panel } from "../../components/Panel";
|
||||
import { validateCreateBlueprintForm } from "../../utils/validate";
|
||||
import { ImageEditor } from "../../components/ImageEditor";
|
||||
@ -28,6 +29,11 @@ const FieldStyle = css`
|
||||
export const UserBlueprintCreate: NextPage = () => {
|
||||
const router = useRouter();
|
||||
|
||||
const tagsOptions = TAGS.map((tag) => ({
|
||||
label: `${tag.category}: ${tag.label}`,
|
||||
value: tag.value,
|
||||
}));
|
||||
|
||||
return (
|
||||
<div css={{ margin: "0.7rem" }}>
|
||||
<Formik
|
||||
@ -98,9 +104,9 @@ export const UserBlueprintCreate: NextPage = () => {
|
||||
isInvalid={meta.touched && !!meta.error}
|
||||
css={FieldStyle}
|
||||
>
|
||||
<FormLabel>Tags (WIP)</FormLabel>
|
||||
<FormLabel>Tags</FormLabel>
|
||||
<Select
|
||||
options={[]}
|
||||
options={tagsOptions}
|
||||
value={field.value}
|
||||
onChange={(tags) => setFieldValue("tags", tags)}
|
||||
/>
|
||||
|
@ -27,6 +27,7 @@ import { Panel } from "../../../components/Panel";
|
||||
import { validateCreateBlueprintForm } from "../../../utils/validate";
|
||||
import { ImageEditor } from "../../../components/ImageEditor";
|
||||
import { Select } from "../../../components/Select";
|
||||
import { TAGS } from "@factorio-sites/common-utils";
|
||||
|
||||
const FieldStyle = css`
|
||||
margin-bottom: 1rem;
|
||||
@ -45,6 +46,11 @@ export const UserBlueprint: NextPage<UserBlueprintProps> = ({ blueprintPage, sel
|
||||
|
||||
if (!blueprintPage) return null;
|
||||
|
||||
const tagsOptions = TAGS.map((tag) => ({
|
||||
label: `${tag.category}: ${tag.label}`,
|
||||
value: tag.value,
|
||||
}));
|
||||
|
||||
return (
|
||||
<div css={{ margin: "0.7rem" }}>
|
||||
<Formik
|
||||
@ -52,7 +58,7 @@ export const UserBlueprint: NextPage<UserBlueprintProps> = ({ blueprintPage, sel
|
||||
title: blueprintPage.title,
|
||||
description: blueprintPage.description_markdown,
|
||||
string: selected.string,
|
||||
tags: [] as string[],
|
||||
tags: blueprintPage.tags || ([] as string[]),
|
||||
}}
|
||||
validate={validateCreateBlueprintForm}
|
||||
onSubmit={async (values, { setSubmitting, setErrors, setStatus }) => {
|
||||
@ -123,9 +129,9 @@ export const UserBlueprint: NextPage<UserBlueprintProps> = ({ blueprintPage, sel
|
||||
isInvalid={meta.touched && !!meta.error}
|
||||
css={FieldStyle}
|
||||
>
|
||||
<FormLabel>Tags (WIP)</FormLabel>
|
||||
<FormLabel>Tags</FormLabel>
|
||||
<Select
|
||||
options={[]}
|
||||
options={tagsOptions}
|
||||
value={field.value}
|
||||
onChange={(tags) => setFieldValue("tags", tags)}
|
||||
/>
|
||||
|
@ -86,6 +86,10 @@ export const validateCreateBlueprintForm = (values: CreateBlueprintValues) => {
|
||||
errors.description = validateRequired(values.description);
|
||||
errors.string = validateRequired(values.string);
|
||||
|
||||
if (values.string) {
|
||||
console.log(parseBlueprintStringClient(values.string));
|
||||
}
|
||||
|
||||
// If string is set also validate it to be parsable
|
||||
if (!errors.string && !parseBlueprintStringClient(values.string)) {
|
||||
errors.string = "Not recognised as a blueprint string";
|
||||
|
@ -1 +1,2 @@
|
||||
export * from "./lib/common-utils";
|
||||
export * from "./lib/tags";
|
||||
|
42
libs/common-utils/src/lib/tags.ts
Normal file
42
libs/common-utils/src/lib/tags.ts
Normal file
@ -0,0 +1,42 @@
|
||||
type Tag = { value: string; label: string; category: string };
|
||||
|
||||
export const TAGS: Tag[] = [
|
||||
{ value: "belt/balancer", label: "Balancer", category: "Belt" },
|
||||
{ value: "belt/prioritizer", label: "Prioritizer", category: "Belt" },
|
||||
{ value: "belt/tap", label: "Tap", category: "Belt" },
|
||||
|
||||
{ value: "circuit/indicator", label: "circuit indicator", category: "Circuit" },
|
||||
{ value: "circuit/counter", label: "circuit counter", category: "Circuit" },
|
||||
|
||||
{ value: "state/early_game", label: "Early game", category: "State" },
|
||||
{ value: "state/mid_game", label: "Mid game", category: "State" },
|
||||
{ value: "state/late_game", label: "Late game", category: "State" },
|
||||
// { value: "meta/beaconized", label: "Beaconized", category: "Meta" },
|
||||
{ value: "meta/compact", label: "Compact", category: "Meta" },
|
||||
{ value: "meta/marathon", label: "Marathon", category: "Meta" },
|
||||
{ value: "meta/inputs_are_marked", label: "Inputs are marked", category: "Meta" },
|
||||
|
||||
{ value: "mods/modded", label: "Modded", category: "Mods" },
|
||||
|
||||
// { value: "power/nuclear", label: "power nuclear", category: "power" },
|
||||
// { value: "power/solar", label: "power solar", category: "power" },
|
||||
// { value: "power/steam", label: "power steam", category: "power" },
|
||||
// { value: "power/accumulator", label: "power accumulator", category: "power" },
|
||||
|
||||
// { value: "oil processing", label: "oil processing", category: "" },
|
||||
// { value: "coal liquification", label: "coal liquification", category: "" },
|
||||
|
||||
{ value: "train/loading_station", label: "Loading station", category: "Train" },
|
||||
{ value: "train/unloading_station", label: "Unloading station", category: "Train" },
|
||||
{ value: "train/pax", label: "Pax", category: "Train" },
|
||||
{ value: "train/intersection", label: "Intersection", category: "Train" },
|
||||
{ value: "train/stacker", label: "Stacker", category: "Train" },
|
||||
{ value: "train/track", label: "Track", category: "Train" },
|
||||
{ value: "train/LHD", label: "Left hand drive", category: "Train" },
|
||||
{ value: "train/RHD", label: "Right hand drive", category: "Train" },
|
||||
];
|
||||
|
||||
export const TAGS_BY_KEY = TAGS.reduce((tags, tag) => {
|
||||
tags[tag.value] = tag;
|
||||
return tags;
|
||||
}, {} as Record<string, Tag>);
|
@ -60,7 +60,7 @@ export async function createBlueprintBook(
|
||||
child_tree.push({
|
||||
type: "blueprint",
|
||||
id: result.id,
|
||||
name: blueprint.blueprint.label,
|
||||
name: blueprint.blueprint.label || "",
|
||||
});
|
||||
blueprint_ids.push(result.id);
|
||||
} else if (blueprint.blueprint_book) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { blueprint_page, user } from "@prisma/client";
|
||||
import { join, raw, sqltag } from "@prisma/client/runtime";
|
||||
import { join, raw, Sql, sqltag } from "@prisma/client/runtime";
|
||||
import { getBlueprintImageRequestTopic } from "../gcp-pubsub";
|
||||
import { prisma } from "../postgres/database";
|
||||
import { BlueprintPage, ChildTree } from "@factorio-sites/types";
|
||||
@ -53,57 +53,78 @@ export async function searchBlueprintPages({
|
||||
perPage = 10,
|
||||
query,
|
||||
order,
|
||||
mode = "AND",
|
||||
tags,
|
||||
entities,
|
||||
items,
|
||||
recipes,
|
||||
user,
|
||||
absolute_snapping,
|
||||
}: {
|
||||
page: number;
|
||||
perPage: number;
|
||||
query?: string;
|
||||
order: "date" | "favorites" | string;
|
||||
mode: "AND" | "OR";
|
||||
tags?: string[];
|
||||
entities?: string[];
|
||||
items?: string[];
|
||||
recipes?: string[];
|
||||
user?: string;
|
||||
absolute_snapping?: boolean;
|
||||
}): Promise<{ count: number; rows: BlueprintPage[] }> {
|
||||
const orderMap: Record<string, string> = {
|
||||
date: "blueprint_page.updated_at",
|
||||
favorites: "favorite_count",
|
||||
};
|
||||
|
||||
const blueprintDataSearch =
|
||||
entities || items || recipes
|
||||
? sqltag`LEFT JOIN blueprint ON blueprint.id = ANY(blueprint_page.blueprint_ids) OR blueprint.id = blueprint_page.blueprint_id`
|
||||
: sqltag``;
|
||||
const entitiesFragment = entities
|
||||
? sqltag`AND blueprint.data -> 'entities' ?& array[${join(entities)}::text]`
|
||||
const conditionals: Sql[] = [];
|
||||
const joins: Sql[] = [];
|
||||
let requires_blueprint_join = false;
|
||||
|
||||
if (query) {
|
||||
conditionals.push(sqltag`blueprint_page.title ILIKE ${`%${query}%`}`);
|
||||
}
|
||||
if (entities) {
|
||||
conditionals.push(sqltag`blueprint.data -> 'entities' ?& array[${join(entities)}::text]`);
|
||||
requires_blueprint_join = true;
|
||||
}
|
||||
if (items) {
|
||||
conditionals.push(sqltag`blueprint.data -> 'items' ?& array[${join(items)}::text]`);
|
||||
requires_blueprint_join = true;
|
||||
}
|
||||
if (recipes) {
|
||||
conditionals.push(sqltag`blueprint.data -> 'recipes' ?& array[${join(recipes)}::text]`);
|
||||
requires_blueprint_join = true;
|
||||
}
|
||||
if (tags) {
|
||||
conditionals.push(sqltag`blueprint_page.tags @> array[${join(tags)}::varchar]`);
|
||||
}
|
||||
if (user) {
|
||||
conditionals.push(sqltag`blueprint_page.user_id = ${user}`);
|
||||
}
|
||||
if (absolute_snapping) {
|
||||
conditionals.push(sqltag`(blueprint.data -> 'absolute_snapping')::boolean = true`);
|
||||
requires_blueprint_join = true;
|
||||
}
|
||||
if (requires_blueprint_join) {
|
||||
joins.push(
|
||||
sqltag`LEFT JOIN blueprint ON blueprint.id = ANY(blueprint_page.blueprint_ids) OR blueprint.id = blueprint_page.blueprint_id`
|
||||
);
|
||||
}
|
||||
|
||||
const joinsFragment = joins.length ? sqltag`${join(joins)}` : sqltag``;
|
||||
const conditionalFragment = conditionals.length
|
||||
? sqltag`WHERE ${join(conditionals, ` ${mode} `)}`
|
||||
: sqltag``;
|
||||
const itemsFragment = items
|
||||
? sqltag`AND blueprint.data -> 'items' ?& array[${join(items)}::text]`
|
||||
: sqltag``;
|
||||
const recipesFragment = recipes
|
||||
? sqltag`AND blueprint.data -> 'recipes' ?& array[${join(recipes)}::text]`
|
||||
: sqltag``;
|
||||
const tagsFragment = tags
|
||||
? sqltag`AND blueprint_page.tags @> array[${join(tags)}::varchar]`
|
||||
: sqltag``;
|
||||
const userFragment = user ? sqltag`AND blueprint_page.user_id = ${user}` : sqltag``;
|
||||
|
||||
try {
|
||||
const result = (
|
||||
await prisma.$queryRaw<(blueprint_page & { favorite_count: number })[]>`
|
||||
SELECT DISTINCT blueprint_page.*, (SELECT COUNT(*) FROM user_favorites where user_favorites.blueprint_page_id = blueprint_page.id) AS favorite_count
|
||||
FROM public.blueprint_page
|
||||
${blueprintDataSearch}
|
||||
WHERE blueprint_page.title ILIKE ${query ? `%${query}%` : "%"}
|
||||
${entitiesFragment}
|
||||
${itemsFragment}
|
||||
${recipesFragment}
|
||||
${tagsFragment}
|
||||
${userFragment}
|
||||
${joinsFragment}
|
||||
${conditionalFragment}
|
||||
ORDER BY ${raw(orderMap[order] || orderMap.date)} DESC
|
||||
LIMIT ${perPage} OFFSET ${(page - 1) * perPage}`
|
||||
).map((blueprintPage) => ({
|
||||
@ -115,13 +136,8 @@ export async function searchBlueprintPages({
|
||||
const countResult = await prisma.$queryRaw<{ count: number }[]>`
|
||||
SELECT COUNT(DISTINCT blueprint_page.id)
|
||||
FROM public.blueprint_page
|
||||
${blueprintDataSearch}
|
||||
WHERE blueprint_page.title ILIKE ${query ? `%${query}%` : "%"}
|
||||
${entitiesFragment}
|
||||
${itemsFragment}
|
||||
${recipesFragment}
|
||||
${tagsFragment}
|
||||
${userFragment}`;
|
||||
${joinsFragment}
|
||||
${conditionalFragment}`;
|
||||
|
||||
return {
|
||||
count: countResult[0].count,
|
||||
|
@ -29,10 +29,14 @@ export interface BlueprintData {
|
||||
tiles?: Tile[];
|
||||
icons: Icon[];
|
||||
item: "blueprint";
|
||||
label: string;
|
||||
label?: string;
|
||||
description?: string;
|
||||
"snap-to-grid": { x: number; y: number };
|
||||
"absolute-snapping": boolean;
|
||||
/** Mode 'absolute' if 'absolute-snapping' is also present, otherwise 'relative */
|
||||
"snap-to-grid"?: { x: number; y: number };
|
||||
/** if true 'snap-to-grid' is also present */
|
||||
"absolute-snapping"?: boolean;
|
||||
/** can only exist in 'absolute' mode. changes offset to the game grid */
|
||||
"position-relative-to-grid"?: { x: number; y: number };
|
||||
version: number;
|
||||
}
|
||||
|
||||
|
16
package.json
16
package.json
@ -36,15 +36,15 @@
|
||||
"@google-cloud/datastore": "6.3.1",
|
||||
"@google-cloud/pubsub": "2.10.0",
|
||||
"@google-cloud/secret-manager": "3.5.0",
|
||||
"@google-cloud/storage": "5.7.4",
|
||||
"@hookstate/core": "3.0.5",
|
||||
"@google-cloud/storage": "5.8.1",
|
||||
"@hookstate/core": "3.0.6",
|
||||
"@prisma/client": "2.18.0",
|
||||
"bbcode-to-react": "0.2.9",
|
||||
"bcrypt": "5.0.1",
|
||||
"cookie": "0.4.1",
|
||||
"document-register-element": "1.14.10",
|
||||
"formik": "2.2.6",
|
||||
"framer-motion": "3.3.0",
|
||||
"framer-motion": "3.10.2",
|
||||
"next": "10.0.7",
|
||||
"nprogress": "0.2.0",
|
||||
"openid": "2.0.7",
|
||||
@ -59,11 +59,11 @@
|
||||
"react-markdown": "5.0.3",
|
||||
"react-multi-select-component": "3.1.5",
|
||||
"sharp": "0.27.2",
|
||||
"ws": "7.4.3"
|
||||
"ws": "7.4.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.13.10",
|
||||
"@babel/preset-env": "7.12.16",
|
||||
"@babel/preset-env": "7.13.10",
|
||||
"@babel/preset-react": "7.12.13",
|
||||
"@babel/preset-typescript": "7.13.0",
|
||||
"@nrwl/cli": "11.5.1",
|
||||
@ -80,13 +80,13 @@
|
||||
"@types/bcrypt": "3.0.0",
|
||||
"@types/cookie": "0.4.0",
|
||||
"@types/jest": "26.0.20",
|
||||
"@types/node": "14.14.28",
|
||||
"@types/node": "14.14.33",
|
||||
"@types/nprogress": "0.2.0",
|
||||
"@types/openid": "2.0.1",
|
||||
"@types/pako": "1.0.1",
|
||||
"@types/puppeteer": "5.4.3",
|
||||
"@types/react": "17.0.2",
|
||||
"@types/react-dom": "17.0.1",
|
||||
"@types/react": "17.0.3",
|
||||
"@types/react-dom": "17.0.2",
|
||||
"@types/sharp": "0.27.1",
|
||||
"@types/ws": "7.4.0",
|
||||
"@typescript-eslint/eslint-plugin": "4.17.0",
|
||||
|
Loading…
x
Reference in New Issue
Block a user