mirror of
https://github.com/jackyzha0/quartz.git
synced 2026-03-21 21:45:42 -05:00
redesign almost done
This commit is contained in:
parent
d4317685b3
commit
9cd90ae22a
1
Logo_tufs-cropped.svg
Normal file
1
Logo_tufs-cropped.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 17 KiB |
@ -5,7 +5,17 @@ import * as Component from "./quartz/components"
|
|||||||
export const sharedPageComponents: SharedLayout = {
|
export const sharedPageComponents: SharedLayout = {
|
||||||
head: Component.Head(),
|
head: Component.Head(),
|
||||||
header: [],
|
header: [],
|
||||||
afterBody: [],
|
afterBody: [
|
||||||
|
Component.ConditionalRender({
|
||||||
|
component: Component.Graph({
|
||||||
|
globalOnly: true,
|
||||||
|
globalGraph: {
|
||||||
|
removeSlugs: ["index"],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
condition: (props) => props.fileData.slug === "index",
|
||||||
|
}),
|
||||||
|
],
|
||||||
footer: Component.Footer({
|
footer: Component.Footer({
|
||||||
links: {},
|
links: {},
|
||||||
}),
|
}),
|
||||||
|
|||||||
@ -15,6 +15,7 @@ export interface D3Config {
|
|||||||
linkDistance: number
|
linkDistance: number
|
||||||
fontSize: number
|
fontSize: number
|
||||||
opacityScale: number
|
opacityScale: number
|
||||||
|
removeSlugs: string[]
|
||||||
removeTags: string[]
|
removeTags: string[]
|
||||||
showTags: boolean
|
showTags: boolean
|
||||||
focusOnHover?: boolean
|
focusOnHover?: boolean
|
||||||
@ -22,11 +23,13 @@ export interface D3Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface GraphOptions {
|
interface GraphOptions {
|
||||||
|
globalOnly: boolean
|
||||||
localGraph: Partial<D3Config> | undefined
|
localGraph: Partial<D3Config> | undefined
|
||||||
globalGraph: Partial<D3Config> | undefined
|
globalGraph: Partial<D3Config> | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultOptions: GraphOptions = {
|
const defaultOptions: GraphOptions = {
|
||||||
|
globalOnly: false,
|
||||||
localGraph: {
|
localGraph: {
|
||||||
drag: true,
|
drag: true,
|
||||||
zoom: true,
|
zoom: true,
|
||||||
@ -37,6 +40,7 @@ const defaultOptions: GraphOptions = {
|
|||||||
linkDistance: 30,
|
linkDistance: 30,
|
||||||
fontSize: 0.6,
|
fontSize: 0.6,
|
||||||
opacityScale: 1,
|
opacityScale: 1,
|
||||||
|
removeSlugs: [],
|
||||||
showTags: true,
|
showTags: true,
|
||||||
removeTags: [],
|
removeTags: [],
|
||||||
focusOnHover: false,
|
focusOnHover: false,
|
||||||
@ -52,6 +56,7 @@ const defaultOptions: GraphOptions = {
|
|||||||
linkDistance: 30,
|
linkDistance: 30,
|
||||||
fontSize: 0.6,
|
fontSize: 0.6,
|
||||||
opacityScale: 1,
|
opacityScale: 1,
|
||||||
|
removeSlugs: [],
|
||||||
showTags: true,
|
showTags: true,
|
||||||
removeTags: [],
|
removeTags: [],
|
||||||
focusOnHover: true,
|
focusOnHover: true,
|
||||||
@ -59,15 +64,46 @@ const defaultOptions: GraphOptions = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const GraphIcon = () => (
|
||||||
|
<svg
|
||||||
|
class="section-icon"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<circle cx="5" cy="6" r="2" />
|
||||||
|
<circle cx="19" cy="5" r="2" />
|
||||||
|
<circle cx="18" cy="19" r="2" />
|
||||||
|
<circle cx="6" cy="18" r="2" />
|
||||||
|
<path d="M7 7.5 11 10" />
|
||||||
|
<path d="M17 6.5 13 10" />
|
||||||
|
<path d="M7.5 16.5 11 13" />
|
||||||
|
<path d="M16.5 17 13 13" />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
|
||||||
export default ((opts?: Partial<GraphOptions>) => {
|
export default ((opts?: Partial<GraphOptions>) => {
|
||||||
const Graph: QuartzComponent = ({ displayClass, cfg }: QuartzComponentProps) => {
|
const Graph: QuartzComponent = ({ displayClass, cfg }: QuartzComponentProps) => {
|
||||||
const localGraph = { ...defaultOptions.localGraph, ...opts?.localGraph }
|
const localGraph = { ...defaultOptions.localGraph, ...opts?.localGraph }
|
||||||
const globalGraph = { ...defaultOptions.globalGraph, ...opts?.globalGraph }
|
const globalGraph = { ...defaultOptions.globalGraph, ...opts?.globalGraph }
|
||||||
|
const graphConfig = opts?.globalOnly ? globalGraph : localGraph
|
||||||
|
const title = opts?.globalOnly ? "Graph" : i18n(cfg.locale).components.graph.title
|
||||||
return (
|
return (
|
||||||
<div class={classNames(displayClass, "graph")}>
|
<div class={classNames(displayClass, "graph", ...(opts?.globalOnly ? ["global-only"] : []))}>
|
||||||
<h3>{i18n(cfg.locale).components.graph.title}</h3>
|
{opts?.globalOnly ? (
|
||||||
|
<h2 class="graph-section-heading">
|
||||||
|
<GraphIcon />
|
||||||
|
{title}
|
||||||
|
</h2>
|
||||||
|
) : (
|
||||||
|
<h3>{title}</h3>
|
||||||
|
)}
|
||||||
<div class="graph-outer">
|
<div class="graph-outer">
|
||||||
<div class="graph-container" data-cfg={JSON.stringify(localGraph)}></div>
|
<div class="graph-container" data-cfg={JSON.stringify(graphConfig)}></div>
|
||||||
|
{!opts?.globalOnly && (
|
||||||
<button class="global-graph-icon" aria-label="Global Graph">
|
<button class="global-graph-icon" aria-label="Global Graph">
|
||||||
<svg
|
<svg
|
||||||
version="1.1"
|
version="1.1"
|
||||||
@ -94,10 +130,13 @@ export default ((opts?: Partial<GraphOptions>) => {
|
|||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
{!opts?.globalOnly && (
|
||||||
<div class="global-graph-outer">
|
<div class="global-graph-outer">
|
||||||
<div class="global-graph-container" data-cfg={JSON.stringify(globalGraph)}></div>
|
<div class="global-graph-container" data-cfg={JSON.stringify(globalGraph)}></div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,7 +31,7 @@ const HomeHero: QuartzComponent = () => {
|
|||||||
<p class="home-bio">
|
<p class="home-bio">
|
||||||
Software engineer and linguist. Interning at MIXI, Inc building iOS features for
|
Software engineer and linguist. Interning at MIXI, Inc building iOS features for
|
||||||
FamilyAlbum. MEXT Scholar at Tokyo University of Foreign Studies. 42 Network alumnus.
|
FamilyAlbum. MEXT Scholar at Tokyo University of Foreign Studies. 42 Network alumnus.
|
||||||
Native in English, Japanese, and Portuguese — also speak Spanish and Mandarin.
|
Native in Japanese and Portuguese, bilingual in English, and also speak Spanish and Mandarin.
|
||||||
</p>
|
</p>
|
||||||
<div class="home-links">
|
<div class="home-links">
|
||||||
<a href="mailto:riceset@icloud.com" class="home-link">
|
<a href="mailto:riceset@icloud.com" class="home-link">
|
||||||
|
|||||||
@ -84,7 +84,7 @@ const education: EducationItem[] = [
|
|||||||
degree: "B.A. Language and Area Studies",
|
degree: "B.A. Language and Area Studies",
|
||||||
institution: "Tokyo University of Foreign Studies",
|
institution: "Tokyo University of Foreign Studies",
|
||||||
institutionUrl: "https://www.tufs.ac.jp/english/",
|
institutionUrl: "https://www.tufs.ac.jp/english/",
|
||||||
logo: "/static/logos/tufs.svg",
|
logo: "/static/logos/Logo_tufs-cropped.svg",
|
||||||
period: "2024 – 2028",
|
period: "2024 – 2028",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -98,10 +98,10 @@ const education: EducationItem[] = [
|
|||||||
|
|
||||||
const languages: Language[] = [
|
const languages: Language[] = [
|
||||||
{ flag: "🇧🇷", name: "Portuguese", level: "Native" },
|
{ flag: "🇧🇷", name: "Portuguese", level: "Native" },
|
||||||
{ flag: "🇺🇸", name: "English", level: "Native" },
|
{ flag: "🇺🇸", name: "English", level: "Bilingual · TOEIC 945" },
|
||||||
{ flag: "🇯🇵", name: "Japanese", level: "Native · JLPT N1" },
|
{ flag: "🇯🇵", name: "Japanese", level: "Native · JLPT N1" },
|
||||||
{ flag: "🇪🇸", name: "Spanish", level: "Professional · TOEIC 945" },
|
{ flag: "🇪🇸", name: "Spanish", level: "Professional" },
|
||||||
{ flag: "🇨🇳", name: "Mandarin", level: "Working · HSK 3 · TOCFL 4" },
|
{ flag: "🇨🇳", name: "Mandarin", level: "Working · HSK 3 · TBCL 4" },
|
||||||
]
|
]
|
||||||
|
|
||||||
// ── Component ──────────────────────────────────────────────────────────────
|
// ── Component ──────────────────────────────────────────────────────────────
|
||||||
|
|||||||
@ -68,6 +68,17 @@ type TweenNode = {
|
|||||||
stop: () => void
|
stop: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getGraphDimensions(graph: HTMLElement) {
|
||||||
|
const parent = graph.parentElement
|
||||||
|
const graphRect = graph.getBoundingClientRect()
|
||||||
|
const parentRect = parent?.getBoundingClientRect()
|
||||||
|
|
||||||
|
return {
|
||||||
|
width: Math.max(Math.round(graphRect.width), Math.round(parentRect?.width ?? 0)),
|
||||||
|
height: Math.max(Math.round(graphRect.height), Math.round(parentRect?.height ?? 0), 250),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function renderGraph(graph: HTMLElement, fullSlug: FullSlug) {
|
async function renderGraph(graph: HTMLElement, fullSlug: FullSlug) {
|
||||||
const slug = simplifySlug(fullSlug)
|
const slug = simplifySlug(fullSlug)
|
||||||
const visited = getVisited()
|
const visited = getVisited()
|
||||||
@ -83,17 +94,18 @@ async function renderGraph(graph: HTMLElement, fullSlug: FullSlug) {
|
|||||||
linkDistance,
|
linkDistance,
|
||||||
fontSize,
|
fontSize,
|
||||||
opacityScale,
|
opacityScale,
|
||||||
|
removeSlugs,
|
||||||
removeTags,
|
removeTags,
|
||||||
showTags,
|
showTags,
|
||||||
focusOnHover,
|
focusOnHover,
|
||||||
enableRadial,
|
enableRadial,
|
||||||
} = JSON.parse(graph.dataset["cfg"]!) as D3Config
|
} = JSON.parse(graph.dataset["cfg"]!) as D3Config
|
||||||
|
|
||||||
|
const hiddenSlugs = new Set(removeSlugs.map((slug) => simplifySlug(slug as FullSlug)))
|
||||||
const data: Map<SimpleSlug, ContentDetails> = new Map(
|
const data: Map<SimpleSlug, ContentDetails> = new Map(
|
||||||
Object.entries<ContentDetails>(await fetchData).map(([k, v]) => [
|
Object.entries<ContentDetails>(await fetchData)
|
||||||
simplifySlug(k as FullSlug),
|
.map(([k, v]) => [simplifySlug(k as FullSlug), v] as const)
|
||||||
v,
|
.filter(([slug]) => !hiddenSlugs.has(slug)),
|
||||||
]),
|
|
||||||
)
|
)
|
||||||
const links: SimpleLinkData[] = []
|
const links: SimpleLinkData[] = []
|
||||||
const tags: SimpleSlug[] = []
|
const tags: SimpleSlug[] = []
|
||||||
@ -123,7 +135,7 @@ async function renderGraph(graph: HTMLElement, fullSlug: FullSlug) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const neighbourhood = new Set<SimpleSlug>()
|
const neighbourhood = new Set<SimpleSlug>()
|
||||||
const wl: (SimpleSlug | "__SENTINEL")[] = [slug, "__SENTINEL"]
|
const wl: (SimpleSlug | "__SENTINEL")[] = validLinks.has(slug) ? [slug, "__SENTINEL"] : []
|
||||||
if (depth >= 0) {
|
if (depth >= 0) {
|
||||||
while (depth >= 0 && wl.length > 0) {
|
while (depth >= 0 && wl.length > 0) {
|
||||||
// compute neighbours
|
// compute neighbours
|
||||||
@ -161,8 +173,7 @@ async function renderGraph(graph: HTMLElement, fullSlug: FullSlug) {
|
|||||||
})),
|
})),
|
||||||
}
|
}
|
||||||
|
|
||||||
const width = graph.offsetWidth
|
const { width, height } = getGraphDimensions(graph)
|
||||||
const height = Math.max(graph.offsetHeight, 250)
|
|
||||||
|
|
||||||
// we virtualize the simulation and use pixi to actually render it
|
// we virtualize the simulation and use pixi to actually render it
|
||||||
const simulation: Simulation<NodeData, LinkData> = forceSimulation<NodeData>(graphData.nodes)
|
const simulation: Simulation<NodeData, LinkData> = forceSimulation<NodeData>(graphData.nodes)
|
||||||
@ -556,6 +567,57 @@ async function renderGraph(graph: HTMLElement, fullSlug: FullSlug) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function mountGraph(graph: HTMLElement, fullSlug: FullSlug) {
|
||||||
|
let cleanup = () => {}
|
||||||
|
let isRendering = false
|
||||||
|
let pendingRender = false
|
||||||
|
let lastWidth = 0
|
||||||
|
let lastHeight = 0
|
||||||
|
|
||||||
|
const render = async (force = false) => {
|
||||||
|
if (isRendering) {
|
||||||
|
pendingRender = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { width, height } = getGraphDimensions(graph)
|
||||||
|
if (!force && width === lastWidth && height === lastHeight) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isRendering = true
|
||||||
|
cleanup()
|
||||||
|
|
||||||
|
try {
|
||||||
|
cleanup = await renderGraph(graph, fullSlug)
|
||||||
|
lastWidth = width
|
||||||
|
lastHeight = height
|
||||||
|
} finally {
|
||||||
|
isRendering = false
|
||||||
|
if (pendingRender) {
|
||||||
|
pendingRender = false
|
||||||
|
void render()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await render(true)
|
||||||
|
|
||||||
|
const resizeObserver = new ResizeObserver(() => {
|
||||||
|
void render()
|
||||||
|
})
|
||||||
|
|
||||||
|
resizeObserver.observe(graph)
|
||||||
|
if (graph.parentElement) {
|
||||||
|
resizeObserver.observe(graph.parentElement)
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
resizeObserver.disconnect()
|
||||||
|
cleanup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let localGraphCleanups: (() => void)[] = []
|
let localGraphCleanups: (() => void)[] = []
|
||||||
let globalGraphCleanups: (() => void)[] = []
|
let globalGraphCleanups: (() => void)[] = []
|
||||||
|
|
||||||
@ -581,7 +643,7 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => {
|
|||||||
cleanupLocalGraphs()
|
cleanupLocalGraphs()
|
||||||
const localGraphContainers = document.getElementsByClassName("graph-container")
|
const localGraphContainers = document.getElementsByClassName("graph-container")
|
||||||
for (const container of localGraphContainers) {
|
for (const container of localGraphContainers) {
|
||||||
localGraphCleanups.push(await renderGraph(container as HTMLElement, slug))
|
localGraphCleanups.push(await mountGraph(container as HTMLElement, slug))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -608,7 +670,7 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => {
|
|||||||
const graphContainer = container.querySelector(".global-graph-container") as HTMLElement
|
const graphContainer = container.querySelector(".global-graph-container") as HTMLElement
|
||||||
registerEscapeHandler(container, hideGlobalGraph)
|
registerEscapeHandler(container, hideGlobalGraph)
|
||||||
if (graphContainer) {
|
if (graphContainer) {
|
||||||
globalGraphCleanups.push(await renderGraph(graphContainer, slug))
|
globalGraphCleanups.push(await mountGraph(graphContainer, slug))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,8 +5,8 @@ footer {
|
|||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
|
|
||||||
&.home-footer {
|
&.home-footer {
|
||||||
|
margin-top: 0;
|
||||||
padding-top: 2rem;
|
padding-top: 2rem;
|
||||||
border-top: 1px solid var(--lightgray);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
& ul {
|
& ul {
|
||||||
|
|||||||
@ -6,6 +6,41 @@
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.global-only {
|
||||||
|
margin-top: 2rem;
|
||||||
|
padding-top: 2rem;
|
||||||
|
border-top: 1px solid var(--lightgray);
|
||||||
|
}
|
||||||
|
|
||||||
|
& .graph-section-heading {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.4rem;
|
||||||
|
font-family: var(--headerFont);
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
color: var(--gray);
|
||||||
|
margin: 0 0 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .section-icon {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
stroke: var(--gray);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.global-only > .graph-outer {
|
||||||
|
height: 420px;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
@media all and ($mobile) {
|
||||||
|
height: 320px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
& > .graph-outer {
|
& > .graph-outer {
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
border: 1px solid var(--lightgray);
|
border: 1px solid var(--lightgray);
|
||||||
@ -15,6 +50,17 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
|
& > .graph-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
& canvas {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
& > .global-graph-icon {
|
& > .global-graph-icon {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background: none;
|
background: none;
|
||||||
|
|||||||
3
quartz/static/logos/Logo_tufs-cropped.svg
Normal file
3
quartz/static/logos/Logo_tufs-cropped.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 8.4 KiB |
Loading…
Reference in New Issue
Block a user