2022-07-28 23:02:21 +01:00

445 lines
13 KiB
Svelte

<div class="parent">
<div class="content">
<Card footer footerRight>
<span slot="title">Forms</span>
<div slot="body" class="body-wrapper">
<div class="section">
<h2 class="section-title">Create New Form</h2>
<form on:submit|preventDefault={createForm}>
<div class="row" id="creation-row">
<Input placeholder="Form Title" col3={true} bind:value={newTitle}/>
<div id="create-button-wrapper">
<Button icon="fas fa-paper-plane" fullWidth={windowWidth <= 950}>Create</Button>
</div>
</div>
</form>
</div>
<div class="section">
<h2 class="section-title">Manage Forms</h2>
{#if editingTitle && activeFormId !== null}
<div class="row form-name-edit-wrapper">
<Input col4 label="Form Title" placeholder="Form Title" bind:value={renamedTitle}/>
<div class="form-name-save-wrapper">
<Button icon="fas fa-floppy-disk" fullWidth={windowWidth <= 950} on:click={updateTitle}>Save</Button>
</div>
</div>
{:else}
<div class="row form-select-row">
<div class="multiselect-super">
<Dropdown col1 bind:value={activeFormId}>
<option value={null}>Select a form...</option>
{#each forms as form}
<option value="{form.form_id}">{form.title}</option>
{/each}
</Dropdown>
</div>
{#if activeFormId !== null}
<Button on:click={() => editingTitle = true}>Rename Form</Button>
<Button danger type="button"
on:click={() => deleteForm(activeFormId)}>Delete {activeFormTitle}</Button>
{/if}
</div>
{/if}
<div class="manage">
{#if activeFormId !== null}
{#each forms.find(form => form.form_id === activeFormId).inputs as input, i (input)}
<div animate:flip="{{duration: 500}}">
<FormInputRow data={input} formId={activeFormId}
withSaveButton={true} withDeleteButton={true} withDirectionButtons={true}
index={i} {formLength}
on:delete={() => deleteInput(activeFormId, input)}
on:move={(e) => changePosition(activeFormId, input, e.detail.direction)}/>
</div>
{/each}
{/if}
{#if activeFormId !== null}
<div class="row" style="justify-content: center; align-items: center; gap: 10px; margin-top: 10px">
<hr class="fill">
<div class="row add-input-container" class:add-input-disabled={formLength >= 5}>
<i class="fas fa-plus"></i>
<a on:click={addInput}>Add Input</a>
</div>
<hr class="fill">
</div>
{/if}
</div>
</div>
</div>
<div slot="footer">
<Button type="submit" icon="fas fa-floppy-disk" disabled={formLength === 0} on:click={saveInputs}>
Save
</Button>
</div>
</Card>
</div>
</div>
<svelte:window bind:innerWidth={windowWidth}/>
<script>
import Card from "../components/Card.svelte";
import {notifyError, notifySuccess, nullIfBlank, withLoadingScreen} from '../js/util'
import Button from "../components/Button.svelte";
import axios from "axios";
import {API_URL} from "../js/constants";
import {setDefaultHeaders} from '../includes/Auth.svelte'
import Input from "../components/form/Input.svelte";
import Dropdown from "../components/form/Dropdown.svelte";
import FormInputRow from "../components/manage/FormInputRow.svelte";
import {flip} from "svelte/animate";
export let currentRoute;
let guildId = currentRoute.namedParams.id;
let defaultTeam = {id: 'default', name: 'Default'};
let newTitle;
let forms = [];
let toDelete = {};
let activeFormId = null;
$: activeFormTitle = activeFormId !== null ? forms.find(f => f.form_id === activeFormId).title : 'Unknown';
$: formLength = activeFormId !== null ? forms.find(f => f.form_id === activeFormId).inputs.length : 0;
let editingTitle = false;
let renamedTitle = "";
$: activeFormId, reflectTitle();
function reflectTitle() {
renamedTitle = activeFormId !== null ? forms.find(f => f.form_id === activeFormId).title : null;
}
$: windowWidth = 0;
function getForm(formId) {
return forms.find(form => form.form_id === formId);
}
async function updateTitle() {
const res = await axios.patch(`${API_URL}/api/${guildId}/forms/${activeFormId}`, {title: renamedTitle});
if (res.status !== 200) {
notifyError('Failed to update form title');
return;
}
editingTitle = false;
getForm(activeFormId).title = renamedTitle;
forms = forms;
notifySuccess('Form title updated');
}
async function createForm() {
let data = {
title: newTitle,
};
const res = await axios.post(`${API_URL}/api/${guildId}/forms`, data);
if (res.status !== 200) {
notifyError(res.data.error);
return;
}
notifySuccess(`Form ${newTitle} has been created`);
newTitle = '';
let form = res.data;
form.inputs = [];
activeFormId = null; // Error thrown from {#each forms.find} if we don't temporarily set this to null?
forms = [...forms, form];
activeFormId = form.form_id;
addInput();
}
async function deleteForm(id) {
const res = await axios.delete(`${API_URL}/api/${guildId}/forms/${id}`);
if (res.status !== 200) {
notifyError(res.data.error);
return;
}
notifySuccess(`Form deleted successfully`);
forms = forms.filter(form => form.form_id !== id);
if (forms.length > 0) {
activeFormId = forms[0].form_id;
} else {
activeFormId = null;
}
}
function addInput() {
const form = getForm(activeFormId);
if (form.inputs.length >= 5) return;
const input = {
form_id: activeFormId,
position: form.inputs.length + 1,
style: "1",
label: "",
placeholder: "",
required: true,
is_new: true,
};
form.inputs = [...form.inputs, input];
forms = forms;
}
async function editInput(formId, inputId, data) {
let mapped = {...data, style: parseInt(data.style)};
const res = await axios.patch(`${API_URL}/api/${guildId}/forms/${formId}/${inputId}`, mapped);
if (res.status !== 200) {
notifyError(res.data.error);
return;
}
let form = getForm(formId);
form.inputs = form.inputs.filter(input => input.id !== inputId);
form.inputs = [...form.inputs, res.data];
notifySuccess('Form input updated successfully');
}
async function deleteInput(formId, input) {
let form = getForm(formId);
let idx = form.inputs.findIndex((i) => i === input);
form.inputs.splice(idx, 1);
for (let i = idx; i < form.inputs.length; i++) {
form.inputs[i].position--;
}
forms = forms;
if (!input.is_new) {
if (toDelete[formId] === undefined) {
toDelete[formId] = [];
}
toDelete[formId] = [...toDelete[formId], input.id];
}
}
function changePosition(formId, input, direction) {
const form = getForm(formId);
let idx = form.inputs.findIndex((i) => i === input);
let inputs = form.inputs;
if (direction === "up") {
[inputs[idx - 1].position, inputs[idx].position] = [inputs[idx].position, inputs[idx - 1].position];
[inputs[idx - 1], inputs[idx]] = [inputs[idx], inputs[idx - 1]];
} else if (direction === "down") {
[inputs[idx + 1].position, inputs[idx].position] = [form.inputs[idx].position, form.inputs[idx + 1].position];
[inputs[idx + 1], inputs[idx]] = [form.inputs[idx], form.inputs[idx + 1]];
}
forms = forms;
}
async function saveInputs() {
const form = getForm(activeFormId);
const data = {
"create": form.inputs.filter(i => i.is_new === true)
.map(i => ({...i, style: parseInt(i.style), placeholder: nullIfBlank(i.placeholder)})),
"update": form.inputs.filter(i => !i.is_new)
.map(i => ({...i, style: parseInt(i.style), placeholder: nullIfBlank(i.placeholder)})),
"delete": toDelete[activeFormId] || [],
}
const res = await axios.patch(`${API_URL}/api/${guildId}/forms/${activeFormId}/inputs`, data);
if (res.status !== 204) {
notifyError(res.data.error);
return;
}
toDelete = {};
const formId = activeFormId;
await loadForms();
activeFormId = formId;
notifySuccess('Form updated successfully');
}
async function loadForms() {
const res = await axios.get(`${API_URL}/api/${guildId}/forms`);
if (res.status !== 200) {
notifyError(res.data.error);
return;
}
forms = res.data || [];
forms.flatMap(f => f.inputs).forEach(i => {
i.style = i.style.toString();
});
if (forms.length > 0) {
activeFormId = forms[0].form_id;
}
}
withLoadingScreen(async () => {
setDefaultHeaders();
await loadForms();
});
</script>
<style>
.parent {
display: flex;
justify-content: center;
width: 100%;
height: 100%;
}
.content {
display: flex;
justify-content: space-between;
width: 96%;
height: 100%;
margin-top: 30px;
margin-bottom: 50px;
}
.body-wrapper {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
padding: 1%;
}
.section {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
}
.section:not(:first-child) {
margin-top: 2%;
}
.section-title {
font-size: 36px;
font-weight: bolder !important;
}
h3 {
font-size: 28px;
margin-bottom: 4px;
}
hr.fill {
border-top: 1px solid #777;
border-bottom: 0;
border-left: 0;
border-right: 0;
flex: 1;
}
.row {
display: flex;
flex-direction: row;
justify-content: space-between;
width: 100%;
height: 100%;
}
.form-select-row {
justify-content: flex-start;
gap: 12px;
max-height: 40px;
}
.form-name-edit-wrapper {
justify-content: flex-start;
gap: 12px;
}
.form-name-save-wrapper {
height: 48px;
align-self: flex-end;
}
.multiselect-super {
width: 31%;
}
.manage {
display: flex;
flex-direction: column;
justify-content: space-between;
width: 100%;
height: 100%;
margin-top: 12px;
}
.add-input-container {
display: flex;
align-items: center;
gap: 4px;
width: unset !important;
}
.add-input-container > * {
cursor: pointer;
}
.add-input-disabled > *{
cursor: default !important;
color: #777 !important;
}
#creation-row {
justify-content: flex-start !important;
}
#create-button-wrapper {
margin-left: 15px;
height: 40px;
}
@media only screen and (max-width: 950px) {
.manage {
flex-direction: column;
}
.row {
flex-direction: column;
}
#create-button-wrapper {
margin-left: unset;
}
.form-select-row {
max-height: unset;
gap: 8px;
}
.multiselect-super {
width: 100%;
}
.form-name-edit-wrapper {
gap: unset;
}
.form-name-save-wrapper {
width: 100%;
}
}
</style>