added upload image to dialog
This commit is contained in:
parent
34f38e90d5
commit
4aae0c1724
13
package-lock.json
generated
13
package-lock.json
generated
@ -9,6 +9,7 @@
|
||||
"version": "0.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@solid-primitives/upload": "^0.0.117",
|
||||
"@solidjs/router": "^0.13.5",
|
||||
"solid-collapse": "^1.1.0",
|
||||
"solid-icons": "^1.1.0",
|
||||
@ -1224,11 +1225,21 @@
|
||||
"solid-js": "^1.6.12"
|
||||
}
|
||||
},
|
||||
"node_modules/@solid-primitives/upload": {
|
||||
"version": "0.0.117",
|
||||
"resolved": "https://registry.npmjs.org/@solid-primitives/upload/-/upload-0.0.117.tgz",
|
||||
"integrity": "sha512-szDksm4u67JgiMtkpX8RPccxqfid4OCQ/zpJ1yB1PMFmenLjz8YKldGIIt+Yn3bEcfHBbdkPa2uu/y2FAdPeSw==",
|
||||
"dependencies": {
|
||||
"@solid-primitives/utils": "^6.2.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"solid-js": "^1.6.12"
|
||||
}
|
||||
},
|
||||
"node_modules/@solid-primitives/utils": {
|
||||
"version": "6.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@solid-primitives/utils/-/utils-6.2.3.tgz",
|
||||
"integrity": "sha512-CqAwKb2T5Vi72+rhebSsqNZ9o67buYRdEJrIFzRXz3U59QqezuuxPsyzTSVCacwS5Pf109VRsgCJQoxKRoECZQ==",
|
||||
"dev": true,
|
||||
"peerDependencies": {
|
||||
"solid-js": "^1.6.12"
|
||||
}
|
||||
|
@ -15,6 +15,7 @@
|
||||
"vite-plugin-solid": "^2.8.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@solid-primitives/upload": "^0.0.117",
|
||||
"@solidjs/router": "^0.13.5",
|
||||
"solid-collapse": "^1.1.0",
|
||||
"solid-icons": "^1.1.0",
|
||||
|
32
src/api.ts
32
src/api.ts
@ -1,5 +1,6 @@
|
||||
import { createSignal } from "solid-js";
|
||||
import { Accessor, createSignal } from "solid-js";
|
||||
import { localState } from "./state";
|
||||
import { UploadFile } from "@solid-primitives/upload";
|
||||
|
||||
export interface User {
|
||||
username: string,
|
||||
@ -91,7 +92,7 @@ export interface MapRequest {
|
||||
|
||||
export interface ProfileRequest {
|
||||
name: string,
|
||||
image: File,
|
||||
image: string,
|
||||
}
|
||||
|
||||
export interface PlayerTypeRequest {
|
||||
@ -195,6 +196,33 @@ export class API {
|
||||
await this.checkResponse(response);
|
||||
}
|
||||
|
||||
public async addStratType(groupId: string, stratName: string): Promise<Group> {
|
||||
const response = await fetch(this.apiURL + '/group/' + groupId + '/strat-type', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer ' + this.ensureLoggedIn().token
|
||||
},
|
||||
body: JSON.stringify(stratName)
|
||||
});
|
||||
|
||||
await this.checkResponse(response);
|
||||
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
public async removeStratType(groupId: string, stratName: string): Promise<void> {
|
||||
const response = await fetch(this.apiURL + '/group/' + groupId + '/strat-type/' + stratName, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer ' + this.ensureLoggedIn().token
|
||||
},
|
||||
});
|
||||
|
||||
await this.checkResponse(response);
|
||||
}
|
||||
|
||||
public async updateGroup(id: string, group: GroupRequest): Promise<Group> {
|
||||
const response = await fetch(this.apiURL + '/group/' + id, {
|
||||
method: 'PUT',
|
||||
|
@ -1,11 +1,13 @@
|
||||
import { Component, For } from 'solid-js';
|
||||
import { Component, For, Setter, Show } from 'solid-js';
|
||||
|
||||
import styles from './Dialog.module.css';
|
||||
import { UploadFile, FileUploader } from '@solid-primitives/upload';
|
||||
|
||||
export interface DialogButton {
|
||||
name: string,
|
||||
action?: () => void,
|
||||
type?: 'default' | 'danger' | 'success'
|
||||
type?: 'default' | 'danger' | 'success',
|
||||
closeOnClick?: boolean
|
||||
}
|
||||
|
||||
export interface DialogInputField {
|
||||
@ -13,6 +15,11 @@ export interface DialogInputField {
|
||||
onInput: (text: string) => void,
|
||||
}
|
||||
|
||||
export interface ImageInputField {
|
||||
name: string,
|
||||
onInput: () => Setter<UploadFile[]>,
|
||||
}
|
||||
|
||||
export interface DialogProps {
|
||||
title: string,
|
||||
text: string,
|
||||
@ -21,11 +28,11 @@ export interface DialogProps {
|
||||
inputFields?: DialogInputField[],
|
||||
}
|
||||
|
||||
function createButton(button: (string | DialogButton), dismiss?: () => void) {
|
||||
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?.(); dismiss?.(); }} class={styles[button.type + 'Button']}>{button.name}</button>;
|
||||
return <button onclick={() => { button.action?.(); button.closeOnClick ? dismiss?.() : () => { }; }} class={styles[button.type + 'Button']}>{button.name}</button>;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,7 @@ const MapItem: Component<MapItemProps> = (props) => {
|
||||
{
|
||||
name: 'Remove',
|
||||
type: 'danger',
|
||||
closeOnClick: true,
|
||||
action: async () => {
|
||||
try {
|
||||
await api().removeMap(props.group.id, props.map.id);
|
||||
@ -32,6 +33,7 @@ const MapItem: Component<MapItemProps> = (props) => {
|
||||
},
|
||||
{
|
||||
name: 'Cancel',
|
||||
closeOnClick: true,
|
||||
action: () => { }
|
||||
}
|
||||
]
|
||||
|
@ -21,6 +21,7 @@ const MemberItem: Component<MemberItemProps> = (props) => {
|
||||
{
|
||||
name: 'Remove',
|
||||
type: 'danger',
|
||||
closeOnClick: true,
|
||||
action: async () => {
|
||||
try {
|
||||
await api().removeMember(props.group.id, props.member.username);
|
||||
@ -32,6 +33,7 @@ const MemberItem: Component<MemberItemProps> = (props) => {
|
||||
},
|
||||
{
|
||||
name: 'Cancel',
|
||||
closeOnClick: true,
|
||||
action: () => { }
|
||||
}
|
||||
]
|
||||
|
@ -21,6 +21,7 @@ const ProfileItem: Component<ProfileItemProps> = (props) => {
|
||||
{
|
||||
name: 'Remove',
|
||||
type: 'danger',
|
||||
closeOnClick: true,
|
||||
action: async () => {
|
||||
try {
|
||||
await api().removeProfileFromGroup(props.group.id, props.profile.id);
|
||||
@ -32,6 +33,7 @@ const ProfileItem: Component<ProfileItemProps> = (props) => {
|
||||
},
|
||||
{
|
||||
name: 'Cancel',
|
||||
closeOnClick: true,
|
||||
action: () => { }
|
||||
}
|
||||
]
|
||||
|
@ -21,6 +21,7 @@ const StratTypeItem: Component<StratTypeItem> = (props) => {
|
||||
{
|
||||
name: 'Remove',
|
||||
type: 'danger',
|
||||
closeOnClick: true,
|
||||
action: async () => {
|
||||
try {
|
||||
await api().removeStratType(props.group.id, props.stratType);
|
||||
@ -32,6 +33,7 @@ const StratTypeItem: Component<StratTypeItem> = (props) => {
|
||||
},
|
||||
{
|
||||
name: 'Cancel',
|
||||
closeOnClick: true,
|
||||
action: () => { }
|
||||
}
|
||||
]
|
||||
|
@ -4,7 +4,7 @@ import { Component, Show, createEffect, createSignal } from "solid-js";
|
||||
import styles from './GroupPage.module.css'
|
||||
import { Group, Map, Profile, Strat, User, api } from "../api";
|
||||
import { showDialog, showInputDialog, showMessageDialog } from "../state";
|
||||
import { errorToString, normalize } from "../util";
|
||||
import { _arrayBufferToBase64, errorToString, normalize } from "../util";
|
||||
import { BsCheck2, BsPencil, BsTrash } from "solid-icons/bs";
|
||||
import { Collapse } from "solid-collapse";
|
||||
import MemberItem from "../components/MemberItem";
|
||||
@ -12,6 +12,7 @@ import MapItem from "../components/MapItem";
|
||||
import StratItem from "../components/StratItem";
|
||||
import StratTypeItem from "../components/StratTypeItem";
|
||||
import ProfileItem from "../components/ProfileItem";
|
||||
import { UploadFile, createFileUploader } from "@solid-primitives/upload";
|
||||
|
||||
const GroupPage: Component = () => {
|
||||
const params = useParams();
|
||||
@ -81,7 +82,56 @@ const GroupPage: Component = () => {
|
||||
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({
|
||||
title: 'Add profile to Group',
|
||||
text: 'create a new profile',
|
||||
inputFields: [{
|
||||
onInput: setProfileName,
|
||||
placeholder: 'name'
|
||||
}],
|
||||
onDismiss: async () => {
|
||||
fileReader.readAsArrayBuffer(files()[0].file);
|
||||
},
|
||||
buttons: [{
|
||||
name: 'add image', action: () => selectFiles(([{ source, name, size, file }]) => {
|
||||
console.log({ source, name, size, file });
|
||||
}),
|
||||
closeOnClick: false
|
||||
},
|
||||
'confirm'
|
||||
],
|
||||
})
|
||||
};
|
||||
|
||||
const toggleEdit = async () => {
|
||||
@ -128,31 +178,37 @@ const GroupPage: Component = () => {
|
||||
<hr />
|
||||
<div>
|
||||
<button onClick={() => setMembersIsExpanded(!membersIsExpanded())}>Members</button>
|
||||
<button class={styles.addMemberButton} onClick={addMember}>+</button>
|
||||
<Collapse value={membersIsExpanded()} class={styles.members}>
|
||||
<button class={styles.addMemberButton} onClick={addMember}>Add Member</button>
|
||||
<div class={styles.groupMembers}>
|
||||
{group().members?.map(m => <MemberItem group={group()} member={m} onDelete={onDeleteMember} />)}
|
||||
</div>
|
||||
</Collapse>
|
||||
<br />
|
||||
<button onClick={() => setStratsIsExpanded(!stratsIsExpanded())}>strats</button>
|
||||
<Collapse value={stratsIsExpanded()} class={styles.strats}>
|
||||
<div class={styles.strats}>
|
||||
{group().strats?.map(s => <StratItem group={group()} strat={s} onDelete={onDeleteStrat} />)}
|
||||
</div>
|
||||
</Collapse>
|
||||
<br />
|
||||
<button onClick={() => setMapsIsExpanded(!mapsIsExpanded())}>maps</button>
|
||||
<Collapse value={mapsIsExpanded()} class={styles.maps}>
|
||||
<div class={styles.maps}>
|
||||
{group().maps?.map(m => <MapItem group={group()} map={m} onDelete={onDeleteMap} />)}
|
||||
</div>
|
||||
</Collapse>
|
||||
<br />
|
||||
<button onClick={() => setStratTypesIsExpanded(!stratTypesIsExpanded())}>strat types</button>
|
||||
<button class={styles.addMemberButton} onClick={addStratType}>+</button>
|
||||
<Collapse value={stratTypesIsExpanded()} class={styles.stratTypes}>
|
||||
<div class={styles.stratTypes}>
|
||||
{group().stratTypes?.map(s => <StratTypeItem group={group()} stratType={s} onDelete={onDeleteStratType} />)}
|
||||
</div>
|
||||
</Collapse>
|
||||
<br />
|
||||
<button onClick={() => setProfilesIsExpanded(!profilesIsExpanded())}>profiles</button>
|
||||
<button class={styles.addMemberButton} onClick={addProfile}>+</button>
|
||||
<Collapse value={profilesIsExpanded()} class={styles.profiles}>
|
||||
<div class={styles.profiles}>
|
||||
{group().profiles?.map(p => <ProfileItem group={group()} profile={p} onDelete={onDeleteProfile} />)}
|
||||
|
13
src/state.ts
13
src/state.ts
@ -1,7 +1,7 @@
|
||||
import { createEffect, createSignal } from 'solid-js';
|
||||
import { SetStoreFunction, Store, createStore } from 'solid-js/store';
|
||||
import { AuthenticationResponse } from './api';
|
||||
import { DialogProps } from './components/Dialog';
|
||||
import { DialogInputField, DialogProps, ImageInputField } from './components/Dialog';
|
||||
|
||||
// Source: https://www.solidjs.com/examples/todos
|
||||
function createLocalStore<T extends object>(
|
||||
@ -47,7 +47,16 @@ export const showInputDialog = (title: string, message: string, placeholder: str
|
||||
title,
|
||||
text: message,
|
||||
inputFields: [{ placeholder, onInput: setText }],
|
||||
buttons: [{ name: 'Okay', action: () => callback(text()) }]
|
||||
buttons: [{ name: 'Okay', action: () => callback(text()), closeOnClick: true }]
|
||||
});
|
||||
}
|
||||
|
||||
export const showInputsDialog = async (title: string, message: string, inputFields: DialogInputField[], callback: () => void) => {
|
||||
showDialog({
|
||||
title,
|
||||
text: message,
|
||||
inputFields: inputFields,
|
||||
buttons: [{ name: 'Okay', action: () => callback(), closeOnClick: true }]
|
||||
});
|
||||
}
|
||||
|
||||
|
32
src/util.ts
32
src/util.ts
@ -36,4 +36,34 @@ export function normalizeRating(text: string) {
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user