Skip to content

Commit

Permalink
add save button
Browse files Browse the repository at this point in the history
Signed-off-by: grnd-alt <[email protected]>
  • Loading branch information
grnd-alt committed Nov 5, 2024
1 parent 3d37460 commit 6fa73ff
Show file tree
Hide file tree
Showing 8 changed files with 112 additions and 9 deletions.
28 changes: 26 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import type { ResolvablePromise } from '@excalidraw/excalidraw/types/utils'
import type { NonDeletedExcalidrawElement } from '@excalidraw/excalidraw/types/element/types'
import { getLinkWithPicker } from '@nextcloud/vue/dist/Components/NcRichText.js'
import { useExcalidrawLang } from './hooks/useExcalidrawLang'
import SaveStatus from './SaveStatus'

interface WhiteboardAppProps {
fileId: number
Expand All @@ -46,6 +47,7 @@ export default function App({
const fileNameWithoutExtension = fileName.split('.').slice(0, -1).join('.')

const [viewModeEnabled, setViewModeEnabled] = useState(isEmbedded)
const [roomDataSaved, setRoomDataSaved] = useState(true)
const [zenModeEnabled] = useState(isEmbedded)
const [gridModeEnabled] = useState(false)

Expand Down Expand Up @@ -91,10 +93,17 @@ export default function App({
const [excalidrawAPI, setExcalidrawAPI]
= useState<ExcalidrawImperativeAPI | null>(null)
const [collab, setCollab] = useState<Collab | null>(null)
const [collabStarted, setCollabStarted] = useState(false)

if (excalidrawAPI && !collab) { setCollab(new Collab(excalidrawAPI, fileId, publicSharingToken, setViewModeEnabled)) }
if (collab && !collab.portal.socket) collab.startCollab()
if (excalidrawAPI && !collab) { setCollab(new Collab(excalidrawAPI, fileId, publicSharingToken, setViewModeEnabled, setRoomDataSaved)) }
if (collab && !collabStarted) {
setCollabStarted(true)
collab.startCollab()
}

const [isSmartPickerInserted, setIsSmartPickerInserted] = useState(false)
useEffect(() => {
if (isSmartPickerInserted) return
const extraTools = document.getElementsByClassName(
'App-toolbar__extra-tools-trigger',
)[0]
Expand All @@ -107,6 +116,7 @@ export default function App({
)
const root = createRoot(smartPick)
root.render(renderSmartPicker())
setIsSmartPickerInserted(true)
}
})

Expand Down Expand Up @@ -236,12 +246,26 @@ export default function App({
)
}

const renderTopRightUI = () => {
if (collab?.portal.socket) {
return (
<SaveStatus saving={!(roomDataSaved)} onClick={
() => {
collab?.portal.requestStoreToServer()
}
}
/>
)
}
}

return (
<div className="App">
<div className="excalidraw-wrapper">
<Excalidraw
validateEmbeddable={() => true}
renderEmbeddable={Embeddable}
renderTopRightUI={renderTopRightUI}
excalidrawAPI={(api: ExcalidrawImperativeAPI) => {
console.log(api)
console.log('Setting API')
Expand Down
10 changes: 10 additions & 0 deletions src/SaveStatus.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import SaveStatus from './SaveStatus.vue'
import VueWrapper from './VueWrapper'

export default function(props:{saving: Boolean}) {

Check failure on line 8 in src/SaveStatus.tsx

View workflow job for this annotation

GitHub Actions / NPM lint

Don't use `Boolean` as a type. Use boolean instead
return React.createElement(VueWrapper, { componentProps: props, component: SaveStatus })
}
27 changes: 27 additions & 0 deletions src/SaveStatus.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<template>
<NcButton type="tertiary" v-on:click="onClick">

Check warning on line 2 in src/SaveStatus.vue

View workflow job for this annotation

GitHub Actions / NPM lint

Expected '@' instead of 'v-on:'
<template #icon>
<NcSavingIndicatorIcon :saving="saving" />
</template>
</NcButton>
</template>

<script>
import { NcButton, NcSavingIndicatorIcon } from '@nextcloud/vue'
export default {
name: "SaveStatus",

Check failure on line 12 in src/SaveStatus.vue

View workflow job for this annotation

GitHub Actions / NPM lint

Strings must use singlequote
components: {
NcButton,
NcSavingIndicatorIcon,
},
props: {
saving: {
type: Boolean,
default: false

Check warning on line 20 in src/SaveStatus.vue

View workflow job for this annotation

GitHub Actions / NPM lint

Missing trailing comma
},
onClick: {

Check warning on line 22 in src/SaveStatus.vue

View workflow job for this annotation

GitHub Actions / NPM lint

Prop 'onClick' requires default value to be set
type: Function

Check warning on line 23 in src/SaveStatus.vue

View workflow job for this annotation

GitHub Actions / NPM lint

Missing trailing comma
}

Check warning on line 24 in src/SaveStatus.vue

View workflow job for this annotation

GitHub Actions / NPM lint

Missing trailing comma
},
}
</script>
10 changes: 10 additions & 0 deletions src/collaboration/Portal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,12 @@ export class Portal {
this.socket.on('client-broadcast', (data) =>
this.handleClientBroadcast(data),
)

this.socket.on('room-data-saved', () => this.handleRoomDataSaved())
}

async handleRoomDataSaved() {
this.collab.setRoomDataSaved(true)
}

async handleReadOnlySocket() {
Expand Down Expand Up @@ -240,6 +246,10 @@ export class Portal {
await this._broadcastSocketData(data, true)
}

async requestStoreToServer() {
this.socket?.emit('store-to-server', this.roomId)
}

async sendImageFiles(files: BinaryFiles) {
Object.values(files).forEach(file => {
this.collab.addFile(file)
Expand Down
6 changes: 5 additions & 1 deletion src/collaboration/collab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,17 @@ export class Collab {
portal: Portal
publicSharingToken: string | null
setViewModeEnabled: React.Dispatch<React.SetStateAction<boolean>>
setRoomDataSaved: React.Dispatch<React.SetStateAction<boolean>>
lastBroadcastedOrReceivedSceneVersion: number = -1
private collaborators = new Map<string, Collaborator>()
private files = new Map<string, BinaryFileData>()

constructor(excalidrawAPI: ExcalidrawImperativeAPI, fileId: number, publicSharingToken: string | null, setViewModeEnabled: React.Dispatch<React.SetStateAction<boolean>>) {
constructor(excalidrawAPI: ExcalidrawImperativeAPI, fileId: number, publicSharingToken: string | null, setViewModeEnabled: React.Dispatch<React.SetStateAction<boolean>>, setRoomDataSaved: React.Dispatch<React.SetStateAction<boolean>>) {
this.excalidrawAPI = excalidrawAPI
this.fileId = fileId
this.publicSharingToken = publicSharingToken
this.setViewModeEnabled = setViewModeEnabled
this.setRoomDataSaved = setRoomDataSaved

this.portal = new Portal(`${fileId}`, this, publicSharingToken)
}
Expand Down Expand Up @@ -55,6 +57,7 @@ export class Collab {
elements,
},
)
this.setRoomDataSaved(false)
}

private getLastBroadcastedOrReceivedSceneVersion = () => {
Expand All @@ -69,6 +72,7 @@ export class Collab {
this.lastBroadcastedOrReceivedSceneVersion = hashElementsVersion(elements)
throttle(() => {
this.portal.broadcastScene('SCENE_INIT', elements)
this.setRoomDataSaved(false)

const syncedFiles = Array.from(this.files.keys())
const newFiles = Object.keys(files).filter((id) => !syncedFiles.includes(id)).reduce((acc, id) => {
Expand Down
2 changes: 2 additions & 0 deletions websocket_server/AppManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@
import dotenv from 'dotenv'
import express from 'express'
import PrometheusDataManager from './PrometheusDataManager.js'
import StorageManager from './StorageManager.js'

dotenv.config()

export default class AppManager {

/** @param {StorageManager} storageManager*/

Check warning on line 15 in websocket_server/AppManager.js

View workflow job for this annotation

GitHub Actions / NPM lint

Missing JSDoc @param "storageManager" description

Check failure on line 15 in websocket_server/AppManager.js

View workflow job for this annotation

GitHub Actions / NPM lint

Expected space or tab before '*/' in comment
constructor(storageManager) {
this.app = express()
this.storageManager = storageManager
Expand Down
22 changes: 19 additions & 3 deletions websocket_server/SocketManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,20 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import { Server as SocketIO } from 'socket.io'
import { Socket, Server as SocketIO } from 'socket.io'
import prometheusMetrics from 'socket.io-prometheus'
import jwt from 'jsonwebtoken'
import dotenv from 'dotenv'
import Utils from './Utils.js'
import { createAdapter } from '@socket.io/redis-streams-adapter'
import SocketDataManager from './SocketDataManager.js'
import StorageManager from './StorageManager.js'

dotenv.config()

export default class SocketManager {

/** @param {StorageManager} storageManager */

Check warning on line 21 in websocket_server/SocketManager.js

View workflow job for this annotation

GitHub Actions / NPM lint

Expected @param names to be "server, roomDataManager, storageManager". Got "storageManager"

Check warning on line 21 in websocket_server/SocketManager.js

View workflow job for this annotation

GitHub Actions / NPM lint

Missing JSDoc @param "storageManager" description

Check warning on line 21 in websocket_server/SocketManager.js

View workflow job for this annotation

GitHub Actions / NPM lint

Missing JSDoc @param "server" declaration
constructor(server, roomDataManager, storageManager) {
this.roomDataManager = roomDataManager
this.storageManager = storageManager
Expand Down Expand Up @@ -92,7 +94,7 @@ export default class SocketManager {
},
)
next(new Error('Connection verified'))
} catch (e) {}
} catch (e) { }

next(new Error('Authentication error'))
}
Expand All @@ -107,6 +109,7 @@ export default class SocketManager {
socket.on('server-volatile-broadcast', (roomID, encryptedData) =>
this.serverVolatileBroadcastHandler(socket, roomID, encryptedData),
)
socket.on('store-to-server', (roomID) => this.storeToServerHandler(roomID, socket))
socket.on('image-add', (roomID, id, data) => this.imageAddHandler(socket, roomID, id, data))
socket.on('image-remove', (roomID, id, data) => this.imageRemoveHandler(socket, roomID, id, data))
socket.on('image-get', (roomID, id, data) => this.imageGetHandler(socket, roomID, id, data))
Expand All @@ -117,6 +120,17 @@ export default class SocketManager {
socket.on('disconnect', () => this.handleDisconnect(socket))
}

/**
* @param {int} roomID
* @param {Socket} socket
*/
async storeToServerHandler(roomID, socket) {
this.storageManager.saveRoomDataToServer(roomID).then(() => {
socket.emit("room-data-saved", roomID)

Check failure on line 129 in websocket_server/SocketManager.js

View workflow job for this annotation

GitHub Actions / NPM lint

Strings must use singlequote
socket.broadcast.to(roomID).emit("room-data-saved", roomID)

Check failure on line 130 in websocket_server/SocketManager.js

View workflow job for this annotation

GitHub Actions / NPM lint

Strings must use singlequote
})
}

async handleDisconnect(socket) {
await this.socketDataManager.deleteSocketData(socket.id)
socket.removeAllListeners()
Expand Down Expand Up @@ -213,7 +227,9 @@ export default class SocketManager {
socketData.user.id,
)
}

/**
* @param {Socket} socket
*/
async serverVolatileBroadcastHandler(socket, roomID, encryptedData) {

Check failure on line 233 in websocket_server/SocketManager.js

View workflow job for this annotation

GitHub Actions / NPM lint

Expected blank line between class members
const payload = JSON.parse(
Utils.convertArrayBufferToString(encryptedData),
Expand Down
16 changes: 13 additions & 3 deletions websocket_server/StorageManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,16 @@
import StorageStrategy from './StorageStrategy.js'
import LRUCacheStrategy from './LRUCacheStrategy.js'
import RedisStrategy from './RedisStrategy.js'
import ApiService from './ApiService.js';

Check failure on line 11 in websocket_server/StorageManager.js

View workflow job for this annotation

GitHub Actions / NPM lint

Extra semicolon
import Room from './Room.js'

Check failure on line 12 in websocket_server/StorageManager.js

View workflow job for this annotation

GitHub Actions / NPM lint

'Room' is defined but never used

export default class StorageManager {

Check failure on line 14 in websocket_server/StorageManager.js

View workflow job for this annotation

GitHub Actions / NPM lint

Block must be padded by blank lines

constructor(strategy) {
/** @param {StorageStrategy} strategy
* @param {ApiService} apiService
*/
constructor(strategy, apiService) {
this.setStrategy(strategy)
this.apiService = apiService
}

setStrategy(strategy) {
Expand All @@ -38,6 +43,11 @@ export default class StorageManager {
async clear() {
await this.strategy.clear()
}
/** @param { number }roomId */
async saveRoomDataToServer(roomId) {

Check failure on line 47 in websocket_server/StorageManager.js

View workflow job for this annotation

GitHub Actions / NPM lint

Expected blank line between class members
const room = await this.get(roomId)
this.apiService.saveRoomDataToServer(roomId, room.data, room.lastEditedUser, room.files);
}

getRooms() {
return this.strategy.getRooms()
Expand All @@ -58,7 +68,7 @@ export default class StorageManager {
throw new Error('Invalid storage strategy type')
}

return new StorageManager(strategy)
return new StorageManager(strategy,apiService)
}

}

0 comments on commit 6fa73ff

Please sign in to comment.