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}
onContextMenu={event => {
event.preventDefault()
setExcludedSuggestions([...excludedSuggestions, missing.href])
}}>{missing.name}</li>)
}
</ul>
<div className={styles.missingFactories}>
{ missingFactories.map(missing => (
<EntitySpan
key={missing.href}
value={missing}
onContextMenu={event => {
event.preventDefault()
setExcludedSuggestions([...excludedSuggestions, missing.href])
}}
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>
}