Skip to content

Commit

Permalink
172 implement create section (#174)
Browse files Browse the repository at this point in the history
One should now be able to add sections and sub sections
One should also now be able to delete sections
  • Loading branch information
noremacskich authored Oct 23, 2024
1 parent ae5bc38 commit 4959284
Show file tree
Hide file tree
Showing 9 changed files with 258 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public void Configure(EntityTypeBuilder<ExpressionSection> builder)
builder.ToTable("ExpressionSections");

builder.HasKey(e => e.Id);
builder.Property(e => e.Id).IsRequired();
builder.Property(e => e.Id).IsRequired().ValueGeneratedOnAdd();

builder.HasQueryFilter(x => !x.IsDeleted);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,11 +135,11 @@ public async Task<Result<int>> EditExpressionTextSectionAsync(EditExpressionText
return Result.Ok(section.Id);
}

public async Task<Result> DeleteExpressionTextSectionAsync(int id)
public async Task<Result> DeleteExpressionTextSectionAsync(int expressionId, int id)
{
var section = await context
.ExpressionSections.IgnoreQueryFilters()
.FirstOrDefaultAsync(x => x.Id == id);
.FirstOrDefaultAsync(x => x.ExpressionId == expressionId && x.Id == id);

if (section is null)
return Result.Fail(new NotFoundFailure("Expression Section"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public interface IExpressionTextSectionRepository
{
Task<Result<int>> CreateExpressionTextSectionAsync(CreateExpressionTextSectionDto dto);
Task<Result<int>> EditExpressionTextSectionAsync(EditExpressionTextSectionDto dto);
Task<Result> DeleteExpressionTextSectionAsync(int id);
Task<Result> DeleteExpressionTextSectionAsync(int expressionId, int id);
Task<Result<GetExpressionTextSectionDto>> GetExpressionTextSection(int sectionId);
Task<List<ExpressionSectionDto>> GetExpressionTextSections(int expressionId);
Task<Result<ExpressionTextSectionOptions>> GetExpressionTextSectionOptions(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using ExpressedRealms.Authentication;
using ExpressedRealms.Repositories.Expressions.Expressions.DTOs;
using ExpressedRealms.Repositories.Expressions.ExpressionTextSections;
using ExpressedRealms.Repositories.Expressions.ExpressionTextSections.DTOs;
using ExpressedRealms.Server.EndPoints.CharacterEndPoints;
Expand Down Expand Up @@ -86,7 +87,7 @@ IExpressionTextSectionRepository repository
endpointGroup
.MapGet(
"{expressionId}/{sectionId}/options",
async Task<Results<NotFound, Ok<ExpressionSectionOptionsResponse>>> (
async Task<Results<ValidationProblem, Ok<ExpressionSectionOptionsResponse>>> (
int expressionId,
int sectionId,
IExpressionTextSectionRepository repository
Expand All @@ -100,8 +101,8 @@ IExpressionTextSectionRepository repository
}
);

if (optionsResult.HasNotFound(out var notFound))
return notFound;
if (optionsResult.HasValidationError(out var validationProblem))
return validationProblem;
optionsResult.ThrowIfErrorNotHandled();

return TypedResults.Ok(
Expand Down Expand Up @@ -155,5 +156,61 @@ IExpressionTextSectionRepository repository
}
)
.RequirePolicyAuthorization(Policies.ExpressionEditorPolicy);

endpointGroup
.MapPost(
"{expressionId}",
async Task<Results<NotFound, ValidationProblem, Created<int>>> (
int expressionId,
CreateExpressionSubSectionTextRequest request,
IExpressionTextSectionRepository repository
) =>
{
var results = await repository.CreateExpressionTextSectionAsync(
new CreateExpressionTextSectionDto()
{
ExpressionId = expressionId,
Name = request.Name,
Content = request.Content,
SectionTypeId = request.SectionTypeId,
ParentId = request.ParentId
}
);

if (results.HasNotFound(out var notFound))
return notFound;
if (results.HasValidationError(out var validationProblem))
return validationProblem;
results.ThrowIfErrorNotHandled();

return TypedResults.Created("/", results.Value);
}
)
.RequirePolicyAuthorization(Policies.ExpressionEditorPolicy);

endpointGroup
.MapDelete(
"{expressionId}/{sectionId}",
async Task<Results<NotFound, StatusCodeHttpResult, NoContent>> (
int expressionId,
int sectionId,
IExpressionTextSectionRepository repository
) =>
{
var results = await repository.DeleteExpressionTextSectionAsync(
expressionId,
sectionId
);

if (results.HasNotFound(out var notFound))
return notFound;
if (results.HasBeenDeletedAlready(out var deletedAlready))
return deletedAlready;
results.ThrowIfErrorNotHandled();

return TypedResults.NoContent();
}
)
.RequirePolicyAuthorization(Policies.ExpressionEditorPolicy);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace ExpressedRealms.Server.EndPoints.ExpressionEndpoints.Requests;

public class CreateExpressionSubSectionTextRequest
{
public string Name { get; set; } = null!;
public string Content { get; set; } = null!;
public int SectionTypeId { get; set; }
public int? ParentId { get; set; }
}
115 changes: 115 additions & 0 deletions client/src/components/expressions/CreateExpressionSection.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
<script setup lang="ts">
import Button from "primevue/button";
import {onMounted, ref} from "vue";
import axios from "axios";
import {useForm} from "vee-validate";
import {object, string} from "yup";
import InputTextWrapper from "@/FormWrappers/InputTextWrapper.vue";
import DropdownWrapper from "@/FormWrappers/DropdownWrapper.vue";
import { expressionStore } from "@/stores/expressionStore";
import EditorWrapper from "@/FormWrappers/EditorWrapper.vue";
import toaster from "@/services/Toasters";
const expressionInfo = expressionStore();
const emit = defineEmits<{
cancelEvent: [],
addedSection: []
}>();
const props = defineProps({
parentId: {
type: Number
},
});
onMounted(() => {
loadSectionInfo();
})
const showOptionLoader = ref(true);
const sectionTypeOptions = ref([]);
function cancelEdit(){
emit("cancelEvent");
}
function reset(){
showOptionLoader.value = true;
loadSectionInfo();
}
function loadSectionInfo(){
if(!showOptionLoader.value) return; // Don't load in 2nd time
console.log('expressionid' + expressionInfo.currentExpressionId);
axios.get(`/expressionSubSections/${expressionInfo.currentExpressionId}/0/options`)
.then(async (response) => {
sectionTypeOptions.value = response.data.sectionTypes;
showOptionLoader.value = false;
});
}
const { defineField, handleSubmit, errors } = useForm({
validationSchema: object({
name: string().required()
.label('Name'),
content: string()
.required()
.label('Content'),
sectionType: object().nullable()
.label('Section Type')
})
});
const [name] = defineField('name');
const [content] = defineField('content');
const [sectionType] = defineField('sectionType');
const onSubmit = handleSubmit((values) => {
axios.post(`/expressionSubSections/${expressionInfo.currentExpressionId}`, {
name: values.name,
content: values.content,
sectionTypeId: values.sectionType.id,
parentId: props.parentId
}).then(() => {
emit("addedSection");
toaster.success("Successfully Added Expression Section Info!");
cancelEdit();
});
});
</script>

<template>
<div class="m-2">
<form @submit="onSubmit">
<InputTextWrapper v-model="name" field-name="Name" :error-text="errors.name" />
<EditorWrapper v-model="content" field-name="Content" :error-text="errors.content" />
<DropdownWrapper
v-model="sectionType" option-label="name" :options="sectionTypeOptions" field-name="Section Types" :show-skeleton="showOptionLoader"
:error-text="errors.sectionType"
/>
<div class="flex">
<div class="col-flex flex-grow-1">
<div class="float-end">
<Button label="Reset" class="m-2" @click="reset()" />
<Button label="Cancel" class="m-2" @click="cancelEdit()" />
<Button label="Add" class="m-2" @click="onSubmit" />
</div>
</div>
</div>
</form>
</div>
</template>

<style>
div.ql-editor > p {
font-family: var(--font-family);
font-feature-settings: var(--font-feature-settings, normal);
font-size: 1rem;
font-weight: normal;
margin-top: 1em;
margin-bottom: 1em;
}
</style>
45 changes: 45 additions & 0 deletions client/src/components/expressions/EditExpressionSection.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,14 @@ import DropdownWrapper from "@/FormWrappers/DropdownWrapper.vue";
import { expressionStore } from "@/stores/expressionStore";
import EditorWrapper from "@/FormWrappers/EditorWrapper.vue";
import toaster from "@/services/Toasters";
import CreateExpressionSection from "@/components/expressions/CreateExpressionSection.vue";
import {useConfirm} from "primevue/useconfirm";
const expressionInfo = expressionStore();
const emit = defineEmits<{
refreshList: []
}>();
const props = defineProps({
sectionInfo: {
type: Object,
Expand All @@ -37,12 +43,17 @@ const props = defineProps({
const showEditor = ref(false);
const showOptionLoader = ref(true);
const sectionTypeOptions = ref([]);
const showCreate = ref(false);
function toggleEditor(){
showEditor.value = !showEditor.value;
loadSectionInfo();
}
function passThroughAddedSection(){
emit("refreshList");
}
function cancelEdit(){
showEditor.value = !showEditor.value;
}
Expand Down Expand Up @@ -88,6 +99,10 @@ const [content] = defineField('content');
const [parentSection] = defineField('parentSection');
const [sectionType] = defineField('sectionType');
function toggleCreate(){
showCreate.value = !showCreate.value;
}
const onSubmit = handleSubmit((values) => {
axios.put(`/expressionSubSections/${expressionInfo.currentExpressionId}/${props.sectionInfo.id}`, {
name: values.name,
Expand All @@ -101,6 +116,31 @@ const onSubmit = handleSubmit((values) => {
});
});
const confirm = useConfirm();
const deleteExpression = (event) => {
confirm.require({
target: event.currentTarget,
header: 'Deleting Section',
message: `Are you sure you want delete ${props.sectionInfo.name} section? This will delete this section and any sub children`,
icon: 'pi pi-exclamation-triangle',
rejectProps: {
label: 'Cancel',
severity: 'secondary',
outlined: true
},
acceptProps: {
label: 'Save'
},
accept: () => {
axios.delete(`/expressionSubSections/${expressionInfo.currentExpressionId}/${props.sectionInfo.id}`).then(() => {
emit('refreshList');
toaster.success(`Successfully Deleted Section ${props.sectionInfo.name}!`);
});
},
reject: () => {}
});
};
</script>

<template>
Expand All @@ -118,6 +158,7 @@ const onSubmit = handleSubmit((values) => {
/>
<div class="flex">
<div class="col-flex flex-grow-1">
<Button severity="danger" label="Delete" class="m-2" @click="deleteExpression($event)" />
<div class="float-end">
<Button label="Reset" class="m-2" @click="reset()" />
<Button label="Cancel" class="m-2" @click="cancelEdit()" />
Expand Down Expand Up @@ -150,11 +191,15 @@ const onSubmit = handleSubmit((values) => {
</h6>
</div>
<div class="col-flex">
<Button v-if="showEdit" label="Add Child Section" class="m-2" @click="toggleCreate" />
<Button v-if="!showEditor && showEdit" label="Edit" class="float-end m-2" @click="toggleEditor()" />
</div>
</div>
<div class="mb-2" v-html="props.sectionInfo.content" />
</div>
<div v-if="showCreate && showEdit">
<CreateExpressionSection :parent-id="props.sectionInfo.id" @cancel-event="toggleCreate" @added-section="passThroughAddedSection()" />
</div>
</template>

<style>
Expand Down
13 changes: 12 additions & 1 deletion client/src/components/expressions/ExpressionBase.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import Card from "primevue/card";
import ExpressionToC from "@/components/expressions/ExpressionToC.vue";
import Skeleton from 'primevue/skeleton';
import ScrollTop from 'primevue/scrolltop';
import CreateExpressionSection from "@/components/expressions/CreateExpressionSection.vue";
import Button from "primevue/button";
let sections = ref([
{
Expand All @@ -37,6 +39,7 @@ let sections = ref([
]);
const isLoading = ref(true);
const showEdit = ref(false);
const showCreate = ref(false);
function fetchData(name: string) {
axios.get(`/expressionSubSections/${name}`)
Expand All @@ -52,6 +55,10 @@ function fetchData(name: string) {
});
}
function toggleCreate(){
showCreate.value = !showCreate.value;
}
onMounted(() =>{
fetchData(route.params.name);
})
Expand Down Expand Up @@ -87,7 +94,11 @@ onBeforeRouteUpdate(async (to, from) => {
</template>
<template #content>
<article id="expression-body">
<ExpressionSection :sections="sections" :current-level="1" :show-skeleton="isLoading" :show-edit="showEdit" />
<ExpressionSection :sections="sections" :current-level="1" :show-skeleton="isLoading" :show-edit="showEdit" @refresh-list="fetchData(route.params.name)" />
<Button v-if="showEdit" label="Add Section" class="m-2" @click="toggleCreate" />
<div v-if="showCreate">
<CreateExpressionSection @cancel-event="toggleCreate" @added-section="fetchData(route.params.name)" />
</div>
</article>
</template>
</Card>
Expand Down
Loading

0 comments on commit 4959284

Please sign in to comment.