Skip to content

Commit

Permalink
feat: user page and notes;
Browse files Browse the repository at this point in the history
  • Loading branch information
twiddli committed Apr 13, 2023
1 parent e70376b commit 555b6a3
Show file tree
Hide file tree
Showing 16 changed files with 1,227 additions and 90 deletions.
41 changes: 41 additions & 0 deletions packages/client/client/actions/item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,44 @@ import { update } from '../utility';

import type { Server } from '../../services/server';
export class ItemActions {
static async newItem<T extends Partial<ServerItem>>(
data: T,
args: Omit<Parameters<Server['new_item']>[0], 'item'>,
onNewData?: (data: T, mutated: boolean) => void
) {
return new Promise<AxiosResponse<boolean>>((resolve, reject) => {
let mutated = false;
const newData = data;

// TODO: optimistic update

if (mutated) {
onNewData?.(newData, mutated);
}

Query.mutate(
MutatationType.NEW_ITEM,
{ ...args, item: newData },
{
onSettled: (res, err, variables) => {
// return original data if error
if ((err || !res.data) && mutated) {
onNewData?.(data, mutated);
}

// need to be last
if (err) {
reject(err);
} else {
resolve(res);
}
},
},
{ method: 'POST' }
).catch(reject);
});
}

static async updateItem<T extends Partial<ServerItem>>(
item_id: number,
data: T,
Expand Down Expand Up @@ -52,6 +90,9 @@ export class ItemActions {
resolve(res);
}
},
},
{
method: 'PATCH',
}
).catch(reject);
});
Expand Down
19 changes: 15 additions & 4 deletions packages/client/client/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,11 @@ export function useMutationType<
method = 'DELETE';
break;
}
case MutatationType.UPDATE_ITEM:
case MutatationType.NEW_ITEM: {
throw new Error('Not implemented');
}

}

return useMutation(
Expand Down Expand Up @@ -222,8 +227,8 @@ export function useQueryType<
onQueryKey?: () => any[];
infinite?: I;
infinitePageParam?: I extends Falsy
? undefined
: (variables: V, context: QueryFunctionContext) => V;
? undefined
: (variables: V, context: QueryFunctionContext) => V;
} & (I extends Falsy
? Omit<UseQueryOptions<D, E>, 'initialData' | 'placeholderData'>
: Omit<UseInfiniteQueryOptions<D, E>, 'initialData' | 'placeholderData'>)
Expand Down Expand Up @@ -420,7 +425,7 @@ export class Query {
MutationObserverOptions<AxiosResponse<R>, E, V>,
'mutationFn' | 'mutationKey' | 'variables'
>,
reqConfig?: Parameters<AxiosInstance['post']>[2]
reqConfig?: Parameters<AxiosInstance['request']>[0]
) {
const key = JSON.stringify([action.toString(), variables]);

Expand All @@ -439,8 +444,14 @@ export class Query {
];
if (!obs) {
let endpoint = action as string;
let method: AxiosRequestConfig['method'] = 'POST';

const fn = (v: V) => axios.post<R>(urlstring(endpoint), v, reqConfig);
const fn = (v: V) => axios.request<R>({
method,
url: urlstring(endpoint),
data: v,
...reqConfig,
});

obs = new MutationObserver<AxiosResponse<R>, E, V>(queryClient, {
mutationKey: [key],
Expand Down
20 changes: 0 additions & 20 deletions packages/client/components/About.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -169,14 +169,6 @@ function ActivityPane() {
);
}

function StatsPane() {
return (
<Segment basic>
<EmptySegment />
</Segment>
);
}

function TrashPane() {
return (
<Segment basic>
Expand Down Expand Up @@ -218,18 +210,6 @@ export function AboutTab() {
</Tab.Pane>
),
},
{
menuItem: {
key: 'statistics',
icon: 'bar chart',
content: t`Statistics`,
},
render: () => (
<Tab.Pane basic className="no-padding-segment">
<StatsPane />
</Tab.Pane>
),
},
{
menuItem: {
key: 'trash',
Expand Down
148 changes: 148 additions & 0 deletions packages/client/components/Note.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import { useCallback, useEffect, useState } from 'react';
import { Card, Form, Header, Icon, Modal, Segment } from 'semantic-ui-react';

import { ItemActions } from '../client/actions/item';
import t from '../client/lang';
import { ItemType } from '../shared/enums';
import { ServerNote } from '../shared/types';
import { Markdown } from './misc';
import { ModalWithBack } from './misc/BackSupport';
import MarkdownEditor from './misc/MarkdownEditor';

export function NoteForm({
data,
onData,
}: {
data?: Partial<ServerNote>;
onData?: (data: Partial<ServerNote>) => void;
}) {
const [formData, setFormData] = useState<Partial<ServerNote>>(data ?? {});

const onSubmit = useCallback(
(e) => {
e.preventDefault();
onData?.(formData);
},
[formData]
);

return (
<Segment basic>
<Header>{data ? t`Edit note` : t`Create new note`}</Header>
<Form onSubmit={onSubmit}>
<Form.Input
fluid
label={t`Title`}
placeholder={t`Title`}
value={formData?.title ?? ''}
onChange={(e) => {
setFormData({ ...formData, title: e.target.value });
}}
/>
<Form.Field>
<label>{t`Content`}</label>
<MarkdownEditor
content={formData?.content ?? ''}
onChange={(v) => {
setFormData({ ...formData, content: v });
}}
/>
</Form.Field>
<Form.Button primary type="submit">
Submit
</Form.Button>
</Form>
</Segment>
);
}

export default function Note({
data: initalData,
onUpdated,
}: {
data?: Partial<ServerNote>;
onUpdated?: (data: Partial<ServerNote>) => void;
}) {
const [open, setOpen] = useState(false);
const [editOpen, setEditOpen] = useState(false);
const [data, setData] = useState(initalData ?? {});

useEffect(() => {
setData(initalData ?? {});
}, [initalData]);

let content = data?.content ?? '';

if (content.length > 100) {
// cut off at 200 characters and add ellipsis
content = content.slice(0, 200) + '...';
}

return (
<>
<ModalWithBack
closeIcon
open={open}
onOpen={() => setOpen(true)}
onClose={() => setOpen(false)}>
<Modal.Header>{data?.title}</Modal.Header>
<Modal.Content>
<Markdown>{data?.content}</Markdown>
</Modal.Content>
</ModalWithBack>
<ModalWithBack
open={editOpen}
onOpen={() => setEditOpen(true)}
onClose={() => setEditOpen(false)}
closeIcon
content={
<NoteForm
data={data}
onData={(v) => {
ItemActions.updateItem(data?.id, v, {
item_type: ItemType.Note,
item: v,
}).then(() => onUpdated?.(v));
setEditOpen(false);
}}
/>
}
/>
<Card
onClick={(e) => {
e.preventDefault();
setOpen(true);
}}>
<Card.Content>
<Card.Header>
{data?.title}
<Icon
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
setEditOpen(true);
}}
name="pencil"
link
size="small"
className="float-right"
/>
</Card.Header>
{/* <Card.Meta>Friends of Elliot</Card.Meta> */}
<Card.Description>
<span
style={{
whiteSpace: 'pre-wrap',
}}>
{content}
</span>
</Card.Description>
</Card.Content>
</Card>
</>
);
}

export function NoteGroup(props: React.ComponentProps<typeof Card.Group>) {
return <Card.Group {...props} />;
}
21 changes: 9 additions & 12 deletions packages/client/components/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,7 @@ function SidebarItem({
as={href ? 'a' : undefined}
onClick={onClick}
active={active ?? (href ? urlquery.url.startsWith(href) : undefined)}
className={classNames(className)}
>
className={classNames(className)}>
{!!icon && (
<Icon
size="large"
Expand Down Expand Up @@ -161,17 +160,15 @@ export function MainSidebar({
if (!onlyIcons) setIconOnly(true);
}, 100),
[onlyIcons]
)}
>
)}>
<SidebarContext.Provider value={{ iconOnly }}>
<div className="flex-container">
<div className="top-aligned">
<SidebarItem
href="/"
active={false}
icon={{ className: 'hpx-standard huge left' }}
className="center-text small-padding-segment no-left-padding no-right-padding"
></SidebarItem>
className="center-text small-padding-segment no-left-padding no-right-padding"></SidebarItem>
<SidebarItem
href="/add"
icon={{ name: 'plus square', color: 'teal' }} // <-- use React.memo
Expand All @@ -184,18 +181,18 @@ export function MainSidebar({
>{t`Dashboard`}</SidebarItem>
<SidebarItem
href="/library"
icon={'grid layout'}
>{t`Library`}</SidebarItem>
icon={'grid layout'}>{t`Library`}</SidebarItem>
<SidebarItem
href="/directory"
icon={'folder outline'}
>{t`Directory`}</SidebarItem>
icon={'folder outline'}>{t`Directory`}</SidebarItem>
<SidebarItem
href="/management"
icon={'cubes'}
>{t`Management`}</SidebarItem>
icon={'cubes'}>{t`Management`}</SidebarItem>
</div>
<div className="bottom-aligned">
<SidebarItem
href="/user"
icon={'user alternate'}>{t`You`}</SidebarItem>
<SettingsModal
trigger={
<SidebarItem icon={'settings'}>{t`Preferences`}</SidebarItem>
Expand Down
30 changes: 30 additions & 0 deletions packages/client/components/misc/MarkdownEditor.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
.editor {
margin: 0;
-webkit-appearance: none;
-webkit-tap-highlight-color: rgba(255, 255, 255, 0);
padding: 0.91666667em 1.16666667em;
background: #FFFFFF;
border: 1px solid rgba(34, 36, 38, 0.15);
border-top-color: rgba(34, 36, 38, 0.15);
border-right-color: rgba(34, 36, 38, 0.15);
border-bottom-color: rgba(34, 36, 38, 0.15);
border-left-color: rgba(34, 36, 38, 0.15);
outline: none;
color: #212121;
border-radius: 4px;
box-shadow: 0 0 0 0 transparent inset;
transition: color 0.1s ease, border-color 0.1s ease;
font-size: 1em;
line-height: 1.2857;
resize: vertical;
min-height: 6em;
}

.editor p.is_editor_empty:first-child::before {
color: #adb5bd;
content: attr(data-placeholder);
float: left;
height: 0;
pointer-events: none;
}

Loading

0 comments on commit 555b6a3

Please sign in to comment.