style: Adapt responsive design for mobile devices
This commit is contained in:
parent
32a2b92f14
commit
e74139fe72
|
@ -15,6 +15,19 @@ import './bootstrap';
|
||||||
import feather from 'feather-icons';
|
import feather from 'feather-icons';
|
||||||
feather.replace();
|
feather.replace();
|
||||||
|
|
||||||
|
/** Update css variables --{header, footer}-height
|
||||||
|
* by querying elements real height */
|
||||||
|
(function() {
|
||||||
|
let css_root = document.querySelector(':root');
|
||||||
|
let header = document.getElementsByTagName('header')[0];
|
||||||
|
let header_height = header.clientHeight;
|
||||||
|
css_root.style.setProperty('--header-height', header_height + 'px');
|
||||||
|
let footer = document.getElementsByTagName('footer')[0];
|
||||||
|
let footer_height = footer.clientHeight;
|
||||||
|
css_root.style.setProperty('--footer-height', footer_height + 'px');
|
||||||
|
|
||||||
|
})();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
document.getElementsByClassName('prevent').map(
|
document.getElementsByClassName('prevent').map(
|
||||||
(e) => e.addEventListener('click', (e) => e.preventDefault())
|
(e) => e.addEventListener('click', (e) => e.preventDefault())
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { Controller } from '@hotwired/stimulus';
|
||||||
|
|
||||||
|
/* stimulusFetch: 'lazy' */
|
||||||
|
export default class extends Controller {
|
||||||
|
static targets = ['filename']
|
||||||
|
|
||||||
|
delete() {
|
||||||
|
let filename = this.filenameTarget.value;
|
||||||
|
let url = `/records/delete/${filename}`;
|
||||||
|
fetch(url, {
|
||||||
|
method: 'POST'
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
if (response.ok) {
|
||||||
|
window.location.reload();
|
||||||
|
} else {
|
||||||
|
console.log(response);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { Controller } from '@hotwired/stimulus';
|
||||||
|
|
||||||
|
/* stimulusFetch: 'lazy' */
|
||||||
|
export default class extends Controller {
|
||||||
|
static targets = ['filename']
|
||||||
|
|
||||||
|
delete() {
|
||||||
|
let filename = this.filenameTarget.value;
|
||||||
|
let url = `/records/delete/${filename}`;
|
||||||
|
fetch(url, {
|
||||||
|
method: 'POST'
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
if (response.ok) {
|
||||||
|
window.location.reload();
|
||||||
|
} else {
|
||||||
|
console.log(response);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +0,0 @@
|
||||||
(function() {
|
|
||||||
try {
|
|
||||||
let delete_buttons = document.getElementsByClassName("delete-button");
|
|
||||||
} catch {
|
|
||||||
console.debug("no delete buttons found");
|
|
||||||
}
|
|
||||||
})();
|
|
|
@ -1,9 +1,24 @@
|
||||||
:root {
|
:root {
|
||||||
--bg: lightgray;
|
--bg: white;
|
||||||
|
--font-family: 'Latin Modern Math';
|
||||||
|
--font-size: 2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
font-family: var(--font-family);
|
||||||
|
font-size: large;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
position: relative;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
|
@ -20,6 +35,18 @@
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.grow {
|
||||||
|
flex-grow: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.end {
|
||||||
|
justify-self: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item {
|
||||||
|
align-self: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
.grid {
|
.grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
}
|
}
|
||||||
|
@ -38,7 +65,7 @@
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button, input {
|
.button, input[type=submit], input[type=button] {
|
||||||
background-color: #f1f1f1;
|
background-color: #f1f1f1;
|
||||||
color: black;
|
color: black;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
|
@ -129,23 +156,31 @@ body {
|
||||||
|
|
||||||
header {
|
header {
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
/** Align text and center of image */
|
/** Align text and center of image */
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
header h1 {
|
||||||
|
font-size: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
header:first-child {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
header img.logo {
|
header img.logo {
|
||||||
width: 100px;
|
width: auto;
|
||||||
height: 100px;
|
height: 10rem;
|
||||||
|
position: relative;
|
||||||
|
top: -2rem;
|
||||||
|
padding-top: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
main {
|
main {
|
||||||
min-height: 100vh;
|
min-height: calc(100vh - (var(--header-height, 4em) + var(--footer-height, 4em)) );
|
||||||
padding: 5em;
|
padding: 5em;
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
position: relative;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
footer {
|
footer {
|
||||||
|
@ -153,6 +188,7 @@ footer {
|
||||||
background-color: black;
|
background-color: black;
|
||||||
padding: 2em;
|
padding: 2em;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
footer a {
|
footer a {
|
||||||
|
@ -168,8 +204,6 @@ li, td {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
td
|
|
||||||
|
|
||||||
/* .dropdown-button:hover {
|
/* .dropdown-button:hover {
|
||||||
background-color: #900;
|
background-color: #900;
|
||||||
color: white
|
color: white
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
nav {
|
nav {
|
||||||
--nav-width: 20em;
|
--nav-width: 20em;
|
||||||
--nav-bg: white;
|
--nav-bg: lightgrey;
|
||||||
--burger-size: 2em;
|
--burger-size: 5em;
|
||||||
|
--burger-weight: 3px;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
@ -40,7 +41,7 @@ nav {
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
background: black;
|
background: black;
|
||||||
height: 2px;
|
height: var(--burger-weight);
|
||||||
width: 75%;
|
width: 75%;
|
||||||
transition: all 0.4s ease;
|
transition: all 0.4s ease;
|
||||||
color: #000;
|
color: #000;
|
||||||
|
@ -51,19 +52,19 @@ nav {
|
||||||
.hamburger>div::after {
|
.hamburger>div::after {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -7px;
|
top: 10px;
|
||||||
background: black;
|
background: black;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 2px;
|
height: var(--burger-weight);
|
||||||
transition: all 0.4s ease;
|
transition: all 0.4s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hamburger>div::after {
|
.hamburger>div::after {
|
||||||
top: 7px;
|
top: -10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toggler:checked+.hamburger>div {
|
.toggler:checked+.hamburger>div {
|
||||||
background: rgba(0, 0, 0, 0);
|
background-color: var(--nav-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.toggler:checked+.hamburger>div::before {
|
.toggler:checked+.hamburger>div::before {
|
||||||
|
@ -87,7 +88,7 @@ nav {
|
||||||
.toggler:checked~.menu {
|
.toggler:checked~.menu {
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
height: fit-content;
|
height: fit-content;
|
||||||
background-color: rgba(255, 255, 255, 1) !important;
|
background-color: var(--nav-bg) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu>ul {
|
.menu>ul {
|
||||||
|
@ -95,12 +96,12 @@ nav {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
width: 0;
|
width: 0;
|
||||||
height: 100vmax;
|
height: calc(100vh - var(--burger-size));
|
||||||
padding-left: 1em;
|
padding-left: 1em;
|
||||||
padding-right: 1em;
|
padding-right: 1em;
|
||||||
margin-top: var(--burger-size);
|
margin-top: var(--burger-size);
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
background-color: white;
|
background-color: var(--nav-bg);
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,7 +113,7 @@ nav {
|
||||||
.menu>ul>li>a {
|
.menu>ul>li>a {
|
||||||
color: black;
|
color: black;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-size: 2rem;
|
font-size: var(--font-size);
|
||||||
}
|
}
|
||||||
|
|
||||||
.toggler~.fill {
|
.toggler~.fill {
|
||||||
|
@ -120,11 +121,11 @@ nav {
|
||||||
}
|
}
|
||||||
|
|
||||||
.toggler:checked~.fill {
|
.toggler:checked~.fill {
|
||||||
background: white;
|
background: var(--nav-bg);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
height: 100vh;
|
height: var(--burger-size);
|
||||||
width: var(--nav-width);
|
width: var(--nav-width);
|
||||||
display: block;
|
display: block;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
* https://codepen.io/jakealbaugh/pen/jvQweW
|
* https://codepen.io/jakealbaugh/pen/jvQweW
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// UPDATE: there is a problem in chrome with starting audio context
|
const ICECAST_URL = '/stream';
|
||||||
// before a user gesture. This fixes it.
|
|
||||||
var started = false;
|
var started = false;
|
||||||
try {
|
try {
|
||||||
var spectro_button = document.getElementById('spectro-button');
|
var spectro_button = document.getElementById('spectro-button');
|
||||||
|
@ -19,6 +19,7 @@ try {
|
||||||
}
|
}
|
||||||
|
|
||||||
function initialize() {
|
function initialize() {
|
||||||
|
const AUDIO_ELEMENT = document.getElementById('player');
|
||||||
const CVS = document.getElementById('spectro-canvas');
|
const CVS = document.getElementById('spectro-canvas');
|
||||||
const CTX = CVS.getContext('2d');
|
const CTX = CVS.getContext('2d');
|
||||||
const W = CVS.width = window.innerWidth;
|
const W = CVS.width = window.innerWidth;
|
||||||
|
@ -29,12 +30,27 @@ function initialize() {
|
||||||
|
|
||||||
ANALYSER.fftSize = 4096;
|
ANALYSER.fftSize = 4096;
|
||||||
|
|
||||||
navigator.mediaDevices
|
// navigator.mediaDevices
|
||||||
.getUserMedia({ audio: true })
|
// .getUserMedia({ audio: true })
|
||||||
.then(process);
|
// .then(process);
|
||||||
|
|
||||||
function process(stream) {
|
// Add icecast stream
|
||||||
const SOURCE = ACTX.createMediaStreamSource(stream);
|
// var audio = new Audio(ICECAST_URL);
|
||||||
|
let stream;
|
||||||
|
AUDIO_ELEMENT.src = ICECAST_URL;
|
||||||
|
AUDIO_ELEMENT.play();
|
||||||
|
AUDIO_ELEMENT.onplay = function () {
|
||||||
|
if (navigator.userAgent.indexOf('Firefox') > -1) {
|
||||||
|
stream = AUDIO_ELEMENT.mozCaptureStream();
|
||||||
|
} else {
|
||||||
|
console.debug('Not a firefox browser, defaults to `captureStream()`');
|
||||||
|
stream = AUDIO_ELEMENT.captureStream();
|
||||||
|
}
|
||||||
|
process(AUDIO_ELEMENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
function process(audio) {
|
||||||
|
const SOURCE = ACTX.createMediaElementSource(audio);
|
||||||
SOURCE.connect(ANALYSER);
|
SOURCE.connect(ANALYSER);
|
||||||
const DATA = new Uint8Array(ANALYSER.frequencyBinCount);
|
const DATA = new Uint8Array(ANALYSER.frequencyBinCount);
|
||||||
const LEN = DATA.length;
|
const LEN = DATA.length;
|
||||||
|
|
|
@ -62,7 +62,7 @@ class HomeController extends AbstractController
|
||||||
ORDER BY `date` DESC LIMIT 1";
|
ORDER BY `date` DESC LIMIT 1";
|
||||||
$stmt = $this->connection->prepare($sql);
|
$stmt = $this->connection->prepare($sql);
|
||||||
$result = $stmt->executeQuery();
|
$result = $stmt->executeQuery();
|
||||||
return $result->fetchAllAssociative()[0];
|
return $result->fetchAllAssociative();
|
||||||
}
|
}
|
||||||
|
|
||||||
private function last_chart_generated() {
|
private function last_chart_generated() {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Controller;
|
namespace App\Controller;
|
||||||
|
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
@ -14,7 +15,7 @@ class RecordsController extends AbstractController
|
||||||
/**
|
/**
|
||||||
* @Route("/records/{date}", name="records_index")
|
* @Route("/records/{date}", name="records_index")
|
||||||
*/
|
*/
|
||||||
public function records_index($date="now")
|
public function records_index($date = "now")
|
||||||
{
|
{
|
||||||
if ($date == "now") {
|
if ($date == "now") {
|
||||||
$date = date("Y-m-d");
|
$date = date("Y-m-d");
|
||||||
|
@ -28,19 +29,20 @@ class RecordsController extends AbstractController
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Route("/records/remove/{basename}", name="record_remove")
|
* @Route("/records/delete/{basename}", name="record_delete")
|
||||||
*/
|
*/
|
||||||
public function remove_record($basename)
|
public function delete_record(Connection $connection, $basename)
|
||||||
{
|
{
|
||||||
|
$this->connection = $connection;
|
||||||
$this->remove_record_by_basename($basename);
|
$this->remove_record_by_basename($basename);
|
||||||
return $this->redirectToRoute('records_index');
|
return $this->redirectToRoute('records_index');
|
||||||
}
|
}
|
||||||
|
|
||||||
private function list_records()
|
private function list_records()
|
||||||
{
|
{
|
||||||
$records_path = $this->getParameter('app.records_dir')."/out/*.wav";
|
$records_path = $this->getParameter('app.records_dir') . "/out/*.wav";
|
||||||
$records = glob($records_path);
|
$records = glob($records_path);
|
||||||
$records = array_map(function($record) {
|
$records = array_map(function ($record) {
|
||||||
$record = basename($record);
|
$record = basename($record);
|
||||||
return $record;
|
return $record;
|
||||||
}, $records);
|
}, $records);
|
||||||
|
@ -58,17 +60,37 @@ class RecordsController extends AbstractController
|
||||||
return $date;
|
return $date;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function only_on($date, $records) {
|
private function only_on($date, $records)
|
||||||
$filtered_records = array_filter($records, function($record) use ($date) {
|
{
|
||||||
|
$filtered_records = array_filter($records, function ($record) use ($date) {
|
||||||
return $this->get_record_date($record) == $date;
|
return $this->get_record_date($record) == $date;
|
||||||
});
|
});
|
||||||
return $filtered_records;
|
return $filtered_records;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function remove_record_by_basename($basename) {
|
private function remove_record_by_basename($basename)
|
||||||
$record_path = $this->getParameter('app.records_dir')."/out/$basename";
|
{
|
||||||
unlink($record_path);
|
if (strlen($basename) > 1) {
|
||||||
unlink($record_path.".d/model.out.csv");
|
/** Remove files associated with this filename */
|
||||||
rmdir($this->getParameter('app.records_dir')."/out/$basename.d");
|
$record_path = $this->getParameter('app.records_dir') . "/out/$basename";
|
||||||
|
if (is_file($record_path))
|
||||||
|
unlink($record_path);
|
||||||
|
$model_out_dir = $record_path.".d";
|
||||||
|
$model_out_path = $model_out_dir."/model.out.csv";
|
||||||
|
if (is_file($model_out_path))
|
||||||
|
unlink($model_out_path);
|
||||||
|
if (is_dir($model_out_dir))
|
||||||
|
rmdir($model_out_dir);
|
||||||
|
/** Remove database entry associated with this filename */
|
||||||
|
$this->remove_observations_from_record($basename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function remove_observations_from_record($basename)
|
||||||
|
{
|
||||||
|
$sql = "DELETE FROM observation WHERE audio_file = :filename";
|
||||||
|
$stmt = $this->connection->prepare($sql);
|
||||||
|
$stmt->bindValue(':filename', $basename);
|
||||||
|
$stmt->executeStatement();
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>
|
<title>
|
||||||
|
@ -16,12 +16,16 @@
|
||||||
{{ encore_entry_script_tags('app') }}
|
{{ encore_entry_script_tags('app') }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body class="container col">
|
||||||
{% block body %}
|
{% block body %}
|
||||||
{% include "menu.html.twig" %}
|
{% include "menu.html.twig" %}
|
||||||
<header>
|
<header>
|
||||||
<img class="logo" src="/media/logo.svg" alt="BirdNET logo">
|
<a href="/">
|
||||||
<h1>BirdNET-stream</h1>
|
<div class="container row">
|
||||||
|
<img class="logo" src="/media/logo.svg" alt="BirdNET logo">
|
||||||
|
<h1>BirdNET-stream</h1>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
</header>
|
</header>
|
||||||
<main>
|
<main>
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<footer>
|
<footer class="end">
|
||||||
<p>
|
<p>
|
||||||
© <span class="creation-date">2022
|
© <span class="creation-date">2022
|
||||||
<!-- check if the current year is the same as the creation year -->
|
<!-- check if the current year is the same as the creation year -->
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
<div {{ stimulus_controller('delete-record') }}>
|
||||||
|
<button class="delete button" value="{{ filename }}" {{ stimulus_target('delete-record', 'filename') }} {{ stimulus_action('delete-record', 'delete') }}>
|
||||||
|
<i data-feather="trash-2"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
|
@ -7,7 +7,10 @@
|
||||||
{% if records is defined and records | length > 0 %}
|
{% if records is defined and records | length > 0 %}
|
||||||
<ul>
|
<ul>
|
||||||
{% for record in records %}
|
{% for record in records %}
|
||||||
<li>{{ record }}</li>
|
<li>{{ record }}
|
||||||
|
{% include "records/delete_button.html.twig" with { filename: record } only %}
|
||||||
|
{% include "records/player.html.twig" with { filename: record } only %}
|
||||||
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
<audio preload="none" controls>
|
||||||
|
<source src="/media/records/{{ filename }}">
|
||||||
|
</audio>
|
|
@ -4,6 +4,12 @@
|
||||||
<h2>{{ "Spectrogram" | trans }}</h2>
|
<h2>{{ "Spectrogram" | trans }}</h2>
|
||||||
<div>
|
<div>
|
||||||
<button id="spectro-button">{{ "Launch Live Spectrogram" | trans }}</button>
|
<button id="spectro-button">{{ "Launch Live Spectrogram" | trans }}</button>
|
||||||
|
<audio id="player" controls>
|
||||||
|
<source media="/stream">
|
||||||
|
{{ "No audio sources yet" | trans }}
|
||||||
|
</audio>
|
||||||
<canvas id="spectro-canvas"></canvas>
|
<canvas id="spectro-canvas"></canvas>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{ encore_entry_script_tags('spectro') }}
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -3,6 +3,7 @@
|
||||||
<ul>
|
<ul>
|
||||||
<li class="most-recorded-species">
|
<li class="most-recorded-species">
|
||||||
{{ "Most recorded species" | trans }}:
|
{{ "Most recorded species" | trans }}:
|
||||||
|
{% if stats["most-recorded-species"] is defined %}
|
||||||
<span class="scientific-name">
|
<span class="scientific-name">
|
||||||
{{ stats["most-recorded-species"]["scientific_name"] }}
|
{{ stats["most-recorded-species"]["scientific_name"] }}
|
||||||
</span>
|
</span>
|
||||||
|
@ -12,9 +13,13 @@
|
||||||
{{ stats["most-recorded-species"]["contact_count"] }}
|
{{ stats["most-recorded-species"]["contact_count"] }}
|
||||||
</span>
|
</span>
|
||||||
{{ "contacts" | trans }}.
|
{{ "contacts" | trans }}.
|
||||||
|
{% else %}
|
||||||
|
{{ "No species in database." | trans }}
|
||||||
|
{% endif %}
|
||||||
</li>
|
</li>
|
||||||
<li class="last-recorded-species">
|
<li class="last-recorded-species">
|
||||||
{{ "Last detected species" | trans }}:
|
{{ "Last detected species" | trans }}:
|
||||||
|
{% if stats["last-recorded-species"] is defined %}
|
||||||
<span class="scientific-name">
|
<span class="scientific-name">
|
||||||
{{ stats["last-detected-species"]["scientific_name"] }}
|
{{ stats["last-detected-species"]["scientific_name"] }}
|
||||||
</span>
|
</span>
|
||||||
|
@ -37,6 +42,9 @@
|
||||||
{{ date | date("H:i") }}
|
{{ date | date("H:i") }}
|
||||||
</span>
|
</span>
|
||||||
</span>.
|
</span>.
|
||||||
|
{% else %}
|
||||||
|
{{ "No species in database" | trans }}
|
||||||
|
{% endif %}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
|
@ -0,0 +1,8 @@
|
||||||
|
<div class="manager">
|
||||||
|
<button class="button" id="select-all" title="{{ "Select all" | trans }}">
|
||||||
|
<i data-feather="check-square"></i>
|
||||||
|
</button>
|
||||||
|
<button class="delete button" title="{{ "Delete selected" | trans }}">
|
||||||
|
<i data-feather="trash-2"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
|
@ -35,9 +35,11 @@
|
||||||
<div class="records">
|
<div class="records">
|
||||||
<h3>{{ "Contact records" | trans }}</h3>
|
<h3>{{ "Contact records" | trans }}</h3>
|
||||||
{% if records is defined and records | length > 0 %}
|
{% if records is defined and records | length > 0 %}
|
||||||
|
{% include "today/manage.html.twig" %}
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
<th></th>
|
||||||
<th>{{ "Filename" | trans }}</th>
|
<th>{{ "Filename" | trans }}</th>
|
||||||
<th>{{ "Time" | trans }}</th>
|
<th>{{ "Time" | trans }}</th>
|
||||||
<th>{{ "Confidence" | trans }}</th>
|
<th>{{ "Confidence" | trans }}</th>
|
||||||
|
@ -46,6 +48,9 @@
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for record in records %}
|
{% for record in records %}
|
||||||
<tr>
|
<tr>
|
||||||
|
<td>
|
||||||
|
<input title="{{ "Select this record" | trans }}" type="checkbox" name="select-file" value="{{ record.audio_file }}">
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a title="{{ "Download audio file" | trans }}" href="/media/records/{{ record.audio_file }}">
|
<a title="{{ "Download audio file" | trans }}" href="/media/records/{{ record.audio_file }}">
|
||||||
{{ record.audio_file }}
|
{{ record.audio_file }}
|
||||||
|
@ -54,9 +59,8 @@
|
||||||
<td>{{ record.date | date("H:m") }}</td>
|
<td>{{ record.date | date("H:m") }}</td>
|
||||||
<td>{{ record.confidence }}</td>
|
<td>{{ record.confidence }}</td>
|
||||||
<td>
|
<td>
|
||||||
{% include "utils/player.html.twig" with { "file": record.audio_file } only %}
|
{% include "records/player.html.twig" with { "filename": record.audio_file } only %}
|
||||||
<button class="delete" value="{{ record.audio_file }}"><i data-feather="trash-2"></i></button>
|
{% include "records/delete_button.html.twig" with { "filename": record.audio_file } only %} </td>
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<div class="date-selector container row">
|
<div class="date-selector container row">
|
||||||
<label for="date">{{ "Date" | trans }}</label>
|
<label for="date">{{ "Date" | trans }}</label>
|
||||||
<button class="previous-date-button button"><</button>
|
<button class="previous-date-button button"><</button>
|
||||||
<input type="date" id="date" name="date" value="{{ date ? date : "now" | date("Y-m-d") }}" placeholder="YYYY-MM-DD" required pattern="[0-9]{4}-[0-9]{2}-[0-9]{2}" title="{{ "Enter a date in this format YYYY-MM-DD" | trans }}"/>
|
<input type="date" id="date" name="date" value="{{ date ? date : "now" | date("Y-m-d") }}" required title="{{ "Enter a date in this format YYYY-MM-DD" | trans }}"/>
|
||||||
<button class="next-date-button button prevent">></button>
|
<button class="next-date-button button prevent">></button>
|
||||||
<a href="/{{ endpoint }}/{{ date ? date : " now" | date("Y-m-d") }}/" class="button main">{{ "Go" | trans }}</a>
|
<a href="/{{ endpoint }}/{{ date ? date : " now" | date("Y-m-d") }}/" class="button main">{{ "Go" | trans }}</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
<audio preload="none" controls>
|
|
||||||
<source src="/media/records/{{ file }}">
|
|
||||||
</audio>
|
|
Loading…
Reference in New Issue