teslacam-browser
Version:
A minimal TeslaCam Browser
250 lines (219 loc) • 12.2 kB
HTML
<html>
<head>
<meta charset="UTF-8">
<title>TeslaCam Browser</title>
<link rel="stylesheet" href="node_modules/flatpickr/dist/flatpickr.min.css">
<link rel="stylesheet" href="node_modules/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="node_modules/open-iconic/font/css/open-iconic-bootstrap.css" />
<link rel="stylesheet" href="content/app.css" />
<script type="text/javascript" src="node_modules/jquery/dist/jquery.min.js"></script>
<script type="text/javascript" src="node_modules/bootstrap/dist/js/bootstrap.min.js"></script>
<script type="text/javascript" src="node_modules/vue/dist/vue.js"></script>
<script type="text/javascript" src="node_modules/flatpickr/dist/flatpickr.min.js"></script>
<script type="text/javascript" src="content/helpers.js"></script>
<script type="text/javascript" src="content/ui.js"></script>
</head>
<body>
<div id="root" class="d-flex flex-column">
<div v-cloak class="d-flex flex-column">
<div class="d-flex flex-column">
<div id="heading" class="d-flex justify-content-between">
<div class="d-flex">
<div class="p-2 align-self-center" @click="showSidebar = !showSidebar">
<button class="btn btn-primary btn-lg" style="width: 60px;" v-show="!showSidebar">
<span class="oi oi-menu" title="Folders"></span>
</button>
<button class="btn btn-primary btn-lg" style="width: 60px;" v-show="showSidebar">
<span class="oi oi-chevron-left" title="Collapse"></span>
</button>
</div>
<div class="p-2 align-self-baseline text-nowrap">
<span class="heading" @click="showSidebar = !showSidebar">TeslaCam Browser</span>
</div>
<div class="p-2 align-self-baseline text-nowrap">
<span class="version" id="version">{{ args.version }}</span>
</div>
<div class="p-2 align-self-baseline text-nowrap">
<span class="credits">by Chris Cavanagh</span>
</div>
</div>
<div class="p-2 donate mt-2">
<div class="d-flex flex-row-reverse">
<form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_blank">
<input type="hidden" name="cmd" value="_donations" />
<input type="hidden" name="business" value="32J86B5QYPD6Y" />
<input type="hidden" name="currency_code" value="USD" />
<input type="image" src="https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif" border="0"
name="submit" title="PayPal - The safer, easier way to pay online!" alt="Donate with PayPal button" />
<img alt="" border="0" src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1" />
</form>
</div>
<div class="d-flex flex-row custom-control custom-switch mr-2">
<input type="checkbox" v-model="combineClips" class="custom-control-input" id="combineClips">
<label class="custom-control-label" for="combineClips">Combine clips</label>
</div>
</div>
</div>
<div id="content" class="d-flex">
<nav id="sidebar" class="d-flex flex-column align-items-start" :class="{ hidden: !showSidebar }">
<div class="navigation overflow-auto">
<div class="px-2 mb-2 overflow-auto">
<button class="btn btn-primary btn-block dropdown-toggle" type="button"
@click.prevent="showFolders = !showFolders" style="word-wrap: break-word; white-space: normal; max-width: 320px;">
<span class="oi oi-folder text-warning"></span>
<span>{{args.folder}}</span>
</button>
<div v-show="showFolders">
<div v-for="f, i in args.folderPathParts" :style="{ marginLeft: ( i * 10 ) + 'px' }">
<a href="" @click.prevent="selectedFolder = f.path">
<span class="oi oi-folder text-warning"></span>
<span
:style="{ fontWeight: i < args.folderPathParts.length - 1 ? 'normal' : 'bold' }">{{f.name}}</span>
</a>
</div>
<div v-for="subfolder in args.subfolders"
:style="{ marginLeft: ( args.folderPathParts.length * 10 ) + 'px' }">
<a href="" @click.prevent="selectedFolder = subfolder.path">
<span class="oi oi-folder text-warning"></span>
<span>{{subfolder.name}}</span>
</a>
</div>
</div>
</div>
<div class="d-flex align-items-center">
<div class="px-2 mb-2 flex-grow-1" style="font-size: small;">
<div>{{ args && args.folderInfos ? args.folderInfos.length : 0 }}
event{{ args && args.folderInfos && args.folderInfos.length != 1 ? "s" : "" }}</div>
<div>{{ args && args.dates ? args.dates.length : 0 }}
day{{ args && args.dates && args.dates != 1 ? "s" : "" }}</div>
</div>
<!--div class="px-2 mb-2 text-nowrap">
<button id="openButton" type="button" class="btn btn-primary" @click="openFolder()">Open...</button>
<button id="browseButton" type="button" class="btn btn-secondary" @click="openBrowser()">Open in
browser</button>
</div-->
</div>
<div class="px-2 mb-2">
<input id="calendar" class="flatpickr flatpickr-input dates form-control" type="text"
placeholder="Select Date..." readonly="readonly" />
</div>
<div class="px-2 mb-2">
<div class="list-group list-group-flush overflow-auto times">
<a v-for="t in times" href="" class="list-group-item list-group-item-action"
:class="{active: t === selectedTime}" @click.prevent="selectedTime = t"
style="cursor: pointer;">{{ t.name }}</a>
</div>
</div>
<div class="px-2 mb-2">
<button id="deleteButton" class="btn btn-danger" @click="deleteFolder( selectedPath )">Delete
folder</button>
<button id="copyButton" class="btn btn-info" @click="copyPath( selectedPath )">Copy path</button>
</div>
</div>
</nav>
<div id="detail" class="d-flex flex-column flex-grow-1">
<div id="videos">
<div class="timespan card mb-2 mx-2" v-if="combineClips">
<div class="d-flex align-items-center titleContainer card-header">
<div class="px-2 controls">
<button class="btn btn-success play" v-show="!controls.playing"
@click="controls.playing = !controls.playing"><span class="oi oi-media-play"
title="Play"></span></button>
<button class="btn btn-warning pause" v-show="controls.playing"
@click="controls.playing = !controls.playing"><span class="oi oi-media-pause"
title="Pause"></span></button>
</div>
<div class="px-2 flex-grow-1 controls">
<input class="custom-range scrub" v-model="currentTime" type="range" :max="duration"
@input="controls.playing = false"/>
</div>
<div class="px-2 controls">
<select class="form-control" v-model="controls.speed">
<option value="1">1x</option>
<option value="2">2x</option>
<option value="10">10x</option>
</select>
</div>
</div>
<div class="videoContainer card-body">
<div class="alert alert-info" v-if="loading != null">Loading {{ Math.round( loading ) }}%...<span
v-if="selectedTime.time.recent"> (Clips from RecentFiles will take longer to load)</span></div>
<video-group :controls="controls" :timespans="timespans"></video-group>
<div class="text-center">{{ timespanTime( controls.timespan ) }}</div>
</div>
</div>
<div v-if="!combineClips">
<div v-for="timespan in timespans" class="timespan card mb-2 mx-2">
<div class="d-flex align-items-center titleContainer card-header">
<div class="px-2 flex-grow-1 title" title="Show / hide"
@click="timespan.visible = !timespan.visible"><a href="#">{{timespan.visible ? "↑" : "↓"}}</a>
{{timespan.title}}</div>
<div class="px-2 controls">
<button class="btn btn-danger delete" @click="deleteFiles( timespan )">Delete</button>
<button class="btn btn-info copy" @click="copyFilePaths( timespan )">Copy paths</button>
<button class="btn btn-success play" v-show="!timespan.playing"
@click="playPause( timespan )"><span class="oi oi-media-play" title="Play"></span></button>
<button class="btn btn-warning pause" v-show="timespan.playing"
@click="playPause( timespan )"><span class="oi oi-media-pause" title="Pause"></span></button>
</div>
<div class="px-2 flex-grow-1 controls" v-if="timespan.visible">
<input class="custom-range scrub" v-model="timespan.currentTime" type="range"
:max="timespan.duration" @input="scrubInput( timespan )"/>
</div>
<div class="px-2 controls">
<select class="form-control" v-model="controls.speed">
<option value="1">1x</option>
<option value="2">2x</option>
<option value="10">10x</option>
</select>
</div>
</div>
<div class="videoContainer card-body" v-if="timespan.visible">
<videos :controls="controls" :timespan="timespan"></videos>
<div class="text-center">{{ timespanTime( timespan ) }}</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script type="text/javascript">
(function () {
var lastArgs = { folder: "" }
function joinFilePaths( filePaths )
{
return filePaths.map( f => `"${joinFolderPath( f )}` ).join( " " )
}
function joinFolderPath( folderPath )
{
return lastArgs.folder + folderPath
}
function copyToClipboard( value )
{
var $temp = $("<input>");
$("body").append( $temp );
$temp.val( value ).select();
document.execCommand( "copy" );
$temp.remove();
}
var vueApp = ui.initialize(
{
openFolders: success => $.getJSON("openFolders", success),
reopenFolders: success => $.getJSON("reopenFolders", success),
openFolder: (p, success) => $.getJSON( p ? "openFolder" + p : "openDefaultFolder", a => success( lastArgs = a ) ),
getFiles: (p, success) => $.getJSON("files/" + p, success),
openBrowser: () => $.post("openBrowser"),
deleteFiles: files => $.post("deleteFiles", files),
deleteFolder: folder => $.post("deleteFolder", folder),
copyFilePaths: filePaths => copyToClipboard( joinFilePaths( filePaths ) ),
copyPath: path => copyToClipboard( joinFolderPath( path ) ),
openExternal: path => window.open(path)
} )
})();
</script>
</body>
</html>