Add Technical Changelog page

This commit is contained in:
Misode
2021-10-31 18:22:10 +01:00
parent bb1223df60
commit 765f96372f
11 changed files with 268 additions and 3 deletions

30
package-lock.json generated
View File

@@ -18,6 +18,7 @@
"deepslate": "^0.9.0-beta.6",
"deepslate-rs": "^0.1.6",
"howler": "^2.2.3",
"marked": "^3.0.8",
"rfdc": "^1.3.0"
},
"devDependencies": {
@@ -25,6 +26,7 @@
"@rollup/plugin-html": "^0.2.3",
"@types/google.analytics": "0.0.40",
"@types/howler": "^2.2.4",
"@types/marked": "^3.0.2",
"@types/seedrandom": "^2.4.28",
"@typescript-eslint/eslint-plugin": "^4.25.0",
"@typescript-eslint/parser": "^4.25.0",
@@ -514,6 +516,12 @@
"integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==",
"dev": true
},
"node_modules/@types/marked": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@types/marked/-/marked-3.0.2.tgz",
"integrity": "sha512-mGYI9qFs+i5eYaytWKBbtEMbIkrXGKuhsDpDcj70ogKS2gk1NmgEy9Z3VEKz922Lfms6eITXXqv5idlX7C/irw==",
"dev": true
},
"node_modules/@types/minimatch": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz",
@@ -1920,6 +1928,17 @@
"node": ">=10"
}
},
"node_modules/marked": {
"version": "3.0.8",
"resolved": "https://registry.npmjs.org/marked/-/marked-3.0.8.tgz",
"integrity": "sha512-0gVrAjo5m0VZSJb4rpL59K1unJAMb/hm8HRXqasD8VeC8m91ytDPMritgFSlKonfdt+rRYYpP/JfLxgIX8yoSw==",
"bin": {
"marked": "bin/marked"
},
"engines": {
"node": ">= 12"
}
},
"node_modules/md5": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz",
@@ -3128,6 +3147,12 @@
"integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==",
"dev": true
},
"@types/marked": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@types/marked/-/marked-3.0.2.tgz",
"integrity": "sha512-mGYI9qFs+i5eYaytWKBbtEMbIkrXGKuhsDpDcj70ogKS2gk1NmgEy9Z3VEKz922Lfms6eITXXqv5idlX7C/irw==",
"dev": true
},
"@types/minimatch": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz",
@@ -4170,6 +4195,11 @@
"yallist": "^4.0.0"
}
},
"marked": {
"version": "3.0.8",
"resolved": "https://registry.npmjs.org/marked/-/marked-3.0.8.tgz",
"integrity": "sha512-0gVrAjo5m0VZSJb4rpL59K1unJAMb/hm8HRXqasD8VeC8m91ytDPMritgFSlKonfdt+rRYYpP/JfLxgIX8yoSw=="
},
"md5": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz",

View File

@@ -24,6 +24,7 @@
"deepslate": "^0.9.0-beta.6",
"deepslate-rs": "^0.1.6",
"howler": "^2.2.3",
"marked": "^3.0.8",
"rfdc": "^1.3.0"
},
"devDependencies": {
@@ -31,6 +32,7 @@
"@rollup/plugin-html": "^0.2.3",
"@types/google.analytics": "0.0.40",
"@types/howler": "^2.2.4",
"@types/marked": "^3.0.2",
"@types/seedrandom": "^2.4.28",
"@typescript-eslint/eslint-plugin": "^4.25.0",
"@typescript-eslint/parser": "^4.25.0",

View File

@@ -43,7 +43,6 @@ export namespace Analytics {
export function setGenerator(generator: string) {
dimension(DIM_GENERATOR, generator)
console.log(generator)
}
export function generatorEvent(action: string, label?: string) {

View File

@@ -8,7 +8,7 @@ import '../styles/nodes.css'
import { Analytics } from './Analytics'
import { Header } from './components'
import { loadLocale, locale, Locales } from './Locales'
import { Generator, Home, Sounds, Worldgen } from './pages'
import { Changelog, Generator, Home, Sounds, Worldgen } from './pages'
import type { VersionId } from './Schemas'
import { Store } from './Store'
import { cleanUrl } from './Utils'
@@ -72,6 +72,7 @@ function Main() {
<Home path="/" {...{lang, changeTitle}} />
<Worldgen path="/worldgen" {...{lang, changeTitle}} />
<Sounds path="/sounds" {...{lang, version, changeTitle, changeVersion}} />
<Changelog path="/changelog" {...{lang, changeTitle}} />
<Generator default {...{lang, version, changeTitle, changeVersion}} />
</Router>
</>

View File

@@ -0,0 +1,87 @@
import marked from 'marked'
import { useEffect, useMemo, useState } from 'preact/hooks'
import { Ad, ErrorPanel, TextInput } from '../components'
import { locale } from '../Locales'
import type { VersionId } from '../Schemas'
import type { ChangelogEntry } from '../services/Changelogs'
import { getChangelogs } from '../services/Changelogs'
import { hashString } from '../Utils'
type ChangelogProps = {
path?: string,
lang: string,
changeTitle: (title: string, versions?: VersionId[]) => unknown,
}
export function Changelog({ lang, changeTitle }: ChangelogProps) {
const loc = locale.bind(null, lang)
const [error, setError] = useState<string | null>(null)
changeTitle(loc('title.changelog'))
const [changelogs, setChangelogs] = useState<ChangelogEntry[]>([])
useEffect(() => {
getChangelogs()
.then(changelogs => setChangelogs(changelogs))
.catch(e => { console.error(e); setError(e) })
}, [])
const [search, setSearch] = useState('')
const [tags, setTags] = useState<string[]>([])
const addTag = (tag: string) => {
if (!tags.includes(tag)) {
setTags([...tags, 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 => content.includes(q))
})
}, [changelogs, search, tags])
return <main>
<Ad type="text" id="changelog" />
{error && <ErrorPanel error={error} onDismiss={() => setError(null)} />}
<div class="changelog-controls">
<TextInput class="btn btn-input changelog-search" list="sound-list" placeholder={loc('changelog.search')}
value={search} onChange={setSearch} />
{tags.length > 0 && <div class="changelog-tags">
{tags.map(tag => <Tag label={tag} onClick={() => setTags(tags.filter(t => t !== tag))} />)}
</div>}
</div>
<div class="changelog">
{filteredChangelogs.map(change =>
<Change change={change} activeTags={tags} addTag={addTag} />)}
</div>
</main>
}
type ChangeProps = {
change: ChangelogEntry,
activeTags: string[],
addTag: (tag: string) => unknown,
}
function Change({ change, activeTags, addTag }: ChangeProps) {
return <div class="changelog-entry">
<div class="changelog-tags">
{change.tags.map(tag => <Tag label={tag} onClick={() => addTag(tag)} active={activeTags.includes(tag)} />)}
<a class="changelog-version" href={`https://www.minecraft.net/en-us/article/minecraft-snapshot-${change.version}`}>{change.version}</a>
</div>
<div dangerouslySetInnerHTML={{ __html: marked(change.content) }} />
</div>
}
type TagProps = {
label: string,
active?: boolean,
onClick?: () => unknown,
}
function Tag({ label, active, onClick }: TagProps) {
const color = hashString(label) % 360
return <div class={`changelog-tag${active ? ' active' : ''}${onClick ? ' clickable' : ''}`} style={`--tint: ${color}`} onClick={onClick}>{label}</div>
}

View File

@@ -27,6 +27,7 @@ export function Home({ lang, changeTitle }: HomeProps) {
<ToolCard title="Data Pack Upgrader" link="https://misode.github.io/upgrader/">
<p>Convert your 1.16 data packs to 1.17</p>
</ToolCard>
<ToolCard title="Technical Changelog" link="/changelog/" />
</div>
</main>
}

View File

@@ -1,3 +1,4 @@
export * from './Changelog'
export * from './Generator'
export * from './Home'
export * from './Sounds'

View File

@@ -0,0 +1,47 @@
const repo = 'https://raw.githubusercontent.com/misode/technical-changes/main'
export type ChangelogEntry = {
group: string,
version: string,
tags: string[],
content: string,
}
let Changelogs: ChangelogEntry[] | Promise<ChangelogEntry[]> | 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 => fetchGroup(group))
)).flat()
}
return Changelogs
}
async function fetchGroup(group: string) {
const index = await (await fetch(`${repo}/${group}/index.json`)).json() as string[]
return (await Promise.all(
index.map(version => fetchChangelog(group, version))
)).flat()
}
async function fetchChangelog(group: string, version: string) {
const text = await (await fetch(`${repo}/${group}/${version}.md`)).text()
return parseChangelog(text).map(change => ({
version,
group,
...change,
}))
}
function parseChangelog(text: string) {
return text.split('\n\n')
.map(entry => {
const i = entry.indexOf('|')
return {
tags: entry.substring(0, i).trim().split(' '),
content: entry.slice(i + 1).trim(),
}
})
}

View File

@@ -3,6 +3,7 @@
"add_bottom": "Add to bottom",
"add_top": "Add to top",
"advancement": "Advancement",
"changelog.search": "Search changes",
"collapse": "Collapse",
"collapse_all": "Hold %0% to collapse all",
"configure_layers": "Configure layers",
@@ -63,6 +64,7 @@
"theme.dark": "Dark",
"theme.light": "Light",
"theme.system": "System",
"title.changelog": "Technical Changelog",
"title.generator": "%0% Generator",
"title.generator_category": "%0% Generators",
"title.home": "Data Pack Generators",

View File

@@ -22,6 +22,8 @@
--errors-background: #62190f;
--errors-text: #ffffffcc;
--invalid-text: #fd7951;
--text-saturation: 60%;
--text-lightness: 45%;
}
:root[data-theme=light] {
@@ -48,6 +50,8 @@
--errors-background: #f66653;
--errors-text: #000000cc;
--invalid-text: #a32600;
--text-saturation: 100%;
--text-lightness: 30%;
}
@media (prefers-color-scheme: light) {
@@ -75,6 +79,8 @@
--errors-background: #f66653;
--errors-text: #000000cc;
--invalid-text: #a32600;
--text-saturation: 100%;
--text-lightness: 35%;
}
}
@@ -379,11 +385,18 @@ main.has-preview {
display: flex;
flex-direction: column;
position: absolute;
right: 0;
top: 100%;
margin-top: 8px;
}
.btn-menu.menu-sw .btn-group {
right: 0;
}
.btn-menu.menu-se .btn-group {
left: 0;
}
.btn-group {
border-radius: 6px;
box-shadow: 0 0 7px -2px #000;
@@ -975,6 +988,83 @@ hr {
color: var(--invalid-text);
}
.changelog {
display: flex;
flex-direction: column;
padding: 16px;
}
.changelog-entry {
background: var(--background-2);
border-radius: 4px;
margin-bottom: 8px;
padding: 8px;
color: var(--text-2);
}
.changelog-tags {
display: flex;
margin-bottom: 8px;
}
.changelog-tag {
--color: hsl(var(--tint, 0), var(--text-saturation), var(--text-lightness));
margin-right: 8px;
border: 1.5px solid var(--color);
height: 24px;
border-radius: 12px;
padding: 0 8px;
color: var(--color);
user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
}
.changelog-tag.clickable {
cursor: pointer;
}
.changelog-tag.active {
background-color: var(--color);
color: var(--background-2);
}
.changelog-version {
margin-left: auto;
font-size: 15px;
color: var(--text-3);
text-decoration: none;
}
.changelog-version:hover {
text-decoration: underline;
}
.changelog-entry code {
background-color: var(--background-5);
padding: 1px 4px;
border-radius: 4px;
color: var(--text-1);
}
.changelog-controls {
display: flex;
flex-direction: column;
padding: 0 16px;
}
.changelog-search {
flex-basis: 100%;
padding: 8px;
background-color: var(--background-2);
border-radius: 6px;
}
.changelog-controls .changelog-tags {
margin: 8px 0 0;
}
@media screen and (max-width: 720px) {
.sound-search-group {
margin-bottom: 8px;

View File

@@ -21,6 +21,11 @@ export default defineConfig({
title: getTitle({ id: 'title.sounds', page: true }),
template: template,
}),
html({
fileName: `changelog/index.html`,
title: getTitle({ id: 'title.changelog', page: true }),
template: template,
}),
...config.generators.map(m => html({
fileName: `${m.url}/index.html`,
title: getTitle(m),