Add logic for (de)serializing pages

This commit is contained in:
Garrett Mills 2022-04-10 03:59:37 -05:00
parent ac4a5e8055
commit bcd7628c0a
5 changed files with 171 additions and 13 deletions

View File

@ -63,7 +63,7 @@ Dark.set(true)
<span style="font-family: 'Cinzel Decorative', cursive;">
Crystal Math Worktable
</span>
<q-icon icon="fa-solid fa-arrow-up-left-from-circle" v-if="status" @click="logout()" label="Logout" />
<q-btn icon="fa-solid fa-arrow-up-left-from-circle" v-if="status" @click="logout()" label="Logout" />
</q-toolbar-title>
</q-toolbar>

View File

@ -18,7 +18,12 @@ import { stepX, stepY } from '../support/const'
import { checkLoggedIn, loggedOut } from '../support/auth'
import router from '../router'
const math = new MathPage(uuidv4());
const props = defineProps<{
pageId?: number,
}>()
const math = ref(new MathPage(uuidv4()));
const pageTitle = ref('New Page');
const statements = ref<MathStatement[]>([]);
const evaluation = ref<EvaluationResult | undefined>();
const statementsKey = ref<string>(uuidv4());
@ -99,9 +104,9 @@ const openEditFunctionModal = () => {
}
const updateStatements = () => {
statements.value = math.getStatements();
statements.value = math.value.getStatements();
try {
evaluation.value = math.evaluate()
evaluation.value = math.value.evaluate()
const variableValues: ({ name: string, value: string })[] = []
for (const name in evaluation.value!.variables) {
@ -124,7 +129,7 @@ const updateStatements = () => {
variableListingRows.value = variableValues
const functionValues: ({ name: string, value: string })[] = []
for (const stmt of math.functions()) {
for (const stmt of math.value.functions()) {
const node = stmt.parse() as math.FunctionAssignmentNode
functionValues.push({
name: node.name,
@ -145,19 +150,19 @@ onMounted(() => {
})
const saveNewVariable = (stmt: MathStatement) => {
math.addStatement(stmt)
math.value.addStatement(stmt)
newVariableModalOpen.value = false
updateStatements()
};
const saveNewExpression = (stmt: MathStatement) => {
math.addStatement(stmt)
math.value.addStatement(stmt)
newExpressionModalOpen.value = false
updateStatements()
};
const saveNewFunction = (stmt: MathStatement) => {
math.addStatement(stmt)
math.value.addStatement(stmt)
newFunctionModalOpen.value = false
updateStatements()
}
@ -175,7 +180,7 @@ const editStatement = (stmt: MathStatement) => {
}
const removeStatement = (stmt: MathStatement) => {
math.removeStatement(stmt.id)
math.value.removeStatement(stmt.id)
updateStatements()
}
@ -323,6 +328,79 @@ onMounted(() => {
}
})
const serialize = () => {
return {
title: pageTitle.value,
page: math.value.serialize(),
textBoxes: richTextStatements.value.map(x => x.serialize()),
chartBoxes: chartBoxes.value.map(x => x.serialize()),
imageBoxes: images.value.map(x => x.serialize()),
}
}
const editorPageId = ref<number|undefined>()
const saveEditorPage = async () => {
const params = {
serialData: JSON.stringify(serialize()),
} as any
if ( editorPageId.value ) {
params.pageId = editorPageId.value
}
const response = await fetch('/api/editor/page', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(params),
})
const result = (await response.json()) as unknown as any
if (!result.success) {
return alert('Failed to save page: ' + result.message)
}
editorPageId.value = result.data.pageId
}
const loadEditorPage = async () => {
if ( !editorPageId.value ) {
return
}
const pageId = editorPageId.value
const response = await fetch(`/api/editor/page?pageId=${pageId}`, {
method: 'GET',
headers: {
'Accept': 'application/json',
},
})
const result = (await response.json()) as unknown as any
if (!result.success) {
return alert('Failed to load page: ' + result.message)
}
const serialData = result.data.serialData
const serialized = JSON.parse(serialData) as any
math.value = MathPage.deserialize(serialized.page)
richTextStatements.value = serialized.textBoxes.map((box: any) => RichTextBox.deserialize(box))
chartBoxes.value = serialized.chartBoxes.map((box: any) => ChartBox.deserialize(box))
images.value = serialized.imageBoxes.map((box: any) => ImageContainer.deserialize(box))
pageTitle.value = serialized.title ?? 'New Page'
updateStatements()
chartBoxKey.value = uuidv4()
}
onMounted(() => {
if ( props.pageId ) {
editorPageId.value = props.pageId
loadEditorPage()
}
})
</script>
<template>
@ -335,7 +413,9 @@ onMounted(() => {
<q-avatar size="50px">
<img src="../assets/l2.svg" />
</q-avatar>
<span style="font-family: 'Cinzel Decorative', cursive;">Crystal Math Worktable</span>
<span style="font-family: 'Cinzel Decorative', cursive;">
Crystal Math Worktable
</span>
</q-toolbar-title>
<span v-if="status" @click="logout()" label="Logout">Logout</span>
@ -344,6 +424,13 @@ onMounted(() => {
<q-drawer show-if-above v-model="leftDrawerOpen" side="left" bordered>
<div class="column" style="height: 100%">
<div>
<q-input
v-model="pageTitle"
label="Title"
style="padding: 10px"
></q-input>
</div>
<div class="col">
<q-table
flat
@ -394,9 +481,7 @@ onMounted(() => {
<!-- drawer content -->
</q-drawer>
<q-page-container id="editor" style="padding=0">
<!-- <WrapperBox />-->
<q-page-container id="editor" style='padding: 0'>
<span v-for="(chartBox, index) in chartBoxes" style="display: flex">
<RangeChart
:fn="math.getFunctionByNameOrFail(chartBox.fnName)"
@ -463,6 +548,10 @@ onMounted(() => {
/>
</q-dialog>
<q-page-sticky position="top-right" :offset="[18, 18]">
<q-btn fab icon="save" color="primary" @click="() => saveEditorPage()"/>
</q-page-sticky>
<q-page-sticky position="bottom-right" :offset="[32, 32]">
<q-fab color="primary" icon="add" direction="left">
<q-fab-action

View File

@ -11,11 +11,28 @@ export class MathPage {
/** The statements on the page. */
protected statements: Record<StatementID, MathStatement> = {}
static deserialize(vals: [string, [StatementID, string, number, number][]]) {
const inst = new this(vals[0])
for ( const row of vals[1] ) {
inst.statements[row[0]] = MathStatement.deserialize(row)
}
return inst
}
constructor(
/** Unique page ID. */
public readonly id: string,
) {}
serialize(): [string, [StatementID, string, number, number][]] {
return [
this.id,
Object.values(this.statements).map(x => x.serialize()),
]
}
/** Get all defined statements. */
getStatements(): MathStatement[] {
return Object.values(this.statements)

View File

@ -264,6 +264,10 @@ export class MathStatement {
return new MathStatement(uuidv4() as StatementID, raw)
}
static deserialize(vals: [StatementID, string, number, number]) {
return new this(...vals)
}
constructor(
/** Unique ID of this statement. */
public readonly id: StatementID,
@ -278,6 +282,15 @@ export class MathStatement {
public y: number = 0,
) {}
serialize(): [StatementID, string, number, number] {
return [
this.id,
this.raw,
this.x,
this.y,
]
}
/** Parse the raw statement to an AST. */
parse(): math.MathNode {
return math.parse(this.raw)

View File

@ -94,24 +94,52 @@ export interface EvaluationResult {
export class RichTextBox {
static deserialize(vals: [string, number, number]) {
return new this(...vals)
}
constructor(
public text: string = '',
public x: number = 0,
public y: number = 0,
) {}
serialize(): [string, number, number] {
return [
this.text,
this.x,
this.y,
]
}
}
export class ImageContainer {
static deserialize(vals: [string, number, number]) {
return new this(...vals)
}
constructor(
public url: string = '',
public x: number = 0,
public y: number = 0,
) {}
serialize(): [string, number, number] {
return [
this.url,
this.x,
this.y,
]
}
}
export class ChartBox {
static deserialize(vals: [string, number, number, number, number, number]) {
return new this(...vals)
}
// eslint-disable-next-line max-params
constructor(
public fnName: string,
@ -121,6 +149,17 @@ export class ChartBox {
public x: number = 0,
public y: number = 0,
) {}
serialize(): [string, number, number, number, number, number] {
return [
this.fnName,
this.minX,
this.maxX,
this.stepX,
this.x,
this.y,
]
}
}