mirror of
https://github.com/jackyzha0/quartz.git
synced 2026-03-21 13:35: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 = {
|
||||
head: Component.Head(),
|
||||
header: [],
|
||||
afterBody: [],
|
||||
afterBody: [
|
||||
Component.ConditionalRender({
|
||||
component: Component.Graph({
|
||||
globalOnly: true,
|
||||
globalGraph: {
|
||||
removeSlugs: ["index"],
|
||||
},
|
||||
}),
|
||||
condition: (props) => props.fileData.slug === "index",
|
||||
}),
|
||||
],
|
||||
footer: Component.Footer({
|
||||
links: {},
|
||||
}),
|
||||
|
||||
@ -15,6 +15,7 @@ export interface D3Config {
|
||||
linkDistance: number
|
||||
fontSize: number
|
||||
opacityScale: number
|
||||
removeSlugs: string[]
|
||||
removeTags: string[]
|
||||
showTags: boolean
|
||||
focusOnHover?: boolean
|
||||
@ -22,11 +23,13 @@ export interface D3Config {
|
||||
}
|
||||
|
||||
interface GraphOptions {
|
||||
globalOnly: boolean
|
||||
localGraph: Partial<D3Config> | undefined
|
||||
globalGraph: Partial<D3Config> | undefined
|
||||
}
|
||||
|
||||
const defaultOptions: GraphOptions = {
|
||||
globalOnly: false,
|
||||
localGraph: {
|
||||
drag: true,
|
||||
zoom: true,
|
||||
@ -37,6 +40,7 @@ const defaultOptions: GraphOptions = {
|
||||
linkDistance: 30,
|
||||
fontSize: 0.6,
|
||||
opacityScale: 1,
|
||||
removeSlugs: [],
|
||||
showTags: true,
|
||||
removeTags: [],
|
||||
focusOnHover: false,
|
||||
@ -52,6 +56,7 @@ const defaultOptions: GraphOptions = {
|
||||
linkDistance: 30,
|
||||
fontSize: 0.6,
|
||||
opacityScale: 1,
|
||||
removeSlugs: [],
|
||||
showTags: true,
|
||||
removeTags: [],
|
||||
focusOnHover: true,
|
||||
@ -59,45 +64,79 @@ 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>) => {
|
||||
const Graph: QuartzComponent = ({ displayClass, cfg }: QuartzComponentProps) => {
|
||||
const localGraph = { ...defaultOptions.localGraph, ...opts?.localGraph }
|
||||
const globalGraph = { ...defaultOptions.globalGraph, ...opts?.globalGraph }
|
||||
const graphConfig = opts?.globalOnly ? globalGraph : localGraph
|
||||
const title = opts?.globalOnly ? "Graph" : i18n(cfg.locale).components.graph.title
|
||||
return (
|
||||
<div class={classNames(displayClass, "graph")}>
|
||||
<h3>{i18n(cfg.locale).components.graph.title}</h3>
|
||||
<div class={classNames(displayClass, "graph", ...(opts?.globalOnly ? ["global-only"] : []))}>
|
||||
{opts?.globalOnly ? (
|
||||
<h2 class="graph-section-heading">
|
||||
<GraphIcon />
|
||||
{title}
|
||||
</h2>
|
||||
) : (
|
||||
<h3>{title}</h3>
|
||||
)}
|
||||
<div class="graph-outer">
|
||||
<div class="graph-container" data-cfg={JSON.stringify(localGraph)}></div>
|
||||
<button class="global-graph-icon" aria-label="Global Graph">
|
||||
<svg
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlnsXlink="http://www.w3.org/1999/xlink"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 55 55"
|
||||
fill="currentColor"
|
||||
xmlSpace="preserve"
|
||||
>
|
||||
<path
|
||||
d="M49,0c-3.309,0-6,2.691-6,6c0,1.035,0.263,2.009,0.726,2.86l-9.829,9.829C32.542,17.634,30.846,17,29,17
|
||||
s-3.542,0.634-4.898,1.688l-7.669-7.669C16.785,10.424,17,9.74,17,9c0-2.206-1.794-4-4-4S9,6.794,9,9s1.794,4,4,4
|
||||
c0.74,0,1.424-0.215,2.019-0.567l7.669,7.669C21.634,21.458,21,23.154,21,25s0.634,3.542,1.688,4.897L10.024,42.562
|
||||
C8.958,41.595,7.549,41,6,41c-3.309,0-6,2.691-6,6s2.691,6,6,6s6-2.691,6-6c0-1.035-0.263-2.009-0.726-2.86l12.829-12.829
|
||||
c1.106,0.86,2.44,1.436,3.898,1.619v10.16c-2.833,0.478-5,2.942-5,5.91c0,3.309,2.691,6,6,6s6-2.691,6-6c0-2.967-2.167-5.431-5-5.91
|
||||
v-10.16c1.458-0.183,2.792-0.759,3.898-1.619l7.669,7.669C41.215,39.576,41,40.26,41,41c0,2.206,1.794,4,4,4s4-1.794,4-4
|
||||
s-1.794-4-4-4c-0.74,0-1.424,0.215-2.019,0.567l-7.669-7.669C36.366,28.542,37,26.846,37,25s-0.634-3.542-1.688-4.897l9.665-9.665
|
||||
C46.042,11.405,47.451,12,49,12c3.309,0,6-2.691,6-6S52.309,0,49,0z M11,9c0-1.103,0.897-2,2-2s2,0.897,2,2s-0.897,2-2,2
|
||||
S11,10.103,11,9z M6,51c-2.206,0-4-1.794-4-4s1.794-4,4-4s4,1.794,4,4S8.206,51,6,51z M33,49c0,2.206-1.794,4-4,4s-4-1.794-4-4
|
||||
s1.794-4,4-4S33,46.794,33,49z M29,31c-3.309,0-6-2.691-6-6s2.691-6,6-6s6,2.691,6,6S32.309,31,29,31z M47,41c0,1.103-0.897,2-2,2
|
||||
s-2-0.897-2-2s0.897-2,2-2S47,39.897,47,41z M49,10c-2.206,0-4-1.794-4-4s1.794-4,4-4s4,1.794,4,4S51.206,10,49,10z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="global-graph-outer">
|
||||
<div class="global-graph-container" data-cfg={JSON.stringify(globalGraph)}></div>
|
||||
<div class="graph-container" data-cfg={JSON.stringify(graphConfig)}></div>
|
||||
{!opts?.globalOnly && (
|
||||
<button class="global-graph-icon" aria-label="Global Graph">
|
||||
<svg
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlnsXlink="http://www.w3.org/1999/xlink"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 55 55"
|
||||
fill="currentColor"
|
||||
xmlSpace="preserve"
|
||||
>
|
||||
<path
|
||||
d="M49,0c-3.309,0-6,2.691-6,6c0,1.035,0.263,2.009,0.726,2.86l-9.829,9.829C32.542,17.634,30.846,17,29,17
|
||||
s-3.542,0.634-4.898,1.688l-7.669-7.669C16.785,10.424,17,9.74,17,9c0-2.206-1.794-4-4-4S9,6.794,9,9s1.794,4,4,4
|
||||
c0.74,0,1.424-0.215,2.019-0.567l7.669,7.669C21.634,21.458,21,23.154,21,25s0.634,3.542,1.688,4.897L10.024,42.562
|
||||
C8.958,41.595,7.549,41,6,41c-3.309,0-6,2.691-6,6s2.691,6,6,6s6-2.691,6-6c0-1.035-0.263-2.009-0.726-2.86l12.829-12.829
|
||||
c1.106,0.86,2.44,1.436,3.898,1.619v10.16c-2.833,0.478-5,2.942-5,5.91c0,3.309,2.691,6,6,6s6-2.691,6-6c0-2.967-2.167-5.431-5-5.91
|
||||
v-10.16c1.458-0.183,2.792-0.759,3.898-1.619l7.669,7.669C41.215,39.576,41,40.26,41,41c0,2.206,1.794,4,4,4s4-1.794,4-4
|
||||
s-1.794-4-4-4c-0.74,0-1.424,0.215-2.019,0.567l-7.669-7.669C36.366,28.542,37,26.846,37,25s-0.634-3.542-1.688-4.897l9.665-9.665
|
||||
C46.042,11.405,47.451,12,49,12c3.309,0,6-2.691,6-6S52.309,0,49,0z M11,9c0-1.103,0.897-2,2-2s2,0.897,2,2s-0.897,2-2,2
|
||||
S11,10.103,11,9z M6,51c-2.206,0-4-1.794-4-4s1.794-4,4-4s4,1.794,4,4S8.206,51,6,51z M33,49c0,2.206-1.794,4-4,4s-4-1.794-4-4
|
||||
s1.794-4,4-4S33,46.794,33,49z M29,31c-3.309,0-6-2.691-6-6s2.691-6,6-6s6,2.691,6,6S32.309,31,29,31z M47,41c0,1.103-0.897,2-2,2
|
||||
s-2-0.897-2-2s0.897-2,2-2S47,39.897,47,41z M49,10c-2.206,0-4-1.794-4-4s1.794-4,4-4s4,1.794,4,4S51.206,10,49,10z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{!opts?.globalOnly && (
|
||||
<div class="global-graph-outer">
|
||||
<div class="global-graph-container" data-cfg={JSON.stringify(globalGraph)}></div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -31,7 +31,7 @@ const HomeHero: QuartzComponent = () => {
|
||||
<p class="home-bio">
|
||||
Software engineer and linguist. Interning at MIXI, Inc building iOS features for
|
||||
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>
|
||||
<div class="home-links">
|
||||
<a href="mailto:riceset@icloud.com" class="home-link">
|
||||
|
||||
@ -84,7 +84,7 @@ const education: EducationItem[] = [
|
||||
degree: "B.A. Language and Area Studies",
|
||||
institution: "Tokyo University of Foreign Studies",
|
||||
institutionUrl: "https://www.tufs.ac.jp/english/",
|
||||
logo: "/static/logos/tufs.svg",
|
||||
logo: "/static/logos/Logo_tufs-cropped.svg",
|
||||
period: "2024 – 2028",
|
||||
},
|
||||
{
|
||||
@ -98,10 +98,10 @@ const education: EducationItem[] = [
|
||||
|
||||
const languages: Language[] = [
|
||||
{ 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: "Spanish", level: "Professional · TOEIC 945" },
|
||||
{ flag: "🇨🇳", name: "Mandarin", level: "Working · HSK 3 · TOCFL 4" },
|
||||
{ flag: "🇪🇸", name: "Spanish", level: "Professional" },
|
||||
{ flag: "🇨🇳", name: "Mandarin", level: "Working · HSK 3 · TBCL 4" },
|
||||
]
|
||||
|
||||
// ── Component ──────────────────────────────────────────────────────────────
|
||||
|
||||
@ -68,6 +68,17 @@ type TweenNode = {
|
||||
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) {
|
||||
const slug = simplifySlug(fullSlug)
|
||||
const visited = getVisited()
|
||||
@ -83,17 +94,18 @@ async function renderGraph(graph: HTMLElement, fullSlug: FullSlug) {
|
||||
linkDistance,
|
||||
fontSize,
|
||||
opacityScale,
|
||||
removeSlugs,
|
||||
removeTags,
|
||||
showTags,
|
||||
focusOnHover,
|
||||
enableRadial,
|
||||
} = 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(
|
||||
Object.entries<ContentDetails>(await fetchData).map(([k, v]) => [
|
||||
simplifySlug(k as FullSlug),
|
||||
v,
|
||||
]),
|
||||
Object.entries<ContentDetails>(await fetchData)
|
||||
.map(([k, v]) => [simplifySlug(k as FullSlug), v] as const)
|
||||
.filter(([slug]) => !hiddenSlugs.has(slug)),
|
||||
)
|
||||
const links: SimpleLinkData[] = []
|
||||
const tags: SimpleSlug[] = []
|
||||
@ -123,7 +135,7 @@ async function renderGraph(graph: HTMLElement, fullSlug: FullSlug) {
|
||||
}
|
||||
|
||||
const neighbourhood = new Set<SimpleSlug>()
|
||||
const wl: (SimpleSlug | "__SENTINEL")[] = [slug, "__SENTINEL"]
|
||||
const wl: (SimpleSlug | "__SENTINEL")[] = validLinks.has(slug) ? [slug, "__SENTINEL"] : []
|
||||
if (depth >= 0) {
|
||||
while (depth >= 0 && wl.length > 0) {
|
||||
// compute neighbours
|
||||
@ -161,8 +173,7 @@ async function renderGraph(graph: HTMLElement, fullSlug: FullSlug) {
|
||||
})),
|
||||
}
|
||||
|
||||
const width = graph.offsetWidth
|
||||
const height = Math.max(graph.offsetHeight, 250)
|
||||
const { width, height } = getGraphDimensions(graph)
|
||||
|
||||
// we virtualize the simulation and use pixi to actually render it
|
||||
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 globalGraphCleanups: (() => void)[] = []
|
||||
|
||||
@ -581,7 +643,7 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => {
|
||||
cleanupLocalGraphs()
|
||||
const localGraphContainers = document.getElementsByClassName("graph-container")
|
||||
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
|
||||
registerEscapeHandler(container, hideGlobalGraph)
|
||||
if (graphContainer) {
|
||||
globalGraphCleanups.push(await renderGraph(graphContainer, slug))
|
||||
globalGraphCleanups.push(await mountGraph(graphContainer, slug))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,8 +5,8 @@ footer {
|
||||
opacity: 0.7;
|
||||
|
||||
&.home-footer {
|
||||
margin-top: 0;
|
||||
padding-top: 2rem;
|
||||
border-top: 1px solid var(--lightgray);
|
||||
}
|
||||
|
||||
& ul {
|
||||
|
||||
@ -6,6 +6,41 @@
|
||||
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 {
|
||||
border-radius: 5px;
|
||||
border: 1px solid var(--lightgray);
|
||||
@ -15,6 +50,17 @@
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
& > .graph-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
& canvas {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
& > .global-graph-icon {
|
||||
cursor: pointer;
|
||||
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