Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Admin Walk #581

Merged
merged 10 commits into from
Sep 6, 2023
1 change: 0 additions & 1 deletion next/jest.setup.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
// eslint-disable-next-line import/no-extraneous-dependencies
import '@testing-library/jest-dom'
3 changes: 3 additions & 0 deletions next/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,8 @@ const nextConfig = {
images: {
domains: ['localhost', '127.0.0.1'],
},
eslint: {
dirs: ['pages', 'src', '__tests__'],
},
}
export default nextConfig
461 changes: 213 additions & 248 deletions next/package-lock.json

Large diffs are not rendered by default.

9 changes: 5 additions & 4 deletions next/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@
"@emotion/styled": "^11.11.0",
"@mui/joy": "^5.0.0-alpha.89",
"@types/react-image-gallery": "^1.2.0",
"@types/xml2js": "^0.4.11",
"@types/xml2js": "^0.4.12",
"boom": "^7.3.0",
"camelcase": "^6.3.0",
"color-thief-react": "^2.1.0",
"glob": "^10.3.3",
"glob": "^10.3.4",
"mapbox-gl": "^2.15.0",
"mime-types": "^2.1.35",
"next": "^13.4.19",
Expand All @@ -45,9 +45,10 @@
"@types/geojson": "^7946.0.10",
"@types/jest": "^29.5.4",
"@types/mime-types": "^2.1.1",
"@types/node": "^20.5.7",
"@types/node": "^20.5.9",
"@types/react": "^18.2.21",
"@types/react-dom": "^18.2.7",
"eslint": "^8.48.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-next": "^13.4.19",
"eslint-plugin-import": "^2.28.1",
Expand All @@ -60,7 +61,7 @@
"jest-environment-jsdom": "^29.6.4",
"jest-styled-components": "^7.1.1",
"next-test-api-route-handler": "^3.1.8",
"snyk": "^1.1207.0",
"snyk": "^1.1211.0",
"standard-version": "^9.5.0",
"ts-jest": "^29.1.1",
"typescript": "^5.2.2"
Expand Down
43 changes: 39 additions & 4 deletions next/pages/_app.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { CssVarsProvider, extendTheme } from '@mui/joy/styles'
import { type AppProps } from 'next/app'
import { createGlobalStyle, ThemeProvider } from 'styled-components'
import { ThemeProvider, createGlobalStyle } from 'styled-components'

const GlobalStyle = createGlobalStyle`
body {
Expand All @@ -14,7 +15,39 @@ const GlobalStyle = createGlobalStyle`
}
`

const theme = {
const themeMui = extendTheme({
components: {
JoyListDivider: {
defaultProps: {
inset: 'gutter',
},
styleOverrides: {
root: {
backgroundColor: 'silver',
},
},
},
JoyList: {
styleOverrides: {
root: {
border: '1px solid silver',
borderRadius: '3px',
backgroundColor: '#545454',
color: 'red',
},
},
},
JoyListItem: {
styleOverrides: {
root: {
color: '#C0C0C0',
},
},
},
},
})

const themeStyled = {
colors: {
primary: '#0070f3',
},
Expand All @@ -24,8 +57,10 @@ export default function App({ Component, pageProps }: AppProps) {
return (
<>
<GlobalStyle />
<ThemeProvider theme={theme}>
<Component {...pageProps} />
<ThemeProvider theme={themeStyled}>
<CssVarsProvider theme={themeMui}>
<Component {...pageProps} />
</CssVarsProvider>
</ThemeProvider>
</>
)
Expand Down
61 changes: 61 additions & 0 deletions next/pages/admin/walk.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { List, ListDivider, ListItem } from '@mui/joy'
import { useRouter } from 'next/router'
import { Fragment, useEffect, useState } from 'react'

import ListFile from '../../src/components/Walk/ListFile'
import type { Filesystem, FilesystemBody } from '../../src/lib/filesystems'
import {
addParentDirectoryNav,
isImage,
organizeByMedia,
parseHash,
} from '../../src/utils/walk'

type ItemFile = Partial<Filesystem> & {
id: Filesystem['id'];
path: Filesystem['path'];
label: string;
grouped?: string;
flat?: string;
}

function WalkPage() {
const { asPath } = useRouter()
const [data, setData] = useState<FilesystemBody | null>(null)
const [isLoading, setLoading] = useState(false)
const pathQs = parseHash('path', asPath)

useEffect(() => {
setLoading(true)
fetch(`/api/admin/filesystems?path=${pathQs ?? '/'}`)
.then((response) => response.json())
.then((result: FilesystemBody) => {
setData(result)
setLoading(false)
})
}, [asPath])

if (isLoading) return <p>Loading...</p>
if (!data) return <p>No filesystem data</p>

const itemImages = data.files.filter((file) => isImage(file))
const hasImages = !isLoading && itemImages.length > 0
const fsItems = addParentDirectoryNav(organizeByMedia(data.files), pathQs)

return (
<>
<List>
{fsItems.map((item, i) => (
<Fragment key={item.id}>
{i > 0 && <ListDivider />}
<ListFile item={item} />
</Fragment>
))}
</List>
{hasImages && (<div>TODO display OrganizeThumbs</div>)}
</>
)
}

export { type ItemFile }
export default WalkPage
14 changes: 11 additions & 3 deletions next/pages/api/admin/filesystems/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,20 @@ describe('Filesystem API', () => {
})
})

function matchFile(expect: jest.Expect, file: Filesystem) {
function matchPFile(expect: jest.Expect, file: Filesystem) {
expect(file.filename).toEqual('P1160066.JPG')
expect(file.ext).toEqual('JPG')
expect(file.mediumType).toEqual('image')
expect(file.name).toEqual('P1160066')
}

function matchJFile(expect: jest.Expect, file: Filesystem) {
expect(file.filename).toEqual('jay.js')
expect(file.ext).toEqual('js')
expect(file.mediumType).toEqual('application')
expect(file.name).toEqual('jay')
}

test('* GET fixtures has test files', async () => {
await testApiHandler({
handler,
Expand All @@ -42,7 +49,8 @@ describe('Filesystem API', () => {

expect(response.status).toBe(200)

matchFile(expect, result.files[2])
matchPFile(expect, result.files[1])
matchJFile(expect, result.files[0])
expect(result.files.length).toEqual(3)
},
params: { path: 'test/fixtures/walkable' },
Expand All @@ -58,7 +66,7 @@ describe('Filesystem API', () => {

expect(response.status).toBe(200)

matchFile(expect, result.files[0])
matchPFile(expect, result.files[0])
expect(result.files.length).toEqual(1)
},
params: { path: 'test/fixtures/walk%20able' },
Expand Down
62 changes: 15 additions & 47 deletions next/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { List, ListDivider, ListItem } from '@mui/joy'
import { CssVarsProvider } from '@mui/joy/styles'
import { type GetStaticProps } from 'next'
import Head from 'next/head'
import { Fragment } from 'react'
import styled from 'styled-components'

import Link from '../src/components/Link'
import getGalleries, { type Gallery } from '../src/lib/galleries'
Expand All @@ -22,16 +20,6 @@ export const getStaticProps: GetStaticProps<Props> = async () => {
}
}

const Wrapper = styled.div`
padding: 0;
margin: 0;
width: 100%;
background-color: #545454;
border: 1px solid #ccc;
border-radius: 3px;
overflow: hidden;
`

function Home({ galleries }: Props) {
return (
<>
Expand All @@ -40,41 +28,21 @@ function Home({ galleries }: Props) {
<link rel="icon" href="/favicon.ico" />
</Head>

<CssVarsProvider defaultMode="dark">
<main>
<h1>List of Galleries</h1>
<Wrapper>
<List
aria-labelledby="galleries"
variant="outlined"
sx={{
listStyle: 'none',
margin: 0,
padding: '0',
width: '100%',
maxHeight: '30em',
overflowY: 'auto',
}}
>
{galleries && galleries.map((item, i) => (
<Fragment key={`frag${item.gallery}`}>
{i > 0 && (
<ListDivider
sx={{ background: '#ccc' }}
inset="gutter"
/>
)}
<ListItem>
<Link href={`/${item.gallery}`}>
{item.gallery}
</Link>
</ListItem>
</Fragment>
))}
</List>
</Wrapper>
</main>
</CssVarsProvider>
<main>
<h1>List of Galleries</h1>
<List>
{galleries && galleries.map((item, i) => (
<Fragment key={`frag${item.gallery}`}>
{i > 0 && <ListDivider />}
<ListItem>
<Link href={`/${item.gallery}`}>
{item.gallery}
</Link>
</ListItem>
</Fragment>
))}
</List>
</main>
</>
)
}
Expand Down
20 changes: 11 additions & 9 deletions next/src/components/Link/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import NextLink from 'next/link'
import { memo, type ReactNode } from 'react'
import styled from 'styled-components'

const ColouredLink = styled.a`
const ColouredLink = styled(NextLink)`
color: #6cc0e5;

&:hover {
Expand All @@ -22,15 +22,17 @@ const ColouredLink = styled.a`
}
`

function Link({
children, href, title = '', ...props
}: {
children: ReactNode, href: string, title?: string,
}) {
interface InputGroupProps extends React.ComponentPropsWithoutRef<'a'> {
children: ReactNode;
href: string;
}

function Link(
{ children, ...props }:
InputGroupProps,
) {
return (
<NextLink href={href} {...props} passHref legacyBehavior>
<ColouredLink title={title}>{children}</ColouredLink>
</NextLink>
<ColouredLink {...props}>{children}</ColouredLink>
)
}

Expand Down
19 changes: 19 additions & 0 deletions next/src/components/Walk/ListFile.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { ListItem } from '@mui/joy'

import { type ItemFile } from '../../../pages/admin/walk'
import Link from '../Link'

function ListFile({ item: file }: { item: ItemFile }) {
if (file.mediumType === 'folder') {
const href = file.path ? `#path=${file.path}` : ''
return (
<ListItem>
<Link href={href}>{file.label}</Link>
</ListItem>
)
}

return <ListItem>{file.label}</ListItem>
}

export default ListFile
43 changes: 43 additions & 0 deletions next/src/components/Walk/__tests__/ListFile.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { render } from '@testing-library/react'

import ListFile from '../ListFile'
import { type ItemFile } from '../../../../pages/admin/walk'

describe('<ListFile />', () => {
const mockItemFile: ItemFile = {
id: '123',
label: 'One Two Three',
path: '123',
}
test('should render file label', () => {
const label = 'label'
const { getByText } = render(<ListFile item={{ ...mockItemFile, label }} />)
expect(getByText(label)).toBeInTheDocument()
})
test('should render a folder with path', () => {
const path = 'testPath'
const label = 'Link text'
const { getByText } = render(<ListFile
item={{
...mockItemFile,
mediumType: 'folder',
path,
label,
}}
/>)
expect(getByText(label).closest('a')?.href).toBe(`http://localhost/#path=${path}`)
})
test('should render a folder with path', () => {
const path = ''
const label = 'Link text'
const { getByText } = render(<ListFile
item={{
...mockItemFile,
mediumType: 'folder',
path,
label,
}}
/>)
expect(getByText(label).closest('a')?.href).toBe('http://localhost/')
})
})
Loading
Loading