From c6c52ca41af71b8a69a5743433a6f90cd98e1ebf Mon Sep 17 00:00:00 2001 From: Misode Date: Tue, 18 Jan 2022 01:02:19 +0100 Subject: [PATCH] Projects (#192) * Add file save UI and drafts project * Fix build * Create SearchList component as abstraction * Add project page and file tree view * Create Locale context * Create Theme context * Create Version context * Create Title context * Create Project context * Store current file in project context * Fix issues when renaming file and implement deleting * Style improvements * Make all project strings translatable * Fix z-index --- src/app/App.tsx | 28 +++ src/app/Locales.ts | 36 ---- src/app/Main.tsx | 98 ++-------- src/app/Store.ts | 15 ++ src/app/components/BtnMenu.tsx | 5 +- src/app/components/Header.tsx | 35 ++-- src/app/components/Octicon.tsx | 4 + src/app/components/TreeView.tsx | 58 ++++++ src/app/components/forms/SearchList.tsx | 24 +++ src/app/components/forms/index.ts | 1 + src/app/components/generator/PreviewPanel.tsx | 11 +- src/app/components/generator/SourcePanel.tsx | 29 ++- src/app/components/generator/Tree.tsx | 5 +- src/app/components/index.ts | 1 + .../previews/BiomeSourcePreview.tsx | 17 +- .../components/previews/DecoratorPreview.tsx | 11 +- src/app/components/previews/NoisePreview.tsx | 11 +- .../previews/NoiseSettingsPreview.tsx | 14 +- src/app/components/previews/index.ts | 1 - src/app/components/sounds/SoundConfig.tsx | 19 +- src/app/contexts/Locale.tsx | 89 +++++++++ src/app/contexts/Project.tsx | 129 +++++++++++++ src/app/contexts/Theme.tsx | 41 ++++ src/app/contexts/Title.tsx | 48 +++++ src/app/contexts/Version.tsx | 54 ++++++ src/app/contexts/index.ts | 5 + src/app/hooks/index.ts | 1 + src/app/hooks/useActiveTimout.ts | 21 +++ src/app/pages/Category.tsx | 14 +- src/app/pages/Changelog.tsx | 16 +- src/app/pages/Generator.tsx | 176 ++++++++++++------ src/app/pages/Home.tsx | 18 +- src/app/pages/Project.tsx | 28 +++ src/app/pages/Sounds.tsx | 25 ++- src/app/pages/index.ts | 1 + src/app/schema/renderHtml.tsx | 58 +++--- src/app/schema/transformOutput.ts | 8 +- src/locales/en.json | 11 +- src/styles/global.css | 165 ++++++++++++---- 39 files changed, 958 insertions(+), 373 deletions(-) create mode 100644 src/app/App.tsx delete mode 100644 src/app/Locales.ts create mode 100644 src/app/components/TreeView.tsx create mode 100644 src/app/components/forms/SearchList.tsx create mode 100644 src/app/contexts/Locale.tsx create mode 100644 src/app/contexts/Project.tsx create mode 100644 src/app/contexts/Theme.tsx create mode 100644 src/app/contexts/Title.tsx create mode 100644 src/app/contexts/Version.tsx create mode 100644 src/app/contexts/index.ts create mode 100644 src/app/hooks/useActiveTimout.ts create mode 100644 src/app/pages/Project.tsx diff --git a/src/app/App.tsx b/src/app/App.tsx new file mode 100644 index 00000000..12f90202 --- /dev/null +++ b/src/app/App.tsx @@ -0,0 +1,28 @@ +import type { RouterOnChangeArgs } from 'preact-router' +import { Router } from 'preact-router' +import '../styles/global.css' +import '../styles/nodes.css' +import { Analytics } from './Analytics' +import { Header } from './components' +import { Category, Changelog, Generator, Home, Project, Sounds } from './pages' +import { cleanUrl } from './Utils' + +export function App() { + const changeRoute = (e: RouterOnChangeArgs) => { + // Needs a timeout to ensure the title is set correctly + setTimeout(() => Analytics.pageview(cleanUrl(e.url))) + } + + return <> +
+ + + + + + + + + + +} diff --git a/src/app/Locales.ts b/src/app/Locales.ts deleted file mode 100644 index 2aee9f80..00000000 --- a/src/app/Locales.ts +++ /dev/null @@ -1,36 +0,0 @@ -import config from '../config.json' -import English from '../locales/en.json' - -export type Localize = (key: string, ...params: string[]) => string - -interface Locale { - [key: string]: string -} - -export const Locales: { - [key: string]: Locale, -} = { - fallback: English, -} - -function resolveLocaleParams(value: string, params?: string[]): string { - return value.replace(/%\d+%/g, match => { - const index = parseInt(match.slice(1, -1)) - return params?.[index] !== undefined ? params[index] : match - }) -} - -export function locale(language: string, key: string, ...params: string[]): string { - const value: string | undefined = Locales[language]?.[key] - ?? Locales.en?.[key] ?? Locales.fallback[key] ?? key - return resolveLocaleParams(value, params) -} - -export async function loadLocale(language: string) { - const langConfig = config.languages.find(lang => lang.code === language) - if (!langConfig) return - const data = await import(`../locales/${language}.json`) - const schema = langConfig.schemas !== false - && await import(`../../node_modules/@mcschema/locales/src/${language}.json`) - Locales[language] = { ...data.default, ...schema.default } -} diff --git a/src/app/Main.tsx b/src/app/Main.tsx index c7070ac8..d1478fee 100644 --- a/src/app/Main.tsx +++ b/src/app/Main.tsx @@ -1,93 +1,21 @@ import { render } from 'preact' -import type { RouterOnChangeArgs } from 'preact-router' -import { getCurrentUrl, Router } from 'preact-router' -import { useCallback, useEffect, useState } from 'preact/hooks' -import config from '../config.json' import '../styles/global.css' import '../styles/nodes.css' -import { Analytics } from './Analytics' -import { Header } from './components' -import { loadLocale, locale, Locales } from './Locales' -import { Category, Changelog, Generator, Home, Sounds } from './pages' -import type { VersionId } from './services' -import { VersionIds } from './services' -import { Store } from './Store' -import { cleanUrl, getSearchParams, setSeachParams } from './Utils' - -const VERSIONS_IN_TITLE = 3 +import { App } from './App' +import { LocaleProvider, ProjectProvider, ThemeProvider, TitleProvider, VersionProvider } from './contexts' function Main() { - const [lang, setLanguage] = useState('none') - const changeLanguage = async (language: string) => { - if (!Locales[language]) { - await loadLocale(language) - } - Analytics.setLanguage(language) - Store.setLanguage(language) - setLanguage(language) - } - useEffect(() => { - (async () => { - const target = Store.getLanguage() - await Promise.all([ - loadLocale('en'), - ...(target !== 'en' ? [loadLocale(target)] : []), - ]) - setLanguage(target) - })() - }, []) - - const [theme, setTheme] = useState(Store.getTheme()) - const changeTheme = (theme: string) => { - Analytics.setTheme(theme) - Store.setTheme(theme) - setTheme(theme) - } - useEffect(() => { - document.documentElement.setAttribute('data-theme', theme) - }, [theme]) - - const searchParams = getSearchParams(getCurrentUrl()) - const targetVersion = searchParams.get('version') - const [version, setVersion] = useState(Store.getVersion()) - const changeVersion = useCallback((version: VersionId) => { - if (getSearchParams(getCurrentUrl()).has('version')) { - setSeachParams({ version }) - } - Analytics.setVersion(version) - Store.setVersion(version) - setVersion(version) - }, [targetVersion]) - useEffect(() => { - if (VersionIds.includes(targetVersion as VersionId) && version !== targetVersion) { - setVersion(targetVersion as VersionId) - } - }, [version, targetVersion]) - - const [title, setTitle] = useState(locale(lang, 'title.home')) - const changeTitle = (title: string, versions?: VersionId[]) => { - versions ??= config.versions.map(v => v.id as VersionId) - const titleVersions = versions.slice(versions.length - VERSIONS_IN_TITLE) - document.title = `${title} Minecraft ${titleVersions.join(', ')}` - setTitle(title) - } - - const changeRoute = (e: RouterOnChangeArgs) => { - // Needs a timeout to ensure the title is set correctly - setTimeout(() => Analytics.pageview(cleanUrl(e.url))) - } - - return <> -
- - - - - - - - - + return + + + + + + + + + + } render(
, document.body) diff --git a/src/app/Store.ts b/src/app/Store.ts index 4a79ebb4..b56cf877 100644 --- a/src/app/Store.ts +++ b/src/app/Store.ts @@ -1,3 +1,5 @@ +import type { Project } from './contexts' +import { DRAFT_PROJECT } from './contexts' import type { VersionId } from './services' import { VersionIds } from './services' @@ -8,6 +10,7 @@ export namespace Store { export const ID_INDENT = 'indentation' export const ID_FORMAT = 'output_format' export const ID_SOUNDS_VERSION = 'minecraft_sounds_version' + export const ID_PROJECTS = 'misode_projects' export function getLanguage() { return localStorage.getItem(ID_LANGUAGE) ?? 'en' @@ -37,6 +40,14 @@ export namespace Store { return localStorage.getItem(ID_SOUNDS_VERSION) ?? 'latest' } + export function getProjects(): Project[] { + const projects = localStorage.getItem(ID_PROJECTS) + if (projects) { + return JSON.parse(projects) as Project[] + } + return [DRAFT_PROJECT] + } + export function setLanguage(language: string | undefined) { if (language) localStorage.setItem(ID_LANGUAGE, language) } @@ -60,4 +71,8 @@ export namespace Store { export function setSoundsVersion(version: string | undefined) { if (version) localStorage.setItem(ID_SOUNDS_VERSION, version) } + + export function setProjects(projects: Project[] | undefined) { + if (projects) localStorage.setItem(ID_PROJECTS, JSON.stringify(projects)) + } } diff --git a/src/app/components/BtnMenu.tsx b/src/app/components/BtnMenu.tsx index 44c58349..c2294954 100644 --- a/src/app/components/BtnMenu.tsx +++ b/src/app/components/BtnMenu.tsx @@ -8,14 +8,15 @@ interface BtnMenuProps extends JSX.HTMLAttributes { label?: string, relative?: boolean, tooltip?: string, + tooltipLoc?: 'se' | 'sw' | 'nw', children: ComponentChildren, } export function BtnMenu(props: BtnMenuProps) { - const { icon, label, relative, tooltip, children } = props + const { icon, label, relative, tooltip, tooltipLoc, children } = props const [active, setActive] = useFocus() return
- + {active &&
{children}
} diff --git a/src/app/components/Header.tsx b/src/app/components/Header.tsx index 6630f6a7..22dca404 100644 --- a/src/app/components/Header.tsx +++ b/src/app/components/Header.tsx @@ -1,8 +1,7 @@ import { getCurrentUrl, Link, route } from 'preact-router' import { Btn, BtnMenu, Icons, Octicon } from '.' import config from '../../config.json' -import { locale } from '../Locales' -import type { VersionId } from '../services' +import { useLocale, useTheme, useTitle, useVersion } from '../contexts' import { checkVersion } from '../services' import { cleanUrl, getGenerator } from '../Utils' @@ -12,51 +11,45 @@ const Themes: Record = { light: 'sun', } -type HeaderProps = { - lang: string, - title: string, - version: VersionId, - theme: string, - changeTheme: (theme: string) => unknown, - language: string, - changeLanguage: (language: string) => unknown, -} -export function Header({ lang, title, version, theme, changeTheme, language, changeLanguage }: HeaderProps) { - const loc = locale.bind(null, lang) +export function Header() { + const { lang, locale, changeLanguage } = useLocale() + const { theme, changeTheme } = useTheme() + const { version } = useVersion() + const { title } = useTitle() const gen = getGenerator(getCurrentUrl()) return
- {Icons.home} + {Icons.home}

{title}

- {gen && + {gen && {config.generators .filter(g => g.category === gen?.category && checkVersion(version, g.minVersion)) .map(g => - route(cleanUrl(g.url))} /> + route(cleanUrl(g.url))} /> )} }