diff --git a/src/app/App.tsx b/src/app/App.tsx index 12f90202..dce6453d 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -4,7 +4,7 @@ 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 { Category, Changelog, Generator, Home, Project, Sounds, Versions } from './pages' import { cleanUrl } from './Utils' export function App() { @@ -21,6 +21,7 @@ export function App() { + diff --git a/src/app/components/forms/Checkbox.tsx b/src/app/components/forms/Checkbox.tsx new file mode 100644 index 00000000..a3d81180 --- /dev/null +++ b/src/app/components/forms/Checkbox.tsx @@ -0,0 +1,14 @@ +import { hexId } from '../../Utils' + +interface Props { + label: string, + value: boolean, + onChange: (value: boolean) => unknown, +} +export function Checkbox({ label, value, onChange }: Props) { + const id = hexId() + return +} diff --git a/src/app/components/forms/index.ts b/src/app/components/forms/index.ts index 3f157625..7706c73c 100644 --- a/src/app/components/forms/index.ts +++ b/src/app/components/forms/index.ts @@ -1,2 +1,3 @@ +export * from './Checkbox' export * from './Input' export * from './SearchList' diff --git a/src/app/components/index.ts b/src/app/components/index.ts index ae9363c7..4bb2d31d 100644 --- a/src/app/components/index.ts +++ b/src/app/components/index.ts @@ -12,3 +12,4 @@ export * from './previews' export * from './sounds' export * from './ToolCard' export * from './TreeView' +export * from './versions' diff --git a/src/app/components/versions/ChangelogEntry.tsx b/src/app/components/versions/ChangelogEntry.tsx new file mode 100644 index 00000000..c5651b7b --- /dev/null +++ b/src/app/components/versions/ChangelogEntry.tsx @@ -0,0 +1,27 @@ +import { marked } from 'marked' +import { ChangelogTag } from '.' +import type { Change, ChangelogVersion } from '../../services' + +type Props = { + change: Change, + activeTags?: string[], + toggleTag?: (tag: string) => unknown, +} +export function ChangelogEntry({ change, activeTags, toggleTag }: Props) { + return
+
+ + +
+
+ {change.tags.map(tag => toggleTag(tag) : undefined} active={activeTags?.includes(tag)} />)} +
+
+
+} + +function ArticleLink({ id, article }: ChangelogVersion) { + return article === null + ? {id} + : {id} +} diff --git a/src/app/components/versions/ChangelogList.tsx b/src/app/components/versions/ChangelogList.tsx new file mode 100644 index 00000000..18410758 --- /dev/null +++ b/src/app/components/versions/ChangelogList.tsx @@ -0,0 +1,66 @@ +import { useMemo, useState } from 'preact/hooks' +import { Btn, TextInput } from '..' +import { useLocale } from '../../contexts' +import type { Change } from '../../services' +import { ChangelogEntry } from './ChangelogEntry' +import { ChangelogTag } from './ChangelogTag' + +interface Props { + changes: Change[] | undefined, + defaultOrder: 'asc' | 'desc', +} +export function ChangelogList({ changes, defaultOrder }: Props) { + const { locale } = useLocale() + + const [search, setSearch] = useState('') + const [tags, setTags] = useState([]) + const toggleTag = (tag: string) => { + if (!tags.includes(tag)) { + setTags([...tags, tag]) + } else { + setTags(tags.filter(t => t !== tag)) + } + } + + const filteredChangelogs = useMemo(() => { + const query = search.split(' ').map(q => q.trim().toLowerCase()).filter(q => q.length > 0) + if (query.length === 0 && tags.length === 0) return changes + return changes?.filter(change => { + if (!tags.every(tag => change.tags.includes(tag))) { + return false + } + const content = change.tags.join(' ') + ' ' + change.content.toLowerCase() + return query.every(q => { + if (q.startsWith('!')) { + return q.length === 1 || !content.includes(q.slice(1)) + } + return content.includes(q) + }) + }) + }, [changes, search, tags]) + + const [sort, setSort] = useState(defaultOrder === 'desc') + + const sortedChangelogs = useMemo(() => { + return filteredChangelogs?.sort((a, b) => sort ? b.order - a.order : a.order - b.order) + }, [filteredChangelogs, sort]) + + return <> +
+ + setSort(!sort)} /> +
+ {tags.length > 0 &&
+ {tags.map(tag => setTags(tags.filter(t => t !== tag))} />)} +
} +
+ {sortedChangelogs === undefined + ? {locale('loading')} + : sortedChangelogs.length === 0 + ? {locale('changelog.no_results')} + : sortedChangelogs.map(change => + )} +
+ +} diff --git a/src/app/components/versions/ChangelogTag.tsx b/src/app/components/versions/ChangelogTag.tsx new file mode 100644 index 00000000..10e75159 --- /dev/null +++ b/src/app/components/versions/ChangelogTag.tsx @@ -0,0 +1,15 @@ +import { Octicon } from '..' +import { hashString } from '../../Utils' + +type TagProps = { + label: string, + active?: boolean, + onClick?: () => unknown, +} +export function ChangelogTag({ label, active, onClick }: TagProps) { + const color = label === 'breaking' ? 5 : hashString(label) % 360 + return
+ {label === 'breaking' && Octicon.alert} + {label} +
+} diff --git a/src/app/components/versions/VersionDetail.tsx b/src/app/components/versions/VersionDetail.tsx new file mode 100644 index 00000000..c5b8c6b1 --- /dev/null +++ b/src/app/components/versions/VersionDetail.tsx @@ -0,0 +1,48 @@ +import { useEffect, useMemo, useState } from 'preact/hooks' +import { VersionMetaData } from '.' +import { useLocale } from '../../contexts' +import type { Change, VersionMeta } from '../../services' +import { getChangelogs } from '../../services' +import { ChangelogList } from './ChangelogList' + +interface Props { + version: VersionMeta +} +export function VersionDetail({ version }: Props) { + const { locale } = useLocale() + + const [changelogs, setChangelogs] = useState(undefined) + useEffect(() => { + getChangelogs() + .then(changelogs => setChangelogs( + changelogs.map(c => ({ ...c, tags: c.tags.filter(t => t !== c.group.id) })) + )) + .catch(e => console.error(e)) + }, []) + + const filteredChangelogs = useMemo(() => + changelogs?.filter(c => c.version.id === version.id || c.group.id === version.id), + [version.id, changelogs]) + + return <> +
+

{version.name}

+
+ + + + + + +
+

{locale('versions.technical_changes')}

+
+ +
+
+ +} + +export function releaseDate(version: VersionMeta) { + return new Date(version.release_time).toLocaleDateString(undefined, { day: 'numeric', month: 'short', year: 'numeric' }) +} diff --git a/src/app/components/versions/VersionEntry.tsx b/src/app/components/versions/VersionEntry.tsx new file mode 100644 index 00000000..5c965db8 --- /dev/null +++ b/src/app/components/versions/VersionEntry.tsx @@ -0,0 +1,18 @@ +import { releaseDate, VersionMetaData } from '.' +import { useLocale } from '../../contexts' +import type { VersionMeta } from '../../services' + +interface Props { + version: VersionMeta, + link?: string, +} +export function VersionEntry({ version, link }: Props) { + const { locale } = useLocale() + + return + {version.id} + + + + +} diff --git a/src/app/components/versions/VersionList.tsx b/src/app/components/versions/VersionList.tsx new file mode 100644 index 00000000..9617b83c --- /dev/null +++ b/src/app/components/versions/VersionList.tsx @@ -0,0 +1,36 @@ +import { useMemo, useState } from 'preact/hooks' +import { Checkbox, TextInput } from '..' +import { useLocale } from '../../contexts' +import type { VersionMeta } from '../../services' +import { VersionEntry } from './VersionEntry' + +interface Props { + versions: VersionMeta[] + link?: (id: string) => string +} +export function VersionList({ versions, link }: Props) { + const { locale } = useLocale() + + const [snapshots, setSnapshots] = useState(true) + const [search, setSearch] = useState('') + + const filteredVersions = useMemo(() => versions.filter(v => { + if (v.type === 'snapshot' && !snapshots) return false + return v.id.includes(search) + }), [versions, snapshots, search]) + + + return <> +
+ + +
+
+ {filteredVersions.map(v => )} + {filteredVersions.length === 0 && + {locale('versions.no_results')} + } +
+ +} diff --git a/src/app/components/versions/VersionMetaData.tsx b/src/app/components/versions/VersionMetaData.tsx new file mode 100644 index 00000000..dbd38427 --- /dev/null +++ b/src/app/components/versions/VersionMetaData.tsx @@ -0,0 +1,16 @@ +import { Octicon } from '..' + +interface Props { + label: string, + value: string | number, + link?: string, + compact?: boolean, + optional?: boolean, +} +export function VersionMetaData({ label, value, link, compact, optional }: Props) { + return
+ {label}: + + {link && } +
+} diff --git a/src/app/components/versions/index.ts b/src/app/components/versions/index.ts new file mode 100644 index 00000000..601d9fa4 --- /dev/null +++ b/src/app/components/versions/index.ts @@ -0,0 +1,7 @@ +export * from './ChangelogEntry' +export * from './ChangelogList' +export * from './ChangelogTag' +export * from './VersionDetail' +export * from './VersionEntry' +export * from './VersionList' +export * from './VersionMetaData' diff --git a/src/app/pages/Changelog.tsx b/src/app/pages/Changelog.tsx index bdb5fd71..34e0c700 100644 --- a/src/app/pages/Changelog.tsx +++ b/src/app/pages/Changelog.tsx @@ -1,10 +1,8 @@ -import { marked } from 'marked' -import { useEffect, useMemo, useState } from 'preact/hooks' -import { Ad, Btn, ErrorPanel, Octicon, TextInput } from '../components' +import { useEffect, useState } from 'preact/hooks' +import { Ad, ChangelogList, ErrorPanel } from '../components' import { useLocale, useTitle } from '../contexts' -import type { ChangelogEntry, ChangelogVersion } from '../services' +import type { Change } from '../services' import { getChangelogs } from '../services' -import { hashString } from '../Utils' interface Props { path?: string, @@ -14,99 +12,19 @@ export function Changelog({}: Props) { const [error, setError] = useState(null) useTitle(locale('title.changelog')) - const [changelogs, setChangelogs] = useState([]) + const [changelogs, setChangelogs] = useState([]) useEffect(() => { getChangelogs() .then(changelogs => setChangelogs(changelogs)) .catch(e => { console.error(e); setError(e) }) }, []) - const [search, setSearch] = useState('') - const [tags, setTags] = useState([]) - const toggleTag = (tag: string) => { - if (!tags.includes(tag)) { - setTags([...tags, tag]) - } else { - setTags(tags.filter(t => t !== tag)) - } - } - - const filteredChangelogs = useMemo(() => { - const query = search.split(' ').map(q => q.trim().toLowerCase()).filter(q => q.length > 0) - if (query.length === 0 && tags.length === 0) return changelogs - return changelogs.filter(change => { - if (!tags.every(tag => change.tags.includes(tag))) { - return false - } - const content = change.tags.join(' ') + ' ' + change.content.toLowerCase() - return query.every(q => { - if (q.startsWith('!')) { - return q.length === 1 || !content.includes(q.slice(1)) - } - return content.includes(q) - }) - }) - }, [changelogs, search, tags]) - - const [sort, setSort] = useState(true) - - const sortedChangelogs = useMemo(() => { - return filteredChangelogs.sort((a, b) => sort ? b.order - a.order : a.order - b.order) - }, [filteredChangelogs, sort]) return
{error && setError(null)} />} -
-
- - setSort(!sort)} /> -
- {tags.length > 0 &&
- {tags.map(tag => setTags(tags.filter(t => t !== tag))} />)} -
} -
- {sortedChangelogs.map(change => - )} +
} - -type ChangeProps = { - change: ChangelogEntry, - activeTags: string[], - toggleTag: (tag: string) => unknown, -} -function Change({ change, activeTags, toggleTag }: ChangeProps) { - return
-
- - -
-
- {change.tags.map(tag => toggleTag(tag)} active={activeTags.includes(tag)} />)} -
-
-
-} - -function ArticleLink({ id, article }: ChangelogVersion) { - return article === null - ? {id} - : {id} -} - -type TagProps = { - label: string, - active?: boolean, - onClick?: () => unknown, -} -function Tag({ label, active, onClick }: TagProps) { - const color = label === 'breaking' ? 5 : hashString(label) % 360 - return
- {label === 'breaking' && Octicon.alert} - {label} -
-} diff --git a/src/app/pages/Home.tsx b/src/app/pages/Home.tsx index 89325dde..c5868373 100644 --- a/src/app/pages/Home.tsx +++ b/src/app/pages/Home.tsx @@ -32,6 +32,7 @@ export function Home({}: Props) { link="https://misode.github.io/upgrader/" desc="Convert your data packs from 1.16 to 1.17 to 1.18" /> +
} diff --git a/src/app/pages/Versions.tsx b/src/app/pages/Versions.tsx new file mode 100644 index 00000000..b27b2b38 --- /dev/null +++ b/src/app/pages/Versions.tsx @@ -0,0 +1,64 @@ +import { getCurrentUrl } from 'preact-router' +import { useEffect, useState } from 'preact/hooks' +import { Ad, ErrorPanel, Octicon, VersionDetail, VersionList } from '../components' +import { useLocale, useTitle } from '../contexts' +import type { VersionMeta } from '../services' +import { fetchVersions } from '../services' +import { getSearchParams } from '../Utils' + +interface Props { + path?: string, +} +export function Versions({}: Props) { + const { locale } = useLocale() + const [error, setError] = useState(null) + useTitle(locale('title.versions')) + + const [versions, setVersions] = useState([]) + useEffect(() => { + fetchVersions() + .then(versions => setVersions(versions)) + .catch(e => { console.error(e); setError(e) }) + }, []) + + const selectedId = getSearchParams(getCurrentUrl()).get('id') + const selected = versions.find(v => v.id === selectedId) + + useTitle(selected ? selected.name : 'Versions Explorer', selected ? [] : undefined) + + const nextVersion = selected && getOffsetVersion(versions, selected, -1) + const previousVersion = selected && getOffsetVersion(versions, selected, 1) + + return
+ + {error && setError(null)} />} + +
+} + +function getOffsetVersion(versions: VersionMeta[], current: VersionMeta, offset: number) { + const currentIndex = versions.findIndex(v => v.id === current.id) + const offsetIndex = currentIndex + offset + if (offsetIndex < 0 || offsetIndex >= versions.length) { + return undefined + } + return versions[offsetIndex] +} diff --git a/src/app/pages/index.ts b/src/app/pages/index.ts index 33362a44..71084095 100644 --- a/src/app/pages/index.ts +++ b/src/app/pages/index.ts @@ -4,3 +4,4 @@ export * from './Generator' export * from './Home' export * from './Project' export * from './Sounds' +export * from './Versions' diff --git a/src/app/services/Changelogs.ts b/src/app/services/Changelogs.ts index 1ad14350..3ab0d5ea 100644 --- a/src/app/services/Changelogs.ts +++ b/src/app/services/Changelogs.ts @@ -2,7 +2,7 @@ import { isObject } from '../Utils' const repo = 'https://raw.githubusercontent.com/misode/technical-changes/main' -export type ChangelogEntry = { +export type Change = { group: ChangelogVersion, version: ChangelogVersion, order: number, @@ -15,14 +15,14 @@ export type ChangelogVersion = { article: string | null, } -let Changelogs: ChangelogEntry[] | Promise | null = null +let Changelogs: Change[] | Promise | null = null export async function getChangelogs() { if (!Changelogs) { const index = await (await fetch(`${repo}/index.json`)).json() as string[] Changelogs = (await Promise.all( index.map((group, i) => fetchGroup(parseVersion(group), i)) - )).flat().map(change => ({ + )).flat().map(change => ({ ...change, tags: [change.group.id, ...change.tags], })) diff --git a/src/app/services/DataFetcher.ts b/src/app/services/DataFetcher.ts index 4b39f169..7d737421 100644 --- a/src/app/services/DataFetcher.ts +++ b/src/app/services/DataFetcher.ts @@ -1,7 +1,7 @@ -import type { CollectionRegistry } from '@mcschema/core'; -import config from '../../config.json'; -import { message } from '../Utils'; -import type { BlockStateRegistry, VersionId } from './Schemas'; +import type { CollectionRegistry } from '@mcschema/core' +import config from '../../config.json' +import { message } from '../Utils' +import type { BlockStateRegistry, VersionId } from './Schemas' // Cleanup old caches ['1.15', '1.16', '1.17'].forEach(v => localStorage.removeItem(`cache_${v}`)); @@ -23,10 +23,20 @@ const mcmetaUrl = 'https://raw.githubusercontent.com/misode/mcmeta' type McmetaTypes = 'summary' | 'data' | 'assets' | 'registries' -function mcmeta(version: Version, type: McmetaTypes) { +function mcmeta(version: { dynamic: true } | { dynamic?: false, ref?: string}, type: McmetaTypes) { return `${mcmetaUrl}/${version.dynamic ? type : `${version.ref}-${type}`}` } +async function validateCache(version: Version) { + if (version.dynamic) { + if (localStorage.getItem(CACHE_LATEST_VERSION) !== latestVersion) { + await deleteMatching(url => url.startsWith(`${mcmetaUrl}/summary/`) || url.startsWith(`${mcmetaUrl}/data/`)) + localStorage.setItem(CACHE_LATEST_VERSION, latestVersion) + } + version.ref = latestVersion + } +} + export async function fetchData(versionId: string, collectionTarget: CollectionRegistry, blockStateTarget: BlockStateRegistry) { const version = config.versions.find(v => v.id === versionId) as Version | undefined if (!version) { @@ -34,13 +44,7 @@ export async function fetchData(versionId: string, collectionTarget: CollectionR return } - if (version.dynamic) { - if (localStorage.getItem(CACHE_LATEST_VERSION) !== latestVersion) { - await deleteMatching(url => url.startsWith(`${mcmetaUrl}/summary/`) || url.startsWith(`${mcmetaUrl}/data/`)) - localStorage.setItem(CACHE_LATEST_VERSION, latestVersion) - } - version.ref = latestVersion - } + await validateCache(version) await Promise.all([ fetchRegistries(version, collectionTarget), @@ -91,6 +95,7 @@ export async function fetchPreset(versionId: VersionId, registry: string, id: st export async function fetchAllPresets(versionId: VersionId, registry: string) { console.debug(`[fetchAllPresets] ${versionId} ${registry}`) const version = config.versions.find(v => v.id === versionId)! + await validateCache(version) try { const entries = await getData(`${mcmeta(version, 'registries')}/${registry}/data.min.json`) return new Map(await Promise.all( @@ -109,6 +114,7 @@ export type SoundEvents = { } export async function fetchSounds(versionId: VersionId): Promise { const version = config.versions.find(v => v.id === versionId)! + await validateCache(version) try { const url = `${mcmeta(version, 'summary')}/sounds/data.min.json` return await getData(url) @@ -122,6 +128,30 @@ export function getSoundUrl(versionId: VersionId, path: string) { return `${mcmeta(version, 'assets')}/assets/minecraft/sounds/${path}.ogg` } +export type VersionMeta = { + id: string, + name: string, + release_target: string, + type: 'snapshot' | 'release', + stable: boolean, + data_version: number, + protocol_version: number, + data_pack_version: number, + resource_pack_version: number, + build_time: string, + release_time: string, + sha1: string, +} +export async function fetchVersions(): Promise { + const version = config.versions[config.versions.length - 1] + await validateCache(version) + try { + return getData(`${mcmeta(version, 'summary')}/versions/data.min.json`) + } catch (e) { + throw new Error(`Error occured while fetching versions: ${message(e)}`) + } +} + async function getData(url: string, fn: (v: any) => T = (v: any) => v): Promise { try { const cache = await caches.open(CACHE_NAME) diff --git a/src/locales/en.json b/src/locales/en.json index 2d2d447d..4d8277a9 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -6,6 +6,7 @@ "assets": "Assets", "block_definition": "Blockstate", "changelog.search": "Search changes", + "changelog.no_results": "No changes", "collapse": "Collapse", "collapse_all": "Hold %0% to collapse all", "configure_layers": "Configure layers", @@ -51,6 +52,7 @@ "layer.factor": "Factor", "layer.jaggedness": "Jaggedness", "highlighting": "Highlighting", + "loading": "Loading...", "loot_table": "Loot Table", "model": "Model", "more": "More", @@ -78,6 +80,7 @@ "title.home": "Data Pack Generators", "title.project": "%0% Project", "title.sounds": "Sound Explorer", + "title.versions": "Versions Explorer", "presets": "Presets", "preview": "Visualize", "preview.auto_scroll": "Auto scroll", @@ -117,6 +120,19 @@ "switch_version": "Switch version", "terrain_settings": "Terrain settings", "undo": "Undo", + "versions.search": "Search versions", + "versions.no_results": "No results", + "versions.all": "All versions", + "versions.previous": "Previous", + "versions.next": "Next", + "versions.released": "Released", + "versions.release_target": "Release target", + "versions.data_version": "Data version", + "versions.protocol_version": "Protocol version", + "versions.pack_format": "Pack format", + "versions.data_pack_format": "Data pack format", + "versions.resource_pack_format": "Resource pack format", + "versions.technical_changes": "Technical changes", "world": "World Settings", "worldgen": "Worldgen", "worldgen/biome": "Biome", diff --git a/src/styles/global.css b/src/styles/global.css index 620a51ed..b1b00730 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -273,7 +273,8 @@ main > .controls { .sounds-controls > *:not(:last-child), .preview-controls > *:not(:last-child), -.generator-controls > *:not(:last-child) { +.generator-controls > *:not(:last-child), +.versions-controls > *:not(:last-child) { margin-right: 8px; } @@ -458,6 +459,21 @@ main.has-preview { margin-right: 5px; } +.btn-link { + text-decoration: none; + display: inline-flex; +} + +.btn-link svg { + margin-left: 4px; + margin-right: 4px; +} + +.btn-link:not([href]) { + cursor: default; + background-color: var(--background-2) !important; +} + .btn-menu:not(.no-relative) { position: relative; } @@ -749,7 +765,7 @@ main.has-preview { color: var(--text-1) } -.home, .category, .project { +.home, .category, .project, .versions { padding: 16px; max-width: 960px; margin: 0 auto; @@ -1230,6 +1246,10 @@ hr { text-decoration: underline; } +.changelog-content { + word-wrap: break-word; +} + .changelog-content ul { padding-left: 24px; } @@ -1241,14 +1261,9 @@ hr { color: var(--text-1); } -.changelog-controls { - display: flex; - flex-direction: column; - padding: 0 16px; -} - .changelog-query { display: flex; + margin-bottom: 8px; } .changelog-query > *:not(:first-child) { @@ -1257,13 +1272,121 @@ hr { .changelog-search { flex-basis: 100%; - padding: 8px; background-color: var(--background-2); - border-radius: 6px; } -.changelog-controls .changelog-tags { - margin: 8px 0 0; +.changelog-tags { + margin-bottom: 8px; +} + +.versions-controls { + display: flex; + padding-bottom: 16px; +} + +.version-search { + min-width: 0px; + background-color: var(--background-2); +} + +.checkbox { + display: flex; + align-items: center; + padding: 7px 11px; + border-radius: 6px; + height: 32px; + font-size: 1rem; + color: var(--text-2); + background-color: var(--background-2); + box-shadow: 0 1px 7px -2px #000; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; +} + +.checkbox input { + margin-right: 8px; +} + +.versions-controls .checkbox { + white-space: nowrap; +} + +.version-list { + display: flex; + flex-direction: column; +} + +.version-entry { + display: grid; + grid-template-columns: 0.8fr 1.2fr 1fr 0.8fr; + gap: 8px; + background: var(--background-2); + border-radius: 4px; + margin-bottom: 8px; + padding: 8px; + text-decoration: none; +} + +.version-entry:hover { + background: var(--background-3); +} + +.version-entry > .version-metadata { + font-size: 1rem; + align-self: center; +} + +.version-entry > .version-id { + color: var(--text-1); + font-size: 1.1rem; +} + +.version-navigation { + display: flex; +} + +.version-navigation > *:not(:last-child) { + margin-right: 8px; +} + +.version-detail { + color: var(--text-3); +} + +.version-detail h2, +.version-detail h3, +.version-detail h4 { + color: var(--text-2); + margin-top: 24px; + margin-bottom: 8px; +} + +.version-info { + background: var(--background-2); + border-radius: 6px; + padding: 7px 11px; + box-shadow: 0 1px 5px -2px #000; +} + +.version-metadata { + color: var(--text-3); + font-size: 1.2rem; +} + +.version-metadata-value { + color: var(--text-1); +} + +.version-metadata-link { + fill: var(--text-2); + vertical-align: middle; + margin-left: 8px; +} + +.version-metadata-link:hover { + fill: var(--accent-primary); } .ace_editor, @@ -1348,6 +1471,10 @@ hr { .sound-config .volume { grid-area: volume; } .sound-config .copy { grid-area: copy; } .sound-config .remove { grid-area: remove; } + + .version-entry { + grid-template-columns: 1fr 1fr; + } } @keyframes spinning { @@ -1428,4 +1555,8 @@ hr { .generator-picker { justify-content: center; } + + .version-metadata-hide { + display: none; + } }