added upload image to dialog

This commit is contained in:
Akito123321 2024-06-24 13:11:20 +02:00
parent 34f38e90d5
commit 4aae0c1724
11 changed files with 162 additions and 12 deletions

13
package-lock.json generated
View File

@ -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"
}

View File

@ -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",

View File

@ -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',

View File

@ -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>;
}
}

View File

@ -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: () => { }
}
]

View File

@ -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: () => { }
}
]

View File

@ -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: () => { }
}
]

View File

@ -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: () => { }
}
]

View File

@ -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} />)}

View File

@ -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 }]
});
}

View File

@ -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;
}