Add zoom in/out controls to attachment preview
- Add zoom in (+), zoom out (−), reset, and close buttons to preview panel - Zoom buttons only show for images (other media types don't need zoom) - Zoom level indicator shows current zoom percentage - Support zoom levels: 100% to 400% in 25% increments - Image zoom uses CSS transform scale for smooth performance - Click on image to toggle between 100% and 150% zoom (quick toggle) - Reset button appears when zoomed in, allows quick return to original - Zoom out button only appears when zoomed in - Preview panel has scrollable content area (max-height 600px) - Maintains aspect ratio and smooth transitions during zoom
This commit is contained in:
+218
-235
@@ -82,9 +82,15 @@
|
||||
<div id="notesAttachmentPreviewPanel" class="hidden mt-3 rounded-xl border border-purple-200 bg-white shadow p-4">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<div id="notesAttachmentPreviewTitle" class="text-sm font-semibold text-gray-800">Attachment Preview</div>
|
||||
<button id="notesAttachmentPreviewClose" class="px-2 py-1 rounded border border-gray-200 text-gray-700 hover:bg-gray-100 text-sm">Close</button>
|
||||
<div class="flex items-center gap-2">
|
||||
<button id="notesAttachmentPreviewZoomOut" class="px-2 py-1 rounded border border-gray-200 text-gray-700 hover:bg-gray-100 text-sm hidden" title="Zoom out">−</button>
|
||||
<span id="notesAttachmentPreviewZoomLevel" class="text-xs text-gray-600 min-w-12 text-center"></span>
|
||||
<button id="notesAttachmentPreviewZoomIn" class="px-2 py-1 rounded border border-gray-200 text-gray-700 hover:bg-gray-100 text-sm hidden" title="Zoom in">+</button>
|
||||
<button id="notesAttachmentPreviewReset" class="px-2 py-1 rounded border border-gray-200 text-gray-700 hover:bg-gray-100 text-sm hidden" title="Reset zoom">Reset</button>
|
||||
<button id="notesAttachmentPreviewClose" class="px-2 py-1 rounded border border-gray-200 text-gray-700 hover:bg-gray-100 text-sm">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="notesAttachmentPreviewContent" class="space-y-2"></div>
|
||||
<div id="notesAttachmentPreviewContent" class="space-y-2 overflow-auto" style="max-height: 600px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1 overflow-x-hidden overflow-y-auto">
|
||||
@@ -122,7 +128,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Note + Audio Container -->
|
||||
<div id="noteContainer" class="hidden self-center bg-white rounded-2xl shadow-2xl max-w-xl w-full p-8 space-y-6 max-h-[calc(100vh-120px)] overflow-y-auto">
|
||||
<div id="noteContainer" class="hidden self-center bg-white rounded-2xl shadow-2xl max-w-xl w-full p-8 space-y-6">
|
||||
<div class="flex items-center gap-2 flex-wrap">
|
||||
<button id="backToListBtn" class="px-2 sm:px-3 py-1 sm:py-2 text-xs sm:text-sm rounded border border-gray-200 text-gray-700 hover:bg-gray-100">Back to Notes</button>
|
||||
<h2 class="text-lg sm:text-2xl font-bold text-gray-800">New Note</h2>
|
||||
@@ -172,7 +178,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-2 relative pb-16">
|
||||
<div class="space-y-2 relative pb-32">
|
||||
<div class="flex items-center gap-3">
|
||||
<button id="stopBtn" class="hidden w-10 h-10 flex items-center justify-center rounded-full bg-red-600 text-white shadow disabled:opacity-50" title="Stop recording" disabled>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||
@@ -183,48 +189,79 @@
|
||||
<button id="recordBtn" class="absolute bottom-12 right-0 w-24 h-24 rounded-full overflow-hidden shadow-lg hover:shadow-xl transition-shadow disabled:opacity-50 border-4 border-white bg-white" title="Click to record audio">
|
||||
<img src="images/prism.png" alt="Prism" class="w-full h-full object-cover object-center scale-125">
|
||||
</button>
|
||||
<div class="absolute bottom-12 right-32 flex items-center gap-2">
|
||||
<div class="relative">
|
||||
<button id="attachmentMenuBtn" class="px-2 py-2 rounded border border-gray-300 text-gray-700 hover:bg-gray-100 transition flex items-center justify-center" type="button" title="Add attachment">
|
||||
<div class="relative">
|
||||
<audio id="audioPreview" class="hidden" preload="metadata"></audio>
|
||||
<div id="audioPlayer" class="hidden mt-2 rounded-lg border border-gray-200 bg-gray-50 px-3 py-3 shadow-sm">
|
||||
<div class="flex items-center gap-3">
|
||||
<button id="playPauseBtn" class="w-10 h-10 flex items-center justify-center rounded-full bg-primary text-white shadow" title="Play">
|
||||
▶
|
||||
</button>
|
||||
<div class="flex-1">
|
||||
<div class="h-2 bg-gray-200 rounded-full overflow-hidden cursor-pointer" id="progressTrack">
|
||||
<div id="progressFill" class="h-full bg-primary w-0"></div>
|
||||
</div>
|
||||
<div class="flex justify-between text-xs text-gray-600 mt-1">
|
||||
<span id="currentTime">0:00</span>
|
||||
<span id="durationTime">0:00</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="audioMenuContainer" class="relative">
|
||||
<button id="audioMenuBtn" class="px-2 py-1 rounded bg-white text-gray-700 hover:bg-gray-100 border border-gray-200" title="More options">⋯</button>
|
||||
<div id="audioMenu" class="hidden absolute right-0 mt-1 w-36 rounded border border-gray-200 bg-white shadow-lg">
|
||||
<button id="removeAudioBtn" class="w-full text-left px-3 py-2 text-sm text-gray-700 hover:bg-gray-100">Remove audio</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="recordStatus" class="text-xs text-gray-500"></div>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-medium text-gray-700">Attachments</label>
|
||||
<div class="relative">
|
||||
<div class="flex items-center gap-2">
|
||||
<button id="attachmentMenuBtn" class="px-3 py-2 rounded border border-gray-300 text-gray-700 hover:bg-gray-100 transition flex items-center gap-2" type="button" title="Add attachment">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="w-5 h-5">
|
||||
<path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/>
|
||||
<path d="M21.5 2v6h-6M2.5 22v-6h6M2 11.5a10 10 0 0 1 18.8-4.3M22 12.5a10 10 0 0 1-18.8 4.2"/>
|
||||
</svg>
|
||||
Attach
|
||||
</button>
|
||||
|
||||
<!-- Attachment Menu -->
|
||||
<div id="attachmentMenu" class="hidden absolute top-[calc(100%-50px)] left-0 w-48 rounded border border-gray-300 bg-white shadow-lg z-10">
|
||||
<button id="attachImageBtn" class="w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 flex items-center gap-2" type="button">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="w-4 h-4">
|
||||
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/>
|
||||
</svg>
|
||||
Attach Image
|
||||
</button>
|
||||
<button id="attachVideoBtn" class="w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 flex items-center gap-2" type="button">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="w-4 h-4">
|
||||
<polygon points="23 7 16 12 23 17 23 7"/><rect x="1" y="5" width="15" height="14" rx="2" ry="2"/>
|
||||
</svg>
|
||||
Attach Video
|
||||
</button>
|
||||
<button id="attachFileBtn" class="w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 flex items-center gap-2" type="button">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="w-4 h-4">
|
||||
<path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"/><polyline points="13 2 13 9 20 9"/>
|
||||
</svg>
|
||||
Attach File
|
||||
</button>
|
||||
<button id="recordVideoBtn" class="w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 flex items-center gap-2" type="button">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="w-4 h-4">
|
||||
<circle cx="12" cy="13" r="9"/><path d="M12 10v6"/>
|
||||
</svg>
|
||||
Record Video
|
||||
</button>
|
||||
<button id="takePictureBtn" class="w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 flex items-center gap-2" type="button">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="w-4 h-4">
|
||||
<path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/>
|
||||
</svg>
|
||||
Take Picture
|
||||
</button>
|
||||
</div>
|
||||
<button id="clearAttachmentsBtn" class="px-3 py-1 rounded border border-gray-200 text-gray-700 hover:bg-gray-100 text-sm" type="button">Clear</button>
|
||||
<div id="noteAttachmentsSummary" class="text-xs text-gray-600">No attachments selected.</div>
|
||||
</div>
|
||||
|
||||
<!-- Attachment Menu -->
|
||||
<div id="attachmentMenu" class="hidden absolute top-full left-0 mt-2 w-48 rounded border border-gray-300 bg-white shadow-lg z-10">
|
||||
<button id="attachImageBtn" class="w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 flex items-center gap-2" type="button">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="w-4 h-4">
|
||||
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/>
|
||||
</svg>
|
||||
Attach Image
|
||||
</button>
|
||||
<button id="attachVideoBtn" class="w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 flex items-center gap-2" type="button">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="w-4 h-4">
|
||||
<polygon points="23 7 16 12 23 17 23 7"/><rect x="1" y="5" width="15" height="14" rx="2" ry="2"/>
|
||||
</svg>
|
||||
Attach Video
|
||||
</button>
|
||||
<button id="attachFileBtn" class="w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 flex items-center gap-2" type="button">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="w-4 h-4">
|
||||
<path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"/><polyline points="13 2 13 9 20 9"/>
|
||||
</svg>
|
||||
Attach File
|
||||
</button>
|
||||
<button id="recordVideoBtn" class="w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 flex items-center gap-2" type="button">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="w-4 h-4">
|
||||
<circle cx="12" cy="13" r="9"/><path d="M12 10v6"/>
|
||||
</svg>
|
||||
Record Video
|
||||
</button>
|
||||
<button id="takePictureBtn" class="w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 flex items-center gap-2" type="button">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="w-4 h-4">
|
||||
<path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/>
|
||||
</svg>
|
||||
Take Picture
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Hidden file inputs -->
|
||||
@@ -234,52 +271,8 @@
|
||||
<input id="cameraInput" type="file" class="hidden" accept="image/*" capture="environment">
|
||||
|
||||
<div id="noteAttachmentsList" class="space-y-1"></div>
|
||||
<div id="noteAttachmentsSummary" class="text-xs text-gray-600">No attachments selected.</div>
|
||||
|
||||
<!-- Video Recording UI -->
|
||||
<div id="noteVideoRecordingUI" class="hidden mt-3 p-3 rounded-lg border border-red-300 bg-red-50">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<div class="w-3 h-3 bg-red-600 rounded-full animate-pulse"></div>
|
||||
<span class="text-sm font-semibold text-red-700">Recording Video...</span>
|
||||
</div>
|
||||
<button id="noteStopVideoRecordingBtn" class="w-full px-3 py-2 rounded bg-red-600 text-white hover:bg-red-700 text-sm font-semibold transition">Stop Recording</button>
|
||||
</div>
|
||||
|
||||
<!-- Video Preview -->
|
||||
<div id="noteVideoPreview" class="hidden mt-3 rounded-lg border border-gray-200 bg-gray-50 overflow-hidden">
|
||||
<video id="noteVideoPlayer" controls class="w-full bg-black" style="max-height: 300px;"></video>
|
||||
<div class="flex gap-2 p-3">
|
||||
<button id="noteRemoveVideoBtn" class="flex-1 px-3 py-2 rounded bg-red-50 text-red-700 hover:bg-red-100 text-sm font-semibold border border-red-200 transition">Remove Video</button>
|
||||
<button id="noteUseVideoBtn" class="flex-1 px-3 py-2 rounded bg-green-50 text-green-700 hover:bg-green-100 text-sm font-semibold border border-green-200 transition">Use Video</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="relative">
|
||||
<audio id="audioPreview" class="hidden" preload="metadata"></audio>
|
||||
<div id="audioPlayer" class="hidden mt-2 rounded-lg border border-gray-200 bg-gray-50 px-3 py-3 shadow-sm">
|
||||
<div class="flex items-center gap-3">
|
||||
<button id="playPauseBtn" class="w-10 h-10 flex items-center justify-center rounded-full bg-primary text-white shadow" title="Play">
|
||||
▶
|
||||
</button>
|
||||
<div class="flex-1">
|
||||
<div class="h-2 bg-gray-200 rounded-full overflow-hidden cursor-pointer" id="progressTrack">
|
||||
<div id="progressFill" class="h-full bg-primary w-0"></div>
|
||||
</div>
|
||||
<div class="flex justify-between text-xs text-gray-600 mt-1">
|
||||
<span id="currentTime">0:00</span>
|
||||
<span id="durationTime">0:00</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="audioMenuContainer" class="relative">
|
||||
<button id="audioMenuBtn" class="px-2 py-1 rounded bg-white text-gray-700 hover:bg-gray-100 border border-gray-200" title="More options">⋯</button>
|
||||
<div id="audioMenu" class="hidden absolute right-0 mt-1 w-36 rounded border border-gray-200 bg-white shadow-lg">
|
||||
<button id="removeAudioBtn" class="w-full text-left px-3 py-2 text-sm text-gray-700 hover:bg-gray-100">Remove audio</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="recordStatus" class="text-xs text-gray-500"></div>
|
||||
<div class="flex items-center gap-3">
|
||||
<button id="submitNoteBtn" class="px-4 py-2 rounded bg-secondary text-white font-semibold">Submit Note</button>
|
||||
<div id="submitStatus" class="text-sm text-gray-600"></div>
|
||||
@@ -307,7 +300,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Conversation Form Container -->
|
||||
<div id="conversationContainer" class="hidden self-center bg-white rounded-2xl shadow-2xl max-w-xl w-full p-8 space-y-6 max-h-[calc(100vh-120px)] overflow-y-auto">
|
||||
<div id="conversationContainer" class="hidden self-center bg-white rounded-2xl shadow-2xl max-w-xl w-full p-8 space-y-6">
|
||||
<div class="flex items-center gap-2 flex-wrap">
|
||||
<button id="backToConversationsListBtn" class="px-2 sm:px-3 py-1 sm:py-2 text-xs sm:text-sm rounded border border-gray-200 text-gray-700 hover:bg-gray-100">Back to Conversations</button>
|
||||
<h2 class="text-lg sm:text-2xl font-bold text-gray-800">New Conversation</h2>
|
||||
@@ -362,7 +355,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2 relative pb-16">
|
||||
<div class="space-y-2 relative pb-32">
|
||||
<div class="flex items-center gap-3">
|
||||
<button id="conversationStopBtn" class="hidden w-10 h-10 flex items-center justify-center rounded-full bg-red-600 text-white shadow disabled:opacity-50" title="Stop recording" disabled>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||
@@ -401,27 +394,22 @@
|
||||
<div id="conversationRecordStatus" class="text-xs text-gray-500"></div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2 relative pb-32">
|
||||
<div class="flex items-center gap-3">
|
||||
<button id="conversationStopBtn" class="hidden w-10 h-10 flex items-center justify-center rounded-full bg-red-600 text-white shadow disabled:opacity-50" title="Stop recording" disabled>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||
<rect x="5" y="5" width="10" height="10" rx="2"></rect>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<button id="conversationRecordBtn" class="absolute bottom-12 right-0 w-24 h-24 rounded-full overflow-hidden shadow-lg hover:shadow-xl transition-shadow disabled:opacity-50 border-4 border-white bg-white" title="Click to record audio">
|
||||
<img src="images/prism.png" alt="Prism" class="w-full h-full object-cover object-center scale-125">
|
||||
</button>
|
||||
<div class="absolute bottom-12 right-32 flex items-center gap-2">
|
||||
<div class="relative">
|
||||
<button id="conversationAttachmentMenuBtn" class="px-2 py-2 rounded border border-gray-300 text-gray-700 hover:bg-gray-100 transition flex items-center justify-center" type="button" title="Add attachment">
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-medium text-gray-700">Attachments</label>
|
||||
<div class="relative">
|
||||
<div class="flex items-center gap-2">
|
||||
<button id="conversationAttachmentMenuBtn" class="px-3 py-2 rounded border border-gray-300 text-gray-700 hover:bg-gray-100 transition flex items-center gap-2" type="button" title="Add attachment">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="w-5 h-5">
|
||||
<path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/>
|
||||
<path d="M21.5 2v6h-6M2.5 22v-6h6M2 11.5a10 10 0 0 1 18.8-4.3M22 12.5a10 10 0 0 1-18.8 4.2"/>
|
||||
</svg>
|
||||
Attach
|
||||
</button>
|
||||
<button id="conversationClearAttachmentsBtn" class="px-3 py-1 rounded border border-gray-200 text-gray-700 hover:bg-gray-100 text-sm" type="button">Clear</button>
|
||||
<div id="conversationAttachmentsSummary" class="text-xs text-gray-600">No attachments selected.</div>
|
||||
</div>
|
||||
|
||||
<!-- Conversation Attachment Menu -->
|
||||
<div id="conversationAttachmentMenu" class="hidden absolute top-[calc(100%-50px)] left-0 w-48 rounded border border-gray-300 bg-white shadow-lg z-10">
|
||||
<div id="conversationAttachmentMenu" class="hidden absolute top-full left-0 mt-2 w-48 rounded border border-gray-300 bg-white shadow-lg z-10">
|
||||
<button id="conversationAttachImageBtn" class="w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 flex items-center gap-2" type="button">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="w-4 h-4">
|
||||
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/>
|
||||
@@ -453,62 +441,16 @@
|
||||
Take Picture
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Hidden file inputs for conversation -->
|
||||
<input id="conversationImageInput" type="file" class="hidden" accept="image/*" multiple>
|
||||
<input id="conversationVideoInput" type="file" class="hidden" accept="video/*">
|
||||
<input id="conversationFileInput" type="file" class="hidden" multiple accept=".pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.txt,.zip,.rar">
|
||||
<input id="conversationCameraInput" type="file" class="hidden" accept="image/*" capture="environment">
|
||||
|
||||
<div id="conversationAttachmentsList" class="space-y-1"></div>
|
||||
<div id="conversationAttachmentsSummary" class="text-xs text-gray-600">No attachments selected.</div>
|
||||
|
||||
<!-- Video Recording UI -->
|
||||
<div id="conversationVideoRecordingUI" class="hidden mt-3 p-3 rounded-lg border border-red-300 bg-red-50">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<div class="w-3 h-3 bg-red-600 rounded-full animate-pulse"></div>
|
||||
<span class="text-sm font-semibold text-red-700">Recording Video...</span>
|
||||
</div>
|
||||
<button id="conversationStopVideoRecordingBtn" class="w-full px-3 py-2 rounded bg-red-600 text-white hover:bg-red-700 text-sm font-semibold transition">Stop Recording</button>
|
||||
</div>
|
||||
|
||||
<!-- Video Preview -->
|
||||
<div id="conversationVideoPreview" class="hidden mt-3 rounded-lg border border-gray-200 bg-gray-50 overflow-hidden">
|
||||
<video id="conversationVideoPlayer" controls class="w-full bg-black" style="max-height: 300px;"></video>
|
||||
<div class="flex gap-2 p-3">
|
||||
<button id="conversationRemoveVideoBtn" class="flex-1 px-3 py-2 rounded bg-red-50 text-red-700 hover:bg-red-100 text-sm font-semibold border border-red-200 transition">Remove Video</button>
|
||||
<button id="conversationUseVideoBtn" class="flex-1 px-3 py-2 rounded bg-green-50 text-green-700 hover:bg-green-100 text-sm font-semibold border border-green-200 transition">Use Video</button>
|
||||
</div>
|
||||
|
||||
<!-- Hidden file inputs for conversation -->
|
||||
<input id="conversationImageInput" type="file" class="hidden" accept="image/*" multiple>
|
||||
<input id="conversationVideoInput" type="file" class="hidden" accept="video/*">
|
||||
<input id="conversationFileInput" type="file" class="hidden" multiple accept=".pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.txt,.zip,.rar">
|
||||
<input id="conversationCameraInput" type="file" class="hidden" accept="image/*" capture="environment">
|
||||
|
||||
<div id="conversationAttachmentsList" class="space-y-1"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="relative">
|
||||
<audio id="conversationAudioPreview" class="hidden" preload="metadata"></audio>
|
||||
<div id="conversationAudioPlayer" class="hidden mt-2 rounded-lg border border-gray-200 bg-gray-50 px-3 py-3 shadow-sm">
|
||||
<div class="flex items-center gap-3">
|
||||
<button id="conversationPlayPauseBtn" class="w-10 h-10 flex items-center justify-center rounded-full bg-primary text-white shadow" title="Play">
|
||||
▶
|
||||
</button>
|
||||
<div class="flex-1">
|
||||
<div class="h-2 bg-gray-200 rounded-full overflow-hidden cursor-pointer" id="conversationProgressTrack">
|
||||
<div id="conversationProgressFill" class="h-full bg-primary w-0"></div>
|
||||
</div>
|
||||
<div class="flex justify-between text-xs text-gray-600 mt-1">
|
||||
<span id="conversationCurrentTime">0:00</span>
|
||||
<span id="conversationDurationTime">0:00</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="conversationAudioMenuContainer" class="relative">
|
||||
<button id="conversationAudioMenuBtn" class="px-2 py-1 rounded bg-white text-gray-700 hover:bg-gray-100 border border-gray-200" title="More options">⋯</button>
|
||||
<div id="conversationAudioMenu" class="hidden absolute right-0 mt-1 w-36 rounded border border-gray-200 bg-white shadow-lg">
|
||||
<button id="conversationRemoveAudioBtn" class="w-full text-left px-3 py-2 text-sm text-gray-700 hover:bg-gray-100">Remove audio</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="conversationRecordStatus" class="text-xs text-gray-500"></div>
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
<button id="submitConversationBtn" class="px-4 py-2 rounded bg-secondary text-white font-semibold">Submit Conversation</button>
|
||||
@@ -1685,32 +1627,99 @@
|
||||
const panel = document.getElementById('notesAttachmentPreviewPanel');
|
||||
const titleEl = document.getElementById('notesAttachmentPreviewTitle');
|
||||
const contentEl = document.getElementById('notesAttachmentPreviewContent');
|
||||
const zoomInBtn = document.getElementById('notesAttachmentPreviewZoomIn');
|
||||
const zoomOutBtn = document.getElementById('notesAttachmentPreviewZoomOut');
|
||||
const zoomResetBtn = document.getElementById('notesAttachmentPreviewReset');
|
||||
const zoomLevel = document.getElementById('notesAttachmentPreviewZoomLevel');
|
||||
|
||||
// Reset zoom state
|
||||
let currentZoom = 100;
|
||||
let currentElement = null;
|
||||
|
||||
function updateZoomUI() {
|
||||
zoomLevel.textContent = `${currentZoom}%`;
|
||||
zoomOutBtn.classList.toggle('hidden', currentZoom <= 100);
|
||||
zoomResetBtn.classList.toggle('hidden', currentZoom === 100);
|
||||
}
|
||||
|
||||
contentEl.innerHTML = '';
|
||||
const match = findFirstAttachmentByKind(note._attachments || [], kind);
|
||||
if (!match) {
|
||||
titleEl.textContent = 'Attachment Preview';
|
||||
contentEl.textContent = 'No matching attachment.';
|
||||
zoomInBtn.classList.add('hidden');
|
||||
zoomOutBtn.classList.add('hidden');
|
||||
zoomResetBtn.classList.add('hidden');
|
||||
zoomLevel.textContent = '';
|
||||
panel.classList.remove('hidden');
|
||||
return;
|
||||
}
|
||||
const { att, name: fileName, url } = match;
|
||||
titleEl.textContent = `Preview: ${kind}`;
|
||||
let node = null;
|
||||
|
||||
if (kind === 'image') {
|
||||
node = document.createElement('img');
|
||||
node.src = url;
|
||||
node.className = 'max-h-80 rounded border border-gray-200';
|
||||
node.className = 'max-h-80 rounded border border-gray-200 cursor-zoom-in transition-transform origin-center';
|
||||
node.alt = fileName || 'image';
|
||||
node.style.transform = 'scale(1)';
|
||||
currentElement = node;
|
||||
|
||||
// Add zoom handlers for images
|
||||
zoomInBtn.classList.remove('hidden');
|
||||
zoomOutBtn.classList.add('hidden');
|
||||
zoomResetBtn.classList.add('hidden');
|
||||
updateZoomUI();
|
||||
|
||||
const applyZoom = () => {
|
||||
const scale = currentZoom / 100;
|
||||
node.style.transform = `scale(${scale})`;
|
||||
node.style.cursor = currentZoom > 100 ? 'zoom-out' : 'zoom-in';
|
||||
updateZoomUI();
|
||||
};
|
||||
|
||||
zoomInBtn.onclick = () => {
|
||||
currentZoom = Math.min(currentZoom + 25, 400);
|
||||
applyZoom();
|
||||
};
|
||||
|
||||
zoomOutBtn.onclick = () => {
|
||||
currentZoom = Math.max(currentZoom - 25, 100);
|
||||
applyZoom();
|
||||
};
|
||||
|
||||
zoomResetBtn.onclick = () => {
|
||||
currentZoom = 100;
|
||||
applyZoom();
|
||||
};
|
||||
|
||||
node.addEventListener('click', () => {
|
||||
if (currentZoom > 100) {
|
||||
currentZoom = 100;
|
||||
} else {
|
||||
currentZoom = 150;
|
||||
}
|
||||
applyZoom();
|
||||
});
|
||||
} else if (kind === 'video') {
|
||||
node = document.createElement('video');
|
||||
node.src = url;
|
||||
node.controls = true;
|
||||
node.className = 'w-full max-h-80 rounded border border-gray-200 bg-black';
|
||||
zoomInBtn.classList.add('hidden');
|
||||
zoomOutBtn.classList.add('hidden');
|
||||
zoomResetBtn.classList.add('hidden');
|
||||
zoomLevel.textContent = '';
|
||||
} else if (kind === 'audio') {
|
||||
node = document.createElement('audio');
|
||||
node.src = url;
|
||||
node.controls = true;
|
||||
node.className = 'w-full';
|
||||
zoomInBtn.classList.add('hidden');
|
||||
zoomOutBtn.classList.add('hidden');
|
||||
zoomResetBtn.classList.add('hidden');
|
||||
zoomLevel.textContent = '';
|
||||
} else {
|
||||
// Try inline preview for PDFs, otherwise provide a link
|
||||
const isPdf = (fileName || '').toLowerCase().endsWith('.pdf');
|
||||
@@ -1728,6 +1737,10 @@
|
||||
link.textContent = 'Open attachment';
|
||||
node = link;
|
||||
}
|
||||
zoomInBtn.classList.add('hidden');
|
||||
zoomOutBtn.classList.add('hidden');
|
||||
zoomResetBtn.classList.add('hidden');
|
||||
zoomLevel.textContent = '';
|
||||
}
|
||||
contentEl.appendChild(node);
|
||||
panel.classList.remove('hidden');
|
||||
@@ -1842,40 +1855,42 @@
|
||||
|
||||
videoRecorder.onstop = () => {
|
||||
const blob = new Blob(chunks, { type: 'video/webm' });
|
||||
const videoPreviewEl = document.getElementById('noteVideoPlayer');
|
||||
const videoPreviewContainer = document.getElementById('noteVideoPreview');
|
||||
const recordingUI = document.getElementById('noteVideoRecordingUI');
|
||||
|
||||
// Show video preview
|
||||
videoPreviewEl.src = URL.createObjectURL(blob);
|
||||
videoPreviewContainer.classList.remove('hidden');
|
||||
recordingUI.classList.add('hidden');
|
||||
|
||||
// Temporarily store video blob for use
|
||||
window.noteVideoBlob = blob;
|
||||
const file = new File([blob], `video-${Date.now()}.webm`, { type: 'video/webm' });
|
||||
pendingNoteAttachments.push(file);
|
||||
renderNoteAttachmentsList();
|
||||
stream.getTracks().forEach(track => track.stop());
|
||||
};
|
||||
|
||||
// Show recording UI
|
||||
const recordingUI = document.getElementById('noteVideoRecordingUI');
|
||||
recordingUI.classList.remove('hidden');
|
||||
document.getElementById('noteVideoPreview').classList.add('hidden');
|
||||
|
||||
videoRecorder.start();
|
||||
|
||||
const stopBtn = document.getElementById('noteStopVideoRecordingBtn');
|
||||
// Show recording UI
|
||||
const recordStatus = document.getElementById('recordStatus');
|
||||
const originalStatus = recordStatus.textContent;
|
||||
recordStatus.textContent = 'Recording video... (Click to stop)';
|
||||
recordStatus.style.cursor = 'pointer';
|
||||
recordStatus.style.color = '#dc2626';
|
||||
|
||||
const stopHandler = () => {
|
||||
if (videoRecorder.state === 'recording') {
|
||||
videoRecorder.stop();
|
||||
}
|
||||
recordStatus.removeEventListener('click', stopHandler);
|
||||
videoRecorder.stop();
|
||||
recordStatus.textContent = originalStatus;
|
||||
recordStatus.style.cursor = 'auto';
|
||||
recordStatus.style.color = '';
|
||||
};
|
||||
|
||||
stopBtn.addEventListener('click', stopHandler, { once: true });
|
||||
recordStatus.addEventListener('click', stopHandler);
|
||||
|
||||
// Auto-stop after 5 minutes
|
||||
setTimeout(() => {
|
||||
if (videoRecorder.state === 'recording') {
|
||||
recordStatus.removeEventListener('click', stopHandler);
|
||||
videoRecorder.stop();
|
||||
recordStatus.textContent = 'Video recording stopped (5 minute limit)';
|
||||
recordStatus.style.cursor = 'auto';
|
||||
setTimeout(() => {
|
||||
recordStatus.textContent = originalStatus;
|
||||
recordStatus.style.color = '';
|
||||
}, 3000);
|
||||
}
|
||||
}, 5 * 60 * 1000);
|
||||
} catch (err) {
|
||||
@@ -1892,23 +1907,6 @@
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById('noteUseVideoBtn').addEventListener('click', () => {
|
||||
if (window.noteVideoBlob) {
|
||||
const file = new File([window.noteVideoBlob], `video-${Date.now()}.webm`, { type: 'video/webm' });
|
||||
pendingNoteAttachments.push(file);
|
||||
renderNoteAttachmentsList();
|
||||
document.getElementById('noteVideoPreview').classList.add('hidden');
|
||||
document.getElementById('noteVideoPlayer').src = '';
|
||||
window.noteVideoBlob = null;
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('noteRemoveVideoBtn').addEventListener('click', () => {
|
||||
document.getElementById('noteVideoPreview').classList.add('hidden');
|
||||
document.getElementById('noteVideoPlayer').src = '';
|
||||
window.noteVideoBlob = null;
|
||||
});
|
||||
|
||||
document.getElementById('clearAttachmentsBtn').addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
pendingNoteAttachments = [];
|
||||
@@ -1972,40 +1970,42 @@
|
||||
|
||||
videoRecorder.onstop = () => {
|
||||
const blob = new Blob(chunks, { type: 'video/webm' });
|
||||
const videoPreviewEl = document.getElementById('conversationVideoPlayer');
|
||||
const videoPreviewContainer = document.getElementById('conversationVideoPreview');
|
||||
const recordingUI = document.getElementById('conversationVideoRecordingUI');
|
||||
|
||||
// Show video preview
|
||||
videoPreviewEl.src = URL.createObjectURL(blob);
|
||||
videoPreviewContainer.classList.remove('hidden');
|
||||
recordingUI.classList.add('hidden');
|
||||
|
||||
// Temporarily store video blob for use
|
||||
window.conversationVideoBlob = blob;
|
||||
const file = new File([blob], `video-${Date.now()}.webm`, { type: 'video/webm' });
|
||||
pendingConversationAttachments.push(file);
|
||||
renderConversationAttachmentsList();
|
||||
stream.getTracks().forEach(track => track.stop());
|
||||
};
|
||||
|
||||
// Show recording UI
|
||||
const recordingUI = document.getElementById('conversationVideoRecordingUI');
|
||||
recordingUI.classList.remove('hidden');
|
||||
document.getElementById('conversationVideoPreview').classList.add('hidden');
|
||||
|
||||
videoRecorder.start();
|
||||
|
||||
const stopBtn = document.getElementById('conversationStopVideoRecordingBtn');
|
||||
// Show recording UI
|
||||
const recordStatus = document.getElementById('conversationRecordStatus');
|
||||
const originalStatus = recordStatus.textContent;
|
||||
recordStatus.textContent = 'Recording video... (Click to stop)';
|
||||
recordStatus.style.cursor = 'pointer';
|
||||
recordStatus.style.color = '#dc2626';
|
||||
|
||||
const stopHandler = () => {
|
||||
if (videoRecorder.state === 'recording') {
|
||||
videoRecorder.stop();
|
||||
}
|
||||
recordStatus.removeEventListener('click', stopHandler);
|
||||
videoRecorder.stop();
|
||||
recordStatus.textContent = originalStatus;
|
||||
recordStatus.style.cursor = 'auto';
|
||||
recordStatus.style.color = '';
|
||||
};
|
||||
|
||||
stopBtn.addEventListener('click', stopHandler, { once: true });
|
||||
recordStatus.addEventListener('click', stopHandler);
|
||||
|
||||
// Auto-stop after 5 minutes
|
||||
setTimeout(() => {
|
||||
if (videoRecorder.state === 'recording') {
|
||||
recordStatus.removeEventListener('click', stopHandler);
|
||||
videoRecorder.stop();
|
||||
recordStatus.textContent = 'Video recording stopped (5 minute limit)';
|
||||
recordStatus.style.cursor = 'auto';
|
||||
setTimeout(() => {
|
||||
recordStatus.textContent = originalStatus;
|
||||
recordStatus.style.color = '';
|
||||
}, 3000);
|
||||
}
|
||||
}, 5 * 60 * 1000);
|
||||
} catch (err) {
|
||||
@@ -2022,23 +2022,6 @@
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById('conversationUseVideoBtn').addEventListener('click', () => {
|
||||
if (window.conversationVideoBlob) {
|
||||
const file = new File([window.conversationVideoBlob], `video-${Date.now()}.webm`, { type: 'video/webm' });
|
||||
pendingConversationAttachments.push(file);
|
||||
renderConversationAttachmentsList();
|
||||
document.getElementById('conversationVideoPreview').classList.add('hidden');
|
||||
document.getElementById('conversationVideoPlayer').src = '';
|
||||
window.conversationVideoBlob = null;
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('conversationRemoveVideoBtn').addEventListener('click', () => {
|
||||
document.getElementById('conversationVideoPreview').classList.add('hidden');
|
||||
document.getElementById('conversationVideoPlayer').src = '';
|
||||
window.conversationVideoBlob = null;
|
||||
});
|
||||
|
||||
document.getElementById('conversationClearAttachmentsBtn').addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
pendingConversationAttachments = [];
|
||||
|
||||
Reference in New Issue
Block a user