Styling of home

This commit is contained in:
Sebastian Seedorf
2022-08-22 17:04:39 +02:00
parent 537a18fb88
commit d964748a66
55 changed files with 1791 additions and 341 deletions

View File

@@ -1,35 +1,39 @@
.span {
background: lightgray;
font-size: 1em;
border: 1px solid white;
display: inline-block;
position: relative;
padding-inline: 0.2em;
display: inline-block;
border: 1px solid var(--md-sys-color-outline);
background: var(--md-sys-color-surface-variant);
border-radius: 4px;
color: var(--md-sys-color-on-surface-variant);
font-size: 1em;
padding-inline: 0.2em;
}
.spanSimple {
padding-inline-start: 0.2em;
position: relative;
display: inline-block;
padding-inline-start: 0.2em;
}
.tooltip {
--background: lightsalmon;
--arrow-width: 0.6em;
--arrow-height: 0.4em;
display: none;
--background: var(--md-sys-color-primary-container);
position: absolute;
left: calc(100% + var(--arrow-width));
top: -5000%;
bottom: -5000%;
margin: auto 0;
z-index: 1;
display: none;
width: max-content;
height: max-content;
background: var(--background);
padding: 0.5em;
margin: auto 0;
background: var(--background);
border-radius: 0.7em;
z-index: 1;
box-shadow: 0 0 6px 1px var(--md-sys-color-shadow);
color: var(--md-sys-color-on-primary-container);
inset-block: -5000%;
inset-inline-start: calc(100% + var(--arrow-width));
--arrow-width: 0.6em;
--arrow-height: 0.4em;
}
.span:is(:focus, :hover) > .tooltip {
@@ -37,16 +41,15 @@
}
.tooltip::before {
content: "";
border-style: solid;
top: 0;
bottom: 0;
margin: auto 0;
position: absolute;
height: max-content;
border-width: var(--arrow-height) var(--arrow-width) var(--arrow-height) 0;
border-style: solid;
border-color: transparent var(--background) transparent transparent;
position: absolute;
left: calc(var(--arrow-width) * -1 + 1px);
margin: auto 0;
content: "";
inset-block: 0;
inset-inline-start: calc(var(--arrow-width) * -1 + 1px);
}
.img {
@@ -58,11 +61,11 @@
}
.base {
color: darkgreen;
color: var(--md-sys-color-tertiary);
}
.produced {
color: darkgoldenrod;
color: var(--md-ref-palette-primary70);
}
.unknown {
@@ -89,32 +92,9 @@
.rightClick {
height: 1em;
transform: scaleX(-1) translateY(0.1em);
transform: scaleX(-1) translateY(0.1em) !important;
}
.clickBtn {
fill: red;
}
@media (prefers-color-scheme: dark) {
.span {
border-color: #111111;
background-color: #444;
}
.base {
color: lightgreen;
}
.produced {
color: lightsalmon;
}
.unknown {
color: lightgray;
}
.tooltip {
--background: darkred;
}
}

View File

@@ -1,24 +1,53 @@
.root {
position: relative;
border: 2px solid black;
padding: 1em 0.5em;
border: 1px solid var(--md-sys-color-outline);
background-color: var(--md-sys-color-surface);
border-radius: 4px;
}
.heading {
display: inline-block;
margin-block-start: 0;
}
.heading:not(:focus-visible)::after {
display: inline-block;
width: 1em;
height: 1em;
background-color: var(--md-sys-color-on-background);
content: "";
mask: url("/factorio/pencil.svg") no-repeat 50% 50%;
opacity: 0.8;
padding-inline-start: 0.5em;
}
.quit {
--color: darkred;
--color: var(--md-sys-color-error);
position: absolute;
right: 1em;
top: 1em;
border-radius: 999999px;
background: transparent;
border: 1px solid var(--color);
background: transparent;
border-radius: 999999px;
color: var(--color);
font-weight: 700;
font-weight: 500;
inset-block-start: 1em;
inset-inline-end: 1em;
}
.quit:is(:focus, :hover) {
--color: red;
filter: drop-shadow(0px 0px 2px rgba(0, 0, 0, 0.5));
--color: var(--md-ref-palette-error60);
filter: drop-shadow(0 0 2px var(--md-ref-palette-neutral-variant70));
}
.label {
display: block;
}
.marginTop {
display: block;
margin-block-start: 1em;
}
.flex {
@@ -30,18 +59,3 @@
.flex > * {
width: max-content;
}
@media (prefers-color-scheme: dark) {
.root {
border-color: gray;
}
.quit {
--color: indianred;
}
.quit:is(:focus, :hover) {
--color: red;
filter: drop-shadow(0px 0px 2px rgba(255, 255, 255, 0.5));
}
}

View File

@@ -5,13 +5,14 @@ import styles from './GroupBox.module.css'
import { EntitySpan } from '../EntitySpan/EntitySpan'
import { useGroups } from '../../contexts/GroupProvider'
import { calculateInputs } from '../../../src/calculateInputs'
import { fixedEncodeURIComponent, uniquify } from '../../../src/utils'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { uniquify } from '../../../src/utils'
import { useFactories } from '../../contexts/FactoryProvider'
import { i18n, I18n } from '../../shared/I18n/I18n'
import { useIntl } from 'react-intl'
import { GraphIcon } from '../../icons/GraphIcon'
import { ButtonVisualize } from '../../shared/ButtonVisualize/ButtonVisualize'
import { Heading } from '../../shared/Heading'
import typography from '../../../styles/typography.module.css'
import cx from 'classnames'
interface Props {
group: Group
@@ -19,7 +20,6 @@ interface Props {
const GroupBoxBase: FC<Props> = ({ group }) => {
const intl = useIntl()
const { query } = useRouter()
const { factories, findFactory } = useFactories()
const {
doNotSuggest,
@@ -33,6 +33,7 @@ const GroupBoxBase: FC<Props> = ({ group }) => {
getInputType
} = useGroups()
const { name, exports, malls } = group
const nameForId = useMemo(() => name.replace(/[^a-z\d_-]/gi, ''), [name])
const [isDeleteConfirm, setDeleteConfirm] = useState(false)
@@ -79,26 +80,30 @@ const GroupBoxBase: FC<Props> = ({ group }) => {
return (
<div className={styles.root}>
<h3
<ButtonVisualize groupId={group.name} />
<Heading
type={'subsection'}
className={styles.heading}
contentEditable={true}
suppressContentEditableWarning={true}
onBlur={event => {
event.currentTarget.innerText = event.currentTarget.innerText.trim()
renameGroup(name, event.currentTarget.innerText)
}}
>
{name}
</h3>
<Link
href={{
pathname: `/visualize/${fixedEncodeURIComponent(group.name)}`,
query
onKeyDown={event => {
if (event.key === 'Enter') {
event.preventDefault()
event.currentTarget.blur()
}
if (event.key === 'Escape') {
event.preventDefault()
event.currentTarget.innerText = name
event.currentTarget.blur()
}
}}
>
<a>
<GraphIcon />
</a>
</Link>
{name}
</Heading>
<button
className={styles.quit}
onBlur={() => setDeleteConfirm(false)}
@@ -107,23 +112,31 @@ const GroupBoxBase: FC<Props> = ({ group }) => {
>
{isDeleteConfirm ? i18n(intl, 'page.home.group.delete.confirmation') : 'X'}
</button>
<h4>
<I18n id={'page.home.group.item.export'} />
</h4>
<FactorySelect
id={name + '-exports'}
factories={exports}
onSetFactories={setExportFactories}
/>
<h4>
<I18n id={'page.home.group.item.mall'} />
</h4>
<FactorySelect id={name + '-malls'} factories={malls} onSetFactories={setMallFactories} />
<label htmlFor={nameForId + '-exports'} className={styles.label}>
<span className={typography.titleMedium}>
<I18n id={'page.home.group.item.export'} />
</span>
<FactorySelect
id={nameForId + '-exports'}
factories={exports}
onSetFactories={setExportFactories}
/>
</label>
<label htmlFor={nameForId + '-exports'} className={cx(styles.label, styles.marginTop)}>
<span className={typography.titleMedium}>
<I18n id={'page.home.group.item.mall'} />
</span>
<FactorySelect
id={nameForId + '-malls'}
factories={malls}
onSetFactories={setMallFactories}
/>
</label>
{inputs.length ? (
<>
<h4>
<span className={cx(typography.titleMedium, styles.marginTop)}>
<I18n id={'page.home.group.item.input'} values={{ amount: inputs.length }} />
</h4>
</span>
<div className={styles.flex}>
{inputs.map(input => (
<EntitySpan
@@ -142,12 +155,12 @@ const GroupBoxBase: FC<Props> = ({ group }) => {
) : null}
{intermediates.length ? (
<>
<h4>
<span className={cx(typography.titleMedium, styles.marginTop)}>
<I18n
id={'page.home.group.item.intermediate'}
values={{ amount: intermediates.length }}
/>
</h4>
</span>
<div className={styles.flex}>
{intermediates.map(intermediate => (
<EntitySpan
@@ -161,9 +174,9 @@ const GroupBoxBase: FC<Props> = ({ group }) => {
) : null}
{suggestionsExport.length ? (
<>
<h4>
<span className={cx(typography.titleMedium, styles.marginTop)}>
<I18n id={'page.home.group.item.suggestion.export'} />
</h4>
</span>
<div className={styles.flex}>
{suggestionsExport.map(suggestion => (
<EntitySpan
@@ -183,9 +196,9 @@ const GroupBoxBase: FC<Props> = ({ group }) => {
) : null}
{suggestionMall.length ? (
<>
<h4>
<span className={cx(typography.titleMedium, styles.marginTop)}>
<I18n id={'page.home.group.item.suggestion.mall'} />
</h4>
</span>
<div className={styles.flex}>
{suggestionMall.map(suggestion => (
<EntitySpan

View File

@@ -1,17 +1,17 @@
.content {
max-width: 80ch;
margin: 0 auto;
text-align: justify;
}
.grid {
display: grid;
margin-top: 2em;
gap: 1em;
grid-template-columns: repeat(auto-fit, minmax(450px, max-content));
margin-block-start: -2em;
}
.missingFactories {
display: flex;
flex-wrap: wrap;
gap: 0.1em;
}
.missingFactories > * {
.entitySpanList > * {
width: max-content;
}

View File

@@ -1,125 +1,38 @@
import { ElementRef, FC, useMemo, useRef } from 'react'
import { FC } from 'react'
import { GroupBox } from './GroupBox/GroupBox'
import styles from './Home.module.css'
import { EnrichedEntity } from '../../src/types'
import { EntitySpan } from './EntitySpan/EntitySpan'
import { useGroups } from '../contexts/GroupProvider'
import { Preferences } from './Preferences/Preferences'
import { download, streamToArrayBuffer } from '../../src/download'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { i18n, I18n } from '../shared/I18n/I18n'
import { useFactories } from '../contexts/FactoryProvider'
import { GraphIcon } from '../icons/GraphIcon'
import { useIntl } from 'react-intl'
import { SectionPreferences } from './SectionPreferences/SectionPreferences'
import { I18n } from '../shared/I18n/I18n'
import { Heading } from '../shared/Heading'
import { Paragraph } from '../shared/Paragraph/Paragraph'
import { Section } from '../shared/Section/Section'
import { SectionAddMissing } from './SectionAddMissing/SectionAddMissing'
import { SectionShare } from './SectionShare/SectionShare'
import { ButtonVisualize } from '../shared/ButtonVisualize/ButtonVisualize'
export const Home: FC = () => {
const intl = useIntl()
const { query } = useRouter()
const { factories } = useFactories()
const preferencesRef = useRef<ElementRef<typeof Preferences>>(null)
const { groups, doNotSuggest, ignoredFactories, setIgnoredFactories, store, load } = useGroups()
const inputRef = useRef<HTMLInputElement>(null)
const [missingExport, missingMall] = useMemo<[EnrichedEntity[], EnrichedEntity[]]>(() => {
return factories
.filter(factory => !doNotSuggest.has(factory.href) && factory.recipe)
.reduce(
(acc, factory) =>
(factory.usedBy?.length ?? 0) >= 3
? [[...acc[0], factory], acc[1]]
: [acc[0], [...acc[1], factory]],
[[], []] as [EnrichedEntity[], EnrichedEntity[]]
)
}, [factories, doNotSuggest])
const { groups } = useGroups()
return (
<main>
<h1>
<I18n id={'page.home.title'} />
</h1>
<p>
<I18n id={'page.home.description'} />
</p>
<button
onClick={() => {
download('factorio-microservices.bin', store())
}}
>
<I18n id={'page.home.pref.download'} />
</button>
<input
type={'file'}
multiple={false}
ref={inputRef}
onChange={async evt => {
const stream = evt.currentTarget.files?.[0].stream() as
| globalThis.ReadableStream<Uint8Array>
| undefined
if (stream) {
const array = await streamToArrayBuffer(stream)
load(array)
if (inputRef.current) inputRef.current.value = null as unknown as string
}
}}
/>
<Link href={{ pathname: '/visualize', query }}>
<a>
<I18n id={'page.home.pref.visualize'} />
<GraphIcon />
</a>
</Link>
<Preferences ref={preferencesRef} />
<fieldset>
<legend>
<I18n id={'page.home.group.missing.export.title'} />
</legend>
<span>
<I18n id={'page.home.group.missing.export.description'} />
</span>
<div className={styles.missingFactories}>
{missingExport.map(missing => (
<EntitySpan
key={missing.href}
value={missing}
onClick={() => {
preferencesRef.current?.addGroup(missing.name, [missing.href])
}}
onContextMenu={event => {
event.preventDefault()
setIgnoredFactories([...ignoredFactories, missing.href])
}}
leftClickText={i18n(intl, 'page.home.tooltip.action.add_to_new_export')}
rightClickText={i18n(intl, 'page.home.tooltip.action.exclude_suggestion')}
/>
))}
</div>
</fieldset>
<fieldset>
<legend>
<I18n id={'page.home.group.missing.mall.title'} />
</legend>
<span>
<I18n id={'page.home.group.missing.mall.description'} />
</span>
<div className={styles.missingFactories}>
{missingMall.map(missing => (
<EntitySpan
key={missing.href}
value={missing}
onClick={() => {
preferencesRef.current?.addGroup(missing.name, undefined, [missing.href])
}}
onContextMenu={event => {
event.preventDefault()
setIgnoredFactories([...ignoredFactories, missing.href])
}}
leftClickText={i18n(intl, 'page.home.tooltip.action.add_to_new_mall')}
rightClickText={i18n(intl, 'page.home.tooltip.action.exclude_suggestion')}
/>
))}
</div>
</fieldset>
<Section>
<Heading type={'pageTitle'}>
<I18n id={'page.home.title'} />
</Heading>
<Paragraph size={'large'}>
<I18n id={'page.home.description'} />
</Paragraph>
</Section>
<SectionShare />
<SectionPreferences />
<SectionAddMissing />
<Section>
<Heading type={'section'}>
<I18n id={'page.home.group.title'} />
<ButtonVisualize large={true} />
</Heading>
</Section>
<div className={styles.grid}>
{Object.values(groups)
.sort((a, b) => a.name.localeCompare(b.name))

View File

@@ -1,74 +0,0 @@
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'
interface Handle {
addGroup(preferredName: string, exported?: string[], mall?: string[]): boolean
}
const PreferencesBase: ForwardRefRenderFunction<Handle> = (_, 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(DEFAULT_NAME)
return (
<>
<fieldset>
<legend>
<I18n id={'page.home.pref.basic.title'} />
</legend>
<span>
<I18n id={'page.home.pref.basic.description'} />
</span>
<FactorySelect
id={'baseFactoriesSelect'}
factories={baseFactories}
onSetFactories={setBaseFactories}
fixInputs={true}
/>
</fieldset>
<fieldset>
<legend>
<I18n id={'page.home.pref.ignored.title'} />
</legend>
<span>
<I18n id={'page.home.pref.ignored.description'} />
</span>
<FactorySelect
id={'ignoredFactoriesSelect'}
factories={ignoredFactories}
onSetFactories={setIgnoredFactories}
/>
</fieldset>
<fieldset>
<legend>
<I18n id={'page.home.group.add.title'} />
</legend>
<input value={newGroupValue} onChange={e => setNewGroupValue(e.target.value)} />
<button
disabled={!newGroupValue}
onClick={() => {
addGroup(newGroupValue)
setNewGroupValue(DEFAULT_NAME)
}}
>
<I18n id={'page.home.group.add.button_text'} values={{ name: newGroupValue }} />
</button>
</fieldset>
</>
)
}
export const Preferences = forwardRef(PreferencesBase)

View File

@@ -2,6 +2,7 @@ import { FC } from 'react'
import { Recipe } from '../../../src/types'
import { EntityIcon } from '../EntityIcon/EntityIcon'
import styles from './Recipe.module.css'
import { I18n } from '../../shared/I18n/I18n'
interface Props {
recipe: Recipe
@@ -23,7 +24,8 @@ export const RecipeSpan: FC<Props> = ({ recipe }) => {
const after = Object.entries({ ...recipe.output }).map(toEntityIcon)
return (
<span className={styles.recipe}>
{joinByPlus([toEntityIcon(['/Time', recipe.time]), ...before])} {joinByPlus(after)}
{joinByPlus([toEntityIcon(['/Time', recipe.time]), ...before])}{' '}
<I18n id={'component.recipe.arrow'} /> {joinByPlus(after)}
</span>
)
}

View File

@@ -0,0 +1,9 @@
.addBtn {
margin-inline: 1em;
}
.entitySpanList {
display: flex;
flex-wrap: wrap;
gap: 0.1em;
}

View File

@@ -0,0 +1,123 @@
import { FC, useCallback, useMemo, useState } from 'react'
import { Heading } from '../../shared/Heading'
import { i18n, I18n } from '../../shared/I18n/I18n'
import { Paragraph } from '../../shared/Paragraph/Paragraph'
import styles from './SectionAddMissing.module.css'
import { EntitySpan } from '../EntitySpan/EntitySpan'
import { Section } from '../../shared/Section/Section'
import { EnrichedEntity } from '../../../src/types'
import { useFactories } from '../../contexts/FactoryProvider'
import { useGroups } from '../../contexts/GroupProvider'
import { useIntl } from 'react-intl'
import { Input } from '../../shared/Input/Input'
import { Button } from '../../shared/Button/Button'
export const SectionAddMissing: FC = () => {
const intl = useIntl()
const DEFAULT_NAME = useMemo(() => i18n(intl, 'page.home.group.add.default_group_name'), [intl])
const { factories } = useFactories()
const { addGroup: addGroupCtx, doNotSuggest, ignoredFactories, setIgnoredFactories } = useGroups()
const [newGroupValue, setNewGroupValue] = useState(DEFAULT_NAME)
const addGroup = useCallback(
(preferredName: string, exported?: string[], mall?: string[]) => {
const name = newGroupValue !== DEFAULT_NAME ? newGroupValue : preferredName
const result = addGroupCtx(name, exported, mall)
result && setNewGroupValue(DEFAULT_NAME)
return result
},
[DEFAULT_NAME, addGroupCtx, newGroupValue]
)
const [missingExport, missingMall] = useMemo<[EnrichedEntity[], EnrichedEntity[]]>(() => {
return factories
.filter(factory => !doNotSuggest.has(factory.href) && factory.recipe)
.reduce(
(acc, factory) =>
(factory.usedBy?.length ?? 0) >= 3
? [[...acc[0], factory], acc[1]]
: [acc[0], [...acc[1], factory]],
[[], []] as [EnrichedEntity[], EnrichedEntity[]]
)
}, [factories, doNotSuggest])
return (
<Section color={'secondary'}>
<Heading type={'section'}>
<I18n id={'page.home.group.add.title'} />
</Heading>
<Input value={newGroupValue} onChange={e => setNewGroupValue(e.target.value)} />
<Button
className={styles.addBtn}
disabled={!newGroupValue}
onClick={() => {
addGroup(newGroupValue)
setNewGroupValue(DEFAULT_NAME)
}}
>
<I18n id={'page.home.group.add.button_text'} values={{ name: newGroupValue }} />
</Button>
<Heading type={'subsection'}>
<I18n id={'page.home.group.missing.export.title'} />
</Heading>
<Paragraph size={'subtitle'}>
<I18n id={'page.home.group.missing.export.description'} />
</Paragraph>
{missingExport.length ? (
<div className={styles.entitySpanList}>
{missingExport.map(missing => (
<EntitySpan
key={missing.href}
value={missing}
onClick={() => {
addGroup(missing.name, [missing.href])
}}
onContextMenu={event => {
event.preventDefault()
setIgnoredFactories([...ignoredFactories, missing.href])
}}
leftClickText={i18n(intl, 'page.home.tooltip.action.add_to_new_export')}
rightClickText={i18n(intl, 'page.home.tooltip.action.exclude_suggestion')}
/>
))}
</div>
) : (
<Paragraph size={'medium'}>
<em>
<I18n id={'page.home.group.missing.none'} />
</em>
</Paragraph>
)}
<Heading type={'subsection'}>
<I18n id={'page.home.group.missing.mall.title'} />
</Heading>
<Paragraph size={'subtitle'}>
<I18n id={'page.home.group.missing.mall.description'} />
</Paragraph>
{missingMall.length ? (
<div className={styles.entitySpanList}>
{missingMall.map(missing => (
<EntitySpan
key={missing.href}
value={missing}
onClick={() => {
addGroup(missing.name, undefined, [missing.href])
}}
onContextMenu={event => {
event.preventDefault()
setIgnoredFactories([...ignoredFactories, missing.href])
}}
leftClickText={i18n(intl, 'page.home.tooltip.action.add_to_new_mall')}
rightClickText={i18n(intl, 'page.home.tooltip.action.exclude_suggestion')}
/>
))}
</div>
) : (
<Paragraph size={'medium'}>
<em>
<I18n id={'page.home.group.missing.none'} />
</em>
</Paragraph>
)}
</Section>
)
}

View File

@@ -0,0 +1,3 @@
.margin-top {
margin-block-start: 2em;
}

View File

@@ -0,0 +1,45 @@
import { FC } from 'react'
import { FactorySelect } from '../FactorySelect/FactorySelect'
import { useGroups } from '../../contexts/GroupProvider'
import { I18n } from '../../shared/I18n/I18n'
import { Section } from '../../shared/Section/Section'
import { Heading } from '../../shared/Heading'
import { Paragraph } from '../../shared/Paragraph/Paragraph'
import { Collapsible } from '../../shared/Collapsible/Collapsible'
import styles from './SectionPreferences.module.css'
export const SectionPreferences: FC = () => {
const { baseFactories, setBaseFactories, ignoredFactories, setIgnoredFactories } = useGroups()
return (
<Section>
<Collapsible id={'collapseBase'}>
<Heading type={'section'}>
<I18n id={'page.home.pref.basic.title'} />
</Heading>
<Paragraph size={'subtitle'}>
<I18n id={'page.home.pref.basic.description'} />
</Paragraph>
<FactorySelect
id={'baseFactoriesSelect'}
factories={baseFactories}
onSetFactories={setBaseFactories}
fixInputs={true}
/>
</Collapsible>
<Collapsible id={'collapseIgnored'} className={styles.marginTop}>
<Heading type={'section'}>
<I18n id={'page.home.pref.ignored.title'} />
</Heading>
<Paragraph size={'subtitle'}>
<I18n id={'page.home.pref.ignored.description'} />
</Paragraph>
<FactorySelect
id={'ignoredFactoriesSelect'}
factories={ignoredFactories}
onSetFactories={setIgnoredFactories}
/>
</Collapsible>
</Section>
)
}

View File

@@ -0,0 +1,30 @@
.shareGrid {
display: grid;
gap: 3em;
grid-template-columns: repeat(auto-fit, minmax(30ch, 1fr));
}
.downloadBtn {
width: 100%;
}
.uploadInput {
display: inline-block;
width: 100%;
padding: 2em 0.5em;
border: 1px dashed var(--md-sys-color-on-secondary-container);
margin: 1em 0;
background-color: var(--md-sys-color-secondary-container);
border-radius: 4px;
color: var(--md-sys-color-on-secondary-container);
text-align: center;
}
.shareInput {
width: 100%;
}
.uploadInput > input {
width: 0;
visibility: hidden;
}

View File

@@ -0,0 +1,80 @@
import { FC, useEffect, useState } from 'react'
import styles from './SectionShare.module.css'
import { Heading } from '../../shared/Heading'
import { Paragraph } from '../../shared/Paragraph/Paragraph'
import { Button } from '../../shared/Button/Button'
import cx from 'classnames'
import typography from '../../../styles/typography.module.css'
import { download, streamToArrayBuffer } from '../../../src/download'
import { I18n } from '../../shared/I18n/I18n'
import { Input } from '../../shared/Input/Input'
import { Section } from '../../shared/Section/Section'
import { useGroups } from '../../contexts/GroupProvider'
export const SectionShare: FC = () => {
const { store, load } = useGroups()
const [location, setLocation] = useState('')
useEffect(() => {
setLocation(window.location.href)
}, [])
return (
<Section color={'primary'}>
<div className={styles.shareGrid}>
<div>
<Heading type={'section'}>
<I18n id={'page.home.share.download.title'} />
</Heading>
<Paragraph size={'medium'}>
<I18n id={'page.home.share.download.description'} />
</Paragraph>
<Button
className={cx(styles.downloadBtn, typography.bodyLarge)}
onClick={() => {
download('factorio-microservices.bin', store())
}}
>
<I18n id={'page.home.pref.download'} />
</Button>
<label className={styles.uploadInput}>
<I18n id={'page.home.share.download.upload_text'} />
<input
type={'file'}
multiple={false}
onChange={async evt => {
const stream = evt.currentTarget.files?.[0].stream() as
| globalThis.ReadableStream<Uint8Array>
| undefined
if (stream) {
const array = await streamToArrayBuffer(stream)
load(array)
evt.currentTarget.value = null as unknown as string
}
}}
/>
</label>
</div>
<div>
<Heading type={'section'}>
<I18n id={'page.home.share.link.title'} />
</Heading>
<Paragraph size={'medium'}>
<I18n id={'page.home.share.link.description'} />
</Paragraph>
<Paragraph size={'medium'}>
<strong>
<I18n id={'page.home.share.link.warning'} />
</strong>
</Paragraph>
<Input
className={styles.shareInput}
readOnly={true}
value={location}
onClick={e => e.currentTarget.select()}
/>
</div>
</div>
</Section>
)
}