Skip to content

Commit

Permalink
[add] Frame Layout
Browse files Browse the repository at this point in the history
[add] i18n switch, LarkImage component
[polish] project, member detail pages
  • Loading branch information
Soecka committed Oct 26, 2024
1 parent 144cbc7 commit 9e53794
Show file tree
Hide file tree
Showing 18 changed files with 300 additions and 113 deletions.
33 changes: 33 additions & 0 deletions components/LarkImage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { TableCellValue } from 'mobx-lark';
import { ImageProps } from 'next/image';
import { FC } from 'react';

import { blobURLOf } from '../models/Base';
import { DefaultImage, fileURLOf } from '../pages/api/Lark/file/[id]';

export interface LarkImageProps extends Omit<ImageProps, 'src'> {
src?: TableCellValue;
}

export const LarkImage: FC<LarkImageProps> = ({ className = '', src = '', alt, ...props }) => (
<img
className={className}
loading="lazy"
{...props}
src={blobURLOf(src)}
alt={alt}
onError={({ currentTarget: image }) => {
const path = fileURLOf(src);

if (alt || !path) return;

const errorURL = decodeURI(image.src);

image.src = errorURL.endsWith(path)
? errorURL.endsWith(DefaultImage)
? ''
: DefaultImage
: path;
}}
/>
);
8 changes: 7 additions & 1 deletion components/Layout/ColorModeDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,13 @@ export default function ColorModeIconDropdown() {
const toggleMode = () => setMode(resolvedMode === 'light' ? 'dark' : 'light');

return (
<IconButton data-screenshot="toggle-mode" size="small" disableRipple onClick={toggleMode}>
<IconButton
color="inherit"
data-screenshot="toggle-mode"
size="small"
disableRipple
onClick={toggleMode}
>
{icon}
</IconButton>
);
Expand Down
26 changes: 25 additions & 1 deletion components/Layout/Footer.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,27 @@
export const Footer = () => (
<div className="container mx-auto flex max-w-screen-xl py-12 text-center">idea2app</div>
<div className="container mx-auto flex max-w-screen-xl items-center justify-between border-t-2 px-4 py-12 text-center">
© 2024 idea2app
<ul className="flex gap-4">
<li>
<a
className="border-b-2 border-b-black py-1 dark:border-b-white"
href="https://web-cell.dev/"
target="_blank"
rel="noreferrer"
>
Web Cell
</a>
</li>
<li>
<a
className="border-b-2 border-b-black py-1 dark:border-b-white"
href="https://tech-query.me/"
target="_blank"
rel="noreferrer"
>
Tech Query
</a>
</li>
</ul>
</div>
);
46 changes: 46 additions & 0 deletions components/Layout/Frame.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { DataObject, Filter, ListModel } from 'mobx-restful';
import { FC, ReactNode } from 'react';

import { i18n } from '../../models/Translation';
import { PageHead } from '../PageHead';
import { ScrollList } from '../ScrollList';

export interface FrameProps<D extends DataObject, F extends Filter<D> = Filter<D>> {
store: ListModel<D, F>;
filter?: F;
defaultData?: D[];
title: string;
header: string;
className?: string;
scrollList?: boolean;
children?: ReactNode;
Layout: FC<{ defaultData: D[]; className?: string }>;
}

/**
* @todo remove ScrollList and use children instead?
*/
export const Frame = <D extends DataObject, F extends Filter<D> = Filter<D>>({
className = '',
scrollList = true,
children,
title,
header,
Layout,
...rest
}: FrameProps<D, F>) => (
<div className={`container mx-auto max-w-screen-xl px-4 pb-6 pt-16 ${className}`}>
<PageHead title={title} />
<h1 className="my-8 text-4xl">{header}</h1>

{scrollList ? (
<ScrollList
translator={i18n}
renderList={allItems => <Layout defaultData={allItems} />}
{...rest}
/>
) : (
children
)}
</div>
);
71 changes: 64 additions & 7 deletions components/Layout/MainNavigator.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
import { AppBar, Drawer, IconButton, PopoverProps, Toolbar } from '@mui/material';
import {
AppBar,
Button,
Drawer,
IconButton,
Menu,
MenuItem,
PopoverProps,
Toolbar
} from '@mui/material';
import { observable } from 'mobx';
import { observer } from 'mobx-react';
import Image from 'next/image';
import Link from 'next/link';
import { Component, SyntheticEvent } from 'react';
import { Component } from 'react';

import { i18n } from '../../models/Translation';
import { i18n, LanguageName } from '../../models/Translation';
import { SymbolIcon } from '../Icon';
import ColorModeIconDropdown from './ColorModeDropdown';
import { GithubIcon } from './Svg';

const { t } = i18n;

Expand All @@ -21,10 +31,10 @@ export const mainNavLinks = () => [
export class MainNavigator extends Component {
@observable accessor menuExpand = false;
@observable accessor menuAnchor: PopoverProps['anchorEl'] = null;
@observable accessor eventKey = 0;

handleChange = (event: SyntheticEvent, newValue: number) => {
this.eventKey = newValue;
switchI18n = (key: string) => {
i18n.changeLanguage(key as keyof typeof LanguageName);
this.menuAnchor = null;
};

renderLinks = () =>
Expand All @@ -34,6 +44,49 @@ export class MainNavigator extends Component {
</Link>
));

renderI18nSwitch = () => {
const { currentLanguage } = i18n,
{ menuAnchor } = this;

return (
<>
<Button
color="inherit"
aria-controls="i18n-menu"
size="small"
id="i18n-selector"
startIcon={<SymbolIcon name="translate" />}
onClick={event => (this.menuAnchor = event.currentTarget)}
>
{LanguageName[currentLanguage]}
</Button>
<Menu
anchorEl={menuAnchor}
id="i18n-menu"
slotProps={{
paper: {
variant: 'outlined',
sx: { my: '4px' }
}
}}
open={Boolean(menuAnchor)}
onClose={() => (this.menuAnchor = null)}
>
{Object.entries(LanguageName).map(([key, name]) => (
<MenuItem
key={key}
value={key}
selected={key === currentLanguage}
onClick={() => this.switchI18n(key)}
>
{name}
</MenuItem>
))}
</Menu>
</>
);
};

renderDrawer = () => (
<nav className="sm:hidden">
<IconButton
Expand Down Expand Up @@ -77,8 +130,12 @@ export class MainNavigator extends Component {

<nav className="item-center hidden flex-row gap-4 sm:flex">{this.renderLinks()}</nav>

<div className="flex flex-row items-center gap-4">
<div className="flex flex-row items-center gap-3 sm:gap-6">
<Link href="https://github.com/idea2app" target="_blank" rel="noopener noreferrer">
<GithubIcon />
</Link>
<ColorModeIconDropdown />
{this.renderI18nSwitch()}
</div>
</div>
</Toolbar>
Expand Down
4 changes: 2 additions & 2 deletions components/Section.tsx → components/Layout/Section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ import { Button } from '@mui/material';
import Link from 'next/link';
import { FC, PropsWithChildren } from 'react';

import { i18n } from '../models/Translation';
import { i18n } from '../../models/Translation';

export type SectionProps = PropsWithChildren<
Partial<Record<'id' | 'title' | 'link' | 'className', string>>
>;

const { t } = i18n;

export const Section: FC<SectionProps> = ({ id, title, children, link, className }) => (
export const Section: FC<SectionProps> = ({ id, title, children, link, className = '' }) => (
<section className={`mx-auto flex max-w-screen-xl flex-col gap-6 py-8 ${className}`}>
<h2 className="text-center" id={id}>
{title}
Expand Down
2 changes: 1 addition & 1 deletion components/Project/Card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const ProjectCard: FC<ProjectCardProps> = ({
className={`${className} flex flex-col justify-between gap-4 rounded-2xl border p-4 elevation-1 hover:elevation-8 dark:border-0`}
>
<h3 className="flex items-center justify-between">
<a className="text-lg" title={String(name)} href={`/project/${id}`}>
<a className="text-lg" title={String(name)} href={`/project/${String(id)}`}>
{String(name)}
</a>
<Chip label={String(workForm)} />
Expand Down
2 changes: 1 addition & 1 deletion components/ScrollList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export class ScrollList<
<div>
{renderList(allItems)}

<footer style={{ marginTop: '1.5rem' }}>
<footer style={{ marginTop: '1.5rem', textAlign: 'center' }}>
{noMore || !allItems.length ? t('no_more') : t('load_more')}
</footer>
</div>
Expand Down
17 changes: 16 additions & 1 deletion models/Base.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { HTTPClient } from 'koajax';
import { TableCellValue } from 'mobx-lark';

export const isServer = () => typeof window === 'undefined';

Expand All @@ -11,7 +12,21 @@ export const API_Host = isServer()
: 'http://localhost:3000'
: globalThis.location.origin;

export const blobClient = new HTTPClient({
baseURI: 'https://ows.blob.core.chinacloudapi.cn/$web/',
responseType: 'arraybuffer'
});

export const fileBaseURI = blobClient.baseURI + 'file';

export const larkClient = new HTTPClient({
baseURI: `${API_Host}/api/Lark/`,
responseType: 'json',
responseType: 'json'
});

export const blobURLOf = (value: TableCellValue) =>
value instanceof Array
? typeof value[0] === 'object' && ('file_token' in value[0] || 'attachmentToken' in value[0])
? `${fileBaseURI}/${value[0].name}`
: ''
: value + '';
8 changes: 4 additions & 4 deletions pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,13 @@ const AppShell = observer(({ Component, pageProps }: AppProps<{}>) => (
* @see {@link https://mui.com/material-ui/integrations/interoperability/#tailwind-css}
*/}
<ThemeProvider theme={theme} defaultMode="system" disableTransitionOnChange>
<MainNavigator />
<div className="flex min-h-screen flex-col justify-between">
<MainNavigator />

<div className="pt-16">
<Component {...pageProps} />
</div>

<Footer />
<Footer />
</div>
</ThemeProvider>
</StyledEngineProvider>
</>
Expand Down
6 changes: 2 additions & 4 deletions pages/api/Lark/bitable/v1/[...slug].ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,10 @@ import { proxyLark } from '../../core';

export default proxyLark((path, data) => {
if (path.split('?')[0].endsWith('/records')) {
const items =
(data as LarkPageData<TableRecord<DataObject>>).data!.items || [];
const items = (data as LarkPageData<TableRecord<DataObject>>).data!.items || [];

for (const { fields } of items)
for (const key of Object.keys(fields))
if (!/^\w+$/.test(key)) delete fields[key];
for (const key of Object.keys(fields)) if (!/^\w+$/.test(key)) delete fields[key];
}
return data;
});
2 changes: 2 additions & 0 deletions pages/api/Lark/file/[id].ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { TableCellMedia, TableCellValue } from 'mobx-lark';
import { safeAPI } from '../../core';
import { lark } from '../core';

export const DefaultImage = '/idea2app.svg';

export const fileURLOf = (field: TableCellValue) =>
field instanceof Array
? field[0]
Expand Down
8 changes: 2 additions & 6 deletions pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import { FC } from 'react';
import { PartnerOverview } from '../components/Client/Partner';
import { GitListLayout } from '../components/Git';
import { SymbolIcon } from '../components/Icon';
import { Section } from '../components/Layout/Section';
import { MemberCard } from '../components/Member/Card';
import { PageHead } from '../components/PageHead';
import { Section } from '../components/Section';
import { MEMBER_VIEW, MemberModel } from '../models/Member';
import { GitRepositoryModel } from '../models/Repository';
import { i18n } from '../models/Translation';
Expand Down Expand Up @@ -46,7 +46,7 @@ const HomePage: FC<InferGetServerSidePropsType<typeof getServerSideProps>> = obs
<>
<PageHead />

<div className="px-2 py-6">
<div className="px-4 py-6 pt-16">
<section className="container mx-auto flex max-w-screen-lg flex-col gap-4">
<div className="flex flex-row items-center justify-around py-12">
<Image src="/idea2app.svg" width={234} height={220} alt="idea2app logo" />
Expand Down Expand Up @@ -105,10 +105,6 @@ const HomePage: FC<InferGetServerSidePropsType<typeof getServerSideProps>> = obs
<div className="absolute right-0 top-0 z-20 block h-24 w-24 bg-gradient-to-l from-background to-transparent" />
</section>

{/* <Section title={t('latest_projects')} link="/project">
<ProjectListLayout defaultData={projects} />
</Section>*/}

<Section title={t('member')} link="/member">
<div className="relative max-h-[45rem] overflow-hidden">
<Masonry
Expand Down
Loading

1 comment on commit 9e53794

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deploy preview for idea2app ready!

✅ Preview
https://idea2app-5qn25x8we-techquerys-projects.vercel.app

Built with commit 9e53794.
This pull request is being automatically deployed with vercel-action

Please sign in to comment.