Add tooltips to all buttons and tweak hover colors

This commit is contained in:
Misode
2021-09-25 07:10:54 +02:00
parent 7db47938b8
commit 2cb14a2c10
13 changed files with 251 additions and 96 deletions

View File

@@ -4,11 +4,13 @@ type BtnProps = {
icon?: keyof typeof Octicon,
label?: string,
active?: boolean,
tooltip?: string,
tooltipLoc?: 'se' | 'sw' | 'nw',
class?: string,
onClick?: (event: MouseEvent) => unknown,
}
export function Btn({ icon, label, active, class: class_, onClick }: BtnProps) {
return <div class={`btn${active ? ' active' : ''}${class_ ? ` ${class_}` : ''}`} onClick={onClick}>
export function Btn({ icon, label, active, class: clazz, tooltip, tooltipLoc, onClick }: BtnProps) {
return <div class={`btn${active ? ' active' : ''}${clazz ? ` ${clazz}` : ''}${tooltip ? ` tooltipped tip-${tooltipLoc ?? 'sw'}` : ''}`} onClick={onClick} aria-label={tooltip}>
{icon && Octicon[icon]}
{label && <span>{label}</span>}
</div>

View File

@@ -7,9 +7,10 @@ type BtnInputProps = {
large?: boolean,
doSelect?: number,
value?: string,
placeholder?: string,
onChange?: (value: string) => unknown,
}
export function BtnInput({ icon, label, large, doSelect, value, onChange }: BtnInputProps) {
export function BtnInput({ icon, label, large, doSelect, value, placeholder, onChange }: BtnInputProps) {
const onInput = onChange === undefined ? () => {} : (e: any) => {
const value = (e.target as HTMLInputElement).value
onChange?.(value)
@@ -25,6 +26,6 @@ export function BtnInput({ icon, label, large, doSelect, value, onChange }: BtnI
return <div class={`btn btn-input ${large ? 'large-input' : ''}`} onClick={e => e.stopPropagation()}>
{icon && Octicon[icon]}
{label && <span>{label}</span>}
<input ref={ref} type="text" value={value} onChange={onInput} />
<input ref={ref} type="text" value={value} onChange={onInput} placeholder={placeholder} />
</div>
}

View File

@@ -7,13 +7,14 @@ type BtnMenuProps = {
icon?: keyof typeof Octicon,
label?: string,
relative?: boolean,
tooltip?: string,
children: ComponentChildren,
}
export function BtnMenu({ icon, label, relative, children }: BtnMenuProps) {
export function BtnMenu({ icon, label, relative, tooltip, children }: BtnMenuProps) {
const [active, setActive] = useFocus()
return <div class={`btn-menu${relative === false ? ' no-relative' : ''}`}>
<Btn icon={icon} label={label} onClick={setActive} />
<Btn {...{icon, label, tooltip}} onClick={setActive} />
{active && <div class="btn-group">
{children}
</div>}

View File

@@ -27,9 +27,9 @@ export function Header({ lang, title, version, theme, changeTheme, language, cha
return <header>
<div class="title">
<Link class="home-link" href="/">{Icons.home}</Link>
<Link class="home-link" href="/" aria-label={loc('home')}>{Icons.home}</Link>
<h2>{title}</h2>
{gen && <BtnMenu icon="chevron_down">
{gen && <BtnMenu icon="chevron_down" tooltip={loc('switch_generator')}>
{config.generators
.filter(g => g.category === gen?.category && checkVersion(version, g.minVersion))
.map(g =>
@@ -40,7 +40,7 @@ export function Header({ lang, title, version, theme, changeTheme, language, cha
<nav>
<ul>
<li>
<BtnMenu icon="globe">
<BtnMenu icon="globe" tooltip={loc('language')}>
{config.languages.map(({ code, name }) =>
<Btn label={name} active={code === language}
onClick={() => changeLanguage(code)} />
@@ -48,7 +48,7 @@ export function Header({ lang, title, version, theme, changeTheme, language, cha
</BtnMenu>
</li>
<li>
<BtnMenu icon={Themes[theme]}>
<BtnMenu icon={Themes[theme]} tooltip={loc('theme')}>
{Object.entries(Themes).map(([th, icon]) =>
<Btn icon={icon} label={loc(`theme.${th}`)} active={th === theme}
onClick={() => changeTheme(th)} />
@@ -56,7 +56,7 @@ export function Header({ lang, title, version, theme, changeTheme, language, cha
</BtnMenu>
</li>
<li class="dimmed">
<a href="https://github.com/misode/misode.github.io" target="_blank" rel="noreferrer" title={loc('github')}>
<a href="https://github.com/misode/misode.github.io" target="_blank" rel="noreferrer" class="tooltipped tip-sw" aria-label={loc('github')}>
{Octicon.mark_github}
</a>
</li>

View File

@@ -37,7 +37,7 @@ export function SourcePanel({ lang, name, model, blockStates, doCopy, doDownload
const getOutput = useCallback((model: DataModel, blockStates: BlockStateRegistry) => {
const data = model.schema.hook(transformOutput, new ModelPath(model), model.data, { blockStates })
return JSON.stringify(data, null, INDENT[indent]) + '\n'
}, [])
}, [indent])
useEffect(() => {
retransform.current = () => {
@@ -109,7 +109,7 @@ export function SourcePanel({ lang, name, model, blockStates, doCopy, doDownload
return <>
<div class="controls">
<BtnMenu icon="gear">
<BtnMenu icon="gear" tooltip={loc('output_settings')}>
{Object.entries(INDENT).map(([key]) =>
<Btn label={loc(`indentation.${key}`)} active={indent === key}
onClick={() => changeIndent(key)}/>

View File

@@ -5,10 +5,11 @@ import { useEffect, useRef, useState } from 'preact/hooks'
import type { PreviewProps } from '.'
import { Btn } from '..'
import { useCanvas } from '../../hooks'
import { locale } from '../../Locales'
import { biomeMap, getBiome } from '../../previews'
import { newSeed } from '../../Utils'
export const BiomeSourcePreview = ({ model, data, shown, version }: PreviewProps) => {
export const BiomeSourcePreview = ({ model, data, shown, lang, version }: PreviewProps) => {
const [scale, setScale] = useState(2)
const [focused, setFocused] = useState<string | undefined>(undefined)
const offset = useRef<[number, number]>([0, 0])
@@ -69,11 +70,14 @@ export const BiomeSourcePreview = ({ model, data, shown, version }: PreviewProps
<div class="controls">
{focused && <Btn label={focused} class="no-pointer" />}
{(type === 'multi_noise' || type === 'checkerboard') && <>
<Btn icon="dash" onClick={() => changeScale(scale * 1.5)} />
<Btn icon="plus" onClick={() => changeScale(scale / 1.5)} />
<Btn icon="dash" tooltip={locale(lang, 'zoom_out')}
onClick={() => changeScale(scale * 1.5)} />
<Btn icon="plus" tooltip={locale(lang, 'zoom_in')}
onClick={() => changeScale(scale / 1.5)} />
</>}
{type === 'multi_noise' &&
<Btn icon="sync" onClick={() => newSeed(model)} />}
<Btn icon="sync" tooltip={locale(lang, 'generate_new_seed')}
onClick={() => newSeed(model)} />}
</div>
<canvas ref={canvas} width="200" height="200"></canvas>
</>

View File

@@ -2,10 +2,11 @@ import { useEffect, useState } from 'preact/hooks'
import type { PreviewProps } from '.'
import { Btn } from '..'
import { useCanvas } from '../../hooks'
import { locale } from '../../Locales'
import { decorator } from '../../previews'
import { randomSeed } from '../../Utils'
export const DecoratorPreview = ({ data, version, shown }: PreviewProps) => {
export const DecoratorPreview = ({ data, version, shown, lang }: PreviewProps) => {
const [scale, setScale] = useState(4)
const [seed, setSeed] = useState(randomSeed())
@@ -27,9 +28,12 @@ export const DecoratorPreview = ({ data, version, shown }: PreviewProps) => {
return <>
<div class="controls">
<Btn icon="dash" onClick={() => setScale(Math.min(16, scale + 1))} />
<Btn icon="plus" onClick={() => setScale(Math.max(1, scale - 1))} />
<Btn icon="sync" onClick={() => setSeed(randomSeed())} />
<Btn icon="dash" tooltip={locale(lang, 'zoom_out')}
onClick={() => setScale(Math.min(16, scale + 1))} />
<Btn icon="plus" tooltip={locale(lang, 'zoom_in')}
onClick={() => setScale(Math.max(1, scale - 1))} />
<Btn icon="sync" tooltip={locale(lang, 'generate_new_seed')}
onClick={() => setSeed(randomSeed())} />
</div>
<canvas ref={canvas} width="64" height="64"></canvas>
</>

View File

@@ -54,7 +54,7 @@ export const NoiseSettingsPreview = ({ lang, data, shown, version }: PreviewProp
return <>
<div class="controls">
{focused && <Btn label={`Y = ${focused}`} class="no-pointer" />}
<BtnMenu icon="gear">
<BtnMenu icon="gear" tooltip={locale(lang, 'terrain_settings')}>
{hasPeaks ? <>
<BtnInput label={loc('preview.factor')} value={`${biomeFactor}`} onChange={v => setBiomeFactor(Number(v))} />
<BtnInput label={loc('preview.offset')} value={`${biomeOffset}`} onChange={v => setBiomeOffset(Number(v))} />
@@ -64,7 +64,8 @@ export const NoiseSettingsPreview = ({ lang, data, shown, version }: PreviewProp
<BtnInput label={loc('preview.depth')} value={`${biomeOffset}`} onChange={v => setBiomeOffset(Number(v))} />
</>}
</BtnMenu>
<Btn icon="sync" onClick={() => setSeed(randomSeed())} />
<Btn icon="sync" tooltip={locale(lang, 'generate_new_seed')}
onClick={() => setSeed(randomSeed())} />
</div>
<canvas ref={canvas} width={size} height={size}></canvas>
</>

View File

@@ -175,7 +175,7 @@ export function Generator({ lang, changeTitle, version, onChangeVersion }: Gener
<div class="controls">
<Btn icon="upload" label={loc('import')} onClick={importSource} />
<BtnMenu icon="archive" label={loc('presets')} relative={false}>
<BtnInput icon="search" large value={presetFilter} onChange={setPresetFilter} doSelect={1} />
<BtnInput icon="search" large value={presetFilter} onChange={setPresetFilter} doSelect={1} placeholder={loc('search')} />
<div class="result-list">
{presetResults.map(preset => <Btn label={preset} onClick={() => loadPreset(preset)} />)}
</div>
@@ -186,7 +186,7 @@ export function Generator({ lang, changeTitle, version, onChangeVersion }: Gener
<Btn label={v} active={v === version} onClick={() => onChangeVersion(v)} />
)}
</BtnMenu>
<BtnMenu icon="kebab_horizontal">
<BtnMenu icon="kebab_horizontal" tooltip={loc('more')}>
<Btn icon="history" label={loc('reset')} onClick={reset} />
<Btn icon="arrow_left" label={loc('undo')} onClick={undo} />
<Btn icon="arrow_right" label={loc('redo')} onClick={redo} />
@@ -195,17 +195,17 @@ export function Generator({ lang, changeTitle, version, onChangeVersion }: Gener
{error && <ErrorPanel error={error} onDismiss={() => setError(null)} />}
<Tree {...{lang, model, version, blockStates}} onError={setError} />
</main>
<div class="popup-actions" style={`--offset: -${10 + actionsShown * 50}px;`}>
<div class={`popup-action action-preview${hasPreview ? ' shown' : ''}`} onClick={togglePreview}>
<div class="popup-actions" style={`--offset: -${8 + actionsShown * 50}px;`}>
<div class={`popup-action action-preview${hasPreview ? ' shown' : ''} tooltipped tip-nw`} aria-label={loc(previewShown ? 'hide_preview' : 'show_preview')} onClick={togglePreview}>
{previewShown ? Octicon.x_circle : Octicon.play}
</div>
<div class={`popup-action action-download${sourceShown ? ' shown' : ''}`} onClick={downloadSource}>
<div class={`popup-action action-download${sourceShown ? ' shown' : ''} tooltipped tip-nw`} aria-label={loc('download')} onClick={downloadSource}>
{Octicon.download}
</div>
<div class={`popup-action action-copy${sourceShown ? ' shown' : ''}${copyActive ? ' active' : ''}`} onClick={copySource}>
<div class={`popup-action action-copy${sourceShown ? ' shown' : ''}${copyActive ? ' active' : ''} tooltipped tip-nw`} aria-label={loc(copyActive ? 'copied' : 'copy')} onClick={copySource}>
{copyActive ? Octicon.check : Octicon.clippy}
</div>
<div class={'popup-action action-code shown'} onClick={toggleSource}>
<div class={'popup-action action-code shown tooltipped tip-nw'} aria-label={loc(sourceShown ? 'hide_output' : 'show_output')} onClick={toggleSource}>
{sourceShown ? Octicon.chevron_right : Octicon.code}
</div>
</div>

View File

@@ -103,7 +103,7 @@ const renderHtml: RenderHook = {
const node = DataModel.wrapLists(children.default())
path.model.set(path, [...value, { node, id: hexId() }])
}
const suffix = <button class="add" onClick={onAdd}>{Octicon.plus_circle}</button>
const suffix = <button class="add tooltipped tip-se" aria-label={locale(lang, 'add_top')} onClick={onAdd}>{Octicon.plus_circle}</button>
const body = <>
{(value && Array.isArray(value)) && value.map(({ node: cValue, id: cId }, index) => {
if (index === maxShown) {
@@ -123,7 +123,7 @@ const renderHtml: RenderHook = {
if (canToggle && (toggle === false || (toggle === undefined && value.length > 20))) {
return <div class="node node-header" data-category={children.category(cPath)}>
<ErrorPopup lang={lang} path={cPath} nested />
<button class="toggle" onClick={expand(cId)}>{Octicon.chevron_right}</button>
<button class="toggle tooltipped tip-se" aria-label={`${locale(lang, 'expand')}\n${locale(lang, 'expand_all', 'Ctrl')}`} onClick={expand(cId)}>{Octicon.chevron_right}</button>
<label>{pathLocale(lang, cPath, `${index}`)}</label>
<Collapsed key={cId} path={cPath} value={cValue} schema={children} />
</div>
@@ -141,16 +141,16 @@ const renderHtml: RenderHook = {
path.model.set(path, v)
}
return <MemoedTreeNode key={cId} path={cPath} schema={children} value={cValue} lang={lang} states={states} ctx={{...ctx, index: (index === 0 ? 1 : 0) + (index === value.length - 1 ? 2 : 0)}}>
{canToggle && <button class="toggle" onClick={collapse(cId)}>{Octicon.chevron_down}</button>}
<button class="remove" onClick={onRemove}>{Octicon.trashcan}</button>
{canToggle && <button class="toggle tooltipped tip-se" aria-label={`${locale(lang, 'collapse')}\n${locale(lang, 'collapse_all', 'Ctrl')}`} onClick={collapse(cId)}>{Octicon.chevron_down}</button>}
<button class="remove tooltipped tip-se" aria-label={locale(lang, 'remove')} onClick={onRemove}>{Octicon.trashcan}</button>
{value.length > 1 && <div class="node-move">
<button class="move" onClick={onMoveUp} disabled={index === 0}>{Octicon.chevron_up}</button>
<button class="move" onClick={onMoveDown} disabled={index === value.length - 1}>{Octicon.chevron_down}</button>
<button class="move tooltipped tip-se" aria-label={locale(lang, 'move_up')} onClick={onMoveUp} disabled={index === 0}>{Octicon.chevron_up}</button>
<button class="move tooltipped tip-se" aria-label={locale(lang, 'move_down')} onClick={onMoveDown} disabled={index === value.length - 1}>{Octicon.chevron_down}</button>
</div>}
</MemoedTreeNode>
})}
{(value && value.length > 2 && value.length <= maxShown) && <div class="node node-header">
<button class="add" onClick={onAddBottom}>{Octicon.plus_circle}</button>
{(value && value.length > 0 && value.length <= maxShown) && <div class="node node-header">
<button class="add tooltipped tip-se" aria-label={locale(lang, 'add_bottom')} onClick={onAddBottom}>{Octicon.plus_circle}</button>
</div>}
</>
return [null, suffix, body]
@@ -183,7 +183,7 @@ const renderHtml: RenderHook = {
}
const suffix = <>
{keysSchema.hook(this, keyPath, keyPath.get() ?? '', lang, states, ctx)[1]}
<button class="add" onClick={onAdd}>{Octicon.plus_circle}</button>
<button class="add tooltipped tip-se" aria-label={locale(lang, 'add')} onClick={onAdd}>{Octicon.plus_circle}</button>
</>
const body = <>
{typeof value === 'object' && Object.entries(value).map(([key, cValue]) => {
@@ -194,7 +194,7 @@ const renderHtml: RenderHook = {
if (canToggle && (toggle === false || (toggle === undefined && value.length > 20))) {
return <div class="node node-header" data-category={children.category(cPath)}>
<ErrorPopup lang={lang} path={cPath} nested />
<button class="toggle" onClick={expand(key)}>{Octicon.chevron_right}</button>
<button class="toggle tooltipped tip-se" aria-label={`${locale(lang, 'expand')}\n${locale(lang, 'expand_all', 'Ctrl')}`} onClick={expand(key)}>{Octicon.chevron_right}</button>
<label>{key}</label>
<Collapsed key={key} path={cPath} value={cValue} schema={children} />
</div>
@@ -208,8 +208,8 @@ const renderHtml: RenderHook = {
}
const onRemove = () => cPath.set(undefined)
return <MemoedTreeNode key={key} schema={cSchema} path={cPath} value={cValue} {...{lang, states, ctx}} label={key}>
{canToggle && <button class="toggle" onClick={collapse(key)}>{Octicon.chevron_down}</button>}
<button class="remove" onClick={onRemove}>{Octicon.trashcan}</button>
{canToggle && <button class="toggle tooltipped tip-se" aria-label={`${locale(lang, 'collapse')}\n${locale(lang, 'collapse_all', 'Ctrl')}`} onClick={collapse(key)}>{Octicon.chevron_down}</button>}
<button class="remove tooltipped tip-se" aria-label={locale(lang, 'remove')} onClick={onRemove}>{Octicon.trashcan}</button>
</MemoedTreeNode>
})}
</>
@@ -226,10 +226,10 @@ const renderHtml: RenderHook = {
if (node.optional()) {
if (value === undefined) {
const onExpand = () => path.set(DataModel.wrapLists(node.default()))
suffix = <button class="collapse closed" onClick={onExpand}>{Octicon.plus_circle}</button>
suffix = <button class="collapse closed tooltipped tip-se" aria-label={locale(lang, 'expand')} onClick={onExpand}>{Octicon.plus_circle}</button>
} else {
const onCollapse = () => path.set(undefined)
suffix = <button class="collapse open" onClick={onCollapse}>{Octicon.trashcan}</button>
suffix = <button class="collapse open tooltipped tip-se" aria-label={locale(lang, 'remove')} onClick={onCollapse}>{Octicon.trashcan}</button>
}
}
const newCtx = (typeof value === 'object' && value !== null && node.default()?.pools)
@@ -318,13 +318,16 @@ function BooleanSuffix({ path, node, value, lang }: NodeProps<BooleanHookParams>
</>
}
function NumberSuffix({ path, config, integer, value }: NodeProps<NumberHookParams>) {
function NumberSuffix({ path, config, integer, value, lang }: NodeProps<NumberHookParams>) {
const [text, setText] = useState(value ?? '')
const commitTimeout = useRef<number>()
const scheduleCommit = (value: number) => {
const commitValue = useRef<number | undefined>()
const scheduleCommit = (newValue: number) => {
if (commitTimeout.current) clearTimeout(commitTimeout.current)
commitValue.current = newValue
commitTimeout.current = setTimeout(() => {
path.model.set(path, value)
path.model.set(path, commitValue.current)
commitValue.current = undefined
}, 500)
}
const onChange = (evt: Event) => {
@@ -334,7 +337,7 @@ function NumberSuffix({ path, config, integer, value }: NodeProps<NumberHookPara
scheduleCommit(parsed)
}
const onBlur = () => {
setText(value ?? '')
setText(commitValue.current ?? value ?? '')
}
const onColor = (evt: Event) => {
const value = (evt.target as HTMLInputElement).value
@@ -345,7 +348,7 @@ function NumberSuffix({ path, config, integer, value }: NodeProps<NumberHookPara
return <>
<input type="text" value={text} onChange={onChange} onBlur={onBlur} />
{config?.color && <input type="color" value={'#' + (value?.toString(16).padStart(6, '0') ?? '000000')} onChange={onColor} />}
{path.equals(new Path(['generator', 'seed'])) && <button onClick={() => newSeed(path.model)}>{Octicon.sync}</button>}
{path.equals(new Path(['generator', 'seed'])) && <button onClick={() => newSeed(path.model)} class="tooltipped tip-se" aria-label={locale(lang, 'generate_new_seed')}>{Octicon.sync}</button>}
</>
}
@@ -425,7 +428,7 @@ function TreeNode({ label, schema, path, value, lang, states, ctx, children }: T
{label ?? pathLocale(lang, path, `${path.last()}`)}
{active && <div class="node-menu">
<div class="menu-item">
<Btn icon="clippy" onClick={() => navigator.clipboard.writeText(context)} />
<Btn icon="clippy" tooltip={locale(lang, 'copy_context')} tooltipLoc="se" onClick={() => navigator.clipboard.writeText(context)} />
Context:
<span class="menu-item-context">{context}</span>
</div>