diff --git a/webfm/__init__.py b/webfm/__init__.py index 932a1d9..bea87ed 100644 --- a/webfm/__init__.py +++ b/webfm/__init__.py @@ -2,11 +2,10 @@ import os, json, secrets from datetime import timedelta + # Lib imports from flask import Flask from flask_oidc import OpenIDConnect -from flask_login import current_user, login_user, logout_user, LoginManager -from flask_bcrypt import Bcrypt # Apoplication imports @@ -41,10 +40,7 @@ app.config.update({ 'OIDC_TOKEN_TYPE_HINT': 'access_token' }) - -login_manager = LoginManager(app) -bcrypt = Bcrypt(app) -oidc = OpenIDConnect(app) +oidc = OpenIDConnect(app) def oidc_loggedin(): return oidc.user_loggedin app.jinja_env.globals['oidc_loggedin'] = oidc_loggedin @@ -66,9 +62,7 @@ def retrieveSettings(): config = retrieveSettings() -from .forms import LoginForm, RegisterForm -from .models import db, Favorites, Settings, User - +from .models import db, Favorites, Settings db.init_app(app) with app.app_context(): db.create_all() from webfm import routes diff --git a/webfm/forms.py b/webfm/forms.py index 19161e8..ad53099 100644 --- a/webfm/forms.py +++ b/webfm/forms.py @@ -2,7 +2,6 @@ from flask_wtf import FlaskForm from wtforms import StringField, PasswordField, SubmitField from wtforms.validators import DataRequired, Length, Email, EqualTo, ValidationError -from .models import User @@ -14,12 +13,4 @@ class RegisterForm(FlaskForm): submit = SubmitField("Sign Up") def validate_username(self, username): - user = User.query.filter_by(username=username.data).first() - if user: - raise ValidationError("User exists already! Please use a different name!") - - -class LoginForm(FlaskForm): - username = StringField('Username', validators=[DataRequired(), Length(min=4, max=24)]) - password = PasswordField('Password', validators=[DataRequired(), Length(min=8, max=32)]) - submit = SubmitField("Login") + pass diff --git a/webfm/models.py b/webfm/models.py index d1a9f33..9d4af00 100644 --- a/webfm/models.py +++ b/webfm/models.py @@ -1,19 +1,14 @@ # System imports # Lib imports -from flask_login.mixins import UserMixin from flask_sqlalchemy import SQLAlchemy # App imports -from . import app, login_manager +from . import app db = SQLAlchemy(app) -@login_manager.user_loader -def load_user(user_id): - return User.query.get(int(user_id)) - class Favorites(db.Model): link = db.Column(db.String, nullable=False, unique=True) @@ -22,19 +17,10 @@ class Favorites(db.Model): def __repr__(self): return f"['{self.link}', '{self.id}']" -class Settings(db.Model, UserMixin): +class Settings(db.Model): key = db.Column(db.String, nullable=False) value = db.Column(db.String, nullable=False) id = db.Column(db.Integer, nullable=False, primary_key=True, unique=True, autoincrement=True) def __repr__(self): return f"['{self.key}', '{self.value}', '{self.id}']" - - -class User(db.Model, UserMixin): - username = db.Column(db.String, unique=True, nullable=False) - password = db.Column(db.String, nullable=False) - id = db.Column(db.Integer, primary_key=True, unique=True, autoincrement=True) - - def __repr__(self): - return f"['{self.username}', '{self.email}', '{self.password}', '{self.id}']" diff --git a/webfm/static/css/main.css b/webfm/static/css/main.css index bf0b145..12c67ad 100644 --- a/webfm/static/css/main.css +++ b/webfm/static/css/main.css @@ -22,20 +22,20 @@ #file-grid { display: grid; - grid-template-columns: repeat(auto-fit, minmax(14em, 1fr)); + grid-template-columns: repeat(auto-fit, minmax(22em, 1fr)); grid-column-gap: 1em; grid-row-gap: 1em; margin: 2em auto; width: 85%; overflow-y: auto; - max-height: 25em; + height: 80vh; } #path { max-width: inherit; } #faves-list > li { width: 100%; } #video-container { - width: 30em; + width: inherit; position: relative; } @@ -56,6 +56,16 @@ } +#volumeIcon { + border-style: solid; + border-width: thin; + border-color: #ffffff; +} + +#volumeIcon:hover { + cursor: pointer; +} + /* CLASSES */ .scroller { @@ -63,6 +73,13 @@ scrollbar-width: thin; } +.volume-control-positioner { + position: absolute; + bottom: 2em; + right: 50%; + left: 50%; +} + .dir-style, .video-style, .file-style { display: block; width: 100%; @@ -84,6 +101,7 @@ .image-style, .video-style { + position: relative; min-height: 6.5em; width: auto; overflow: hidden; @@ -151,7 +169,10 @@ } .video-title { + position: absolute; width: 100%; + left: 0em; + bottom: 1em; margin-top: 5.5em; background-color: rgba(0, 0, 0, 0.64); color: rgb(255, 255, 255); diff --git a/webfm/static/css/overrides.css b/webfm/static/css/overrides.css index 019d26f..2d2afed 100644 --- a/webfm/static/css/overrides.css +++ b/webfm/static/css/overrides.css @@ -1,9 +1,20 @@ +/* Vertical slider */ +input[type=range][orient=vertical] { + writing-mode: bt-lr; /* IE */ + -webkit-appearance: slider-vertical; /* WebKit */ + width: 8px; + height: 175px; + padding: 0 5px; +} + + + #video-container::-webkit-media-controls { - display:none !important; + display: none !important; } #video-container::-webkit-media-controls-enclosure { - display:none !important; + display: none !important; } diff --git a/webfm/static/js/ajax.js b/webfm/static/js/ajax.js index d2a46d0..c05b264 100644 --- a/webfm/static/js/ajax.js +++ b/webfm/static/js/ajax.js @@ -43,6 +43,13 @@ 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); diff --git a/webfm/static/js/events.js b/webfm/static/js/events.js index 59fec97..6a45a50 100644 --- a/webfm/static/js/events.js +++ b/webfm/static/js/events.js @@ -1,6 +1,8 @@ let fullScreenState = 0; -let shouldPlay = null; let clicktapwait = 200; +let shouldPlay = null; +let controlsTimeout = null; +let canHideControls = true; document.body.onload = (eve) => { @@ -21,102 +23,6 @@ document.body.onload = (eve) => { } } - -function togglePlay(video) { - shouldPlay = setTimeout(function () { - shouldPlay = null; - if (video.paused) { - video.play(); - } else { - video.pause(); - } - }, 300); -} - -function toggleFullscreen(video) { - containerElm = document.getElementById("video-container"); - parentElm = video.parentElement; - - - if (video.requestFullscreen) { - parentElm.requestFullscreen(); - containerElm.style.display = "block"; - } else if (video.webkitRequestFullscreen) { /* Safari */ - parentElm.webkitRequestFullscreen(); - containerElm.style.display = "block"; - } else if (video.msRequestFullscreen) { /* IE11 */ - parentElm.msRequestFullscreen(); - containerElm.style.display = "block"; - } - - if (fullScreenState == 2) { - if (document.exitFullscreen) { - document.exitFullscreen(); - containerElm.style.display = "contents"; - } else if (document.webkitExitFullscreen) { /* Safari */ - document.webkitExitFullscreen(); - containerElm.style.display = "contents"; - } else if (document.msExitFullscreen) { /* IE11 */ - document.msExitFullscreen(); - containerElm.style.display = "contents"; - } - - fullScreenState = 0; - } - - fullScreenState += 1; -} - -$("#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) { - const video = eve.target; - const seekto = document.getElementById("seek-slider"); - const vt = video.currentTime * (100 / video.duration); - seekto.value = vt; -}); - -$( "#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; - -}); - - - - $( "#search-files-field" ).bind( "keyup", async function(eve) { searchPage(); }); diff --git a/webfm/static/js/post-ajax.js b/webfm/static/js/post-ajax.js index 701d7ea..3f2ddbf 100644 --- a/webfm/static/js/post-ajax.js +++ b/webfm/static/js/post-ajax.js @@ -72,7 +72,8 @@ const updateHTMLDirList = async (data) => { // Setup background if there is a 000.* in selection if (background_image.match(/000\.(jpg|png|gif)\b/) != null) { - background_image = formatURL("files/" + images[i][1]); + // 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"); @@ -119,7 +120,7 @@ const updateHTMLDirList = async (data) => { hash = images[i][1]; if (thumbnail.match(/000\.(jpg|png|gif)\b/) == null && - !thumbnail.includes("favicon.png")) { + !thumbnail.includes("favicon.png") && !thumbnail.includes("000")) { const clone = imgClone.cloneNode(true); createElmBlock(insertArea, clone, thumbnail, title, hash); } diff --git a/webfm/static/js/ui-logic.js b/webfm/static/js/ui-logic.js index f346737..fa08348 100644 --- a/webfm/static/js/ui-logic.js +++ b/webfm/static/js/ui-logic.js @@ -19,10 +19,11 @@ const scrollFilesToTop = () => { const showMedia = async (hash, extension, type) => { - document.getElementById("image-viewer").style.display = "none"; - document.getElementById("text-viewer").style.display = "none"; - document.getElementById("video-viewer").style.display = "none"; - document.getElementById("pdf-viewer").style.display = "none"; + document.getElementById("image-viewer").style.display = "none"; + document.getElementById("text-viewer").style.display = "none"; + document.getElementById("pdf-viewer").style.display = "none"; + document.getElementById("video-viewer").style.display = "none"; + document.getElementById("video-controls").style.display = "none"; if (type === "video") { setupVideo(hash, extension); @@ -34,9 +35,10 @@ const showMedia = async (hash, extension, type) => { const setupVideo = async (hash, extension) => { let video = document.getElementById("video-viewer"); + let controls = document.getElementById("video-controls"); video.poster = "static/imgs/icons/loading.gif"; - video.autoplay = true; video.style.display = ""; + video.src = "#" video_path = "files/" + hash; try { @@ -46,7 +48,7 @@ const setupVideo = async (hash, extension) => { if ( data.hasOwnProperty('path') ) { video_path = data.path; } else { - displayMessage(data.message.text, data.message.type) + displayMessage(data.message.text, data.message.type); return ; } } else if ((/\.(flv|mov|m4v|mpg|mpeg)$/i).test(extension)) { @@ -56,19 +58,20 @@ const setupVideo = async (hash, extension) => { } } - if ((/\.(flv|f4v)$/i).test(extension)) { - $('#file-view-modal').modal({"focus": true, "show": true}); - video.src = video_path; - } else { - // This is questionable in usage since it loads the full video before - // showing; but, seeking doesn't work otherwise... - $('#file-view-modal').modal({"focus": true, "show": true}); - let response = await fetch(formatURL(video_path)); - let vid_src = URL.createObjectURL(await response.blob()); // IE10+ - video.src = vid_src; - } + $('#file-view-modal').modal({"focus": true, "show": true}); + controls.style.display = "none"; + video.src = video_path; + // if ((/\.(flv|f4v)$/i).test(extension)) { + // video.src = video_path; + // } else { + // // This is questionable in usage since it loads the full video before + // showing; but, seeking doesn't work otherwise... + // let response = await fetch(formatURL(video_path)); + // let vid_src = URL.createObjectURL(await response.blob()); // IE10+ + // video.src = vid_src; + // video.src = video_path; + // } } catch (e) { - video.src = "#"; video.style.display = "none"; console.log(e); } @@ -103,9 +106,8 @@ const setupFile = async (hash, extension) => { } if (type == "text") { - console.log(hash); - let response = await fetch(formatURL("files/" + hash)); - let textData = await response.text(); // IE10+ + let response = await fetch(formatURL("files/" + hash)); + let textData = await response.text(); // IE10+ viewer.innerText = textData; } @@ -177,14 +179,16 @@ const disableEdit = (elm) => { const updateBackground = (srcLink, isvideo = true) => { try { + let elm = document.getElementById("bg"); + + console.log(srcLink); if (isvideo) { - let elm = document.getElementById("bg"); if (elm.getAttribute('src') === "") { elm.src = srcLink; } } else { - document.getElementById("bg").src = ""; - document.getElementById("bg").setAttribute("poster", srcLink); + elm.src = ""; + elm.setAttribute("poster", srcLink); } } catch (e) { } } @@ -223,3 +227,34 @@ 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; }); diff --git a/webfm/templates/index.html b/webfm/templates/index.html index 6f711fe..18303ad 100644 --- a/webfm/templates/index.html +++ b/webfm/templates/index.html @@ -2,6 +2,7 @@ {% block body_header_additional %} + @@ -36,7 +37,7 @@ - {% if current_user.is_authenticated or oidc_loggedin() %} + {% if oidc_loggedin() %} @@ -263,4 +281,5 @@ + {% endblock body_scripts_additional %} diff --git a/webfm/templates/layout.html b/webfm/templates/layout.html index fc058f1..04631b1 100644 --- a/webfm/templates/layout.html +++ b/webfm/templates/layout.html @@ -14,7 +14,6 @@ - {% block header_css_additional %} {% endblock header_css_additional %} {% endblock %} diff --git a/webfm/utils/FileManager.py b/webfm/utils/FileManager.py index 1a598bc..22c8b2c 100644 --- a/webfm/utils/FileManager.py +++ b/webfm/utils/FileManager.py @@ -126,7 +126,6 @@ class FileManager: def returnPathPartFromHash(self, partHash): hashes = [self.dotHash, self.dotdotHash] - print(self.pathParts) path = "/".join(self.pathParts) pathPart = None for f in listdir(path):