checkpoint before google auth
This commit is contained in:
parent
b26b9e6384
commit
202971f343
|
|
@ -172,3 +172,4 @@ dist
|
|||
.pnp.\*
|
||||
|
||||
.wrangler/
|
||||
.*.md
|
||||
|
|
@ -53,6 +53,6 @@
|
|||
"vite-plugin-static-copy": "^1.0.6",
|
||||
"vite-plugin-top-level-await": "^1.3.1",
|
||||
"vite-plugin-wasm": "^3.2.2",
|
||||
"wrangler": "^3.72.3"
|
||||
"wrangler": "^3.88.0"
|
||||
}
|
||||
}
|
||||
|
|
@ -98,6 +98,8 @@ function Home() {
|
|||
const shapes = createShapes(elementsInfo)
|
||||
const [isEditorMounted, setIsEditorMounted] = useState(false);
|
||||
|
||||
//console.log("THIS WORKS SO FAR")
|
||||
|
||||
useEffect(() => {
|
||||
const handleEditorDidMount = () => {
|
||||
setIsEditorMounted(true);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,123 @@
|
|||
import { useSync } from '@tldraw/sync'
|
||||
import {
|
||||
AssetRecordType,
|
||||
getHashForString,
|
||||
TLBookmarkAsset,
|
||||
Tldraw,
|
||||
// useLocalStorageState,
|
||||
} from 'tldraw'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import useLocalStorageState from 'use-local-storage-state'
|
||||
import { ChatBoxTool } from '@/tools/ChatBoxTool'
|
||||
import { ChatBoxShape } from '@/shapes/ChatBoxShapeUtil'
|
||||
import { VideoChatTool } from '@/tools/VideoChatTool'
|
||||
import { VideoChatShape } from '@/shapes/VideoChatShapeUtil'
|
||||
import { multiplayerAssetStore } from '../client/multiplayerAssetStore'
|
||||
import { customSchema } from '../../worker/TldrawDurableObject'
|
||||
import { EmbedShape } from '@/shapes/EmbedShapeUtil'
|
||||
import { EmbedTool } from '@/tools/EmbedTool'
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { ChatBox } from '@/shapes/ChatBoxShapeUtil';
|
||||
import { components, uiOverrides } from '@/ui-overrides'
|
||||
|
||||
const WORKER_URL = `https://jeffemmett-canvas.jeffemmett.workers.dev`
|
||||
|
||||
const shapeUtils = [ChatBoxShape, VideoChatShape, EmbedShape]
|
||||
const tools = [ChatBoxTool, VideoChatTool, EmbedTool]; // Array of tools
|
||||
|
||||
export function Board() {
|
||||
const { slug } = useParams<{ slug: string }>(); // Ensure this is inside the Board component
|
||||
const roomId = slug || 'default-room'; // Declare roomId here
|
||||
|
||||
const store = useSync({
|
||||
uri: `${WORKER_URL}/connect/${roomId}`,
|
||||
assets: multiplayerAssetStore,
|
||||
shapeUtils: shapeUtils,
|
||||
schema: customSchema,
|
||||
});
|
||||
|
||||
const [isChatBoxVisible, setChatBoxVisible] = useState(false);
|
||||
const [userName, setUserName] = useState('');
|
||||
const [isVideoChatVisible, setVideoChatVisible] = useState(false); // Added state for video chat visibility
|
||||
|
||||
const handleNameChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setUserName(event.target.value);
|
||||
};
|
||||
|
||||
const [persistedStore, setPersistedStore] = useLocalStorageState('board-store', { defaultValue: store }
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
setPersistedStore(store);
|
||||
}, [store]);
|
||||
|
||||
return (
|
||||
<div style={{ position: 'fixed', inset: 0 }}>
|
||||
<Tldraw
|
||||
//store={persistedStore}
|
||||
store={store}
|
||||
shapeUtils={shapeUtils}
|
||||
overrides={uiOverrides}
|
||||
components={components}
|
||||
tools={tools}
|
||||
onMount={(editor) => {
|
||||
editor.registerExternalAssetHandler('url', unfurlBookmarkUrl)
|
||||
editor.setCurrentTool('hand')
|
||||
}}
|
||||
/>
|
||||
{isChatBoxVisible && (
|
||||
<div>
|
||||
<input
|
||||
type="text"
|
||||
value={userName}
|
||||
onChange={handleNameChange}
|
||||
placeholder="Enter your name"
|
||||
/>
|
||||
<ChatBox
|
||||
userName={userName}
|
||||
roomId={roomId} // Added roomId
|
||||
w={200} // Set appropriate width
|
||||
h={200} // Set appropriate height
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{isVideoChatVisible && ( // Render the button to join video chat
|
||||
<button onClick={() => setVideoChatVisible(false)} className="bg-green-500 text-white px-4 py-2 rounded">
|
||||
Join Video Call
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// How does our server handle bookmark unfurling?
|
||||
async function unfurlBookmarkUrl({ url }: { url: string }): Promise<TLBookmarkAsset> {
|
||||
const asset: TLBookmarkAsset = {
|
||||
id: AssetRecordType.createId(getHashForString(url)),
|
||||
typeName: 'asset',
|
||||
type: 'bookmark',
|
||||
meta: {},
|
||||
props: {
|
||||
src: url,
|
||||
description: '',
|
||||
image: '',
|
||||
favicon: '',
|
||||
title: '',
|
||||
},
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${WORKER_URL}/unfurl?url=${encodeURIComponent(url)}`)
|
||||
const data = await response.json() as { description: string, image: string, favicon: string, title: string }
|
||||
|
||||
asset.props.description = data?.description ?? ''
|
||||
asset.props.image = data?.image ?? ''
|
||||
asset.props.favicon = data?.favicon ?? ''
|
||||
asset.props.title = data?.title ?? ''
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
|
||||
return asset
|
||||
}
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
import { BaseBoxShapeUtil, TLBaseShape } from "tldraw";
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
export type IEmbedShape = TLBaseShape<
|
||||
'Embed',
|
||||
{
|
||||
w: number;
|
||||
h: number;
|
||||
url: string | null;
|
||||
}
|
||||
>;
|
||||
|
||||
export class EmbedShape extends BaseBoxShapeUtil<IEmbedShape> {
|
||||
static override type = 'Embed';
|
||||
|
||||
getDefaultProps(): IEmbedShape['props'] {
|
||||
return {
|
||||
url: null,
|
||||
w: 640,
|
||||
h: 480,
|
||||
};
|
||||
}
|
||||
|
||||
indicator(shape: IEmbedShape) {
|
||||
return (
|
||||
<g>
|
||||
<rect x={0} y={0} width={shape.props.w} height={shape.props.h} />
|
||||
</g>
|
||||
);
|
||||
}
|
||||
|
||||
component(shape: IEmbedShape) {
|
||||
const [inputUrl, setInputUrl] = useState(shape.props.url || '');
|
||||
const [error, setError] = useState('');
|
||||
|
||||
const handleSubmit = useCallback((e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
let completedUrl = inputUrl.startsWith('http://') || inputUrl.startsWith('https://') ? inputUrl : `https://${inputUrl}`;
|
||||
|
||||
// Handle YouTube links
|
||||
if (completedUrl.includes('youtube.com') || completedUrl.includes('youtu.be')) {
|
||||
const videoId = extractYouTubeVideoId(completedUrl);
|
||||
if (videoId) {
|
||||
completedUrl = `https://www.youtube.com/embed/${videoId}`;
|
||||
} else {
|
||||
setError('Invalid YouTube URL');
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Handle Google Docs links
|
||||
if (completedUrl.includes('docs.google.com')) {
|
||||
// Handle different types of Google Docs URLs
|
||||
if (completedUrl.includes('/document/d/')) {
|
||||
const docId = completedUrl.match(/\/document\/d\/([a-zA-Z0-9-_]+)/)?.[1];
|
||||
if (docId) {
|
||||
completedUrl = `https://docs.google.com/document/d/${docId}/edit`;
|
||||
} else {
|
||||
setError('Invalid Google Docs URL');
|
||||
return;
|
||||
}
|
||||
} else if (completedUrl.includes('/spreadsheets/d/')) {
|
||||
const docId = completedUrl.match(/\/spreadsheets\/d\/([a-zA-Z0-9-_]+)/)?.[1];
|
||||
if (docId) {
|
||||
completedUrl = `https://docs.google.com/spreadsheets/d/${docId}/edit`;
|
||||
} else {
|
||||
setError('Invalid Google Sheets URL');
|
||||
return;
|
||||
}
|
||||
} else if (completedUrl.includes('/presentation/d/')) {
|
||||
const docId = completedUrl.match(/\/presentation\/d\/([a-zA-Z0-9-_]+)/)?.[1];
|
||||
if (docId) {
|
||||
completedUrl = `https://docs.google.com/presentation/d/${docId}/embed`;
|
||||
} else {
|
||||
setError('Invalid Google Slides URL');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Add parameters for access
|
||||
completedUrl += '?authuser=0'; // Allow Google authentication
|
||||
}
|
||||
|
||||
this.editor.updateShape<IEmbedShape>({ id: shape.id, type: 'Embed', props: { ...shape.props, url: completedUrl } });
|
||||
|
||||
// Check if the URL is valid
|
||||
const isValidUrl = completedUrl.match(/(^\w+:|^)\/\//);
|
||||
if (!isValidUrl) {
|
||||
setError('Invalid website URL');
|
||||
} else {
|
||||
setError('');
|
||||
}
|
||||
}, [inputUrl]);
|
||||
|
||||
const extractYouTubeVideoId = (url: string): string | null => {
|
||||
const regExp = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/;
|
||||
const match = url.match(regExp);
|
||||
return (match && match[2].length === 11) ? match[2] : null;
|
||||
};
|
||||
|
||||
const wrapperStyle = {
|
||||
width: `${shape.props.w}px`,
|
||||
height: `${shape.props.h}px`,
|
||||
padding: '15px',
|
||||
boxShadow: '0 2px 4px rgba(0, 0, 0, 0.1)',
|
||||
backgroundColor: '#F0F0F0',
|
||||
borderRadius: '4px',
|
||||
};
|
||||
|
||||
const contentStyle = {
|
||||
pointerEvents: 'all' as const,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
border: '1px solid #D3D3D3',
|
||||
backgroundColor: '#FFFFFF',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
overflow: 'hidden',
|
||||
};
|
||||
|
||||
if (!shape.props.url) {
|
||||
return (
|
||||
<div style={wrapperStyle}>
|
||||
<div style={contentStyle} onClick={() => document.querySelector('input')?.focus()}>
|
||||
<form onSubmit={handleSubmit} style={{ width: '100%', height: '100%', padding: '10px' }}>
|
||||
<input
|
||||
type="text"
|
||||
value={inputUrl}
|
||||
onChange={(e) => setInputUrl(e.target.value)}
|
||||
placeholder="Enter URL"
|
||||
style={{ width: '100%', height: '100%', border: 'none', padding: '10px' }}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
handleSubmit(e);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{error && <div style={{ color: 'red', marginTop: '10px' }}>{error}</div>}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={wrapperStyle}>
|
||||
<div style={contentStyle}>
|
||||
<iframe
|
||||
src={shape.props.url}
|
||||
width="100%"
|
||||
height="100%"
|
||||
style={{ border: 'none' }}
|
||||
allowFullScreen
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue