fixed a bunch of stuff
This commit is contained in:
parent
0f152d1246
commit
434bd116dd
|
|
@ -4,6 +4,8 @@ import {
|
|||
getHashForString,
|
||||
TLBookmarkAsset,
|
||||
Tldraw,
|
||||
TLUiMenuGroup,
|
||||
TLUiOverrides,
|
||||
} from 'tldraw'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { ChatBoxTool } from '@/tools/ChatBoxTool'
|
||||
|
|
@ -43,16 +45,59 @@ export function Board() {
|
|||
setUserName(event.target.value);
|
||||
};
|
||||
|
||||
const customUiOverrides: TLUiOverrides = {
|
||||
...uiOverrides,
|
||||
contextMenu: (editor, contextMenuSchema, helpers) => {
|
||||
const defaultContextMenu = uiOverrides.contextMenu ? uiOverrides.contextMenu(editor, contextMenuSchema, helpers) : contextMenuSchema
|
||||
|
||||
const newContextMenu: TLUiMenuGroup[] = [
|
||||
...defaultContextMenu,
|
||||
{
|
||||
id: 'external-link',
|
||||
type: 'group',
|
||||
checkbox: false,
|
||||
disabled: false,
|
||||
readonlyOk: true,
|
||||
children: [
|
||||
{
|
||||
id: 'add-external-link',
|
||||
type: 'item',
|
||||
readonlyOk: true,
|
||||
label: 'Add External Link',
|
||||
icon: 'link',
|
||||
onSelect: () => {
|
||||
const selectedShapes = editor.getSelectedShapes()
|
||||
if (selectedShapes.length === 1) {
|
||||
const shape = selectedShapes[0]
|
||||
const externalUrl = `${window.location.origin}/board/${roomId}?shapeId=${shape.id}`
|
||||
// Here you can implement the logic to copy the link to clipboard or show it to the user
|
||||
console.log('External link:', externalUrl)
|
||||
// For example, to copy to clipboard:
|
||||
navigator.clipboard.writeText(externalUrl).then(() => {
|
||||
editor.setToast({ id: 'external-link-copied', title: 'External link copied to clipboard' })
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
return newContextMenu
|
||||
},
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ position: 'fixed', inset: 0 }}>
|
||||
<Tldraw
|
||||
store={store}
|
||||
shapeUtils={shapeUtils}
|
||||
overrides={uiOverrides}
|
||||
overrides={customUiOverrides}
|
||||
components={components}
|
||||
tools={tools}
|
||||
onMount={(editor) => {
|
||||
editor.registerExternalAssetHandler('url', unfurlBookmarkUrl)
|
||||
editor.setCurrentTool('hand')
|
||||
}}
|
||||
/>
|
||||
{isChatBoxVisible && (
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ export function Default() {
|
|||
|
||||
<h2>Get in touch</h2>
|
||||
<p>
|
||||
I am on Twitter <a href="https://twitter.com/OrionReedOne">@OrionReedOne</a>,
|
||||
I am on Twitter <a href="https://twitter.com/jeffemmett">@jeffemmett</a>,
|
||||
Mastodon <a href="https://hci.social/@orion">@orion@hci.social</a> and GitHub <a href="https://github.com/orionreed">@orionreed</a>. You can also shoot me an email <a href="mailto:me@orionreed.com">me@orionreed.com</a>
|
||||
</p>
|
||||
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ export const ChatBox: React.FC<IChatBoxShape['props']> = ({ roomId, w, h, userNa
|
|||
};
|
||||
|
||||
return (
|
||||
<div className="chat-container" style={{ pointerEvents: 'all', width: `${w}px`, height: `${h}px`, overflow: 'auto' }}>
|
||||
<div className="chat-container" style={{ pointerEvents: 'all', width: `${w}px`, height: `${h}px`, overflow: 'auto', touchAction: 'auto' }}>
|
||||
<div className="messages-container">
|
||||
{messages.map((msg) => (
|
||||
<div key={msg.id} className={`message ${msg.username === username ? 'own-message' : ''}`}>
|
||||
|
|
@ -114,8 +114,17 @@ export const ChatBox: React.FC<IChatBoxShape['props']> = ({ roomId, w, h, userNa
|
|||
onChange={(e) => setInputMessage(e.target.value)}
|
||||
placeholder="Type a message..."
|
||||
className="message-input"
|
||||
style={{ touchAction: 'manipulation' }}
|
||||
/>
|
||||
<button type="submit" style={{ pointerEvents: 'all', }} onPointerDown={(e) => e.stopPropagation()} className="send-button">Send</button>
|
||||
<button
|
||||
type="submit"
|
||||
style={{ pointerEvents: 'all', touchAction: 'manipulation' }}
|
||||
onPointerDown={(e) => e.stopPropagation()}
|
||||
onTouchStart={(e) => e.stopPropagation()}
|
||||
className="send-button"
|
||||
>
|
||||
Send
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -25,39 +25,113 @@ export class EmbedShape extends BaseBoxShapeUtil<IEmbedShape> {
|
|||
return (
|
||||
<g>
|
||||
<rect x={0} y={0} width={shape.props.w} height={shape.props.h} />
|
||||
<rect x={0} y={0} width={shape.props.w} height={shape.props.h} style={{ stroke: 'black', strokeWidth: 2, fill: 'none' }} />
|
||||
</g>
|
||||
);
|
||||
}
|
||||
|
||||
component(shape: IEmbedShape) {
|
||||
const [inputUrl, setInputUrl] = useState(shape.props.url || '');
|
||||
const [error, setError] = useState('');
|
||||
|
||||
const handleSubmit = useCallback((e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
this.editor.updateShape<IEmbedShape>({ id: shape.id, type: 'Embed', props: { ...shape.props, url: inputUrl } });
|
||||
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')) {
|
||||
const docId = completedUrl.match(/\/d\/([a-zA-Z0-9-_]+)/)?.[1];
|
||||
if (docId) {
|
||||
completedUrl = `https://docs.google.com/document/d/${docId}/preview`;
|
||||
} else {
|
||||
setError('Invalid Google Docs URL');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
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={{ pointerEvents: 'all', border: '1px solid #000', borderRadius: '5px', padding: '5px' }}>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<input
|
||||
type="text"
|
||||
value={inputUrl}
|
||||
onChange={(e) => setInputUrl(e.target.value)}
|
||||
placeholder="Enter URL"
|
||||
style={{ width: shape.props.w, height: shape.props.h }}
|
||||
/>
|
||||
<button type="submit" onTouchStart={handleSubmit} onClick={handleSubmit}>Load</button>
|
||||
</form>
|
||||
<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={{ pointerEvents: 'all' }}>
|
||||
<iframe src={shape.props.url} width={shape.props.w} height={shape.props.h} />
|
||||
<div style={wrapperStyle}>
|
||||
<div style={contentStyle}>
|
||||
<iframe
|
||||
src={shape.props.url}
|
||||
width="100%"
|
||||
height="100%"
|
||||
style={{ border: 'none' }}
|
||||
allowFullScreen
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ export type IVideoChatShape = TLBaseShape<
|
|||
>;
|
||||
|
||||
const WHEREBY_API_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmFwcGVhci5pbiIsImF1ZCI6Imh0dHBzOi8vYXBpLmFwcGVhci5pbi92MSIsImV4cCI6OTAwNzE5OTI1NDc0MDk5MSwiaWF0IjoxNzI5MTkzOTE3LCJvcmdhbml6YXRpb25JZCI6MjY2MDk5LCJqdGkiOiI0MzI0MmUxMC1kZmRjLTRhYmEtYjlhOS01ZjcwNTFlMTYwZjAifQ.RaxXpZKYl_dOWyoATQZrzyMR2XRh3fHf02mALQiuTTs'; // Replace with your actual API key
|
||||
// const ROOM_PREFIX = 'test'
|
||||
|
||||
export class VideoChatShape extends BaseBoxShapeUtil<IVideoChatShape> {
|
||||
static override type = 'VideoChat';
|
||||
|
|
@ -34,8 +33,6 @@ export class VideoChatShape extends BaseBoxShapeUtil<IVideoChatShape> {
|
|||
|
||||
async ensureRoomExists(shape: IVideoChatShape) {
|
||||
|
||||
console.log('This is your roomUrl 1:', shape.props.roomUrl);
|
||||
|
||||
if (shape.props.roomUrl !== null) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -45,14 +42,12 @@ export class VideoChatShape extends BaseBoxShapeUtil<IVideoChatShape> {
|
|||
const response = await fetch(`${CORS_PROXY}https://api.whereby.dev/v1/meetings`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
// 'Access-Control-Allow-Origin': 'https://jeffemmett.com/',
|
||||
'Authorization': `Bearer ${WHEREBY_API_KEY}`,
|
||||
'Content-Type': 'application/json',
|
||||
'X-Requested-With': 'XMLHttpRequest', // Required by some CORS proxies
|
||||
},
|
||||
body: JSON.stringify({
|
||||
isLocked: false,
|
||||
// roomNamePrefix: ROOM_PREFIX,
|
||||
roomMode: 'normal',
|
||||
endDate: expiryDate.toISOString(),
|
||||
fields: ['hostRoomUrl'],
|
||||
|
|
@ -62,10 +57,6 @@ export class VideoChatShape extends BaseBoxShapeUtil<IVideoChatShape> {
|
|||
throw error;
|
||||
});
|
||||
|
||||
console.log('This is your response:', response);
|
||||
|
||||
console.log('This is your roomUrl 2:', shape.props.roomUrl);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
console.error('Whereby API error:', errorData);
|
||||
|
|
@ -85,8 +76,6 @@ export class VideoChatShape extends BaseBoxShapeUtil<IVideoChatShape> {
|
|||
roomUrl
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
}
|
||||
|
||||
component(shape: IVideoChatShape) {
|
||||
|
|
@ -107,7 +96,6 @@ export class VideoChatShape extends BaseBoxShapeUtil<IVideoChatShape> {
|
|||
}, []);
|
||||
|
||||
const joinRoom = async () => {
|
||||
// this.ensureRoomExists(shape);
|
||||
setError("");
|
||||
setIsLoading(true);
|
||||
try {
|
||||
|
|
@ -126,29 +114,53 @@ export class VideoChatShape extends BaseBoxShapeUtil<IVideoChatShape> {
|
|||
};
|
||||
|
||||
return (
|
||||
<div className="p-4" style={{ pointerEvents: 'all', width: '100%', height: '100%' }}>
|
||||
{isLoading ? (
|
||||
<p>Joining room...</p>
|
||||
) : isInRoom && shape.props.roomUrl && typeof window !== 'undefined' ? (
|
||||
<div className="mb-4" style={{ width: '100%', height: '100%' }}>
|
||||
<whereby-embed
|
||||
room={shape.props.roomUrl}
|
||||
background="off"
|
||||
logo="off"
|
||||
chat="off"
|
||||
screenshare="on"
|
||||
people="on"
|
||||
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
|
||||
></whereby-embed>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<button onClick={joinRoom} className="bg-blue-500 text-white px-4 py-2 rounded">
|
||||
Join Room
|
||||
</button>
|
||||
{error && <p className="text-red-500 mt-2">{error}</p>}
|
||||
</div>
|
||||
)}
|
||||
<div style={{
|
||||
pointerEvents: 'all',
|
||||
width: `${shape.props.w}px`,
|
||||
height: `${shape.props.h}px`,
|
||||
position: 'absolute',
|
||||
top: '10px',
|
||||
left: '10px',
|
||||
zIndex: 9999,
|
||||
padding: '15px', // Increased padding by 5px
|
||||
margin: 0,
|
||||
backgroundColor: '#F0F0F0', // Light gray background
|
||||
boxShadow: '0 2px 4px rgba(0, 0, 0, 0.1)', // Added drop shadow
|
||||
borderRadius: '4px', // Slight border radius for softer look
|
||||
}}>
|
||||
<div style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
border: '1px solid #D3D3D3',
|
||||
backgroundColor: '#FFFFFF',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
overflow: 'hidden',
|
||||
}}>
|
||||
{isLoading ? (
|
||||
<p>Joining room...</p>
|
||||
) : isInRoom && shape.props.roomUrl && typeof window !== 'undefined' ? (
|
||||
<div className="mb-4" style={{ width: '100%', height: '100%', objectFit: 'contain' }}>
|
||||
<whereby-embed
|
||||
room={shape.props.roomUrl}
|
||||
background="off"
|
||||
logo="off"
|
||||
chat="off"
|
||||
screenshare="on"
|
||||
people="on"
|
||||
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
|
||||
></whereby-embed>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<button onClick={joinRoom} className="bg-blue-500 text-white px-4 py-2 rounded">
|
||||
Join Room
|
||||
</button>
|
||||
{error && <p className="text-red-500 mt-2">{error}</p>}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,7 +43,6 @@ export class TldrawDurableObject {
|
|||
private readonly ctx: DurableObjectState,
|
||||
env: Environment
|
||||
) {
|
||||
console.log("hello from durable object")
|
||||
this.r2 = env.TLDRAW_BUCKET
|
||||
|
||||
ctx.blockConcurrencyWhile(async () => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue