New player controls, css changes, big fixes

This commit is contained in:
maximstewart 2021-01-25 01:15:46 -06:00
parent c7e76533ec
commit 59c2d0860f
13 changed files with 316 additions and 177 deletions

View File

@ -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

View File

@ -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

View File

@ -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}']"

View File

@ -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);

View File

@ -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;
}

View File

@ -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);

View File

@ -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();
});

View File

@ -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);
}

View File

@ -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<links.length; i++) {
var link = links[i],
href = link.href;
if(rep.test(href)) {
link.href = href+'&'+Date.now();
} else {
link.href = href+'?'+Date.now();
}
}
if(process_scripts) {
for (var i=0; i<scripts.length; i++) {
var script = scripts[i],
src = script.src;
if(rep.test(src)) {
script.src = src+'&'+Date.now();
} else {
script.src = src+'?'+Date.now();
}
}
}
}

View File

@ -0,0 +1,170 @@
const getTimeFormatted = (duration = null) => {
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; });

View File

@ -2,6 +2,7 @@
{% block body_header_additional %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/base.css')}}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/overrides.css')}}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/main.css')}}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/bootstrap/bootstrap-table.min.css')}}">
@ -36,7 +37,7 @@
<button title="File viewer..." class="btn btn-secondary btn-sm"
data-toggle="modal" data-target="#file-view-modal">🖼</button>
{% if current_user.is_authenticated or oidc_loggedin() %}
{% if oidc_loggedin() %}
<a href="/logout">
<button title="Logout..." class="btn btn-danger btn-sm">
Logout
@ -110,31 +111,48 @@
<div class="modal-content">
<div class="modal-header">
<h3>File Viewer:</h3>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<button type="button" onclick="pauseVideo()" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body text-center justify-content-center">
<div class="row">
<div class="col scroller" style="max-height: 30em; overflow: auto;">
<div class="col scroller" style="max-height: 70vh; overflow: auto;">
<!-- For video -->
<div id="video-container" style="display: contents;">
<div id="video-container">
<video id="video-viewer"
loop
src=""
autoplay=""
loop
poster="{{ url_for('static', filename='imgs/icons/loading.gif')}}">
volume="0.75"
poster="{{ url_for('static', filename='imgs/icons/loading.gif')}}"
onmousemove="showControls()">
</video>
<div id="video-controls">
<input id="seek-slider" type="range" min="0" max="100" step="1">
</div>
<div id="video-controls">
<div class="row pl-5 pr-5">
<div class="col-md-8">
<input id="seek-slider" class="form-control-range" type="range" min="0" value="0" max="100" step="1"/>
</div>
<div class="col-md-3">
<span id="videoCurrentTime"></span>
/
<span id="videoDuration"></span>
</div>
<div class="col-md-1">
<span id="volumeIcon" onclick="toggleVolumeControl()">&#128264;</span>
<input id="volume-slider" class="form-control-range volume-control-positioner" style="display: none;"
type="range" orient="vertical" min="0.0" max="1.0" value="0.75" step="0.05" />
</div>
</div>
</div>
</div>
<!-- For image -->
<img id="image-viewer"
style="width: 40em; height: auto; display: none;"
style="width: inherit; height: auto; display: none;"
src="" alt="" />
<!-- For pdf -->
@ -152,14 +170,14 @@
<!-- For text -->
<pre id="text-viewer"
style="width: 45em; height: auto; display: none;">
style="width: inherit; height: auto; display: none;">
</pre>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" data-dismiss="modal" class="btn btn-danger btn-sm">Close</button>
<button onclick="pauseVideo()" type="button" data-dismiss="modal" class="btn btn-danger btn-sm">Close</button>
</div>
</div>
</div>
@ -263,4 +281,5 @@
<script src="{{ url_for('static', filename='js/ajax.js')}}"></script>
<script src="{{ url_for('static', filename='js/ui-logic.js')}}"></script>
<script src="{{ url_for('static', filename='js/events.js')}}"></script>
<script src="{{ url_for('static', filename='js/video-events.js')}}"></script>
{% endblock body_scripts_additional %}

View File

@ -14,7 +14,6 @@
<link rel="stylesheet" href="{{ url_for('static', filename='css/bootstrap/bootstrap.min.css')}}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/bootstrap/bootstrap-datepicker.css')}}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/overrides.css')}}">
{% block header_css_additional %}
{% endblock header_css_additional %}
{% endblock %}

View File

@ -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):