From 4aae0c1724a5a4d147334308953de3e0254776f9 Mon Sep 17 00:00:00 2001 From: Akito123321 Date: Mon, 24 Jun 2024 13:11:20 +0200 Subject: [PATCH] added upload image to dialog --- package-lock.json | 13 ++++++- package.json | 1 + src/api.ts | 32 +++++++++++++++-- src/components/Dialog.tsx | 15 +++++--- src/components/MapItem.tsx | 2 ++ src/components/MemberItem.tsx | 2 ++ src/components/ProfileItem.tsx | 2 ++ src/components/StratTypeItem.tsx | 2 ++ src/pages/GroupPage.tsx | 60 ++++++++++++++++++++++++++++++-- src/state.ts | 13 +++++-- src/util.ts | 32 ++++++++++++++++- 11 files changed, 162 insertions(+), 12 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2ad4bfa..4febc97 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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" } diff --git a/package.json b/package.json index e477655..8b5f863 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/api.ts b/src/api.ts index b98e50c..529f459 100644 --- a/src/api.ts +++ b/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 { + 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 { + 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 { const response = await fetch(this.apiURL + '/group/' + id, { method: 'PUT', diff --git a/src/components/Dialog.tsx b/src/components/Dialog.tsx index dcbadef..308ab2e 100644 --- a/src/components/Dialog.tsx +++ b/src/components/Dialog.tsx @@ -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, +} + 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 ; } else { - return ; + return ; } } diff --git a/src/components/MapItem.tsx b/src/components/MapItem.tsx index 5a5b1d4..cf9f0b7 100644 --- a/src/components/MapItem.tsx +++ b/src/components/MapItem.tsx @@ -21,6 +21,7 @@ const MapItem: Component = (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 = (props) => { }, { name: 'Cancel', + closeOnClick: true, action: () => { } } ] diff --git a/src/components/MemberItem.tsx b/src/components/MemberItem.tsx index 73e6477..f7139b7 100644 --- a/src/components/MemberItem.tsx +++ b/src/components/MemberItem.tsx @@ -21,6 +21,7 @@ const MemberItem: Component = (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 = (props) => { }, { name: 'Cancel', + closeOnClick: true, action: () => { } } ] diff --git a/src/components/ProfileItem.tsx b/src/components/ProfileItem.tsx index a6f9d8d..065b7cf 100644 --- a/src/components/ProfileItem.tsx +++ b/src/components/ProfileItem.tsx @@ -21,6 +21,7 @@ const ProfileItem: Component = (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 = (props) => { }, { name: 'Cancel', + closeOnClick: true, action: () => { } } ] diff --git a/src/components/StratTypeItem.tsx b/src/components/StratTypeItem.tsx index 075f9dc..59f9692 100644 --- a/src/components/StratTypeItem.tsx +++ b/src/components/StratTypeItem.tsx @@ -21,6 +21,7 @@ const StratTypeItem: Component = (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 = (props) => { }, { name: 'Cancel', + closeOnClick: true, action: () => { } } ] diff --git a/src/pages/GroupPage.tsx b/src/pages/GroupPage.tsx index 885098f..98c46e5 100644 --- a/src/pages/GroupPage.tsx +++ b/src/pages/GroupPage.tsx @@ -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 = () => {
+ -
{group().members?.map(m => )}
+
{group().strats?.map(s => )}
+
{group().maps?.map(m => )}
+
+
{group().stratTypes?.map(s => )}
+
+
{group().profiles?.map(p => )} diff --git a/src/state.ts b/src/state.ts index 4e29dcb..bbc371c 100644 --- a/src/state.ts +++ b/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( @@ -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 }] }); } diff --git a/src/util.ts b/src/util.ts index 1c6377f..9f1a5ee 100644 --- a/src/util.ts +++ b/src/util.ts @@ -36,4 +36,34 @@ export function normalizeRating(text: string) { } return clamp(rating, 0, 5); -} \ No newline at end of file +} + +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; +}