Added entity span

This commit is contained in:
Sebastian Seedorf
2022-08-09 12:24:47 +02:00
parent 12c8cacaf6
commit 6d3aae7fe9
12 changed files with 270 additions and 22 deletions

View File

@@ -0,0 +1,27 @@
.span {
background: #DDD;
font-size: 2em;
border: 1px solid white;
display: inline-block;
position: relative;
}
.amount {
position: absolute;
inset-inline-end: 0.2em;
inset-block-end: 0;
font-size: 50%;
color: white;
text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000;
}
.img {
display: inline-block;
width: 1em;
height: 1em;
margin-inline-end: 0.2em;
}
.strong {
font-weight: 600;
}

29
components/EntityIcon.tsx Normal file
View File

@@ -0,0 +1,29 @@
import {FC, HTMLProps, useMemo} from "react"
import {Entity} from "../src/types"
import {useDetails} from "../src/hooks/useDetails"
import styles from './EntityIcon.module.css'
interface Props extends Omit<HTMLProps<HTMLSpanElement>, 'value'> {
value: Entity|string
amount?: number
}
export const EntityIcon: FC<Props> = ({value, amount, ...rest}) => {
const details = useDetails()
const entity = useMemo<Entity>(() => {
return typeof value === "object"
? value
: details.find(detail => detail.href === value) ?? {
href: value,
name: value,
image: value,
recipe: undefined
}
}, [details, value])
return <span className={styles.span} {...rest}>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img className={styles.img} src={`https://wiki.factorio.com${entity.image}`} alt={entity.name}/>
{amount !== undefined && <span className={styles.amount}>{amount}</span>}
</span>
}

View File

@@ -0,0 +1,73 @@
.span {
background: #DDD;
font-size: 1em;
border: 1px solid white;
display: inline-block;
position: relative;
}
.tooltip {
--background: lightsalmon;
--arrow-width: 0.6em;
--arrow-height: 0.4em;
display: none;
position: absolute;
left: calc(100% + var(--arrow-width));
top: -500%;
bottom: -500%;
margin: auto 0;
width: max-content;
height: max-content;
background: var(--background);
padding: 0.5em;
border-radius: 0.7em;
z-index: 1;
}
.span:hover > .tooltip {
display: initial;
}
.tooltip::before {
content: "";
border-style: solid;
top: -500%;
bottom: -500%;
margin: auto 0;
height: max-content;
border-width: var(--arrow-height) var(--arrow-width) var(--arrow-height) 0;
border-color: transparent var(--background) transparent transparent;
position: absolute;
left: calc(var(--arrow-width) * -1 + 1px);
}
.img {
display: inline-block;
width: 1em;
height: 1em;
margin-inline-end: 0.2em;
transform: translateY(0.1em);
}
.strong {
font-weight: 600;
margin-block: 1em 0.4em;
}
.strong:first-child {
margin-block-start: 0;
}
.leftClick {
height: 1em;
transform: translateY(0.1em);
}
.rightClick {
height: 1em;
transform: scaleX(-1) translateY(0.1em);
}
.clickBtn {
fill: red;
}

47
components/EntitySpan.tsx Normal file
View File

@@ -0,0 +1,47 @@
import {FC, HTMLProps, useMemo} from "react"
import {Entity} from "../src/types"
import {useDetails} from "../src/hooks/useDetails"
import styles from './EntitySpan.module.css'
import {RecipeSpan} from "./Recipe";
import {LeftClickIcon} from "./LeftClickIcon";
interface Props extends Omit<HTMLProps<HTMLSpanElement>, 'value'> {
value: Entity|string
leftClickText?: string
rightClickText?: string
}
export const EntitySpan: FC<Props> = ({value, leftClickText, rightClickText, ...rest}) => {
const details = useDetails()
const entity = useMemo<Entity>(() => {
return typeof value === "object"
? value
: details.find(detail => detail.href === value) ?? {
href: value,
name: value,
image: value,
recipe: undefined
}
}, [details, value])
return <span className={styles.span} {...rest}>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img className={styles.img} src={`https://wiki.factorio.com${entity.image}`} alt={entity.name}/>
{entity.name}
<div className={styles.tooltip}>
{entity.recipe && (
<>
<div className={styles.strong}>Recipe</div>
<RecipeSpan recipe={entity.recipe}/>
</>
)}
{(leftClickText || rightClickText) && (
<>
<div className={styles.strong}>Actions</div>
{leftClickText && <div><LeftClickIcon className={styles.leftClick} classClick={styles.clickBtn}/> {leftClickText}</div>}
{rightClickText && <div><LeftClickIcon className={styles.rightClick} classClick={styles.clickBtn}/> {rightClickText}</div>}
</>
)}
</div>
</span>
}

View File

@@ -3,6 +3,7 @@ import {FactorySelect} from "./FactorySelect";
import {useDetails} from "../src/hooks/useDetails";
import {Entity} from "../src/types";
import styles from "./Group.module.css"
import {EntitySpan} from "./EntitySpan";
interface Props {
onRemove: () => void
@@ -109,13 +110,12 @@ export const Group: FC<Props> = ({
<h4>Inputs</h4>
<ul>
{
inputs.map(input => <li key={input}><a
href={'javascript:void(0)'}
inputs.map(input => <li key={input}><EntitySpan
value={input}
onClick={() => addIntermediateFactory(input)}
style={{color: basic.has(input) ? 'darkgreen' : exported.has(input) ? 'orange' : undefined}}
>
{input}
</a></li>)
leftClickText={"Add to intermediate factories"}
/></li>)
}
</ul>
</div>
@@ -123,17 +123,16 @@ export const Group: FC<Props> = ({
<h4>Suggestions</h4>
<ul>
{suggestions.map(suggestion => <li key={suggestion.href}>
<a
href={'javascript:void(0)'}
<EntitySpan
value={suggestion}
onClick={() => addOutputFactory(suggestion.href)}
onContextMenu={event => {
event.preventDefault()
onDoIgnore(suggestion.href)
}}
style={{color: basic.has(suggestion.href) ? 'darkgreen' : exported.has(suggestion.href) ? 'orange' : undefined}}
>
{suggestion.name}
</a>
leftClickText={"Add to output factories"}
rightClickText={"Exclude this recipe from suggestions"}
/>
</li>)}
</ul>
</div>

View File

@@ -2,3 +2,13 @@
display: grid;
grid-template-columns: repeat(auto-fit, minmax(500px, max-content));
}
.missingFactories {
display: flex;
flex-wrap: wrap;
gap: 0.1em;
}
.missingFactories > * {
width: max-content;
}

View File

@@ -7,6 +7,7 @@ import {sortByProperty} from "../src/utils";
import {useDetails} from "../src/hooks/useDetails";
import {Entity} from "../src/types";
import pako from 'pako';
import {EntitySpan} from "./EntitySpan";
interface Group {
name: string
@@ -108,15 +109,19 @@ export const HomeComponent: FC = () => {
</fieldset>
<fieldset>
<legend>Missing factories</legend>
<ul>
{
missingFactories.map(missing => <li key={missing.href}
<div className={styles.missingFactories}>
{ missingFactories.map(missing => (
<EntitySpan
key={missing.href}
value={missing}
onContextMenu={event => {
event.preventDefault()
setExcludedSuggestions([...excludedSuggestions, missing.href])
}}>{missing.name}</li>)
}
</ul>
}}
rightClickText={"Exclude this recipe from suggestions"}
/>
))}
</div>
</fieldset>
<div className={styles.grid}>
{

View File

@@ -0,0 +1,16 @@
import {FC} from "react";
interface Props {
className?: string
classBody?: string
classClick?: string
}
export const LeftClickIcon: FC<Props> = ({className, classBody, classClick}) => {
return (
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" x="0px" y="0px" viewBox="0 0 100 100" className={className}>
<path className={classBody} d="M51.552,8.117v32.554l-3.095,0.009c-9.203,0.027-17.447,0.297-24.509,0.803l-0.291,0.021 c-0.026,1.733-0.036,3.521-0.036,5.378c0,24.854,1.527,45,26.379,45c24.854,0,26.379-20.146,26.379-45 C76.379,22.563,74.903,8.701,51.552,8.117z" />
<path className={classClick} id="click" d="M48.448,37.577V8.117C27.971,8.629,24.313,19.354,23.727,38.388C29.914,37.945,38.002,37.607,48.448,37.577z" />
</svg>
)
}

View File

@@ -0,0 +1,5 @@
.recipe {
align-items: center;
display: flex;
gap: 0.2em;
}

23
components/Recipe.tsx Normal file
View File

@@ -0,0 +1,23 @@
import {FC} from "react"
import {Recipe} from "../src/types"
import {EntityIcon} from "./EntityIcon";
import styles from './Recipe.module.css'
interface Props {
recipe: Recipe
}
export const RecipeSpan: FC<Props> = ({recipe}) => {
const joinByPlus = (elems: JSX.Element[]) => elems.reduce((acc, curr) => {
if (acc.length) {
return [...acc, '+', curr]
} else {
return [curr]
}
}, [] as (JSX.Element|string)[])
const before = Object.entries({...recipe.prerequisites}).map(([key, amount]) => <EntityIcon key={key} value={key} amount={amount} />)
const after = Object.entries({...recipe.output}).map(([key, amount]) => <EntityIcon key={key} value={key} amount={amount} />)
return <span className={styles.recipe}>
{joinByPlus(before)} {joinByPlus(after)}
</span>
}

View File

@@ -13,7 +13,8 @@
"pako": "^2.0.4",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-select": "^5.4.0"
"react-select": "^5.4.0",
"react-tooltip": "^4.2.21"
},
"devDependencies": {
"@types/node": "18.6.4",

View File

@@ -1727,7 +1727,7 @@ prelude-ls@^1.2.1:
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.8.1:
prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1:
version "15.8.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
@@ -1772,6 +1772,14 @@ react-select@^5.4.0:
prop-types "^15.6.0"
react-transition-group "^4.3.0"
react-tooltip@^4.2.21:
version "4.2.21"
resolved "https://registry.yarnpkg.com/react-tooltip/-/react-tooltip-4.2.21.tgz#840123ed86cf33d50ddde8ec8813b2960bfded7f"
integrity sha512-zSLprMymBDowknr0KVDiJ05IjZn9mQhhg4PRsqln0OZtURAJ1snt1xi5daZfagsh6vfsziZrc9pErPTDY1ACig==
dependencies:
prop-types "^15.7.2"
uuid "^7.0.3"
react-transition-group@^4.3.0:
version "4.4.5"
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1"
@@ -2071,6 +2079,11 @@ use-sync-external-store@1.2.0:
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a"
integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==
uuid@^7.0.3:
version "7.0.3"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-7.0.3.tgz#c5c9f2c8cf25dc0a372c4df1441c41f5bd0c680b"
integrity sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==
v8-compile-cache@^2.0.3:
version "2.3.0"
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee"