diff --git a/.eslintrc.json b/.eslintrc.json index f6f385f..abaa8a4 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -49,6 +49,10 @@ { "additionalHooks": "(useAsyncEffect)" } + ], + "react/jsx-no-literals": [ + "error", + { "noStrings": true, "allowedStrings": [], "ignoreProps": true, "noAttributeStrings": false } ] }, "overrides": [ diff --git a/components/contexts/GroupProvider.tsx b/components/contexts/GroupProvider.tsx index 7469543..22a6f73 100644 --- a/components/contexts/GroupProvider.tsx +++ b/components/contexts/GroupProvider.tsx @@ -33,7 +33,7 @@ interface GroupContextType { setBaseFactories(factories: string[]): void groups: Dict - addGroup(name: string, exported?: string[], malls?: string[]): void + addGroup(name: string, exported?: string[], malls?: string[]): boolean removeGroup(name: string): void renameGroup(name: string, newName: string): void @@ -60,7 +60,7 @@ const defaultValues: GroupContextType = { groups: {}, addGroup() { - return + return false }, removeGroup() { return @@ -174,7 +174,7 @@ export const GroupProvider: FC = ({ children, id, initial }) => { `/api/${fixedEncodeURIComponent(id)}/group/${fixedEncodeURIComponent(name)}/factories`, { type: 'malls', - factories: exports + factories: malls } as GroupSetFactoryArrayBody ) } diff --git a/components/home/EntitySpan/EntitySpan.tsx b/components/home/EntitySpan/EntitySpan.tsx index d846771..b298730 100644 --- a/components/home/EntitySpan/EntitySpan.tsx +++ b/components/home/EntitySpan/EntitySpan.tsx @@ -2,10 +2,11 @@ import { FC, HTMLProps, memo, useMemo } from 'react' import { EnrichedEntity } from '../../../src/types' import styles from './EntitySpan.module.css' import { RecipeSpan } from '../Recipe/Recipe' -import { LeftClickIcon } from '../LeftClickIcon/LeftClickIcon' +import { LeftClickIcon } from '../../icons/LeftClickIcon' import cx from 'classnames' import { EntityIcon } from '../EntityIcon/EntityIcon' import { useFactories } from '../../contexts/FactoryProvider' +import { I18n } from '../../shared/I18n/I18n' interface Props extends Omit, 'value'> { value: EnrichedEntity | string @@ -16,7 +17,7 @@ interface Props extends Omit, 'value'> { className?: string } -const EntitySpanUnmemo: FC = ({ +const EntitySpanBase: FC = ({ className, value, state, @@ -50,13 +51,17 @@ const EntitySpanUnmemo: FC = ({
{entity.recipe && ( <> -
Recipe
+
+ +
)} {entity.usedBy?.length ? ( <> -
Used By
+
+ +
{entity.usedBy.map(used => ( @@ -66,7 +71,9 @@ const EntitySpanUnmemo: FC = ({ ) : null} {(leftClickText || rightClickText) && ( <> -
Actions
+
+ +
{leftClickText && (
{' '} @@ -86,4 +93,4 @@ const EntitySpanUnmemo: FC = ({ ) } -export const EntitySpan = memo(EntitySpanUnmemo) +export const EntitySpan = memo(EntitySpanBase) diff --git a/components/home/GroupBox/GroupBox.tsx b/components/home/GroupBox/GroupBox.tsx index 123b24c..e0e8f6b 100644 --- a/components/home/GroupBox/GroupBox.tsx +++ b/components/home/GroupBox/GroupBox.tsx @@ -11,6 +11,7 @@ import { useRouter } from 'next/router' import { useFactories } from '../../contexts/FactoryProvider' import { i18n, I18n } from '../../shared/I18n/I18n' import { useIntl } from 'react-intl' +import { GraphIcon } from '../../icons/GraphIcon' interface Props { group: Group @@ -94,7 +95,9 @@ const GroupBoxBase: FC = ({ group }) => { query }} > - 👁 + + +
diff --git a/components/home/Home.tsx b/components/home/Home.tsx index 3372581..d960640 100644 --- a/components/home/Home.tsx +++ b/components/home/Home.tsx @@ -1,4 +1,4 @@ -import { FC, useMemo, useRef, useState } from 'react' +import { ElementRef, FC, useMemo, useRef } from 'react' import { GroupBox } from './GroupBox/GroupBox' import styles from './Home.module.css' import { EnrichedEntity } from '../../src/types' @@ -8,15 +8,17 @@ import { Preferences } from './Preferences/Preferences' import { download, streamToArrayBuffer } from '../../src/download' import Link from 'next/link' import { useRouter } from 'next/router' -import { I18n } from '../shared/I18n/I18n' +import { i18n, I18n } from '../shared/I18n/I18n' import { useFactories } from '../contexts/FactoryProvider' +import { GraphIcon } from '../icons/GraphIcon' +import { useIntl } from 'react-intl' export const Home: FC = () => { + const intl = useIntl() const { query } = useRouter() const { factories } = useFactories() - const { groups, addGroup, doNotSuggest, ignoredFactories, setIgnoredFactories, store, load } = - useGroups() - const [newGroupValue, setNewGroupValue] = useState('New group') + const preferencesRef = useRef>(null) + const { groups, doNotSuggest, ignoredFactories, setIgnoredFactories, store, load } = useGroups() const inputRef = useRef(null) const [missingExport, missingMall] = useMemo<[EnrichedEntity[], EnrichedEntity[]]>(() => { @@ -44,7 +46,7 @@ export const Home: FC = () => { download('factorio-microservices.bin', store()) }} > - Store + { } }} /> - Visualize - + + + + + + +
@@ -76,17 +83,14 @@ export const Home: FC = () => { key={missing.href} value={missing} onClick={() => { - addGroup(newGroupValue !== 'New group' ? newGroupValue : missing.name, [ - missing.href - ]) - setNewGroupValue('New group') + preferencesRef.current?.addGroup(missing.name, [missing.href]) }} onContextMenu={event => { event.preventDefault() setIgnoredFactories([...ignoredFactories, missing.href]) }} - leftClickText={'Create a new group with this name and item as exported factory'} - rightClickText={'Exclude this recipe from suggestions'} + leftClickText={i18n(intl, 'page.home.tooltip.action.add_to_new_export')} + rightClickText={i18n(intl, 'page.home.tooltip.action.exclude_suggestion')} /> ))}
@@ -104,19 +108,14 @@ export const Home: FC = () => { key={missing.href} value={missing} onClick={() => { - addGroup( - newGroupValue !== 'New group' ? newGroupValue : missing.name, - [], - [missing.href] - ) - setNewGroupValue('New group') + preferencesRef.current?.addGroup(missing.name, undefined, [missing.href]) }} onContextMenu={event => { event.preventDefault() setIgnoredFactories([...ignoredFactories, missing.href]) }} - leftClickText={'Create a new group with this name and item as mall factory'} - rightClickText={'Exclude this recipe from suggestions'} + leftClickText={i18n(intl, 'page.home.tooltip.action.add_to_new_mall')} + rightClickText={i18n(intl, 'page.home.tooltip.action.exclude_suggestion')} /> ))}
diff --git a/components/home/Preferences/Preferences.tsx b/components/home/Preferences/Preferences.tsx index 9a7e0da..e890f01 100644 --- a/components/home/Preferences/Preferences.tsx +++ b/components/home/Preferences/Preferences.tsx @@ -1,16 +1,28 @@ -import { FC, useState } from 'react' +import { forwardRef, ForwardRefRenderFunction, useImperativeHandle, useState } from 'react' import { FactorySelect } from '../FactorySelect/FactorySelect' import { useGroups } from '../../contexts/GroupProvider' import { i18n, I18n } from '../../shared/I18n/I18n' import { useIntl } from 'react-intl' -export const Preferences: FC = () => { +interface Handle { + addGroup(preferredName: string, exported?: string[], mall?: string[]): boolean +} + +const PreferencesBase: ForwardRefRenderFunction = (_, forwardedRef) => { const intl = useIntl() + const DEFAULT_NAME = i18n(intl, 'page.home.group.add.default_group_name') + useImperativeHandle(forwardedRef, () => ({ + addGroup(preferredName: string, exported?: string[], mall?: string[]) { + const name = newGroupValue !== DEFAULT_NAME ? newGroupValue : preferredName + const result = addGroup(name, exported, mall) + result && setNewGroupValue(DEFAULT_NAME) + return result + } + })) + const { addGroup, baseFactories, setBaseFactories, ignoredFactories, setIgnoredFactories } = useGroups() - const [newGroupValue, setNewGroupValue] = useState( - i18n(intl, 'page.home.group.add.default_group_name') - ) + const [newGroupValue, setNewGroupValue] = useState(DEFAULT_NAME) return ( <>
@@ -49,7 +61,7 @@ export const Preferences: FC = () => { disabled={!newGroupValue} onClick={() => { addGroup(newGroupValue) - setNewGroupValue('New group') + setNewGroupValue(DEFAULT_NAME) }} > @@ -58,3 +70,5 @@ export const Preferences: FC = () => { ) } + +export const Preferences = forwardRef(PreferencesBase) diff --git a/components/icons/GraphIcon.tsx b/components/icons/GraphIcon.tsx new file mode 100644 index 0000000..9be4310 --- /dev/null +++ b/components/icons/GraphIcon.tsx @@ -0,0 +1,24 @@ +import { FC } from 'react' +import styles from './Icon.module.css' +import cx from 'classnames' + +interface Props { + className?: string +} + +export const GraphIcon: FC = ({ className }) => { + return ( + + + + + + ) +} diff --git a/components/icons/Icon.module.css b/components/icons/Icon.module.css new file mode 100644 index 0000000..c674e38 --- /dev/null +++ b/components/icons/Icon.module.css @@ -0,0 +1,11 @@ +.icon { + height: 1em; + padding-inline: 0.5ch; + transform: translateY(0.1em); +} + +@media (prefers-color-scheme: dark) { + .icon { + filter: invert(100%); + } +} diff --git a/components/home/LeftClickIcon/LeftClickIcon.tsx b/components/icons/LeftClickIcon.tsx similarity index 88% rename from components/home/LeftClickIcon/LeftClickIcon.tsx rename to components/icons/LeftClickIcon.tsx index 7639222..836b653 100644 --- a/components/home/LeftClickIcon/LeftClickIcon.tsx +++ b/components/icons/LeftClickIcon.tsx @@ -1,4 +1,6 @@ import { FC } from 'react' +import styles from './Icon.module.css' +import cx from 'classnames' interface Props { className?: string @@ -14,7 +16,7 @@ export const LeftClickIcon: FC = ({ className, classBody, classClick }) = x='0px' y='0px' viewBox='0 0 100 100' - className={className} + className={cx(styles.icon, className)} > = ({ node, className, ...props }) => { query }} > - 👁 + {node.name} @@ -40,7 +42,9 @@ export const NodeOverview: FC = ({ node, className, ...props }) => { ))} ) : null} -

Inputs

+

+ +

{node.inputs.map(input => ( @@ -48,7 +52,9 @@ export const NodeOverview: FC = ({ node, className, ...props }) => {
{node.outputs.length ? ( <> -

Outputs

+

+ +

{node.outputs.map(input => ( diff --git a/res/i18n/de.json b/res/i18n/de.json index c0bcdee..64246e1 100644 --- a/res/i18n/de.json +++ b/res/i18n/de.json @@ -2,24 +2,36 @@ "page.home.head.title": "Factorio Mikroservices", "page.home.head.meta.description": "Fasse Factorio Fabriken zu Microservices zusammen", "page.home.title": "Factorio Mikroservices", - "page.home.description": "Auf dieser Seite kannst du alle Einstellungen vornehmen. Gruppiere alle Fabriken zu Microservices zusammen. Ein Microservice ist eine Ansammlung bestimmter Montagemaschinen und anderen Fertigungen, die sich Eingaben (durch Züge) teilen. Optimale Gruppen sollten daher nur wenige Eingaben haben und viele Gegenstände produzieren. \"Exportierte Fabriken\" sind Ausgaben, die z.B. mit Zügen abgeholt, und für andere Services als Eingabe zu Verfügung stehen. \"Mall-Fabriken\" stellen mit Anbieterkisten die Waren zur Abholung durch den Spieler oder Roboter zur Verfügung. Die benötigten Eingabe-und Zwischenfabriken werden automatisch berechnet.", + "page.home.description": "Auf dieser Seite kannst du alle Einstellungen vornehmen. Gruppiere alle Fabriken zu Microservices zusammen. Ein Microservice ist eine Ansammlung bestimmter Montagemaschinen und anderen Fertigungen, die sich Eingaben (durch Züge) teilen. Optimale Gruppen sollten daher nur wenige Eingaben haben und viele Gegenstände produzieren. \"Exportierte Fabriken\" sind Ausgaben, die z.B. mit Zügen abgeholt, und für andere Services als Eingabe zu Verfügung stehen. \"Einkaufszentren\" stellen mit Anbieterkisten die Waren zur Abholung durch den Spieler oder Roboter zur Verfügung. Die benötigten Eingabe-und Zwischenfabriken werden automatisch berechnet.", + "page.home.pref.download": "Herunterladen", + "page.home.pref.visualize": "Visualisieren", "page.home.pref.basic.title": "Grundlegende Waren", "page.home.pref.basic.description": "Grundlegende Waren können nicht in Fabriken hergestellt werden. Es kann sinnvoll sein hier z.B. Eisenplatten hinzuzufügen, wenn jene direkt beim Erz hergestellt werden.", "page.home.pref.ignored.title": "Ignorierte Waren", - "page.home.pref.ignored.description": "Waren, die in in der Mall hergestellt werden sollen (z.B. Befeuerter Greifarm, weil es nicht benötigt wird) und auch nicht zwischen den Mikroservices exportiert werden sollen (z.B. Zahnräder, weil die bei Bedarf in der Fabrik selbst als Zwischenprodukt hergestellt wird).", + "page.home.pref.ignored.description": "Waren, die im Einkaufszentrum hergestellt werden sollen (z.B. Befeuerter Greifarm, weil es nicht benötigt wird) und auch nicht zwischen den Mikroservices exportiert werden sollen (z.B. Zahnräder, weil die bei Bedarf in der Fabrik selbst als Zwischenprodukt hergestellt wird).", "page.home.group.add.title": "Neue Gruppe hinzufügen", "page.home.group.add.default_group_name": "Neue Gruppe", "page.home.group.add.button_text": "Gruppe \"{name}\" hinzufügen", "page.home.group.missing.export.title": "Fehlende Export-Fabriken", - "page.home.group.missing.export.description": "Eine Auflistung aller Waren, die keine grundlegenden oder ignorierten Waren sind, und bei noch keiner Gruppe exportiert werden oder als Mall-Fabrik hinzugefügt wurden. Nur in dieser Liste falls von mindestens 3 anderen Waren verwendet.", - "page.home.group.missing.mall.title": "Fehlende Mall-Fabriken", - "page.home.group.missing.mall.description": "Eine Auflistung aller Waren, die keine grundlegenden oder ignorierten Waren sind, und bei noch keiner Gruppe exportiert werden oder als Mall-Fabrik hinzugefügt wurden. Nur in dieser Liste falls von maximal 2 anderen Waren verwendet.", + "page.home.group.missing.export.description": "Eine Auflistung aller Waren, die keine grundlegenden oder ignorierten Waren sind, und bei noch keiner Gruppe exportiert werden oder als Einkaufszentrum hinzugefügt wurden. Nur in dieser Liste falls von mindestens 3 anderen Waren verwendet.", + "page.home.group.missing.mall.title": "Fehlende Einkaufszentren", + "page.home.group.missing.mall.description": "Eine Auflistung aller Waren, die keine grundlegenden oder ignorierten Waren sind, und bei noch keiner Gruppe exportiert werden oder als Einkaufszentrum hinzugefügt wurden. Nur in dieser Liste falls von maximal 2 anderen Waren verwendet.", "page.home.group.missing.none": "Alle Fabriken hinzugefügt!", "page.home.group.item.export": "Exportierte Waren", - "page.home.group.item.mall": "Waren für die Mall", - "page.home.group.item.input": "Angelieferte Waren", - "page.home.group.item.intermediate": "Zwischenprodukte", - "page.home.group.item.suggestion.export": "Empfohlene Exportwaren", - "page.home.group.item.suggestion.mall": "Empfohlene Mall-Fabriken", - "page.home.group.delete.confirmation": "Gruppe unwiderruflich löschen?" + "page.home.group.item.mall": "Waren als Einkaufszentrum", + "page.home.group.item.input": "Angelieferte Waren ({amount})", + "page.home.group.item.intermediate": "Zwischenprodukte ({amount})", + "page.home.group.item.suggestion.export": "Vorgeschlagene Exportwaren", + "page.home.group.item.suggestion.mall": "Vorgeschlagene Einkaufszentren", + "page.home.group.delete.confirmation": "Gruppe unwiderruflich löschen?", + "page.home.tooltip.recipe": "Rezept", + "page.home.tooltip.used_by": "Verwendet in", + "page.home.tooltip.actions": "Aktionen", + "page.home.tooltip.action.exclude_suggestion": "Diese Ware von Vorschlägen ausschließen", + "page.home.tooltip.action.add_to_new_mall": "Erstelle eine neue Gruppe mit diesem Namen und dieser Ware als Einkaufszentrum", + "page.home.tooltip.action.add_to_new_export": "Erstelle eine neue Gruppe mit diesem Namen und gut als exportierte Fabrik", + "page.home.tooltip.action.add_to_export": "Ware zu exportierten Fabriken hinzufügen", + "page.home.tooltip.action.add_to_mall": "Ware als Einkaufszentrum hinzufügen", + "page.visualize.imports": "Importe", + "page.visualize.exports": "Exporte" } diff --git a/res/i18n/en.json b/res/i18n/en.json index df7fc75..3998910 100644 --- a/res/i18n/en.json +++ b/res/i18n/en.json @@ -3,6 +3,8 @@ "page.home.head.meta.description": "Group Factorio factories together into microservices", "page.home.title": "Factorio Microservices", "page.home.description": "On this page you can make all settings. Group all factories into microservices. A microservice is a collection of certain assembly machines and other fabrications that share inputs (through trains). Optimal groups should therefore have few inputs and produce many items. \"Exported factories\" are outputs that are picked up by trains, for example, and available to other services as inputs. \"Mall factories\" use vendor crates to make goods available for pickup by the player or robot. The required input and intermediate factories are calculated automatically.", + "page.home.pref.download": "Download", + "page.home.pref.visualize": "Visualize", "page.home.pref.basic.title": "Basic goods", "page.home.pref.basic.description": "Basic goods cannot be produced in factories. It may make sense to add iron plates here, for example, if they are produced directly at the ore.", "page.home.pref.ignored.title": "Ignored goods", @@ -17,9 +19,19 @@ "page.home.group.missing.none": "All factories added!", "page.home.group.item.export": "Exported goods", "page.home.group.item.mall": "Mall goods", - "page.home.group.item.input": "Input goods", - "page.home.group.item.intermediate": "Intermediate factories", + "page.home.group.item.input": "Input goods ({amount})", + "page.home.group.item.intermediate": "Intermediate factories ({amount})", "page.home.group.item.suggestion.export": "Suggested export goods", "page.home.group.item.suggestion.mall": "Suggested mall goods", - "page.home.group.delete.confirmation": "Delete group permanently?" + "page.home.group.delete.confirmation": "Delete group permanently?", + "page.home.tooltip.recipe": "Recipe", + "page.home.tooltip.used_by": "Used by", + "page.home.tooltip.actions": "Actions", + "page.home.tooltip.action.exclude_suggestion": "Exclude this good from suggestions", + "page.home.tooltip.action.add_to_new_mall": "Create a new group with this name and good as mall factory", + "page.home.tooltip.action.add_to_new_export": "Create a new group with this name and good as exported factory", + "page.home.tooltip.action.add_to_export": "Add good to exported factories", + "page.home.tooltip.action.add_to_mall": "Add good to mall factories", + "page.visualize.imports": "Imports", + "page.visualize.exports": "Exports" } diff --git a/tsconfig.test.json b/tsconfig.test.json index f208ace..5002bfd 100644 --- a/tsconfig.test.json +++ b/tsconfig.test.json @@ -6,8 +6,7 @@ "jest.config.js", "next.config.js", "src/backend-custom-server/index", - "scripts/*", - "res/**/*.json" + "scripts/*" ], - "exclude": ["dist", ".next", "out", "node_modules", "cypress"] + "exclude": ["dist", ".next", "out", "node_modules", "cypress", "*.json"] }