mirror of
https://github.com/Stone-Red-Code/website.git
synced 2026-09-08 16:06:13 +02:00
resources: set up home page
This commit is contained in:
@@ -0,0 +1,94 @@
|
|||||||
|
import { graphql, useStaticQuery } from "gatsby"
|
||||||
|
import React, { memo } from "react"
|
||||||
|
import "react-perfect-scrollbar/dist/css/styles.css"
|
||||||
|
import * as SC from "./styles"
|
||||||
|
import useBuildTree from "./useBuildTree"
|
||||||
|
|
||||||
|
export interface IFile {
|
||||||
|
title: string
|
||||||
|
type: "file"
|
||||||
|
path: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IFolder {
|
||||||
|
title: string
|
||||||
|
type: "folder"
|
||||||
|
path: string
|
||||||
|
children: IFileOrFolder[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type IFileOrFolder = IFile | IFolder
|
||||||
|
|
||||||
|
export interface IFileQuery {
|
||||||
|
node: {
|
||||||
|
relativePath: string
|
||||||
|
childMarkdownRemark: {
|
||||||
|
frontmatter: {
|
||||||
|
author: string
|
||||||
|
date: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAllResourcesQuery {
|
||||||
|
allFile: {
|
||||||
|
edges: IFileQuery[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ALL_RESOURCES = graphql`
|
||||||
|
query {
|
||||||
|
allFile(filter: { sourceInstanceName: { eq: "resources" } }) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
relativePath
|
||||||
|
childMarkdownRemark {
|
||||||
|
frontmatter {
|
||||||
|
authors
|
||||||
|
title
|
||||||
|
date
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
function plantTree(item: IFileOrFolder, index?: number) {
|
||||||
|
if (item.type === "file") {
|
||||||
|
// remove the first elements, treat as hardcoded
|
||||||
|
const [, , , ...cleanedUpPath] = item.path.split("/")
|
||||||
|
return (
|
||||||
|
<SC.PageLink key={item.title} to={item.path}>
|
||||||
|
{cleanedUpPath.map(node => (
|
||||||
|
// TODO: use helper to format path
|
||||||
|
<SC.NodePart key={node}>{node.replace(".md", "")}</SC.NodePart>
|
||||||
|
))}
|
||||||
|
</SC.PageLink>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Language key={item.title} item={item} index={index!} />
|
||||||
|
}
|
||||||
|
|
||||||
|
const Language = memo(({ item }: { item: IFolder; index: number }) => {
|
||||||
|
return (
|
||||||
|
<SC.TreeWrapper>
|
||||||
|
<SC.LanguageLabel>{item.title}</SC.LanguageLabel>
|
||||||
|
<SC.Children>{item.children.map(node => plantTree(node))}</SC.Children>
|
||||||
|
</SC.TreeWrapper>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export function ResourcesList(props: React.HTMLAttributes<HTMLDivElement>) {
|
||||||
|
const resources = useStaticQuery<IAllResourcesQuery>(ALL_RESOURCES)
|
||||||
|
const tree = useBuildTree(resources)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SC.ResourcesListWrapper {...props}>
|
||||||
|
{tree.map((node, index) => plantTree(node, index))}
|
||||||
|
</SC.ResourcesListWrapper>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
import { Link } from "gatsby"
|
||||||
|
import { transparentize } from "polished"
|
||||||
|
import styled from "styled-components"
|
||||||
|
|
||||||
|
export const ResourcesListWrapper = styled.div`
|
||||||
|
box-sizing: border-box;
|
||||||
|
color: ${props => props.theme.main.foreground};
|
||||||
|
`
|
||||||
|
|
||||||
|
export const Children = styled.div`
|
||||||
|
padding-left: 16px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
overflow: hidden;
|
||||||
|
`
|
||||||
|
|
||||||
|
export const Label = styled.div`
|
||||||
|
user-select: none;
|
||||||
|
position: relative;
|
||||||
|
padding: 4px 0 2px;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: 1px solid transparent;
|
||||||
|
align-self: flex-start;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: ${props =>
|
||||||
|
transparentize(0.7, props.theme.sidebar.foreground)};
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
position: absolute;
|
||||||
|
right: 100%;
|
||||||
|
margin-right: 4px;
|
||||||
|
width: 10px;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export const LanguageLabel = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 4px 15px 4px 0;
|
||||||
|
font-weight: 700;
|
||||||
|
color: ${props => transparentize(0.5, props.theme.sidebar.foreground)};
|
||||||
|
|
||||||
|
svg {
|
||||||
|
margin-left: auto;
|
||||||
|
transform: rotate(90deg);
|
||||||
|
transition: transform 0.3s;
|
||||||
|
|
||||||
|
path {
|
||||||
|
fill: ${props => transparentize(0.5, props.theme.sidebar.foreground)};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export const TreeWrapper = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
font-size: 16px;
|
||||||
|
margin: 32px 0;
|
||||||
|
|
||||||
|
${Children} {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > ${Label} svg {
|
||||||
|
fill: ${props => props.theme.sidebar.foreground};
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export const PageLink = styled(Link)`
|
||||||
|
display: block;
|
||||||
|
padding: 2px 0;
|
||||||
|
color: ${props => props.theme.sidebar.foreground};
|
||||||
|
align-self: flex-start;
|
||||||
|
text-decoration: none;
|
||||||
|
border-bottom: 1px solid transparent;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: ${props =>
|
||||||
|
transparentize(0.7, props.theme.sidebar.foreground)};
|
||||||
|
}
|
||||||
|
|
||||||
|
& + & {
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export const NodePart = styled.span`
|
||||||
|
& + &::before {
|
||||||
|
content: "/";
|
||||||
|
opacity: 0.3;
|
||||||
|
margin: 0 4px;
|
||||||
|
}
|
||||||
|
`
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
import chain from "ramda/es/chain"
|
||||||
|
import partition from "ramda/es/partition"
|
||||||
|
|
||||||
|
import { traversePathsToFiles } from "../../utils"
|
||||||
|
import {
|
||||||
|
IAllResourcesQuery,
|
||||||
|
IFile,
|
||||||
|
IFileOrFolder,
|
||||||
|
IFileQuery,
|
||||||
|
IFolder,
|
||||||
|
} from "./index"
|
||||||
|
|
||||||
|
function generateFolder({
|
||||||
|
title,
|
||||||
|
path,
|
||||||
|
targets,
|
||||||
|
}: {
|
||||||
|
title: IFolder["title"]
|
||||||
|
path: IFile["path"]
|
||||||
|
targets: IFolder[]
|
||||||
|
}): IFolder {
|
||||||
|
const children = join(chain(target => target.children, targets))
|
||||||
|
|
||||||
|
return {
|
||||||
|
title,
|
||||||
|
type: "folder",
|
||||||
|
path,
|
||||||
|
children: children.sort((a, b) =>
|
||||||
|
a.title.split("/").length > b.title.split("/").length
|
||||||
|
? -1
|
||||||
|
: a.title.localeCompare(b.title)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateFile({
|
||||||
|
title,
|
||||||
|
path,
|
||||||
|
}: {
|
||||||
|
title: IFile["title"]
|
||||||
|
path: IFile["path"]
|
||||||
|
}): IFile {
|
||||||
|
return {
|
||||||
|
title,
|
||||||
|
type: "file",
|
||||||
|
path,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function join([head, ...tail]: IFileOrFolder[]): IFileOrFolder[] {
|
||||||
|
if (!head) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const [similarFs, remaining] = partition(
|
||||||
|
obj => obj.title === head.title && obj.type === head.type,
|
||||||
|
tail
|
||||||
|
)
|
||||||
|
const targets = [head, ...similarFs]
|
||||||
|
const { title, path } = head
|
||||||
|
|
||||||
|
const current =
|
||||||
|
head.type === "folder"
|
||||||
|
? generateFolder({ title, path, targets: targets as IFolder[] })
|
||||||
|
: generateFile({ title, path })
|
||||||
|
|
||||||
|
return [current, ...join(remaining)]
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function useBuildTree(resources: IAllResourcesQuery) {
|
||||||
|
const objects = resources.allFile.edges.map(({ node: file }: IFileQuery) =>
|
||||||
|
traversePathsToFiles(file.relativePath.split("/"))
|
||||||
|
)
|
||||||
|
|
||||||
|
return join(objects)
|
||||||
|
}
|
||||||
@@ -1,12 +1,20 @@
|
|||||||
|
import { Link } from "gatsby"
|
||||||
import React, { Fragment } from "react"
|
import React, { Fragment } from "react"
|
||||||
|
|
||||||
|
import { ResourcesList } from "../components/ResourcesList"
|
||||||
import { SEO } from "../components/SEO"
|
import { SEO } from "../components/SEO"
|
||||||
|
|
||||||
function ResourcesPage() {
|
function ResourcesPage() {
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<SEO title="Resources" />
|
<SEO title="Resources" />
|
||||||
the resources
|
<p>Welcome to the TPH resources.</p>
|
||||||
|
<p>
|
||||||
|
This is meant as a small knowledge base for commonly answered questions
|
||||||
|
on our Discord community,{" "}
|
||||||
|
<Link to="/about">The Programmer's Hangout</Link>.
|
||||||
|
</p>
|
||||||
|
<ResourcesList />
|
||||||
</Fragment>
|
</Fragment>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+26
-1
@@ -1,4 +1,4 @@
|
|||||||
import { IFileOrFolder } from "../components/ResourcesSidebar/index"
|
import { IFile, IFileOrFolder } from "../components/ResourcesSidebar/index"
|
||||||
|
|
||||||
export function traversePaths(
|
export function traversePaths(
|
||||||
[head, ...tail]: string[],
|
[head, ...tail]: string[],
|
||||||
@@ -22,3 +22,28 @@ export function traversePaths(
|
|||||||
children: [traversePaths(tail, path)],
|
children: [traversePaths(tail, path)],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function traversePathsToFiles(
|
||||||
|
[head, ...tail]: string[],
|
||||||
|
basePath: string = "/resources",
|
||||||
|
depth: number = 0
|
||||||
|
): IFileOrFolder {
|
||||||
|
const path = basePath + "/" + head + "/" + tail.join("/")
|
||||||
|
|
||||||
|
if (depth !== 0) {
|
||||||
|
// probably not more than one dot
|
||||||
|
const [name] = basePath.split(".")
|
||||||
|
return {
|
||||||
|
title: name,
|
||||||
|
type: "file",
|
||||||
|
path: basePath,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: head,
|
||||||
|
type: "folder",
|
||||||
|
path,
|
||||||
|
children: [traversePathsToFiles(tail, path, 1)],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user