most add/delete api cals in use
This commit is contained in:
parent
4aae0c1724
commit
f06fbd69a9
@ -5,7 +5,8 @@ import { A, HashRouter, Route, RouteSectionProps, useNavigate } from '@solidjs/r
|
|||||||
import Login from './pages/Login';
|
import Login from './pages/Login';
|
||||||
import Groups from './pages/Groups';
|
import Groups from './pages/Groups';
|
||||||
import Account from './pages/Account';
|
import Account from './pages/Account';
|
||||||
import GroupPage from './pages/GroupPage'
|
import GroupPage from './pages/GroupPage';
|
||||||
|
import StratPage from './pages/StratPage';
|
||||||
|
|
||||||
import { BsArrowBarRight, BsBookmarkHeartFill, BsPeopleFill } from 'solid-icons/bs';
|
import { BsArrowBarRight, BsBookmarkHeartFill, BsPeopleFill } from 'solid-icons/bs';
|
||||||
import { API, api, setAPI, setAuthConfig } from './api';
|
import { API, api, setAPI, setAuthConfig } from './api';
|
||||||
@ -88,6 +89,7 @@ const App: Component = () => {
|
|||||||
<HashRouter root={AppLayout}>
|
<HashRouter root={AppLayout}>
|
||||||
<Route path='groups' component={Groups} />
|
<Route path='groups' component={Groups} />
|
||||||
<Route path='group/:id' component={GroupPage} />
|
<Route path='group/:id' component={GroupPage} />
|
||||||
|
<Route path='group/:groupId/strat/:stratId' component={StratPage} />
|
||||||
{localState.accountInfo != null ?
|
{localState.accountInfo != null ?
|
||||||
<>
|
<>
|
||||||
<Route path='account' component={Account} />
|
<Route path='account' component={Account} />
|
||||||
|
@ -81,13 +81,11 @@ export interface StratRequest {
|
|||||||
title: string,
|
title: string,
|
||||||
description: string,
|
description: string,
|
||||||
stratType: string,
|
stratType: string,
|
||||||
attempts: number,
|
|
||||||
success: number,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MapRequest {
|
export interface MapRequest {
|
||||||
name: string,
|
name: string,
|
||||||
image: File,
|
image: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ProfileRequest {
|
export interface ProfileRequest {
|
||||||
@ -102,12 +100,12 @@ export interface PlayerTypeRequest {
|
|||||||
|
|
||||||
export interface StratStateRequest {
|
export interface StratStateRequest {
|
||||||
description: string,
|
description: string,
|
||||||
image: File,
|
image: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LineupRequest {
|
export interface LineupRequest {
|
||||||
description: string,
|
description: string,
|
||||||
image: File,
|
image: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
export class API {
|
export class API {
|
||||||
|
@ -1,58 +1,54 @@
|
|||||||
import { Component, For, Setter, Show } from 'solid-js';
|
import { Component, createSignal, For, JSX } from 'solid-js';
|
||||||
|
|
||||||
import styles from './Dialog.module.css';
|
import styles from './Dialog.module.css';
|
||||||
import { UploadFile, FileUploader } from '@solid-primitives/upload';
|
import DialogButtonComponent from './DialogButtonComponent';
|
||||||
|
|
||||||
export interface DialogButton {
|
export interface DialogButton {
|
||||||
name: string,
|
name: string,
|
||||||
action?: () => void,
|
action?: () => void | boolean | Promise<void> | Promise<boolean>,
|
||||||
type?: 'default' | 'danger' | 'success',
|
type?: 'default' | 'danger' | 'success'
|
||||||
closeOnClick?: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DialogInputField {
|
export interface DialogInputField {
|
||||||
placeholder: string,
|
|
||||||
onInput: (text: string) => void,
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ImageInputField {
|
|
||||||
name: string,
|
name: string,
|
||||||
onInput: () => Setter<UploadFile[]>,
|
placeholder?: string,
|
||||||
|
onInput: (text: string) => void,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DialogProps {
|
export interface DialogProps {
|
||||||
title: string,
|
title: string,
|
||||||
text: string,
|
text?: string,
|
||||||
buttons?: (string | DialogButton)[],
|
buttons?: (string | DialogButton)[],
|
||||||
onDismiss?: () => void,
|
onDismiss?: () => void,
|
||||||
inputFields?: DialogInputField[],
|
inputFields?: DialogInputField[],
|
||||||
}
|
content?: JSX.Element,
|
||||||
|
|
||||||
function createButton(button: (string | DialogButton), dismiss?: () => void) { //TODO configurable if dismiss on click
|
|
||||||
if (typeof button == 'string') {
|
|
||||||
return <button onclick={dismiss} class={styles.defaultButton}>{button}</button>;
|
|
||||||
} else {
|
|
||||||
return <button onclick={() => { button.action?.(); button.closeOnClick ? dismiss?.() : () => { }; }} class={styles[button.type + 'Button']}>{button.name}</button>;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function createInputField(field: DialogInputField) {
|
function createInputField(field: DialogInputField) {
|
||||||
return <input placeholder={field.placeholder} onInput={e => field.onInput(e.currentTarget.value)}></input>
|
return <div class={styles.dialogInput}>
|
||||||
|
<span>{field.name}</span>
|
||||||
|
<input placeholder={field.placeholder ?? field.name} onInput={e => field.onInput(e.currentTarget.value)}></input>
|
||||||
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Dialog: Component<DialogProps> = props => {
|
const Dialog: Component<DialogProps> = props => {
|
||||||
const buttons: () => (string | DialogButton)[] = () => props.buttons == null ? [{ name: 'Okay', action: props.onDismiss }] : props.buttons;
|
const buttons: () => (string | DialogButton)[] = () => props.buttons == null ? ["Okay"] : props.buttons;
|
||||||
|
const [loading, setLoading] = createSignal(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class={styles.dialogContainer}>
|
<div class={styles.dialogContainer}>
|
||||||
<div class={styles.dialog}>
|
<div class={styles.dialog}>
|
||||||
<div class={styles.dialogTitle}>{props.title}</div>
|
<div class={styles.dialogTitle}>{props.title}</div>
|
||||||
<div class={styles.dialogText}>{props.text}</div>
|
<div class={styles.dialogText}>{props.text}</div>
|
||||||
<div class={styles.dialogInputFields}>
|
{props.inputFields && props.inputFields.length > 0 && <div class={styles.dialogInputFields}>
|
||||||
<For each={props.inputFields}>{f => createInputField(f)}</For>
|
<For each={props.inputFields}>{f => createInputField(f)}</For>
|
||||||
</div>
|
</div>}
|
||||||
|
{props.content}
|
||||||
<div class={styles.dialogButtons}>
|
<div class={styles.dialogButtons}>
|
||||||
<For each={buttons()}>{b => createButton(b, props.onDismiss)}</For>
|
<For each={buttons()}>{b => //createButton(b, props.onDismiss);
|
||||||
|
<DialogButtonComponent button={b} dismiss={props.onDismiss} loading={loading()} setLoading={setLoading} />
|
||||||
|
}
|
||||||
|
</For>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
27
src/components/DialogButtonComponent.tsx
Normal file
27
src/components/DialogButtonComponent.tsx
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { Component } from "solid-js";
|
||||||
|
import { DialogButton } from "./Dialog";
|
||||||
|
import styles from './Dialog.module.css';
|
||||||
|
|
||||||
|
export interface DialogButtonProps {
|
||||||
|
dismiss?: () => void
|
||||||
|
button: string | DialogButton
|
||||||
|
loading: boolean
|
||||||
|
setLoading: (b: boolean) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const DialogButtonComponent: Component<DialogButtonProps> = (props) => {
|
||||||
|
if (typeof props.button == 'string') {
|
||||||
|
return <button onclick={props.dismiss} class={styles.defaultButton}>{props.button}</button>;
|
||||||
|
} else {
|
||||||
|
const clickHandler = () => {
|
||||||
|
props.setLoading(true);
|
||||||
|
Promise.resolve((props.button as DialogButton).action?.()).then(v => {
|
||||||
|
if (v === undefined || v === true) props.dismiss?.();
|
||||||
|
props.setLoading(false);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return <button disabled={props.loading} onclick={clickHandler} class={styles[props.button.type + 'Button']}>{props.button.name}</button>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DialogButtonComponent;
|
@ -21,7 +21,6 @@ const ProfileItem: Component<ProfileItemProps> = (props) => {
|
|||||||
{
|
{
|
||||||
name: 'Remove',
|
name: 'Remove',
|
||||||
type: 'danger',
|
type: 'danger',
|
||||||
closeOnClick: true,
|
|
||||||
action: async () => {
|
action: async () => {
|
||||||
try {
|
try {
|
||||||
await api().removeProfileFromGroup(props.group.id, props.profile.id);
|
await api().removeProfileFromGroup(props.group.id, props.profile.id);
|
||||||
@ -31,11 +30,7 @@ const ProfileItem: Component<ProfileItemProps> = (props) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
'Cancel'
|
||||||
name: 'Cancel',
|
|
||||||
closeOnClick: true,
|
|
||||||
action: () => { }
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -4,6 +4,7 @@ import { Group, Strat, api } from "../api";
|
|||||||
import { showDialog, showMessageDialog } from "../state";
|
import { showDialog, showMessageDialog } from "../state";
|
||||||
import { errorToString } from "../util";
|
import { errorToString } from "../util";
|
||||||
import { BsTrash } from 'solid-icons/bs';
|
import { BsTrash } from 'solid-icons/bs';
|
||||||
|
import { useNavigate } from '@solidjs/router';
|
||||||
|
|
||||||
export interface StratItemProps {
|
export interface StratItemProps {
|
||||||
group: Group,
|
group: Group,
|
||||||
@ -12,6 +13,7 @@ export interface StratItemProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const StratItem: Component<StratItemProps> = (props) => {
|
const StratItem: Component<StratItemProps> = (props) => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const deleteMember = async () => {
|
const deleteMember = async () => {
|
||||||
showDialog({
|
showDialog({
|
||||||
@ -41,10 +43,10 @@ const StratItem: Component<StratItemProps> = (props) => {
|
|||||||
return (
|
return (
|
||||||
<div class={styles.member}>
|
<div class={styles.member}>
|
||||||
<div class={styles.memberDetails}>
|
<div class={styles.memberDetails}>
|
||||||
<div class={styles.memberName}>
|
<div class={styles.memberName} onclick={() => navigate('/group/' + props.group.id + '/strat/' + props.strat.id)}>
|
||||||
<span innerText={props.strat.title} />
|
<span innerText={props.strat.title} />
|
||||||
|
<span innerText={props.strat.description} />
|
||||||
<span innerText={props.strat.stratType} />
|
<span innerText={props.strat.stratType} />
|
||||||
<span innerText={props.strat.map.name} />
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<button onClick={deleteMember}><BsTrash /></button>
|
<button onClick={deleteMember}><BsTrash /></button>
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { useNavigate, useParams } from "@solidjs/router";
|
import { useNavigate, useParams } from "@solidjs/router";
|
||||||
import { Component, Show, createEffect, createSignal } from "solid-js";
|
import { Component, Show, createEffect, createSignal, onMount } from "solid-js";
|
||||||
|
|
||||||
import styles from './GroupPage.module.css'
|
import styles from './GroupPage.module.css'
|
||||||
import { Group, Map, Profile, Strat, User, api } from "../api";
|
import { Group, Map, Profile, Strat, User, api } from "../api";
|
||||||
import { showDialog, showInputDialog, showMessageDialog } from "../state";
|
import { showDialog, showInputDialog, showMessageDialog } from "../state";
|
||||||
import { _arrayBufferToBase64, errorToString, normalize } from "../util";
|
import { _arrayBufferToBase64, addMap, addMember, addProfile, addStratType, errorToString, normalize } from "../util";
|
||||||
import { BsCheck2, BsPencil, BsTrash } from "solid-icons/bs";
|
import { BsCheck2, BsPencil, BsTrash } from "solid-icons/bs";
|
||||||
import { Collapse } from "solid-collapse";
|
import { Collapse } from "solid-collapse";
|
||||||
import MemberItem from "../components/MemberItem";
|
import MemberItem from "../components/MemberItem";
|
||||||
@ -29,7 +29,7 @@ const GroupPage: Component = () => {
|
|||||||
const [profilesIsExpanded, setProfilesIsExpanded] = createSignal(false);
|
const [profilesIsExpanded, setProfilesIsExpanded] = createSignal(false);
|
||||||
const [stratTypesIsExpanded, setStratTypesIsExpanded] = createSignal(false);
|
const [stratTypesIsExpanded, setStratTypesIsExpanded] = createSignal(false);
|
||||||
|
|
||||||
createEffect(async () => {
|
onMount(async () => {
|
||||||
try {
|
try {
|
||||||
setGroup(await api().getGroup(params.id));
|
setGroup(await api().getGroup(params.id));
|
||||||
setName(group().name);
|
setName(group().name);
|
||||||
@ -37,7 +37,7 @@ const GroupPage: Component = () => {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
showMessageDialog('Failed to load group', errorToString(e));
|
showMessageDialog('Failed to load group', errorToString(e));
|
||||||
}
|
}
|
||||||
}, []);
|
});
|
||||||
|
|
||||||
const updateGroup = async (newValue: Group) => {
|
const updateGroup = async (newValue: Group) => {
|
||||||
try {
|
try {
|
||||||
@ -73,66 +73,34 @@ const GroupPage: Component = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const addMember = async () => {
|
const createStrat = async () => {
|
||||||
showInputDialog('Add member to Group', 'enter the member id you want to add', 'member ID', async (memberId) => {
|
const [title, setTitle] = createSignal('');
|
||||||
try {
|
const [description, setDescription] = createSignal('');
|
||||||
const newGroup = await api().addMember(group().id, memberId);
|
const [stratType, setStratType] = createSignal('');
|
||||||
setGroup(newGroup);
|
|
||||||
} catch (e) {
|
|
||||||
showMessageDialog('Failed to add member', errorToString(e));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
const addStratType = async () => {
|
|
||||||
showInputDialog('Add strat type to Group', 'enter the name you want to add', 'strat type', async (stratType) => {
|
|
||||||
try {
|
|
||||||
const newGroup = await api().addStratType(group().id, stratType);
|
|
||||||
setGroup(newGroup);
|
|
||||||
} catch (e) {
|
|
||||||
showMessageDialog('Failed to add strat type', errorToString(e));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
const addProfile = async () => {
|
|
||||||
const [profileName, setProfileName] = createSignal('');
|
|
||||||
const { files, selectFiles } = createFileUploader({
|
|
||||||
multiple: false,
|
|
||||||
accept: "image/*",
|
|
||||||
});
|
|
||||||
|
|
||||||
let fileReader = new FileReader();
|
|
||||||
|
|
||||||
fileReader.onload = async event => {
|
|
||||||
try {
|
|
||||||
const newGroup = await api().addProfileToGroup(group().id, { name: profileName(), image: _arrayBufferToBase64(event.target!.result as ArrayBuffer) });
|
|
||||||
setGroup(newGroup);
|
|
||||||
} catch (e) {
|
|
||||||
showMessageDialog('Failed to add profile', errorToString(e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
showDialog({
|
showDialog({
|
||||||
title: 'Add profile to Group',
|
title: 'Add a strat to Group',
|
||||||
text: 'create a new profile',
|
text: 'create a new strat',
|
||||||
inputFields: [{
|
inputFields: [{
|
||||||
onInput: setProfileName,
|
onInput: setTitle,
|
||||||
placeholder: 'name'
|
placeholder: 'title',
|
||||||
|
name: 'title'
|
||||||
|
}, {
|
||||||
|
onInput: setDescription,
|
||||||
|
placeholder: 'description',
|
||||||
|
name: 'description'
|
||||||
}],
|
}],
|
||||||
onDismiss: async () => {
|
onDismiss: async () => {
|
||||||
fileReader.readAsArrayBuffer(files()[0].file);
|
const strat = await api().addStrat(group().id, { title: title(), description: description(), stratType: stratType() });
|
||||||
|
navigate('/group/' + group().id + '/strat/' + strat.id);
|
||||||
},
|
},
|
||||||
buttons: [{
|
content: <select name="stratType" id="stratType">
|
||||||
name: 'add image', action: () => selectFiles(([{ source, name, size, file }]) => {
|
{group().stratTypes.map(p => <option value={p} onclick={() => setStratType(p)}>{p}</option>)}
|
||||||
console.log({ source, name, size, file });
|
</select>,
|
||||||
}),
|
buttons: ['confirm'],
|
||||||
closeOnClick: false
|
|
||||||
},
|
});
|
||||||
'confirm'
|
}
|
||||||
],
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
const toggleEdit = async () => {
|
const toggleEdit = async () => {
|
||||||
if (!editing()) {
|
if (!editing()) {
|
||||||
@ -178,7 +146,7 @@ const GroupPage: Component = () => {
|
|||||||
<hr />
|
<hr />
|
||||||
<div>
|
<div>
|
||||||
<button onClick={() => setMembersIsExpanded(!membersIsExpanded())}>Members</button>
|
<button onClick={() => setMembersIsExpanded(!membersIsExpanded())}>Members</button>
|
||||||
<button class={styles.addMemberButton} onClick={addMember}>+</button>
|
<button class={styles.addMemberButton} onClick={async () => addMember(group(), (g) => setGroup(g))}>+</button>
|
||||||
<Collapse value={membersIsExpanded()} class={styles.members}>
|
<Collapse value={membersIsExpanded()} class={styles.members}>
|
||||||
<div class={styles.groupMembers}>
|
<div class={styles.groupMembers}>
|
||||||
{group().members?.map(m => <MemberItem group={group()} member={m} onDelete={onDeleteMember} />)}
|
{group().members?.map(m => <MemberItem group={group()} member={m} onDelete={onDeleteMember} />)}
|
||||||
@ -186,6 +154,7 @@ const GroupPage: Component = () => {
|
|||||||
</Collapse>
|
</Collapse>
|
||||||
<br />
|
<br />
|
||||||
<button onClick={() => setStratsIsExpanded(!stratsIsExpanded())}>strats</button>
|
<button onClick={() => setStratsIsExpanded(!stratsIsExpanded())}>strats</button>
|
||||||
|
<button class={styles.addStratButton} onClick={() => createStrat()}>+</button>
|
||||||
<Collapse value={stratsIsExpanded()} class={styles.strats}>
|
<Collapse value={stratsIsExpanded()} class={styles.strats}>
|
||||||
<div class={styles.strats}>
|
<div class={styles.strats}>
|
||||||
{group().strats?.map(s => <StratItem group={group()} strat={s} onDelete={onDeleteStrat} />)}
|
{group().strats?.map(s => <StratItem group={group()} strat={s} onDelete={onDeleteStrat} />)}
|
||||||
@ -193,6 +162,7 @@ const GroupPage: Component = () => {
|
|||||||
</Collapse>
|
</Collapse>
|
||||||
<br />
|
<br />
|
||||||
<button onClick={() => setMapsIsExpanded(!mapsIsExpanded())}>maps</button>
|
<button onClick={() => setMapsIsExpanded(!mapsIsExpanded())}>maps</button>
|
||||||
|
<button class={styles.addMapButton} onClick={async () => addMap(group(), (g) => setGroup(g))}>+</button>
|
||||||
<Collapse value={mapsIsExpanded()} class={styles.maps}>
|
<Collapse value={mapsIsExpanded()} class={styles.maps}>
|
||||||
<div class={styles.maps}>
|
<div class={styles.maps}>
|
||||||
{group().maps?.map(m => <MapItem group={group()} map={m} onDelete={onDeleteMap} />)}
|
{group().maps?.map(m => <MapItem group={group()} map={m} onDelete={onDeleteMap} />)}
|
||||||
@ -200,7 +170,7 @@ const GroupPage: Component = () => {
|
|||||||
</Collapse>
|
</Collapse>
|
||||||
<br />
|
<br />
|
||||||
<button onClick={() => setStratTypesIsExpanded(!stratTypesIsExpanded())}>strat types</button>
|
<button onClick={() => setStratTypesIsExpanded(!stratTypesIsExpanded())}>strat types</button>
|
||||||
<button class={styles.addMemberButton} onClick={addStratType}>+</button>
|
<button class={styles.addMemberButton} onClick={async () => addStratType(group(), (g) => setGroup(g))}>+</button>
|
||||||
<Collapse value={stratTypesIsExpanded()} class={styles.stratTypes}>
|
<Collapse value={stratTypesIsExpanded()} class={styles.stratTypes}>
|
||||||
<div class={styles.stratTypes}>
|
<div class={styles.stratTypes}>
|
||||||
{group().stratTypes?.map(s => <StratTypeItem group={group()} stratType={s} onDelete={onDeleteStratType} />)}
|
{group().stratTypes?.map(s => <StratTypeItem group={group()} stratType={s} onDelete={onDeleteStratType} />)}
|
||||||
@ -208,7 +178,7 @@ const GroupPage: Component = () => {
|
|||||||
</Collapse>
|
</Collapse>
|
||||||
<br />
|
<br />
|
||||||
<button onClick={() => setProfilesIsExpanded(!profilesIsExpanded())}>profiles</button>
|
<button onClick={() => setProfilesIsExpanded(!profilesIsExpanded())}>profiles</button>
|
||||||
<button class={styles.addMemberButton} onClick={addProfile}>+</button>
|
<button class={styles.addMemberButton} onClick={async () => addProfile(group(), (g) => setGroup(g))}>+</button>
|
||||||
<Collapse value={profilesIsExpanded()} class={styles.profiles}>
|
<Collapse value={profilesIsExpanded()} class={styles.profiles}>
|
||||||
<div class={styles.profiles}>
|
<div class={styles.profiles}>
|
||||||
{group().profiles?.map(p => <ProfileItem group={group()} profile={p} onDelete={onDeleteProfile} />)}
|
{group().profiles?.map(p => <ProfileItem group={group()} profile={p} onDelete={onDeleteProfile} />)}
|
||||||
|
7
src/pages/StratPage.tsx
Normal file
7
src/pages/StratPage.tsx
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { Component } from "solid-js";
|
||||||
|
|
||||||
|
const StratPage: Component = () => {
|
||||||
|
return <>strat page</>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default StratPage
|
14
src/state.ts
14
src/state.ts
@ -1,7 +1,7 @@
|
|||||||
import { createEffect, createSignal } from 'solid-js';
|
import { createEffect, createSignal } from 'solid-js';
|
||||||
import { SetStoreFunction, Store, createStore } from 'solid-js/store';
|
import { SetStoreFunction, Store, createStore } from 'solid-js/store';
|
||||||
import { AuthenticationResponse } from './api';
|
import { AuthenticationResponse } from './api';
|
||||||
import { DialogInputField, DialogProps, ImageInputField } from './components/Dialog';
|
import { DialogInputField, DialogProps } from './components/Dialog';
|
||||||
|
|
||||||
// Source: https://www.solidjs.com/examples/todos
|
// Source: https://www.solidjs.com/examples/todos
|
||||||
function createLocalStore<T extends object>(
|
function createLocalStore<T extends object>(
|
||||||
@ -30,7 +30,9 @@ export const showDialog = (dialog: DialogProps) => {
|
|||||||
onDismiss: () => {
|
onDismiss: () => {
|
||||||
setDialogs(ds => {
|
setDialogs(ds => {
|
||||||
const newDs = [...ds];
|
const newDs = [...ds];
|
||||||
newDs.splice(ds.indexOf(newDialog), 1);
|
let idx = ds.indexOf(newDialog);
|
||||||
|
if (idx == -1) return newDs;
|
||||||
|
newDs.splice(idx, 1);
|
||||||
return newDs;
|
return newDs;
|
||||||
});
|
});
|
||||||
dialog.onDismiss?.();
|
dialog.onDismiss?.();
|
||||||
@ -40,14 +42,14 @@ export const showDialog = (dialog: DialogProps) => {
|
|||||||
setDialogs([...dialogs(), newDialog]);
|
setDialogs([...dialogs(), newDialog]);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const showInputDialog = (title: string, message: string, placeholder: string, callback: (text: string) => void) => {
|
export const showInputDialog = (title: string, message: string, name: string, callback: (text: string) => void) => {
|
||||||
const [text, setText] = createSignal('');
|
const [text, setText] = createSignal('');
|
||||||
|
|
||||||
showDialog({
|
showDialog({
|
||||||
title,
|
title,
|
||||||
text: message,
|
text: message,
|
||||||
inputFields: [{ placeholder, onInput: setText }],
|
inputFields: [{ name, onInput: setText }],
|
||||||
buttons: [{ name: 'Okay', action: () => callback(text()), closeOnClick: true }]
|
buttons: [{ name: 'Okay', action: () => callback(text()) }, "cancel"]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,7 +58,7 @@ export const showInputsDialog = async (title: string, message: string, inputFiel
|
|||||||
title,
|
title,
|
||||||
text: message,
|
text: message,
|
||||||
inputFields: inputFields,
|
inputFields: inputFields,
|
||||||
buttons: [{ name: 'Okay', action: () => callback(), closeOnClick: true }]
|
buttons: [{ name: 'Okay', action: () => callback() }]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
69
src/util.ts
69
src/util.ts
@ -1,69 +0,0 @@
|
|||||||
export function errorToString(e: any): string { // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
||||||
if (typeof e == 'string') {
|
|
||||||
return e;
|
|
||||||
} else if (e.toString) {
|
|
||||||
return e.toString() as string;
|
|
||||||
} else {
|
|
||||||
return 'Unknown error';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function normalize(text: string, maxLength: number, oneLine: boolean) {
|
|
||||||
if (oneLine) text = text.replaceAll('\n', '');
|
|
||||||
text = text.substring(0, maxLength);
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function normalizeQuantity(text: string) {
|
|
||||||
let quantity = parseInt(text);
|
|
||||||
|
|
||||||
if (isNaN(quantity) || quantity < 0) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return quantity;
|
|
||||||
}
|
|
||||||
|
|
||||||
function clamp(value: number, min: number, max: number) {
|
|
||||||
return Math.min(Math.max(value, min), max);
|
|
||||||
};
|
|
||||||
|
|
||||||
export function normalizeRating(text: string) {
|
|
||||||
let rating = parseInt(text);
|
|
||||||
|
|
||||||
if (isNaN(rating)) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return clamp(rating, 0, 5);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function _arrayBufferToBase64(buffer: ArrayBuffer) {
|
|
||||||
var binary = '';
|
|
||||||
var bytes = new Uint8Array(buffer);
|
|
||||||
var len = bytes.byteLength;
|
|
||||||
for (var i = 0; i < len; i++) {
|
|
||||||
binary += String.fromCharCode(bytes[i]);
|
|
||||||
}
|
|
||||||
return window.btoa(binary);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const b64toBlob = (b64Data: string, contentType = '', sliceSize = 512) => {
|
|
||||||
const byteCharacters = atob(b64Data);
|
|
||||||
const byteArrays = [];
|
|
||||||
|
|
||||||
for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
|
|
||||||
const slice = byteCharacters.slice(offset, offset + sliceSize);
|
|
||||||
|
|
||||||
const byteNumbers = new Array(slice.length);
|
|
||||||
for (let i = 0; i < slice.length; i++) {
|
|
||||||
byteNumbers[i] = slice.charCodeAt(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
const byteArray = new Uint8Array(byteNumbers);
|
|
||||||
byteArrays.push(byteArray);
|
|
||||||
}
|
|
||||||
|
|
||||||
const blob = new Blob(byteArrays, { type: contentType });
|
|
||||||
return blob;
|
|
||||||
}
|
|
164
src/util.tsx
Normal file
164
src/util.tsx
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
import { createSignal } from "solid-js";
|
||||||
|
import { api, Group } from "./api";
|
||||||
|
import { showDialog, showInputDialog, showMessageDialog } from "./state";
|
||||||
|
import { createFileUploader } from "@solid-primitives/upload";
|
||||||
|
|
||||||
|
export function errorToString(e: any): string { // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
|
if (typeof e == 'string') {
|
||||||
|
return e;
|
||||||
|
} else if (e.toString) {
|
||||||
|
return e.toString() as string;
|
||||||
|
} else {
|
||||||
|
return 'Unknown error';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalize(text: string, maxLength: number, oneLine: boolean) {
|
||||||
|
if (oneLine) text = text.replaceAll('\n', '');
|
||||||
|
text = text.substring(0, maxLength);
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeQuantity(text: string) {
|
||||||
|
let quantity = parseInt(text);
|
||||||
|
|
||||||
|
if (isNaN(quantity) || quantity < 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return quantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clamp(value: number, min: number, max: number) {
|
||||||
|
return Math.min(Math.max(value, min), max);
|
||||||
|
};
|
||||||
|
|
||||||
|
export function normalizeRating(text: string) {
|
||||||
|
let rating = parseInt(text);
|
||||||
|
|
||||||
|
if (isNaN(rating)) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return clamp(rating, 0, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function _arrayBufferToBase64(buffer: ArrayBuffer) {
|
||||||
|
var binary = '';
|
||||||
|
var bytes = new Uint8Array(buffer);
|
||||||
|
var len = bytes.byteLength;
|
||||||
|
for (var i = 0; i < len; i++) {
|
||||||
|
binary += String.fromCharCode(bytes[i]);
|
||||||
|
}
|
||||||
|
return window.btoa(binary);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const b64toBlob = (b64Data: string, contentType = '', sliceSize = 512) => {
|
||||||
|
const byteCharacters = atob(b64Data);
|
||||||
|
const byteArrays = [];
|
||||||
|
|
||||||
|
for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
|
||||||
|
const slice = byteCharacters.slice(offset, offset + sliceSize);
|
||||||
|
|
||||||
|
const byteNumbers = new Array(slice.length);
|
||||||
|
for (let i = 0; i < slice.length; i++) {
|
||||||
|
byteNumbers[i] = slice.charCodeAt(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
const byteArray = new Uint8Array(byteNumbers);
|
||||||
|
byteArrays.push(byteArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
const blob = new Blob(byteArrays, { type: contentType });
|
||||||
|
return blob;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const addMember = (group: Group, onClose: (newGroup: Group) => void) => {
|
||||||
|
showInputDialog('Add member to Group', 'enter the member id you want to add', 'member ID', async (memberId) => {
|
||||||
|
try {
|
||||||
|
onClose(await api().addMember(group.id, memberId));
|
||||||
|
} catch (e) {
|
||||||
|
showMessageDialog('Failed to add member', errorToString(e));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
export const addStratType = (group: Group, onClose: (newGroup: Group) => void) => {
|
||||||
|
showInputDialog('Add strat type to Group', 'enter the name you want to add', 'strat type', async (stratType) => {
|
||||||
|
try {
|
||||||
|
onClose(await api().addStratType(group.id, stratType));
|
||||||
|
} catch (e) {
|
||||||
|
showMessageDialog('Failed to add strat type', errorToString(e));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
export const addProfile = (group: Group, onClose: (newGroup: Group) => void) => {
|
||||||
|
const [profileName, setProfileName] = createSignal('');
|
||||||
|
const { files, selectFiles } = createFileUploader({
|
||||||
|
multiple: false,
|
||||||
|
accept: "image/*",
|
||||||
|
});
|
||||||
|
|
||||||
|
let fileReader = new FileReader();
|
||||||
|
|
||||||
|
fileReader.onload = async event => {
|
||||||
|
try {
|
||||||
|
onClose(await api().addProfileToGroup(group.id, { name: profileName(), image: _arrayBufferToBase64(event.target!.result as ArrayBuffer) }));
|
||||||
|
} catch (e) {
|
||||||
|
showMessageDialog('Failed to add profile', errorToString(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showDialog({
|
||||||
|
title: 'Add profile to Group',
|
||||||
|
text: 'create a new profile',
|
||||||
|
inputFields: [{
|
||||||
|
onInput: setProfileName,
|
||||||
|
placeholder: 'name',
|
||||||
|
name: 'name'
|
||||||
|
}],
|
||||||
|
onDismiss: async () => {
|
||||||
|
fileReader.readAsArrayBuffer(files()[0].file);
|
||||||
|
},
|
||||||
|
content: <button onclick={() => selectFiles(([{ source, name, size, file }]) => {
|
||||||
|
console.log({ source, name, size, file });
|
||||||
|
})}> Add Image </button>,
|
||||||
|
buttons: ['confirm'],
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const addMap = async (group: Group, onClose: (newGroup: Group) => void) => {
|
||||||
|
const [mapName, setMapName] = createSignal('');
|
||||||
|
const { files, selectFiles } = createFileUploader({
|
||||||
|
multiple: false,
|
||||||
|
accept: "image/*",
|
||||||
|
});
|
||||||
|
|
||||||
|
let fileReader = new FileReader();
|
||||||
|
|
||||||
|
fileReader.onload = async event => {
|
||||||
|
try {
|
||||||
|
onClose(await api().addMap(group.id, { name: mapName(), image: _arrayBufferToBase64(event.target!.result as ArrayBuffer) }));
|
||||||
|
} catch (e) {
|
||||||
|
showMessageDialog('Failed to add map', errorToString(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showDialog({
|
||||||
|
title: 'Add map to Group',
|
||||||
|
text: 'create a new map',
|
||||||
|
inputFields: [{
|
||||||
|
onInput: setMapName,
|
||||||
|
placeholder: 'name',
|
||||||
|
name: 'name'
|
||||||
|
}],
|
||||||
|
onDismiss: async () => {
|
||||||
|
fileReader.readAsArrayBuffer(files()[0].file);
|
||||||
|
},
|
||||||
|
content: <button onclick={() => selectFiles(([{ source, name, size, file }]) => {
|
||||||
|
console.log({ source, name, size, file });
|
||||||
|
})}> Add Image </button>,
|
||||||
|
buttons: ['confirm'],
|
||||||
|
});
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user