Skip to content

Commit

Permalink
Bc 5135 activate import (#27)
Browse files Browse the repository at this point in the history
* Add WITH_TLDRAW

* Add import functionality

* For testing

* Fix import project file

* Refactor

* Fixed comments and refactored

* Fix imports

---------

Co-authored-by: Viktoriia <1>
Co-authored-by: blazejpass <blazej.szczepanowski@gca.pass-consulting.com>
  • Loading branch information
VikDavydiuk and blazejpass committed Dec 14, 2023
1 parent c689ba2 commit 05fc1cd
Show file tree
Hide file tree
Showing 10 changed files with 236 additions and 75 deletions.
25 changes: 22 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
"@types/react-dom": "^18.0.11",
"@y-presence/client": "^2.0.1",
"@y-presence/react": "^2.0.1",
"browser-fs-access": "^0.35.0",
"eslint": "^8.45.0",
"loglevel": "^1.8.1",
"prettier": "^3.0.0",
"react": "^18.2.0",
"react-cookie": "^6.1.1",
Expand Down
4 changes: 3 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import { awareness, roomID } from './store/store';
import './App.css';

function Editor({ roomId }: { roomId: string }) {
const { onSaveProjectAs, onSaveProject, onOpenMedia } = useFileSystem();
const { onSaveProjectAs, onSaveProject, onOpenMedia, onOpenProject } =
useFileSystem();
const { onMount, saveUserSettings, getDarkMode, ...events } =
useMultiplayerState(roomId);

Expand All @@ -20,6 +21,7 @@ function Editor({ roomId }: { roomId: string }) {
darkMode={getDarkMode()}
showMultiplayerMenu={false}
{...events}
onOpenProject={onOpenProject}
onSaveProject={onSaveProject}
onSaveProjectAs={onSaveProjectAs}
onOpenMedia={onOpenMedia}
Expand Down
214 changes: 144 additions & 70 deletions src/hooks/useMultiplayerState.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,3 @@
import {
TDAsset,
TDBinding,
TDShape,
TDSnapshot,
TDUser,
TldrawApp,
TldrawPatch,
} from '@tldraw/tldraw';
import { useCallback, useEffect, useState } from 'react';
import { Room } from '@y-presence/client';
import {
Expand All @@ -18,35 +9,45 @@ import {
yBindings,
yShapes,
} from '../store/store';
import { fileOpen } from 'browser-fs-access';
import { TldrawPresence } from '../types';
import {
FileBuilder,
FileBuilderResult,
errorLogger,
STORAGE_SETTINGS_KEY,
getUserSettings,
castToString,
} from '../utilities';
import {
TDAsset,
TDBinding,
TDShape,
TldrawApp,
TldrawPatch,
TDUser,
} from '@tldraw/tldraw';

export const room = new Room<TldrawPresence>(awareness, {});

const STORAGE_SETTINGS_KEY = 'sc_tldraw_settings';

const getUserSettings = (): TDSnapshot['settings'] | undefined => {
const settingsString = localStorage.getItem(STORAGE_SETTINGS_KEY);
return settingsString ? JSON.parse(settingsString) : undefined;
};

const setDefaultState = () => {
const userSettings = getUserSettings();
if (userSettings) {
TldrawApp.defaultState.settings = userSettings;
} else {
TldrawApp.defaultState.settings.language = 'de';
}
};

export function useMultiplayerState(roomId: string) {
const [appInstance, setAppInstance] = useState<TldrawApp | undefined>(
undefined,
);
const [loading, setLoading] = useState<boolean>(true);

const setDefaultState = () => {
const userSettings = getUserSettings();
if (userSettings) {
TldrawApp.defaultState.settings = userSettings;
} else {
TldrawApp.defaultState.settings.language = 'de';
}
};

setDefaultState();

const getDarkMode = (): boolean | false => {
const getDarkMode = (): boolean => {
const settings = getUserSettings();
return settings ? settings.isDarkMode : false;
};
Expand All @@ -63,14 +64,113 @@ export function useMultiplayerState(roomId: string) {
[],
);

const onMount = useCallback(
(app: TldrawApp) => {
app.loadRoom(roomId);
const openFromFileSystem = async (): Promise<null | FileBuilderResult> => {
try {
const blob = await fileOpen({
description: 'Tldraw File',
extensions: [`.tldr`],
multiple: false,
});

if (!blob) throw new Error('No file selected');

const json: string | null = await readBlobAsText(blob);

return FileBuilder.build(json, blob.handle ?? null);
} catch (error: any) {
errorLogger('Error opening file', error);
return null;
}
};

const readBlobAsText = async (blob: Blob): Promise<string | null> =>
new Promise((resolve) => {
const reader = new FileReader();
reader.onloadend = () => {
if (reader.readyState === FileReader.DONE) {
const result = castToString(reader.result);
resolve(result);
}
};
reader.readAsText(blob, 'utf8');
});

const updateYMapsInTransaction = (
shapes: Record<string, TDShape | undefined>,
bindings: Record<string, TDBinding | undefined>,
assets: Record<string, TDAsset | undefined>,
) => {
doc.transact(() => {
updateYShapes(shapes);
updateYBindings(bindings);
updateYAssets(assets);
});
};

const updateYShapes = (shapes: Record<string, TDShape | undefined>) => {
Object.entries(shapes).forEach(([id, shape]) => {
if (!shape) {
yShapes.delete(id);
} else {
yShapes.set(shape.id, shape);
}
});
};

const updateYBindings = (bindings: Record<string, TDBinding | undefined>) => {
Object.entries(bindings).forEach(([id, binding]) => {
if (!binding) {
yBindings.delete(id);
} else {
yBindings.set(binding.id, binding);
}
});
};

const updateYAssets = (assets: Record<string, TDAsset | undefined>) => {
Object.entries(assets).forEach(([id, asset]) => {
if (!asset) {
yAssets.delete(id);
} else {
yAssets.set(asset.id, asset);
}
});
};

const onMount = useCallback(async (app: TldrawApp) => {
try {
await app.loadRoom(roomId);
app.pause();
setAppInstance(app);
},
[roomId],
);

app.openProject = async () => {
try {
const result = await openFromFileSystem();
if (!result || result === null) {
throw new Error('Failed to open project');
}

const { document } = result;

yShapes.clear();
yBindings.clear();
yAssets.clear();

updateYMapsInTransaction(
document.pages.page.shapes,
document.pages.page.bindings,
document.assets,
);

app.zoomToFit();
} catch (error: any) {
errorLogger('Error opening project', error);
}
};
} catch (error: any) {
errorLogger('Error loading room', error);
}
}, []);

const onChangePage = useCallback(
(
Expand All @@ -79,30 +179,7 @@ export function useMultiplayerState(roomId: string) {
bindings: Record<string, TDBinding | undefined>,
assets: Record<string, TDAsset | undefined>,
) => {
undoManager.stopCapturing();
doc.transact(() => {
Object.entries(shapes).forEach(([id, shape]) => {
if (!shape) {
yShapes.delete(id);
} else {
yShapes.set(shape.id, shape);
}
});
Object.entries(bindings).forEach(([id, binding]) => {
if (!binding) {
yBindings.delete(id);
} else {
yBindings.set(binding.id, binding);
}
});
Object.entries(assets).forEach(([id, asset]) => {
if (!asset) {
yAssets.delete(id);
} else {
yAssets.set(asset.id, asset);
}
});
});
updateYMapsInTransaction(shapes, bindings, assets);
},
[],
);
Expand All @@ -115,14 +192,14 @@ export function useMultiplayerState(roomId: string) {
undoManager.redo();
}, []);

const onChangePresence = useCallback((app: TldrawApp, user: TDUser) => {
if (!app.room) return;
room.setPresence({ id: app.room.userId, tdUser: user });
}, []);
const onChangePresence = useCallback(
(app: TldrawApp, user: TDUser) => {
if (!app.room) return;
room.setPresence({ id: app.room.userId, tdUser: user });
},
[room.updatePresence],
);

/**
* Update app users whenever there is a change in the room users
*/
useEffect(() => {
if (!appInstance || !room) return;

Expand Down Expand Up @@ -160,25 +237,22 @@ export function useMultiplayerState(roomId: string) {
useEffect(() => {
if (!appInstance) return;

function handleDisconnect() {
provider.disconnect();
}
const handleDisconnect = () => provider.disconnect();

window.addEventListener('beforeunload', handleDisconnect);

function handleChanges() {
const handleChanges = () =>
appInstance?.replacePageContent(
Object.fromEntries(yShapes.entries()),
Object.fromEntries(yBindings.entries()),
Object.fromEntries(yAssets.entries()),
);
}

async function setup() {
const setup = async () => {
yShapes.observeDeep(handleChanges);
handleChanges();
setLoading(false);
}
};

setup();

Expand Down
2 changes: 1 addition & 1 deletion src/store/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export const awareness = provider.awareness;
export const yShapes: Map<TDShape> = doc.getMap('shapes');
export const yBindings: Map<TDBinding> = doc.getMap('bindings');
export const yAssets: Map<TDAsset> = doc.getMap('assets');
export const undoManager = new UndoManager([yShapes, yBindings]);
export const undoManager = new UndoManager([yShapes, yBindings, yAssets]);

export function configure(options: any) {
Object.assign(defaultOptions, options);
Expand Down
Loading

0 comments on commit 05fc1cd

Please sign in to comment.