// uses functions in utils.js var host = window.location.protocol + "//" + window.location.host; var collection_id; var latest_timestamp = -1; // "global" map variables var map; var location_picking = false; // layer variables const media_markers_l = L.layerGroup([]); // media markers const media_markers_ref = {}; const text_markers = L.layerGroup([]); // polyline routes // collection info in JSON const collection_media = {}; const collection_notes = {}; const collection_perms = {}; const collection_info = {}; const media_divs = {}; const changes = { "name" : false, "info" : false, "timestamp" : false } // var mode = 0; // 0 = media, 1 = notes, 2 = routes. the index of hte currently selected tab var ascending = true; const selected_media_ids = []; var is_picking_location = false; var last_selected_index = -1; var media_modal_open = false; var share_modal_open = false; var collection_modal_open = false; var Media_Icon = L.Icon.extend({ options: { shadowUrl: 'static/img/marker_background.png', iconSize: [60, 45], shadowSize: [72, 62], iconAnchor: [30, 55], shadowAnchor: [36, 62], popupAnchor: [-3, -76] } }); // RUN ON PAGE LOAD window.onload = function() { document.getElementById("share-modal").addEventListener("click", close_share_modal); document.getElementById("share-box").addEventListener("click", stop_propagation); document.getElementById("collection-modal").addEventListener("click", close_collection_modal); document.getElementById("collection-box").addEventListener("click", stop_propagation); collection_id = document.getElementById("collection_id").innerText; map = L.map('map').setView([13.6760758587756, -89.21535464697462], 10); L.tileLayer('https://tiles.stadiamaps.com/tiles/outdoors/{z}/{x}/{y}{r}.{ext}?api_key=24746ff5-179d-482f-97a8-0267c5b276a1', { minZoom: 0, maxZoom: 20, ext: 'png' }).addTo(map); map.addLayer(media_markers_l); map.addLayer(text_markers); var layerControl = L.control.layers(null,null,{collapsed:false}); layerControl.addOverlay(media_markers_l, "Media"); layerControl.addOverlay(text_markers, "Notes"); layerControl.addTo(map); map.on('click', function(e) { var coords = e.latlng; handle_map_click(coords["lat"], coords["lng"]); }); request_latest_info(); } var intervalId = window.setInterval(function(){ // constantly get latest info from server request_latest_info(); }, 5000); // keyboard event listener for ESC to clear selection or cancel location picking document.addEventListener('keydown', function(e) { if (e.key === 'Escape') { if(media_modal_open) { close_media_preview(); return; } if(share_modal_open) { close_share_modal(); return; } if(collection_modal_open) { close_collection_modal(); return; } if (is_picking_location) { disable_picking(); return; } clear_selection(); update_content_editing_box(); } if (e.key == 'c') { if( e.target.nodeName == "INPUT" || e.target.nodeName == "TEXTAREA" ) return; if( e.target.isContentEditable ) return; if(selected_media_ids.length)picker_clicked(); return; } // select elements by arrow keys if (e.key == 'a' || e.key == 'd') { if( e.target.nodeName == "INPUT" || e.target.nodeName == "TEXTAREA" ) return; if(last_selected_index != -1) { const new_index = last_selected_index + (e.key == 'a' ? -1 : 1); const media = document.querySelector(`.media[data-index="${new_index}"]`); if (media) { clear_selection(); media.classList.add('selected'); selected_media_ids.push(media.dataset.id); last_selected_index = parseInt(media.dataset.index); media.scrollIntoView(); //map.panTo(media_markers_ref[media.dataset.id].latlng); update_content_editing_box(); } } } if (e.key == 'w' || e.key == 's') { if( e.target.nodeName == "INPUT" || e.target.nodeName == "TEXTAREA" ) return; if(last_selected_index != -1) { const grid = document.getElementById("media_grid"); const grid_computed_style = window.getComputedStyle(grid); const grid_cols = grid_computed_style.getPropertyValue("grid-template-columns").split(" ").length; const new_index = last_selected_index + (e.key == 'w' ? -1 : 1)*grid_cols; const media = document.querySelector(`.media[data-index="${new_index}"]`); console.log(media); if (media) { clear_selection(); media.classList.add('selected'); selected_media_ids.push(media.dataset.id); last_selected_index = parseInt(media.dataset.index); media.scrollIntoView(); //map.panTo(media_markers_ref[media.dataset.id].latlng); update_content_editing_box(); } } } }); function stop_propagation(event) { console.log("stop event propagation"); event.stopPropagation(); } function set_tab(evt, tab_id) { if(is_picking_location) return; // Hide all tabs' content and mark all tabs as unselected var tab_content = document.getElementsByClassName("tab-content"); for (var i = 0; i < tab_content.length; i++) { tab_content[i].style.display = "none"; } var tabs = document.getElementsByClassName("tab"); for (var i = 0; i < tabs.length; i++) { tabs[i].className = tabs[i].className.replace(" active", ""); } // Display selected tab's content, show tab as selected document.getElementById(tab_id).style.display = "flex"; evt.currentTarget.className += " active"; mode = {"media_tab" : 0, "text_tab" : 1, "routes_tab" : 2}[tab_id]; update_content_editing_box(); } function upload_media_files() { var files = document.getElementById("media_upload").files;; if (files.length == 0) { alert("No files selected!"); return; } const formData = new FormData(); for (var i = 0; i < files.length; i++){ console.log(files[i]); formData.append("files", files[i]); } formData.append("collection_id", collection_id); fetch( "/editormediaupload", { method: "POST", credentials: "include", body: formData }).then((response) => response.json()) .then((json) => { console.log('Gotcha'); }).catch((err) => { console.log(err); }); } function edit_media_grid_element(media_dict) { const media_div = media_divs[media_dict["id"]]; const name = media_div.querySelector("p"); name.innerText = media_dict["name"]; const icons = media_div.querySelectorAll("div.media div img"); icons[0].src = media_dict["longitude"] ? "static/img/yes-location.png" : "static/img/no-location.png"; icons[1].src = (media_dict["timestamp"] != "") ? "static/img/yes-datetime.png" : "static/img/no-datetime.png"; const timestamp = media_div.querySelector("p.media-timestamp"); timestamp.innerText = media_dict["timestamp"].replace("T", " "); } function add_media(media_dict){ // add data collection_media[media_dict["id"]] = media_dict; // add HTML element const media_element = document.createElement('div'); media_element.className = "media"; media_element.dataset.id = media_dict["id"]; const img = document.createElement('img'); img.src = media_dict["thumbbig"]; const fn = document.createElement('p'); fn.innerText = media_dict["name"]; const info = document.createElement('div'); info.style = "display:flex; flex-direction:horizontal; margin:0; justify-content: center;"; const loc = document.createElement('img'); loc.src = media_dict["longitude"] ? "static/img/yes-location.png" : "static/img/no-location.png"; loc.style.width = "20px"; loc.style.height = "20px"; const dt = document.createElement('img'); dt.src = (media_dict["timestamp"] != "") ? "static/img/yes-datetime.png" : "static/img/no-datetime.png"; dt.style.width = "20px"; dt.style.height = "20px"; const dtext = document.createElement('p'); dtext.classList.add("media-timestamp") dtext.innerText = media_dict["timestamp"].replace("T", " "); info.appendChild(loc); info.appendChild(dt); media_element.appendChild(img); media_element.appendChild(fn); media_element.appendChild(dtext); media_element.appendChild(info); media_divs[media_dict["id"]] = media_element; // add map icon console.log(media_dict); const media_icon = new Media_Icon({iconUrl : media_dict['thumbsmall']}); var lat = 0; var lng = 0; if(media_dict['latitude'] && media_dict['longitude']) { lat = media_dict['latitude']; lng = media_dict['longitude']; } const media_marker = L.marker( [lat, lng], {icon:media_icon} ).on('click', function(e) { map_marker_clicked(media_dict["id"], 'media', e) }); media_markers_ref[media_dict["id"]] = media_marker; media_markers_l.addLayer(media_marker); } function remove_media(media_id) { for(var i = 0; i < selected_media_ids.length; i++) { if (selected_media_ids[i] == media_id) { selected_media_ids.pop(i); break; } } media_divs[media_id].remove(); media_markers_l.removeLayer(media_markers_ref[media_id]); delete media_markers_ref[media_id]; delete media_divs[media_id]; delete collection_media[media_id]; update_content_editing_box(); } // media grid stuff made by deepseek, not finished cleaning up document.addEventListener('DOMContentLoaded', function() { const media_grid = document.getElementById('media_grid'); // Add click event listeners media_grid.addEventListener('click', function(e) { const media_element = e.target.closest('.media'); const isShiftKey = e.shiftKey; const isCtrlKey = e.ctrlKey || e.metaKey; // metaKey for Mac Command key if (!media_element) { if(!isShiftKey && !isCtrlKey) clear_selection(); update_content_editing_box(); return; } const isVisible = function (ele, container) { const { bottom, height, top } = ele.getBoundingClientRect(); const containerRect = container.getBoundingClientRect(); //console.log( ele.getBoundingClientRect()); //console.log(containerRect); return top <= containerRect.top ? containerRect.top - top <= height : bottom - containerRect.bottom <= height; }; const currentIndex = parseInt(media_element.dataset.index); const current_media_id = media_element.dataset.id; if (isShiftKey && last_selected_index !== -1) { // Range selection with Shift key select_range(last_selected_index, currentIndex, isCtrlKey); } else if (isCtrlKey) { // Toggle selection with Ctrl key media_element.classList.toggle('selected'); selected_media_ids.push(current_media_id); last_selected_index = currentIndex; } else { // Single selection clear_selection(); media_element.classList.add('selected'); last_selected_index = currentIndex; selected_media_ids.push(current_media_id); } update_content_editing_box(); if(!isVisible(media_element, media_element.parentElement))media_element.scrollIntoView(); }); }); // Clear all selections function clear_selection() { selected_media_ids.length = 0; // clear selected media ids array... kind of strange way to do it but appears to be the most readable way of doing it document.querySelectorAll('.media.selected').forEach(el => { el.classList.remove('selected'); }); last_selected_index = -1; } // Select range of photos function select_range(start_index, end_index, additive) { if (!additive) { clear_selection(); } const start = Math.min(start_index, end_index); const end = Math.max(start_index, end_index); for (let i = start; i <= end; i++) { const photo = document.querySelector(`.media[data-index="${i}"]`); if (photo) { photo.classList.add('selected'); selected_media_ids.push(photo.dataset.id); } } } function update_media_grid() { const media_grid = document.getElementById('media_grid'); while (media_grid.firstChild) { media_grid.removeChild(media_grid.lastChild); } const hide_timestamp = document.getElementById("hide-timestamp").checked; const hide_location = document.getElementById("hide-location").checked; const sort_by = document.getElementById("sort-type").value; const media_ids = [...Object.keys(media_divs)]; if (sort_by != "none") media_ids.sort( (a, b) => { if(ascending) { return collection_media[a][sort_by].localeCompare(collection_media[b][sort_by]); } else { return collection_media[b][sort_by].localeCompare(collection_media[a][sort_by]); } }); // remove hidden entries for (var i = media_ids.length-1; i >= 0; i-- ) { if (hide_timestamp && (collection_media[media_ids[i]]["timestamp"] != "")) media_ids.splice(i, 1); else if (hide_location && (collection_media[media_ids[i]]["latitude"] && collection_media[media_ids[i]]["longitude"])) media_ids.splice(i, 1); } var i = 0; for (var media_id of media_ids) { const media_div = media_divs[media_id]; media_div.dataset.index = i; media_grid.appendChild(media_div); i += 1; } } function update_content_editing_box() { submit_edit_changes(); // make sure user changes are submitted const content_box = document.getElementById("content_info"); const preview_box = document.getElementById("media-preview"); const video = document.getElementById("preview-video"); video.pause(); if(mode == 0) { // media editing mode if(selected_media_ids.length == 0){ content_box.style.display="none"; preview_box.style.display="none"; return; } content_box.style.display="block"; preview_box.style.display="flex"; if(selected_media_ids.length == 1){ const selected_media = collection_media[selected_media_ids[0]]; for (const [key, value] of Object.entries(selected_media)) { var field = document.getElementById("media_"+key); if (field) field.value = value; } const location_text = document.getElementById("media_coords"); if(selected_media['latitude'] && selected_media['longitude']) { map.panTo( [selected_media['latitude'], selected_media['longitude']] ); location_text.innerText = `(${selected_media['latitude']}, ${selected_media['longitude']})`; } else { location_text.innerText = "No location given! Press the picker button or 'C' to mark location."; } if (selected_media["is_video"]) { const img = document.getElementById("preview-img"); video.src = selected_media["mediapath"]; img.style.display = "none"; video.style.display = "block"; } else { const img = document.getElementById("preview-img"); img.src = selected_media["mediapath"]; video.style.display = "none"; img.style.display = "block"; } } else { // multiple media selected const fields = { ...collection_media[selected_media_ids[0]] }; for (var m_i = 1; m_i < selected_media_ids.length; m_i++) { const selected_media = collection_media[selected_media_ids[ m_i ]]; for (const [key, value] of Object.entries(selected_media)) { if( value != fields[key] ) fields[key] = "< ... >"; } } for (const [key, value] of Object.entries(fields)) { var field = document.getElementById("media_"+key); if (field) field.value = value; } const location_text = document.getElementById("media_coords"); if(fields['latitude'] && fields['longitude'] && Number(fields['latitude']) && Number(fields['longitude'])) { map.panTo( [fields['latitude'], fields['longitude']]); location_text.innerText = `(${fields['latitude']}, ${fields['longitude']})`; } else if ( !(fields['latitude'] && fields['longitude']) ) location_text.innerText = "No location given! Press the picker button or 'C' to mark location."; else { location_text.innerText = "< ... >"; } } } } function picker_clicked() { const picking_style = document.createElement('style'); picking_style.innerHTML = '*{cursor: crosshair;}'; picking_style.id = 'picking-style'; document.head.appendChild(picking_style); L.DomUtil.addClass(map._container, "crosshair-cursor-enabled"); const media_boxes = document.getElementsByClassName("media"); for(var i = 0; i < media_boxes.length; i++) { media_boxes[i].classList.add('no-hover'); } const tabs = document.getElementsByClassName("tab-header"); for(var i = 0; i < tabs.length; i++) { tabs[i].classList.add('no-hover'); } map.removeLayer(media_markers_l); is_picking_location = true; } function disable_picking() { is_picking_location = false; document.getElementById('picking-style').remove(); L.DomUtil.removeClass(map._container, "crosshair-cursor-enabled"); const media_boxes = document.getElementsByClassName("media"); for(var i = 0; i < media_boxes.length; i++) { media_boxes[i].classList.remove('no-hover'); } const tabs = document.getElementsByClassName("tab-header"); for(var i = 0; i < tabs.length; i++) { tabs[i].classList.remove('no-hover'); } map.addLayer(media_markers_l); } function handle_map_click(latitude, longitude) { if(!is_picking_location) return; map.addLayer(media_markers_l); edit_media_info({"latitude" : latitude, "longitude" : longitude}); disable_picking(); } function map_marker_clicked(id, type, e) { clear_selection(); if (type === "media") { const media = document.querySelector(`.media[data-id="${id}"]`); if (media) { media.classList.add('selected'); selected_media_ids.push(media.dataset.id); last_selected_index = parseInt(media.dataset.index); media.scrollIntoView(); } else { // media filtered document.getElementById("hide-timestamp").checked = false; document.getElementById("hide-location").checked = false; update_media_grid(); const media = document.querySelector(`.media[data-id="${id}"]`); media.classList.add('selected'); selected_media_ids.push(media.dataset.id); last_selected_index = parseInt(media.dataset.index); media.scrollIntoView(); } update_content_editing_box(); } } function open_media_preview() { const modal = document.getElementById('media-preview-modal'); const preview_img = document.getElementById("preview-img"); const modal_media = document.getElementById("modal-img"); modal.style.display = "block"; modal_media.src = preview_img.src; media_modal_open = true; } function close_media_preview() { const modal = document.getElementById('media-preview-modal'); modal.style.display = "none"; media_modal_open = false; } function open_share_modal() { update_share_info(); var modal = document.getElementById('share-modal'); share_modal_open = true; modal.style.display = "block"; } function close_share_modal() { var modal = document.getElementById('share-modal'); share_modal_open = false; modal.style.display = "none"; } function update_share_info() { const publicity = document.getElementById("visibility-dropdown"); publicity.value = collection_perms["public"] ? "public" : "private"; const user_section = document.getElementById("shared-users"); while (user_section.firstChild) user_section.removeChild(user_section.firstChild); const editor_option = document.createElement("option"); editor_option.innerText = "Editor"; editor_option.value = "editor"; const viewer_option = document.createElement("option"); viewer_option.innerText = "Viewer"; viewer_option.value = "viewer"; for (var t = 0; t < 2; t++) { const user_type = t == 0 ? "editor" : "viewer"; for (var i = 0; i < collection_perms[user_type+"s"].length; i++) { const user_email_str = collection_perms[user_type+"s"][i]; const user_email = document.createElement("p"); user_email.innerText = collection_perms[user_type+"s"][i]; user_email.style.margin = "0"; const user_permissions = document.createElement("select"); const e = editor_option.cloneNode(true); const v = viewer_option.cloneNode(true); user_permissions.appendChild(e); user_permissions.appendChild(v); user_permissions.value = user_type; user_permissions.onchange = () => { edit_permissions(user_email_str, user_permissions.value) } const user_unshare = document.createElement("p"); user_unshare.innerText = "Unshare"; user_unshare.classList.add("user-unshare-button"); user_unshare.onclick = function () { edit_permissions(user_email_str, ""); } user_section.appendChild(user_email); user_section.appendChild(user_permissions); user_section.appendChild(user_unshare); } } } function open_collection_modal() { update_collection_info(); var modal = document.getElementById('collection-modal'); collection_modal_open = true; modal.style.display = "block"; } function close_collection_modal() { var modal = document.getElementById('collection-modal'); collection_modal_open = false; modal.style.display = "none"; } function update_collection_info() { document.getElementById("collection-title-field").value = collection_info["title"]; document.getElementById("collection-subtitle-field").value = collection_info["subtitle"]; document.getElementById("collection-info-field").value = collection_info["info"]; } function mark_info_change(field) { changes[field] = true; } function submit_edit_changes() { console.log(changes); var edits = {}; for (const [field, was_changed] of Object.entries(changes)) { if (was_changed) { const new_val = document.getElementById("media_"+field).value; if (new_val != collection_media[selected_media_ids[0]][field]) edits[field] = new_val; } changes[field] = false; // reset dictionary } if(Object.keys(edits).length > 0) { edit_media_info(edits); } } // communicate // with // the // server // run every x seconds to get latest info from server function request_latest_info(){ console.log("requesting update"); function handleLatestInfo(json){ console.log(json); if(json['status'] != "OK") return; // ADD ERROR NOTIF ON MENU BAR if(!json['update']) return; // no changes let new_collection = json['collection']; let new_media_ids = []; for(var i = 0; i < new_collection['media'].length; i++) { const media_id = new_collection['media'][i]['id']; new_media_ids.push(media_id); if(collection_media[media_id] == undefined) { add_media(new_collection['media'][i]); } else { // update existing collection_media[media_id] = new_collection['media'][i] edit_media_grid_element(collection_media[media_id]); if(collection_media[media_id]['latitude'] && collection_media[media_id]['longitude']) media_markers_ref[media_id].setLatLng([collection_media[media_id]['latitude'], collection_media[media_id]['longitude']]); } } let to_be_removed = []; for (const [key, _] of Object.entries(collection_media)) { if (!new_media_ids.includes(key)) to_be_removed.push(key); } console.log(to_be_removed); for (var i = 0; i < to_be_removed.length; i++){ remove_media(to_be_removed[i]); } // update permissions info collection_perms["owner"] = new_collection["owner"]; collection_perms["editors"] = new_collection["editors"]; collection_perms["viewers"] = new_collection["viewers"]; collection_perms["public"] = new_collection["public"]; if(share_modal_open) update_share_info(); document.getElementById("collection-title").innerText = new_collection["title"]; collection_info["title"] = new_collection["title"]; collection_info["subtitle"] = new_collection["subtitle"]; collection_info["info"] = new_collection["info"]; if(collection_modal_open) update_share_info(); update_media_grid(); // make smarter later update_content_editing_box(); latest_timestamp = new_collection["last_edited"]; } fetch(host+'/editorgetlatestinfo', { method: "POST", headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify({ "collection" : collection_id, "last_updated" : latest_timestamp })}) .then((response) => response.json()) .then((json) => handleLatestInfo(json)); } function delete_media() { if(!confirm("Are you sure you want to delete these pieces of media? This cannot be undone!")) return; fetch(host+'/editordeletemedia', { method: "POST", headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify({ "collection" : collection_id, "to_be_removed" : selected_media_ids })}) .then((response) => response.json()) .then((json) => { clear_selection(); request_latest_info(); }); } function edit_media_info(changes) { console.log("edit"); const data = []; for (var i = 0; i < selected_media_ids.length; i++) { data.push({"media_id" : selected_media_ids[i], "changes" : changes}) } fetch(host+'/editoreditmedia', { method: "POST", headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify({ "collection" : collection_id, "edits" : data })}) .then((response) => response.json()) .then((json) => {console.log(json); request_latest_info();}); } function update_visibility() { const visibility = document.getElementById("visibility-dropdown").value == "public" ? true : false; const data = { "collection" : collection_id, "public" : visibility }; fetch(host+'/editorshare', { method: "POST", headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify(data)}) .then((response) => response.json()) .then((json) => {console.log(json); request_latest_info();}); } function add_shared_user() { const user_email_e = document.getElementById("share-email"); const permission_e = document.getElementById("share-permissions"); edit_permissions(user_email_e.value, permission_e.value); user_email_e.value = ""; // reset email } function edit_permissions(user_email, permission) { console.log(user_email); console.log(permission); const data = { "collection" : collection_id, "user_email" : user_email, "perm" : permission }; fetch(host+'/editorshare', { method: "POST", headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify(data)}) .then((response) => response.json()) .then((json) => {console.log(json); request_latest_info();}); } function collection_info_updated(field) { const field_input = document.getElementById('collection-'+field+'-field'); if(field_input) { edit_collection_info(field, field_input.value); } } function edit_collection_info(field, value) { const edits = {}; edits[field] = value; const data = { "collection" : collection_id, "edits" : edits }; fetch(host+'/editorcollectioninfo', { method: "POST", headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify(data)}) .then((response) => response.json()) .then((json) => {console.log(json); request_latest_info();}); } function toggle_sort_direction(){ ascending = !ascending; document.getElementById("sort-direction").innerText = ascending ? "Ascending" : "Descending"; update_media_grid(); }