From 47a03155d5472bcaf259e5aab8c22af03057e11c Mon Sep 17 00:00:00 2001 From: maximstewart Date: Sun, 7 Feb 2021 20:07:13 -0600 Subject: [PATCH] javascript updates --- src/core/__init__.py | 2 +- src/core/routes/Routes.py | 52 +++-- src/core/static/js/ajax.js | 45 +++- src/core/static/js/events.js | 5 + src/core/static/js/post-ajax.js | 196 +++++++++++++++++- ...react-ui-logic.js.js => react-ui-logic.js} | 0 src/core/static/js/ui-logic.js | 68 ++++++ src/core/static/js/video-events.js | 177 ++++++++++++++++ 8 files changed, 503 insertions(+), 42 deletions(-) rename src/core/static/js/{react-ui-logic.js.js => react-ui-logic.js} (100%) create mode 100644 src/core/static/js/video-events.js diff --git a/src/core/__init__.py b/src/core/__init__.py index 6d395e1..6b8c7e1 100644 --- a/src/core/__init__.py +++ b/src/core/__init__.py @@ -38,7 +38,7 @@ app.jinja_env.globals['oidc_isAdmin'] = oidc_isAdmin app.jinja_env.globals['TITLE'] = app.config["TITLE"] -from core.models import db, User +from core.models import db, User, Favorites db.init_app(app) with app.app_context(): db.create_all() diff --git a/src/core/routes/Routes.py b/src/core/routes/Routes.py index cfe28db..d329a11 100644 --- a/src/core/routes/Routes.py +++ b/src/core/routes/Routes.py @@ -1,5 +1,5 @@ # Python imports -import secrets +import json, secrets # Lib imports from flask import request, session, render_template, send_from_directory, redirect @@ -7,9 +7,9 @@ from flask_login import current_user # App imports -from core import app, logger, oidc, db # Get from __init__ -from core.utils import MessageHandler # Get simple message processor -from core.utils.shellfm import WindowController # Get file manager controller +from core import app, logger, oidc, db, Favorites # Get from __init__ +from core.utils import MessageHandler # Get simple message processor +from core.utils.shellfm import WindowController # Get file manager controller msgHandler = MessageHandler() @@ -46,35 +46,33 @@ def home(): @app.route('/api/list-files/<_hash>', methods=['GET', 'POST']) def listFilesRoute(_hash = None): if request.method == 'POST': - view = get_window_controller().get_window(1).get_view(0) + view = get_window_controller().get_window(1).get_view(0) + dot_dots = view.get_dot_dots() - if _dot_dots[0][1] == HASH: # Refresh + if dot_dots[0][1] == _hash: # Refresh view.load_directory() - elif _dot_dots[1][1] == HASH: # Pop from dir + elif dot_dots[1][1] == _hash: # Pop from dir view.pop_from_path() - else: # Push to dir - _path = view.get_path_part_from_hash(HASH) - view.push_to_path(_path) - msg = "Log in with an Admin privlidged user to view the requested path!" - fave = db.session.query(Favorites).filter_by(link=_path).first() - _in_fave = "true" if fave else "false" - _dot_dots = view.get_dot_dots() - _files = view.get_files_formatted() - _path = view.get_current_directory() - is_locked = view.is_folder_locked(HASH) - - files.update({'in_fave': _in_fave}) + is_locked = view.is_folder_locked(_hash) if is_locked and not oidc.user_loggedin: - return msgHandler.createMessageJSON("danger", msg) + return msgHandler.createMessageJSON("danger") elif is_locked and oidc.user_loggedin: isAdmin = oidc.user_getfield("isAdmin") if isAdmin != "yes" : - return msgHandler.createMessageJSON("danger", msg) + return msgHandler.createMessageJSON("danger") + if dot_dots[0][1] != _hash and dot_dots[1][1] != _hash: + path = view.get_path_part_from_hash(_hash) + view.push_to_path(path) + + path = view.get_current_directory() + files = view.get_files_formatted() + fave = db.session.query(Favorites).filter_by(link = path).first() + in_fave = "true" if fave else "false" + files.update({'in_fave': in_fave}) return files - else: msg = "Can't manage the request type..." return msgHandler.createMessageJSON("danger", msg) @@ -124,10 +122,9 @@ def loadFavorite(_id): try: ID = int(_id) fave = db.session.query(Favorites).filter_by(id=ID).first() - file_manager.setNewPathFromFavorites(fave.link) - file_manager.loadPreviousPath() - - return '{"refresh":"true"}' + view = get_window_controller().get_window(1).get_view(0) + view.set_path(fave.link) + return '{"refresh": "true"}' except Exception as e: print(repr(e)) msg = "Incorrect Favorites ID..." @@ -141,7 +138,8 @@ def loadFavorite(_id): def manageFavoritesRoute(_action): if request.method == 'POST': ACTION = _action.strip() - path = file_manager.getPath() + view = get_window_controller().get_window(1).get_view(0) + path = view.getPath() if ACTION == "add": fave = Favorites(link=path) diff --git a/src/core/static/js/ajax.js b/src/core/static/js/ajax.js index 8a500fe..87a4307 100644 --- a/src/core/static/js/ajax.js +++ b/src/core/static/js/ajax.js @@ -1,3 +1,29 @@ +const listFilesAjax = async (hash) => { + const data = "empty=NULL"; + doAjax("api/list-files/" + hash, data, "list-files"); +} + +const getFavesAjax = async () => { + const data = "empty=NULL"; + doAjax("api/get-favorites", data, "favorites"); +} + +const loadFavoriteLink = async (id) => { + const data = "empty=NULL"; + doAjax("api/load-favorite/" + id, data, "load-favorite"); +} + +const manageFavoritesAjax = async (action) => { + const data = "empty=NULL"; + doAjax("api/manage-favorites/" + action, data, "manage-favorites"); +} + +const lockFoldersAjax = async () => { + const data = "empty=NULL"; + doAjax("logout", data, "lock-folders"); +} + + const doAjax = (actionPath, data, action) => { let xhttp = new XMLHttpRequest(); @@ -14,23 +40,22 @@ const doAjax = (actionPath, data, action) => { } }; + // xhttp.open("POST", formatURL(actionPath), true); xhttp.open("POST", actionPath, true); xhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + + if (action === "list-files") { + xhttp.setRequestHeader("Cache-Control", "no-cache, no-store"); + xhttp.setRequestHeader("Pragma", "no-cache"); + xhttp.setRequestHeader("Expires", "0"); + } + // Force return to be JSON NOTE: Use application/xml to force XML xhttp.overrideMimeType('application/json'); xhttp.send(data); } -const formatURL = (basePath) => { - url = window.location.href; - if ( url.endsWith('/') ) - return url + basePath; - else - return url + '/' + basePath; -} - const fetchData = async (url) => { - let response = null; - response = await fetch(url); + let response = await fetch(url); return await response.json(); } diff --git a/src/core/static/js/events.js b/src/core/static/js/events.js index 902ad7a..0535780 100644 --- a/src/core/static/js/events.js +++ b/src/core/static/js/events.js @@ -1,3 +1,8 @@ window.onload = (eve) => { console.log("Loaded..."); } + +document.body.onload = (eve) => { + getFavesAjax(); + reloadDirectory(); +} diff --git a/src/core/static/js/post-ajax.js b/src/core/static/js/post-ajax.js index 8eab137..91763de 100644 --- a/src/core/static/js/post-ajax.js +++ b/src/core/static/js/post-ajax.js @@ -1,11 +1,199 @@ const postAjaxController = (data, action) => { if (data.message) { message = data.message + displayMessage(message.text, message.type); + return ; + } - if (action == "someAction" && message.type.includes("success")) { - console.log("Success!"); + if (data.hasOwnProperty('path_head')) + console.log(data); + // updateHTMLDirList(data); + if (data.hasOwnProperty('faves_list')) + // generateFavesList(data.faves_list); + console.log("faves stub..."); + if (data.hasOwnProperty("refresh")) { + if (data.refresh == "true") { + reloadDirectory(); } - - displayMessage(message.text, message.type, 0); } } + + +const generateFavesList = (data) => { + let listView = document.getElementById("faves-list"); + clearChildNodes(listView); + + data.forEach(faveArry => { + let fave = faveArry[0] + let faveId = faveArry[1] + let liTag = document.createElement("LI"); + let parts = (fave.includes("/")) ? fave.split("/") : fave.split("\\"); + + let part = parts[parts.length - 1] + if (part.toLowerCase().includes("season")) { + part = parts[parts.length - 2] + "/" + part + } + + let txtNode = document.createTextNode(part); + liTag.setAttribute("class", "btn btn-secondary btn-sm"); + liTag.setAttribute("name", fave); + liTag.setAttribute("title", fave); + liTag.setAttribute("onclick", "loadFave(" + faveId +")"); + liTag.appendChild(txtNode); + listView.appendChild(liTag); + }); +} + + +const updateHTMLDirList = async (data) => { + var dirTemplate = document.querySelector('#dirTemplate'); + var vidTemplate = document.querySelector('#vidTemplate'); + var imgTemplate = document.querySelector('#imgTemplate'); + var filTemplate = document.querySelector('#filTemplate'); + let insertArea = document.getElementById('file-grid'); + let pathElm = document.getElementById("path"); + let thumbnail = formatURL("static/imgs/icons/folder.png"); + let pathParts = data.path_head.split("/"); + let dirPath = pathParts[pathParts.length - 1] + "/"; + + let title = ""; + let hash = ""; + + // Can still set the path info if locked... + pathElm.innerText = dirPath; + // If locked return and create password field.... + + let dirs = (data.list.dirs) ? data.list.dirs : []; + let videos = (data.list.vids) ? data.list.vids : []; + let images = (data.list.imgs) ? data.list.imgs : []; + let files = (data.list.files) ? data.list.files : []; + let isInFaves = data.in_fave; + let background_image = images[0] ? images[0][0] : ""; + let i = 0; + let size = 0; + + // Setup background if there is a 000.* in selection + if (background_image.match(/000\.(jpg|png|gif)\b/) != null) { + // Due to same hash for 000 we add date to make link unique for each run to bypass cache issues... + background_image = formatURL("files/" + images[i][1] + '?d=' + Date.now()); + updateBackground(background_image, false); + } else { + background_image = formatURL("static/imgs/backgrounds/particles.mp4"); + updateBackground(background_image); + } + + // See if in faves + let elm = document.getElementById("tggl-faves-btn"); + if (isInFaves == "true") + elm.classList.add("btn-info"); + else + elm.classList.remove("btn-info"); + + clearChildNodes(insertArea); + // Insert dirs + let dirClone = document.importNode(dirTemplate.content, true); + let dir = null; + size = dirs.length; + for (; i < size; i++) { + const clone = dirClone.cloneNode(true); + title = dirs[i][0]; + hash = dirs[i][1]; + createElmBlock(insertArea, clone, thumbnail, title, hash); + } + + // Insert videos + let vidClone = document.importNode(vidTemplate.content, true); + size = videos.length; + for (i = 0; i < size; i++) { + const clone = vidClone.cloneNode(true); + title = videos[i][0]; + thumbnail = formatURL(videos[i][1]); + hash = videos[i][2]; + createElmBlock(insertArea, clone, thumbnail, title, hash, true); + } + + // Insert images + let imgClone = document.importNode(imgTemplate.content, true); + thumbnail = ""; + size = images.length; + for (i = 0; i < size; i++) { + title = images[i][0]; + thumbnail = formatURL("files/" + images[i][1]); + hash = images[i][1]; + + if (thumbnail.match(/000\.(jpg|png|gif)\b/) == null && + !thumbnail.includes("favicon.png") && !thumbnail.includes("000")) { + const clone = imgClone.cloneNode(true); + createElmBlock(insertArea, clone, thumbnail, title, hash); + } + } + + // Insert files + let fileClone = document.importNode(filTemplate.content, true); + size = files.length; + for (i = 0; i < size; i++) { + const clone = fileClone.cloneNode(true); + title = files[i][0]; + thumbnail = setFileIconType(files[i][0]); + hash = files[i][1]; + createElmBlock(insertArea, clone, thumbnail, title, hash); + } +} + +const createElmBlock = (ulElm, clone, thumbnail, title, hash, isVideo=false) => { + let containerTag = clone.firstElementChild; + containerTag.setAttribute("style", "background-image: url('" + thumbnail + "')"); + containerTag.setAttribute("title", title); + containerTag.setAttribute("hash", hash); + + // If image tag this sink to oblivion since there are no input tags in the template + try { + let inputTag = clone.querySelector("input"); + inputTag.setAttribute("value", title); + // NOTE: Keeping this just incase i do want to rename stuff... + // + // inputTag.addEventListener("dblclick", function (eve) { + // enableEdit(eve.target); + // }); + // inputTag.addEventListener("focusout", function (eve) { + // disableEdit(eve.target); + // }); + } catch (e) { } + + if (isVideo) { + let popoutTag = clone.querySelector(".card-popout-btn"); + popoutTag.setAttribute("hash", hash); + popoutTag.addEventListener("click", function (eve) { + let target = eve.target; + const hash = target.getAttribute("hash"); + openWithLocalProgram(hash); + }); + } + + + ulElm.appendChild(clone); +} + +const setFileIconType = (fileName) => { + icoPath = ""; + + if (fileName.match(/\.(doc|docx|xls|xlsx|rtf)\b/) != null) { + icoPath = "static/imgs/icons/doc.png"; + } else if (fileName.match(/\.(7z|7zip|zip|tar.gz|tar.xz|gz|rar|jar)\b/) != null) { + icoPath = "resources/images/icons/arc.png"; + } else if (fileName.match(/\.(pdf)\b/) != null) { + icoPath = "static/imgs/icons/pdf.png"; + } else if (fileName.match(/\.(html)\b/) != null) { + icoPath = "static/imgs/icons/html.png"; + } else if (fileName.match(/\.(txt|conf)\b/) != null) { + icoPath = "static/imgs/icons/text.png"; + } else if (fileName.match(/\.(iso|img)\b/) != null) { + icoPath = "static/imgs/icons/img.png"; + } else if (fileName.match(/\.(sh|batch|exe)\b/) != null) { + icoPath = "static/imgs/icons/scrip.png"; + } else { + icoPath = "static/imgs/icons/bin.png"; + } + + return formatURL(icoPath) +} diff --git a/src/core/static/js/react-ui-logic.js.js b/src/core/static/js/react-ui-logic.js similarity index 100% rename from src/core/static/js/react-ui-logic.js.js rename to src/core/static/js/react-ui-logic.js diff --git a/src/core/static/js/ui-logic.js b/src/core/static/js/ui-logic.js index 6445534..158b725 100644 --- a/src/core/static/js/ui-logic.js +++ b/src/core/static/js/ui-logic.js @@ -15,6 +15,42 @@ +const reloadDirectory = () => { + let target = document.getElementById('refresh-btn') + const hash = target.getAttribute("hash"); + listFilesAjax(hash); +} + +const goUpADirectory = () => { + let target = document.getElementById('back-btn') + const hash = target.getAttribute("hash"); + listFilesAjax(hash); +} + +const scrollFilesToTop = () => { + let target = document.getElementById('file-grid') + target.scrollTop = 0; +} + + + +const updateBackground = (srcLink, isvideo = true) => { + try { + let elm = document.getElementById("bg"); + + console.log(srcLink); + if (isvideo) { + if (elm.getAttribute('src') === "") { + elm.src = srcLink; + } + } else { + elm.src = ""; + elm.setAttribute("poster", srcLink); + } + } catch (e) { } +} + + // Message handler const displayMessage = (message, type, timeout, msgWindow = "page-alert-zone") => { let alertField = document.getElementById(msgWindow); @@ -49,3 +85,35 @@ const clearChildNodes = (parent) => { parent.removeChild(parent.firstChild); } } + + +//Cache Buster +const clearCache = () => { + var rep = /.*\?.*/, + links = document.getElementsByTagName('link'), + scripts = document.getElementsByTagName('script'), + links = document.getElementsByTagName('video'), + process_scripts = false; + + for (var i=0; i { + if (duration == null) { return "00:00"; } + + let hours = (duration / 3600).toFixed(2).split(".")[0]; + let minutes = (duration / 60).toFixed(2).split(".")[0]; + let time = (duration / 60).toFixed(2) + let seconds = Math.floor( (time - Math.floor(time) ) * 60); + + return hours + ":" + minutes + ":" + seconds; +} + +const pauseVideo = () => { + const video = document.getElementById("video-viewer"); + video.style.cursor = ''; + video.pause(); +} + +const togglePlay = (video) => { + shouldPlay = setTimeout(function () { + let controls = document.getElementById("video-controls"); + shouldPlay = null; + if (video.paused) { + video.style.cursor = 'none'; + controls.style.display = "none"; + video.play(); + } else { + video.style.cursor = ''; + controls.style.display = ""; + video.pause(); + } + }, 300); +} + +const toggleFullscreen = (video) => { + containerElm = document.getElementById("video-container"); + parentElm = video.parentElement; + + if (video.requestFullscreen) { + parentElm.requestFullscreen(); + video.style.cursor = 'none'; + containerElm.style.display = "block"; + } else if (video.webkitRequestFullscreen) { /* Safari */ + parentElm.webkitRequestFullscreen(); + video.style.cursor = 'none'; + containerElm.style.display = "block"; + } else if (video.msRequestFullscreen) { /* IE11 */ + parentElm.msRequestFullscreen(); + video.style.cursor = 'none'; + containerElm.style.display = "block"; + } + + if (fullScreenState == 2) { + if (document.exitFullscreen) { + document.exitFullscreen(); + video.style.cursor = ''; + containerElm.style.display = "contents"; + } else if (document.webkitExitFullscreen) { /* Safari */ + document.webkitExitFullscreen(); + video.style.cursor = ''; + containerElm.style.display = "contents"; + } else if (document.msExitFullscreen) { /* IE11 */ + document.msExitFullscreen(); + video.style.cursor = ''; + containerElm.style.display = "contents"; + } + + fullScreenState = 0; + } + + fullScreenState += 1; +} + +const toggleVolumeControl = () => { + const volume = document.getElementById("volume-slider"); + if (volume.style.display === "none") { + volume.style.display = ""; + } else { + volume.style.display = "none"; + } +} + +const showControls = () => { + const video = document.getElementById("video-viewer"); + const controls = document.getElementById("video-controls"); + + video.style.cursor = ''; + controls.style.display = ""; + if (controlsTimeout) { + clearTimeout(controlsTimeout); + } + + controlsTimeout = setTimeout(function () { + if (!video.paused) { + if (canHideControls) { + video.style.cursor = 'none'; + controls.style.display = "none"; + controlsTimeout = null; + } else { + showControls(); + } + } + }, 3000); +} + + +$("#video-viewer").on("loadedmetadata", function(eve){ + let video = eve.target; + let videoDuration = document.getElementById("videoDuration"); + videoDuration.innerText = getTimeFormatted(video.duration); +}); + +$("#video-viewer").on("click", function(eve){ + const video = eve.target; + + if(!shouldPlay) { + shouldPlay = setTimeout( function() { + shouldPlay = null; + togglePlay(video); + }, clicktapwait); + } else { + clearTimeout(shouldPlay); // Stop single tap callback + shouldPlay = null; + toggleFullscreen(video); + } + eve.preventDefault(); +}); + +$("#video-viewer").on("touchend", function(eve){ + const video = eve.target; + + if(!shouldPlay) { + shouldPlay = setTimeout( function() { + shouldPlay = null; + togglePlay(video); + }, clicktapwait); + } else { + clearTimeout(shouldPlay); // Stop single tap callback + shouldPlay = null; + toggleFullscreen(video); + } + eve.preventDefault(); +}); + +$( "#video-viewer" ).bind( "timeupdate", async function(eve) { + let videoDuration = document.getElementById("videoCurrentTime"); + const video = eve.target; + const seekto = document.getElementById("seek-slider"); + const vt = video.currentTime * (100 / video.duration); + + seekto.value = vt; + videoDuration.innerText = getTimeFormatted(video.currentTime); +}); + +$( "#seek-slider" ).bind( "change", async function(eve) { + const slider = eve.target; + let video = document.getElementById("video-viewer"); + let seekto = video.duration * (slider.value / 100); + video.currentTime = seekto; +}); + +$( "#volume-slider" ).bind( "change", async function(eve) { + const slider = eve.target; + let video = document.getElementById("video-viewer"); + let volumeto = slider.value; + video.volume = volumeto; + +}); + +$( "#video-controls" ).bind( "mouseenter", async function(eve) { canHideControls = false; }); +$( "#video-controls" ).bind( "mouseleave", async function(eve) { canHideControls = true; });