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
+}
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)} />}
+
+ {selected ? <>
+
+
+ > :
`/versions/?id=${id}`} />}
+
+
+}
+
+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;
+ }
}