Skip to content

Commit

Permalink
major (#226): Implemented client-side state management
Browse files Browse the repository at this point in the history
  • Loading branch information
Luna-Klatzer committed Nov 6, 2024
1 parent b232d82 commit b4a09bf
Show file tree
Hide file tree
Showing 18 changed files with 1,788 additions and 859 deletions.
3 changes: 2 additions & 1 deletion solardoc/frontend/src/components/common/InfoBox.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ defineProps<{ infoText: any }>()
<template>
<span class="info-box-wrapper">
<span class="info-box">
<span class="info-box-content"><code>{{ infoText }}</code></span
<span class="info-box-content"
><code>{{ infoText }}</code></span
>
</span>
</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
PhoenixBadRequestError,
PhoenixInternalError,
} from '@/services/phoenix/errors'
import UserRef from "@/components/common/UserRef.vue";
import UserRef from '@/components/common/UserRef.vue'
const props = defineProps<{ filePermission: FilePermission }>()
const permissionValue = ref(props.filePermission.permission === 3)
Expand Down Expand Up @@ -64,7 +64,8 @@ async function saveChanges() {
<template>
<div id="user-permission-card">
<p>
<span>User:</span><UserRef :id="filePermission.user_id" :user-name="filePermission.username" />
<span>User:</span
><UserRef :id="filePermission.user_id" :user-name="filePermission.username" />
</p>
<p>
<span id="permission-text">Permission:</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
PhoenixInternalError,
} from '@/services/phoenix/errors'
import CollaboratorCard from '@/components/editor/dropdown/editor-settings/CollaboratorCard.vue'
import type {FilePermission, FilePermissions} from "@/services/phoenix/api-service";
import type { FilePermission, FilePermissions } from '@/services/phoenix/api-service'
const currentFileStore = useCurrentFileStore()
const currentUserStore = useCurrentUserStore()
Expand All @@ -23,7 +23,10 @@ const permissionUsers = ref<Array<FilePermission>>([])
async function fetchFilePermissions(bearerToken: string) {
let resp: Awaited<ReturnType<typeof phoenixRestService.getV2FilesByFileIdPermissions>>
try {
resp = await phoenixRestService.getV2FilesByFileIdPermissions(bearerToken, currentFileStore.fileId!)
resp = await phoenixRestService.getV2FilesByFileIdPermissions(
bearerToken,
currentFileStore.fileId!,
)
} catch (e) {
throw new PhoenixInternalError(
'Critically failed to fetch file permissions for file. Cause: ' + (<Error>e).message,
Expand Down
109 changes: 55 additions & 54 deletions solardoc/frontend/src/plugins/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,60 +47,61 @@ export default Object.freeze({
},
} satisfies { [key: string]: { title: string; text: string } },
defaultFileName: 'untitled.adoc',
defaultFileContent: '= Welcome to SolarDoc\n' +
':revealjs_theme: dracula\n' +
':revealjs_progress: true\n' +
'\n' +
'== Getting Started\n' +
'\n' +
'*AsciiDoc* - a powerful, simple markup language.\n' +
'\n' +
'- Lightweight\n' +
'- Converts to many formats\n' +
'- Perfect for presentations!\n' +
'\n' +
'== Headings & Text Styles\n' +
'\n' +
'=== Subheading Example\n' +
'Basic paragraph of text with **bold**, _italic_, and `monospaced` styles.\n' +
'\n' +
'== Lists\n' +
'\n' +
'. First ordered item\n' +
'. Second ordered item\n' +
'. Third ordered item\n' +
'\n' +
'* Unordered item A\n' +
'* Unordered item B\n' +
'\n' +
'== Code Example\n' +
'\n' +
'[source,python]\n' +
'----\n' +
'# Simple Python function\n' +
'def greet(name):\n' +
' return f"Hello, {name}!"\n' +
'print(greet("AsciiDoc"))\n' +
'----\n' +
'\n' +
'== Tables\n' +
'\n' +
'[cols="1,1,1", options="header"]\n' +
'|===\n' +
'| Header 1 | Header 2 | Header 3\n' +
'| A1 | A2 | A3\n' +
'| B1 | B2 | B3\n' +
'|===\n' +
'\n' +
'== Images\n' +
'\n' +
'image::https://i0.wp.com/picjumbo.com/wp-content/uploads/portrait-of-a-cat-lying-in-a-sweater-free-image.jpeg?w=600&quality=80[width=800, align="center"]\n' +
'\n' +
'== Links\n' +
'\n' +
'https://asciidoctor.org[AsciiDoctor Site]\n' +
'\n' +
'== Enjoy!',
defaultFileContent:
'= Welcome to SolarDoc\n' +
':revealjs_theme: dracula\n' +
':revealjs_progress: true\n' +
'\n' +
'== Getting Started\n' +
'\n' +
'*AsciiDoc* - a powerful, simple markup language.\n' +
'\n' +
'- Lightweight\n' +
'- Converts to many formats\n' +
'- Perfect for presentations!\n' +
'\n' +
'== Headings & Text Styles\n' +
'\n' +
'=== Subheading Example\n' +
'Basic paragraph of text with **bold**, _italic_, and `monospaced` styles.\n' +
'\n' +
'== Lists\n' +
'\n' +
'. First ordered item\n' +
'. Second ordered item\n' +
'. Third ordered item\n' +
'\n' +
'* Unordered item A\n' +
'* Unordered item B\n' +
'\n' +
'== Code Example\n' +
'\n' +
'[source,python]\n' +
'----\n' +
'# Simple Python function\n' +
'def greet(name):\n' +
' return f"Hello, {name}!"\n' +
'print(greet("AsciiDoc"))\n' +
'----\n' +
'\n' +
'== Tables\n' +
'\n' +
'[cols="1,1,1", options="header"]\n' +
'|===\n' +
'| Header 1 | Header 2 | Header 3\n' +
'| A1 | A2 | A3\n' +
'| B1 | B2 | B3\n' +
'|===\n' +
'\n' +
'== Images\n' +
'\n' +
'image::https://i0.wp.com/picjumbo.com/wp-content/uploads/portrait-of-a-cat-lying-in-a-sweater-free-image.jpeg?w=600&quality=80[width=800, align="center"]\n' +
'\n' +
'== Links\n' +
'\n' +
'https://asciidoctor.org[AsciiDoctor Site]\n' +
'\n' +
'== Enjoy!',
saveStates: {
shared: 'Shared File',
local: 'Saved Locally',
Expand Down
101 changes: 101 additions & 0 deletions solardoc/frontend/src/scripts/editor/ot/client/awaiting-confirm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/**
* @file awaiting-confirm.ts
* @since 1.0.0
* @author Luna Klatzer
*
* This file contains the implementation of the AwaitingConfirm class and is based on the original implementation in
* ot.js from Tim Baumann and as such will be licensed under the MIT license.
*
* License:
*
* Copyright © 2012-2014 Tim Baumann, http://timbaumann.info
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the “Software”), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
import { type OTBaseClient } from '@/scripts/editor/ot/client/ot-client'
import type { TextTransformation } from '@/scripts/editor/ot/text-operation'
import { type OTState } from '@/scripts/editor/ot/client/ot-state'
import {AwaitingWithBuffer} from "@/scripts/editor/ot/client/awaiting-with-buffer";
import {Synchronised} from "@/scripts/editor/ot/client/synchronised";
import type {Selection} from "@/scripts/editor/ot/client/selection";

/**
* Represents the state of the client when it is waiting for the server to acknowledge an operation.
* @since 1.0.0
*/
export class AwaitingConfirm extends OTState {
private readonly _outstanding: TextTransformation

constructor(outstanding: TextTransformation) {
this._outstanding = outstanding
}

/**
* The outstanding operation that has not been acknowledged by the server.
* @since 1.0.0
*/
get outstanding(): TextTransformation {
return this._outstanding
}

/**
* Applies the client operation to the state. We don't send it to the server immediately but buffer it.
* @param client The client to apply the operation to.
* @param operation The operation to apply.
*/
async applyClient(client: OTBaseClient, operation: TextTransformation): Promise<OTState> {
return new AwaitingWithBuffer(this._outstanding, operation)
}

/**
* This is another client's operation. Visualization:
*
* /\
* this.outstanding / \ operation
* / \
* \ /
* pair[1] \ / pair[0] (new outstanding)
* (can be applied \/
* to the client's
* current document)
* @param client The client to apply the operation to.
* @param operation The operation from the server to apply.
*/
async applyServer(client: OTBaseClient, operation: TextTransformation): Promise<OTState> {
const pair = TextTransformation.transform(this._outstanding, operation)
await client.applyOperation(pair[1])
return new AwaitingConfirm(pair[0])
}

async serverAck(): Promise<OTState> {
return Synchronised.instance
}

async transformSelection(selection: Selection): Promise<Selection> {
return selection.transform(this._outstanding)
}

/**
* Resends the outstanding operation to the server.
* @param client The client to resend the operation to.
*/
async resend(client: OTBaseClient): Promise<void> {
await client.sendOperation(client.revision, this._outstanding)
}
}
119 changes: 119 additions & 0 deletions solardoc/frontend/src/scripts/editor/ot/client/awaiting-with-buffer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/**
* @file awaiting-with-buffer.ts
* @since 1.0.0
* @author Luna Klatzer
*
* This file contains the implementation of the AwaitingWithBuffer class and is based on the original implementation in
* ot.js from Tim Baumann and as such will be licensed under the MIT license.
*
* License:
*
* Copyright © 2012-2014 Tim Baumann, http://timbaumann.info
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the “Software”), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
import type { TextTransformation } from '@/scripts/editor/ot/text-operation'
import type {OTBaseClient} from "@/scripts/editor/ot/client/ot-client";
import {AwaitingConfirm} from "@/scripts/editor/ot/client/awaiting-confirm";
import {OTState} from "@/scripts/editor/ot/client/ot-state";
import type {Selection} from "@/scripts/editor/ot/client/selection";

export class AwaitingWithBuffer extends OTState {
private readonly _outstanding: TextTransformation
private readonly _buffer: TextTransformation

constructor(outstanding: TextTransformation, buffer: TextTransformation) {
this._outstanding = outstanding
this._buffer = buffer
}

/**
* The outstanding operation that has not been acknowledged by the server.
* @since 1.0.0
*/
get outstanding(): TextTransformation {
return this._outstanding
}

/**
* The buffer of operations that have not been acknowledged by the server.
* @since 1.0.0
*/
get buffer(): TextTransformation {
return this._buffer
}

/**
* Compose the users changes onto the buffer.
* @param client The client to apply the operation to.
* @param operation The operation to apply.
*/
async applyClient(client: OTBaseClient, operation: TextTransformation): Promise<AwaitingWithBuffer> {
return new AwaitingWithBuffer(this._outstanding, this._buffer.compose(operation))
}

/**
* Apply the server operation to the client.
*
* Operation comes from another client
*
* /\
* this.outstanding / \ operation
* / \
* /\ /
* this.buffer / \* / pair1[0] (new outstanding)
* / \/
* \ /
* pair2[1] \ / pair2[0] (new buffer)
* the transformed \/
* operation -- can be applied to the client's current document
* @param client The client to apply the operation to.
* @param operation The operation to apply.
*/
async applyServer(client: OTBaseClient, operation: TextTransformation): Promise<AwaitingWithBuffer> {
const pair1 = TextTransformation.transform(this.outstanding, operation) // -> [newOutstanding, transformedOperation]
const pair2 = TextTransformation.transform(this.buffer, pair1[1]) // -> [newBuffer, transformedOperation]
await client.applyServer(pair2[1]) // Apply the transformed received operation to the client's document
return new AwaitingWithBuffer(pair1[0], pair2[0])
}

/**
* The pending operation has been acknowledged by the server.
*
* The buffer is now the outstanding operation and the buffer is empty. The client will now wait for the buffer to
* acknowledge by the server.
* @param client The client to apply the operation to.
*/
async serverAck(client: OTBaseClient): Promise<void> {
await client.sendOperation(client.revision, this.buffer)
return new AwaitingConfirm(this.buffer)
}

async transformSelection(selection: Selection): Promise<Selection> {
return selection.transform(this._outstanding).transform(this._buffer)
}

/**
* Resend the outstanding operation to the server.
* @param client The client to resend the operation to.
*/
async resend(client: OTBaseClient): Promise<void> {
await client.sendOperation(client.revision, this._outstanding)
}
}
Loading

0 comments on commit b4a09bf

Please sign in to comment.