morphoweb-3d-architect / collaborate.html
sam-bottes's picture
évoluer vers :
f925175 verified
Raw
History Blame Contribute Delete
16.6 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MorphoWeb 3D - Real-time Collaboration</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/feather-icons"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script src="https://cdn.socket.io/4.5.0/socket.io.min.js"></script>
<style>
.collab-cursor {
position: absolute;
width: 16px;
height: 16px;
border-radius: 50%;
pointer-events: none;
z-index: 1000;
}
.user-avatar {
width: 32px;
height: 32px;
border-radius: 50%;
border: 2px solid;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
font-weight: bold;
}
#preview-container {
background-color: #f3f4f6;
border-radius: 0.5rem;
height: 500px;
position: relative;
overflow: hidden;
}
</style>
</head>
<body class="bg-gray-100 min-h-screen">
<div class="flex flex-col h-screen">
<!-- Top Bar -->
<div class="bg-white shadow-sm py-3 px-6 flex justify-between items-center">
<div class="flex items-center space-x-4">
<h1 class="font-bold text-xl flex items-center">
<i data-feather="users" class="text-indigo-600 mr-2"></i>
Real-time Collaboration
</h1>
<div class="flex items-center space-x-2 bg-indigo-50 text-indigo-700 px-3 py-1 rounded-full">
<div class="w-2 h-2 bg-green-500 rounded-full"></div>
<span class="text-sm">Connected</span>
</div>
</div>
<div class="flex items-center space-x-4">
<div class="flex -space-x-2">
<div class="user-avatar bg-red-100 border-red-400 text-red-600">JD</div>
<div class="user-avatar bg-blue-100 border-blue-400 text-blue-600">AM</div>
<div class="user-avatar bg-green-100 border-green-400 text-green-600">SK</div>
</div>
<button class="bg-indigo-600 text-white px-4 py-2 rounded-md flex items-center">
<i data-feather="share-2" class="mr-2"></i> Invite
</button>
</div>
</div>
<!-- Main Content -->
<div class="flex flex-1 overflow-hidden">
<!-- Sidebar -->
<div class="w-64 bg-white shadow-md p-4 overflow-y-auto">
<div class="mb-6">
<h2 class="font-bold mb-3 flex items-center">
<i data-feather="users" class="mr-2"></i> Team Members
</h2>
<div class="space-y-2">
<div class="flex items-center p-2 bg-indigo-50 rounded-lg">
<div class="user-avatar bg-red-100 border-red-400 text-red-600 mr-3">JD</div>
<div>
<p class="font-medium">John Doe</p>
<p class="text-xs text-gray-500">Editing</p>
</div>
</div>
<div class="flex items-center p-2 hover:bg-gray-50 rounded-lg">
<div class="user-avatar bg-blue-100 border-blue-400 text-blue-600 mr-3">AM</div>
<div>
<p class="font-medium">Alice Martin</p>
<p class="text-xs text-gray-500">Viewing</p>
</div>
</div>
<div class="flex items-center p-2 hover:bg-gray-50 rounded-lg">
<div class="user-avatar bg-green-100 border-green-400 text-green-600 mr-3">SK</div>
<div>
<p class="font-medium">Sam Kim</p>
<p class="text-xs text-gray-500">Viewing</p>
</div>
</div>
</div>
</div>
<div class="mb-6">
<h2 class="font-bold mb-3 flex items-center">
<i data-feather="message-square" class="mr-2"></i> Chat
</h2>
<div id="chat-messages" class="h-48 overflow-y-auto mb-3 space-y-2">
<div class="chat-message">
<p class="text-sm font-medium">John Doe:</p>
<p class="text-sm">Let's work on the header section first</p>
</div>
<div class="chat-message">
<p class="text-sm font-medium">Alice Martin:</p>
<p class="text-sm">Sounds good, I'll handle the styling</p>
</div>
</div>
<div class="flex">
<input type="text" placeholder="Type a message..." class="flex-1 border border-gray-300 rounded-l-lg px-3 py-2 text-sm focus:outline-none">
<button class="bg-indigo-600 text-white px-3 py-2 rounded-r-lg">
<i data-feather="send" class="w-4 h-4"></i>
</button>
</div>
</div>
<div>
<h2 class="font-bold mb-3 flex items-center">
<i data-feather="clock" class="mr-2"></i> Version History
</h2>
<div class="space-y-2">
<div class="text-sm p-2 bg-gray-50 rounded-lg cursor-pointer">
<p class="font-medium">Version 1.2</p>
<p class="text-xs text-gray-500">Saved 5 minutes ago</p>
</div>
<div class="text-sm p-2 hover:bg-gray-50 rounded-lg cursor-pointer">
<p class="font-medium">Version 1.1</p>
<p class="text-xs text-gray-500">Saved 1 hour ago</p>
</div>
</div>
</div>
</div>
<!-- Editor Area -->
<div class="flex-1 flex flex-col overflow-hidden">
<!-- Toolbar -->
<div class="bg-white border-b border-gray-200 p-3 flex items-center justify-between">
<div class="flex items-center space-x-4">
<h2 class="font-medium">Project: My Website</h2>
<div class="flex items-center text-sm bg-green-50 text-green-700 px-3 py-1 rounded-full">
<i data-feather="globe" class="w-4 h-4 mr-1"></i>
<span>Public Editing</span>
</div>
</div>
<div class="flex space-x-3">
<button class="flex items-center px-3 py-1 bg-gray-100 rounded-md text-sm hover:bg-gray-200">
<i data-feather="save" class="w-4 h-4 mr-1"></i>
Save Version
</button>
<button class="flex items-center px-3 py-1 bg-indigo-600 text-white rounded-md text-sm hover:bg-indigo-700">
<i data-feather="download" class="w-4 h-4 mr-1"></i>
Export
</button>
</div>
</div>
<!-- Collaborative Editor -->
<div class="flex-1 flex overflow-hidden">
<div id="preview-container">
<!-- Collaborative preview will be rendered here -->
<div id="collaborative-scene"></div>
</div>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
feather.replace();
// Connect to Socket.IO server
const socket = io('https://collab.morphoweb3d.com');
// Initialize Three.js scene
const scene = new THREE.Scene();
const container = document.getElementById('collaborative-scene');
const width = container.clientWidth;
const height = container.clientHeight;
const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(width, height);
container.appendChild(renderer.domElement);
// Add lights
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(1, 1, 1);
scene.add(directionalLight);
// Create a grid helper
const gridHelper = new THREE.GridHelper(10, 10);
scene.add(gridHelper);
// Camera position
camera.position.z = 5;
// Animation loop
const animate = () => {
requestAnimationFrame(animate);
renderer.render(scene, camera);
};
animate();
// Handle window resize
window.addEventListener('resize', () => {
camera.aspect = container.clientWidth / container.clientHeight;
camera.updateProjectionMatrix();
renderer.setSize(container.clientWidth, container.clientHeight);
});
// Real-time collaboration functionality
const users = {};
const cursors = {};
// Create a cursor for the current user
const currentUserId = 'user-' + Math.random().toString(36).substr(2, 9);
const currentUserColor = `hsl(${Math.floor(Math.random() * 360)}, 70%, 60%)`;
// Handle incoming updates from other users
socket.on('user-update', (data) => {
if (data.userId === currentUserId) return;
// Update or create user cursor
if (!cursors[data.userId]) {
const cursor = document.createElement('div');
cursor.className = 'collab-cursor';
cursor.style.backgroundColor = data.color;
cursor.style.border = `2px solid ${data.color}`;
document.getElementById('preview-container').appendChild(cursor);
cursors[data.userId] = cursor;
// Add user info tooltip
const tooltip = document.createElement('div');
tooltip.className = 'absolute text-xs font-medium whitespace-nowrap -mt-6';
tooltip.style.color = data.color;
tooltip.textContent = data.username;
cursor.appendChild(tooltip);
}
// Update cursor position
const cursor = cursors[data.userId];
if (cursor) {
cursor.style.left = `${data.x}px`;
cursor.style.top = `${data.y}px`;
}
// Update scene objects if needed
if (data.action === 'add-object') {
// Handle object addition from other users
const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshBasicMaterial({ color: new THREE.Color(data.color) });
const cube = new THREE.Mesh(geometry, material);
cube.position.set(data.position.x, data.position.y, data.position.z);
cube.userData.userId = data.userId;
scene.add(cube);
}
});
// Handle mouse movement
document.getElementById('preview-container').addEventListener('mousemove', (e) => {
const rect = e.target.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
// Send cursor position to server
socket.emit('user-update', {
userId: currentUserId,
username: 'You',
color: currentUserColor,
x: x,
y: y
});
});
// Handle clicks to add objects
document.getElementById('preview-container').addEventListener('click', (e) => {
const rect = e.target.getBoundingClientRect();
const x = (e.clientX - rect.left) / rect.width * 2 - 1;
const y = -(e.clientY - rect.top) / rect.height * 2 + 1;
// Calculate 3D position from screen coordinates
const mouse = new THREE.Vector2(x, y);
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObject(gridHelper);
if (intersects.length > 0) {
const position = intersects[0].point;
// Create object locally
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ color: new THREE.Color(currentUserColor) });
const cube = new THREE.Mesh(geometry, material);
cube.position.copy(position);
cube.userData.userId = currentUserId;
scene.add(cube);
// Send object creation to server
socket.emit('user-update', {
userId: currentUserId,
username: 'You',
color: currentUserColor,
action: 'add-object',
position: {
x: position.x,
y: position.y,
z: position.z
}
});
}
});
// Initialize chat functionality
const chatInput = document.querySelector('#chat-messages + div input');
const chatButton = document.querySelector('#chat-messages + div button');
chatButton.addEventListener('click', sendMessage);
chatInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') sendMessage();
});
function sendMessage() {
const message = chatInput.value.trim();
if (message) {
socket.emit('chat-message', {
userId: currentUserId,
username: 'You',
message: message,
color: currentUserColor
});
chatInput.value = '';
}
}
socket.on('chat-message', (data) => {
const chat = document.getElementById('chat-messages');
const messageDiv = document.createElement('div');
messageDiv.className = 'chat-message';
messageDiv.innerHTML = `
<p class="text-sm font-medium" style="color: ${data.color}">${data.username}:</p>
<p class="text-sm">${data.message}</p>
`;
chat.appendChild(messageDiv);
chat.scrollTop = chat.scrollHeight;
});
});
</script>
</body>
</html>