mirror of
https://github.com/misode/misode.github.io.git
synced 2026-04-26 16:35:39 +00:00
Improve homepage (#245)
* Improve how generators are listed on home * Add some icons for generators * Remove debug * Refactor cachedFetch and use generated changelogs * Add limit to how many changes are shown by default * Add more generator icons * Refactor cards * Fix generator icons for light theme * Add more worldgen icons * Add remaining generator icons * Refactor navigation and badges style * Group on homepage for guides and tools * Fix header button style * Add versions and technical changelog to homepage * Make it clear that not all changes could be documented
This commit is contained in:
@@ -1,70 +1,5 @@
|
||||
import { isObject } from '../Utils.js'
|
||||
|
||||
const repo = 'https://raw.githubusercontent.com/misode/technical-changes/main'
|
||||
|
||||
export type Change = {
|
||||
group: string,
|
||||
version: string,
|
||||
order: number,
|
||||
tags: string[],
|
||||
content: string,
|
||||
}
|
||||
|
||||
let Changelogs: Change[] | Promise<Change[]> | 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>(change => ({
|
||||
...change,
|
||||
tags: [change.group, ...change.tags],
|
||||
}))
|
||||
}
|
||||
return Changelogs
|
||||
}
|
||||
|
||||
async function fetchGroup(group: string, groupIndex: number) {
|
||||
const index = await (await fetch(`${repo}/${group}/index.json`)).json() as string[]
|
||||
return (await Promise.all(
|
||||
index.map((version, i) => fetchChangelog(group, parseVersion(version), groupIndex, i))
|
||||
)).flat()
|
||||
}
|
||||
|
||||
async function fetchChangelog(group: string, version: string, groupIndex: number, versionIndex: number) {
|
||||
const text = await (await fetch(`${repo}/${group}/${version}.md`)).text()
|
||||
return parseChangelog(text).map(change => ({
|
||||
version,
|
||||
group,
|
||||
order: groupIndex * 1000 + versionIndex,
|
||||
...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()
|
||||
.replaceAll('->', '→')
|
||||
.replaceAll('\n...\n', '\n\n'),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function parseVersion(version: unknown): string {
|
||||
if (typeof version === 'string') {
|
||||
return version
|
||||
} else if (isObject(version)) {
|
||||
return version.id
|
||||
}
|
||||
return 'unknown'
|
||||
}
|
||||
|
||||
const ARTICLE_PREFIX = 'https://www.minecraft.net/article/'
|
||||
|
||||
const ARTICLE_OVERRIDES = new Map(Object.entries({
|
||||
'1.16-pre2': 'minecraft-1-16-pre-release-1',
|
||||
'1.16-pre4': 'minecraft-1-16-pre-release-3',
|
||||
@@ -16,6 +16,7 @@ type Version = {
|
||||
declare var __LATEST_VERSION__: string
|
||||
const latestVersion = __LATEST_VERSION__ ?? ''
|
||||
const mcmetaUrl = 'https://raw.githubusercontent.com/misode/mcmeta'
|
||||
const changesUrl = 'https://raw.githubusercontent.com/misode/technical-changes'
|
||||
|
||||
type McmetaTypes = 'summary' | 'data' | 'assets' | 'registries'
|
||||
|
||||
@@ -52,7 +53,7 @@ export async function fetchData(versionId: string, collectionTarget: CollectionR
|
||||
async function fetchRegistries(version: Version, target: CollectionRegistry) {
|
||||
console.debug(`[fetchRegistries] ${version.id}`)
|
||||
try {
|
||||
const data = await getData(`${mcmeta(version, 'summary')}/registries/data.min.json`)
|
||||
const data = await cachedFetch<any>(`${mcmeta(version, 'summary')}/registries/data.min.json`)
|
||||
for (const id in data) {
|
||||
target.register(id, data[id].map((e: string) => 'minecraft:' + e))
|
||||
}
|
||||
@@ -64,7 +65,7 @@ async function fetchRegistries(version: Version, target: CollectionRegistry) {
|
||||
async function fetchBlockStateMap(version: Version, target: BlockStateRegistry) {
|
||||
console.debug(`[fetchBlockStateMap] ${version.id}`)
|
||||
try {
|
||||
const data = await getData(`${mcmeta(version, 'summary')}/blocks/data.min.json`)
|
||||
const data = await cachedFetch<any>(`${mcmeta(version, 'summary')}/blocks/data.min.json`)
|
||||
for (const id in data) {
|
||||
target['minecraft:' + id] = {
|
||||
properties: data[id][0],
|
||||
@@ -99,10 +100,10 @@ export async function fetchAllPresets(versionId: VersionId, registry: string) {
|
||||
const version = config.versions.find(v => v.id === versionId)!
|
||||
await validateCache(version)
|
||||
try {
|
||||
const entries = await getData(`${mcmeta(version, 'registries')}/${registry}/data.min.json`)
|
||||
const entries = await cachedFetch<any>(`${mcmeta(version, 'registries')}/${registry}/data.min.json`)
|
||||
return new Map<string, unknown>(await Promise.all(
|
||||
entries.map(async (e: string) =>
|
||||
[e, await getData(`${mcmeta(version, 'data')}/data/minecraft/${registry}/${e}.json`)])
|
||||
[e, await cachedFetch(`${mcmeta(version, 'data')}/data/minecraft/${registry}/${e}.json`)])
|
||||
))
|
||||
} catch (e) {
|
||||
throw new Error(`Error occurred while fetching all ${registry} presets: ${message(e)}`)
|
||||
@@ -119,7 +120,7 @@ export async function fetchSounds(versionId: VersionId): Promise<SoundEvents> {
|
||||
await validateCache(version)
|
||||
try {
|
||||
const url = `${mcmeta(version, 'summary')}/sounds/data.min.json`
|
||||
return await getData(url)
|
||||
return await cachedFetch(url)
|
||||
} catch (e) {
|
||||
throw new Error(`Error occurred while fetching sounds for ${version}: ${message(e)}`)
|
||||
}
|
||||
@@ -148,7 +149,7 @@ export async function fetchVersions(): Promise<VersionMeta[]> {
|
||||
const version = config.versions[config.versions.length - 1]
|
||||
await validateCache(version)
|
||||
try {
|
||||
return getData(`${mcmeta(version, 'summary')}/versions/data.min.json`)
|
||||
return cachedFetch(`${mcmeta(version, 'summary')}/versions/data.min.json`, { refresh: true })
|
||||
} catch (e) {
|
||||
throw new Error(`Error occured while fetching versions: ${message(e)}`)
|
||||
}
|
||||
@@ -159,32 +160,84 @@ export function getTextureUrl(versionId: VersionId, path: string): string {
|
||||
return `${mcmeta(version, 'assets')}/assets/minecraft/textures/${path}.png`
|
||||
}
|
||||
|
||||
async function getData<T = any>(url: string, fn: (v: any) => T = (v: any) => v): Promise<T> {
|
||||
export interface Change {
|
||||
group: string,
|
||||
version: string,
|
||||
order: number,
|
||||
tags: string[],
|
||||
content: string,
|
||||
}
|
||||
|
||||
export async function fetchChangelogs(): Promise<Change[]> {
|
||||
try {
|
||||
const [changes, versions] = await Promise.all([
|
||||
cachedFetch<Omit<Change, 'order'>[]>(`${changesUrl}/generated/changes.json`, { refresh: true }),
|
||||
fetchVersions(),
|
||||
])
|
||||
const versionMap = new Map(versions.map((v, i) => [v.id, versions.length - i]))
|
||||
return changes.map(c => ({ ...c, order: versionMap.get(c.version) ?? 0 }))
|
||||
} catch (e) {
|
||||
throw new Error(`Error occured while fetching technical changes: ${message(e)}`)
|
||||
}
|
||||
}
|
||||
|
||||
interface FetchOptions<D> {
|
||||
decode?: (r: Response) => Promise<D>
|
||||
refresh?: boolean
|
||||
}
|
||||
|
||||
const REFRESHED = new Set<string>()
|
||||
|
||||
async function cachedFetch<D = unknown>(url: string, { decode = (r => r.json()), refresh }: FetchOptions<D> = {}): Promise<D> {
|
||||
try {
|
||||
const cache = await caches.open(CACHE_NAME)
|
||||
console.debug(`[getData] Opened cache ${CACHE_NAME} ${url}`)
|
||||
console.debug(`[cachedFetch] Opened cache ${CACHE_NAME} ${url}`)
|
||||
const cacheResponse = await cache.match(url)
|
||||
|
||||
if (cacheResponse && cacheResponse.ok) {
|
||||
console.debug(`[getData] Retrieving cached data ${url}`)
|
||||
return await cacheResponse.json()
|
||||
}
|
||||
|
||||
console.debug(`[getData] fetching data ${url}`)
|
||||
const fetchResponse = await fetch(url)
|
||||
const responseData = fn(await fetchResponse.json())
|
||||
await cache.put(url, new Response(JSON.stringify(responseData)))
|
||||
return responseData
|
||||
} catch (e) {
|
||||
console.warn(`[getData] Failed to open cache ${CACHE_NAME}: ${message(e)}`)
|
||||
|
||||
console.debug(`[getData] fetching data ${url}`)
|
||||
if (refresh) {
|
||||
if (REFRESHED.has(url)) {
|
||||
refresh = false
|
||||
} else {
|
||||
REFRESHED.add(url)
|
||||
}
|
||||
}
|
||||
|
||||
if (refresh) {
|
||||
try {
|
||||
return await fetchAndCache(cache, url, decode)
|
||||
} catch (e) {
|
||||
if (cacheResponse && cacheResponse.ok) {
|
||||
console.debug(`[cachedFetch] Cannot refresh, using cache ${url}`)
|
||||
return await decode(cacheResponse)
|
||||
}
|
||||
throw new Error('Failed to fetch')
|
||||
}
|
||||
} else {
|
||||
if (cacheResponse && cacheResponse.ok) {
|
||||
console.debug(`[cachedFetch] Retrieving cached data ${url}`)
|
||||
return await decode(cacheResponse)
|
||||
}
|
||||
return await fetchAndCache(cache, url, decode)
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.warn(`[cachedFetch] Failed to open cache ${CACHE_NAME}: ${e.message}`)
|
||||
|
||||
console.debug(`[cachedFetch] Fetching data ${url}`)
|
||||
const fetchResponse = await fetch(url)
|
||||
const responseData = fn(await fetchResponse.json())
|
||||
return responseData
|
||||
const fetchData = await decode(fetchResponse)
|
||||
return fetchData
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchAndCache<D>(cache: Cache, url: string, decode: (r: Response) => Promise<D>) {
|
||||
console.debug(`[cachedFetch] Fetching data ${url}`)
|
||||
const fetchResponse = await fetch(url)
|
||||
const fetchClone = fetchResponse.clone()
|
||||
const fetchData = await decode(fetchResponse)
|
||||
await cache.put(url, fetchClone)
|
||||
return fetchData
|
||||
}
|
||||
|
||||
async function deleteMatching(matches: (url: string) => boolean) {
|
||||
try {
|
||||
const cache = await caches.open(CACHE_NAME)
|
||||
|
||||
20
src/app/services/Guides.ts
Normal file
20
src/app/services/Guides.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
export interface Guide {
|
||||
id: string,
|
||||
title: string,
|
||||
versions?: string[],
|
||||
tags?: string[],
|
||||
}
|
||||
|
||||
declare var __GUIDES__: Guide[]
|
||||
|
||||
export function getGuides() {
|
||||
return __GUIDES__
|
||||
}
|
||||
|
||||
export function getGuide(id: string): Guide {
|
||||
const guide = getGuides().find(g => g.id === id)
|
||||
if (guide === undefined) {
|
||||
return { id, title: 'Unknown Guide' }
|
||||
}
|
||||
return guide
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
export * from './Changelogs.js'
|
||||
export * from './Article.js'
|
||||
export * from './DataFetcher.js'
|
||||
export * from './Schemas.js'
|
||||
export * from './Sharing.js'
|
||||
|
||||
Reference in New Issue
Block a user