Add logic for (de)serializing pages
This commit is contained in:
parent
ac4a5e8055
commit
bcd7628c0a
@ -63,7 +63,7 @@ Dark.set(true)
|
|||||||
<span style="font-family: 'Cinzel Decorative', cursive;">
|
<span style="font-family: 'Cinzel Decorative', cursive;">
|
||||||
Crystal Math Worktable
|
Crystal Math Worktable
|
||||||
</span>
|
</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-title>
|
||||||
</q-toolbar>
|
</q-toolbar>
|
||||||
|
|
||||||
|
@ -18,7 +18,12 @@ import { stepX, stepY } from '../support/const'
|
|||||||
import { checkLoggedIn, loggedOut } from '../support/auth'
|
import { checkLoggedIn, loggedOut } from '../support/auth'
|
||||||
import router from '../router'
|
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 statements = ref<MathStatement[]>([]);
|
||||||
const evaluation = ref<EvaluationResult | undefined>();
|
const evaluation = ref<EvaluationResult | undefined>();
|
||||||
const statementsKey = ref<string>(uuidv4());
|
const statementsKey = ref<string>(uuidv4());
|
||||||
@ -99,9 +104,9 @@ const openEditFunctionModal = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const updateStatements = () => {
|
const updateStatements = () => {
|
||||||
statements.value = math.getStatements();
|
statements.value = math.value.getStatements();
|
||||||
try {
|
try {
|
||||||
evaluation.value = math.evaluate()
|
evaluation.value = math.value.evaluate()
|
||||||
const variableValues: ({ name: string, value: string })[] = []
|
const variableValues: ({ name: string, value: string })[] = []
|
||||||
|
|
||||||
for (const name in evaluation.value!.variables) {
|
for (const name in evaluation.value!.variables) {
|
||||||
@ -124,7 +129,7 @@ const updateStatements = () => {
|
|||||||
variableListingRows.value = variableValues
|
variableListingRows.value = variableValues
|
||||||
|
|
||||||
const functionValues: ({ name: string, value: string })[] = []
|
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
|
const node = stmt.parse() as math.FunctionAssignmentNode
|
||||||
functionValues.push({
|
functionValues.push({
|
||||||
name: node.name,
|
name: node.name,
|
||||||
@ -145,19 +150,19 @@ onMounted(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const saveNewVariable = (stmt: MathStatement) => {
|
const saveNewVariable = (stmt: MathStatement) => {
|
||||||
math.addStatement(stmt)
|
math.value.addStatement(stmt)
|
||||||
newVariableModalOpen.value = false
|
newVariableModalOpen.value = false
|
||||||
updateStatements()
|
updateStatements()
|
||||||
};
|
};
|
||||||
|
|
||||||
const saveNewExpression = (stmt: MathStatement) => {
|
const saveNewExpression = (stmt: MathStatement) => {
|
||||||
math.addStatement(stmt)
|
math.value.addStatement(stmt)
|
||||||
newExpressionModalOpen.value = false
|
newExpressionModalOpen.value = false
|
||||||
updateStatements()
|
updateStatements()
|
||||||
};
|
};
|
||||||
|
|
||||||
const saveNewFunction = (stmt: MathStatement) => {
|
const saveNewFunction = (stmt: MathStatement) => {
|
||||||
math.addStatement(stmt)
|
math.value.addStatement(stmt)
|
||||||
newFunctionModalOpen.value = false
|
newFunctionModalOpen.value = false
|
||||||
updateStatements()
|
updateStatements()
|
||||||
}
|
}
|
||||||
@ -175,7 +180,7 @@ const editStatement = (stmt: MathStatement) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const removeStatement = (stmt: MathStatement) => {
|
const removeStatement = (stmt: MathStatement) => {
|
||||||
math.removeStatement(stmt.id)
|
math.value.removeStatement(stmt.id)
|
||||||
updateStatements()
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -335,7 +413,9 @@ onMounted(() => {
|
|||||||
<q-avatar size="50px">
|
<q-avatar size="50px">
|
||||||
<img src="../assets/l2.svg" />
|
<img src="../assets/l2.svg" />
|
||||||
</q-avatar>
|
</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>
|
</q-toolbar-title>
|
||||||
|
|
||||||
<span v-if="status" @click="logout()" label="Logout">Logout</span>
|
<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>
|
<q-drawer show-if-above v-model="leftDrawerOpen" side="left" bordered>
|
||||||
<div class="column" style="height: 100%">
|
<div class="column" style="height: 100%">
|
||||||
|
<div>
|
||||||
|
<q-input
|
||||||
|
v-model="pageTitle"
|
||||||
|
label="Title"
|
||||||
|
style="padding: 10px"
|
||||||
|
></q-input>
|
||||||
|
</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<q-table
|
<q-table
|
||||||
flat
|
flat
|
||||||
@ -394,9 +481,7 @@ onMounted(() => {
|
|||||||
<!-- drawer content -->
|
<!-- drawer content -->
|
||||||
</q-drawer>
|
</q-drawer>
|
||||||
|
|
||||||
<q-page-container id="editor" style="padding=0">
|
<q-page-container id="editor" style='padding: 0'>
|
||||||
<!-- <WrapperBox />-->
|
|
||||||
|
|
||||||
<span v-for="(chartBox, index) in chartBoxes" style="display: flex">
|
<span v-for="(chartBox, index) in chartBoxes" style="display: flex">
|
||||||
<RangeChart
|
<RangeChart
|
||||||
:fn="math.getFunctionByNameOrFail(chartBox.fnName)"
|
:fn="math.getFunctionByNameOrFail(chartBox.fnName)"
|
||||||
@ -463,6 +548,10 @@ onMounted(() => {
|
|||||||
/>
|
/>
|
||||||
</q-dialog>
|
</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-page-sticky position="bottom-right" :offset="[32, 32]">
|
||||||
<q-fab color="primary" icon="add" direction="left">
|
<q-fab color="primary" icon="add" direction="left">
|
||||||
<q-fab-action
|
<q-fab-action
|
||||||
|
@ -11,11 +11,28 @@ export class MathPage {
|
|||||||
/** The statements on the page. */
|
/** The statements on the page. */
|
||||||
protected statements: Record<StatementID, MathStatement> = {}
|
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(
|
constructor(
|
||||||
/** Unique page ID. */
|
/** Unique page ID. */
|
||||||
public readonly id: string,
|
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. */
|
/** Get all defined statements. */
|
||||||
getStatements(): MathStatement[] {
|
getStatements(): MathStatement[] {
|
||||||
return Object.values(this.statements)
|
return Object.values(this.statements)
|
||||||
|
@ -264,6 +264,10 @@ export class MathStatement {
|
|||||||
return new MathStatement(uuidv4() as StatementID, raw)
|
return new MathStatement(uuidv4() as StatementID, raw)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static deserialize(vals: [StatementID, string, number, number]) {
|
||||||
|
return new this(...vals)
|
||||||
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
/** Unique ID of this statement. */
|
/** Unique ID of this statement. */
|
||||||
public readonly id: StatementID,
|
public readonly id: StatementID,
|
||||||
@ -278,6 +282,15 @@ export class MathStatement {
|
|||||||
public y: number = 0,
|
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 the raw statement to an AST. */
|
||||||
parse(): math.MathNode {
|
parse(): math.MathNode {
|
||||||
return math.parse(this.raw)
|
return math.parse(this.raw)
|
||||||
|
@ -94,24 +94,52 @@ export interface EvaluationResult {
|
|||||||
|
|
||||||
|
|
||||||
export class RichTextBox {
|
export class RichTextBox {
|
||||||
|
static deserialize(vals: [string, number, number]) {
|
||||||
|
return new this(...vals)
|
||||||
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public text: string = '',
|
public text: string = '',
|
||||||
public x: number = 0,
|
public x: number = 0,
|
||||||
public y: number = 0,
|
public y: number = 0,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
serialize(): [string, number, number] {
|
||||||
|
return [
|
||||||
|
this.text,
|
||||||
|
this.x,
|
||||||
|
this.y,
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export class ImageContainer {
|
export class ImageContainer {
|
||||||
|
static deserialize(vals: [string, number, number]) {
|
||||||
|
return new this(...vals)
|
||||||
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public url: string = '',
|
public url: string = '',
|
||||||
public x: number = 0,
|
public x: number = 0,
|
||||||
public y: number = 0,
|
public y: number = 0,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
serialize(): [string, number, number] {
|
||||||
|
return [
|
||||||
|
this.url,
|
||||||
|
this.x,
|
||||||
|
this.y,
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export class ChartBox {
|
export class ChartBox {
|
||||||
|
static deserialize(vals: [string, number, number, number, number, number]) {
|
||||||
|
return new this(...vals)
|
||||||
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line max-params
|
// eslint-disable-next-line max-params
|
||||||
constructor(
|
constructor(
|
||||||
public fnName: string,
|
public fnName: string,
|
||||||
@ -121,6 +149,17 @@ export class ChartBox {
|
|||||||
public x: number = 0,
|
public x: number = 0,
|
||||||
public y: 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,
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user