Commit d631633f authored by gijs's avatar gijs

Better attachment display, supporting audio

parent 61a29a60
......@@ -26,7 +26,7 @@
--row-indent: 60px;
--axis-line-offset: 30px;
--axis-line-width: 2px;
--axis-bottom-padding: .4em;
--axis-bottom-padding: 1em;
--axis-tag-size: 1.8em;
/**
......@@ -44,6 +44,13 @@
* Position of the tag bubble. Relative to the position of the vertical line
*/
--axis-metadata-offset: calc(var(--axis-vertical-line-middle) + var(--axis-tag-size) * .5);
--font-size: 12pt;
--line-height: 18pt;
--font-size--small: 75%;
--dash-size: .3em;
}
......@@ -88,8 +95,8 @@
body {
font-family: 'GothicA1', sans-serif;
font-size: 13pt;
line-height: 21px;
font-size: var(--font-size);
line-height: var(--line-height);
font-weight: 500;
background-color: var(--background-color);
color: var(--text-color);
......@@ -150,7 +157,7 @@ textarea {
button, input, select, option, textarea {
font-family: 'GothicA1', sans-serif;
font-size: inherit;
line-height: 21px;
line-height: inherit;
font-weight: 500;
}
......@@ -655,7 +662,7 @@ section.hidden { display: none; }
/* overflow: auto; */
padding: 0;
flex: 1;
font-size: 11pt;
font-size: var(--font-size);
}
......@@ -810,7 +817,8 @@ ol ol::after {
display: inline-block;
vertical-align: middle;
background-color: var(--row-color);
padding: 8px 5px 2px 5px;
padding: .25em .5em;
text-transform: lowercase;
}
.axis-row--body {
......@@ -870,8 +878,8 @@ ol ol::after {
right: 0;
font-size: 120%;
text-align: center;
padding-right: 50%;
padding-top: .25em;
padding-right: 70%;
padding-top: .35em;
/* Keep visible while it crosses the metadata, z-index 1 */
z-index: 2;
}
......@@ -914,14 +922,37 @@ ol ol > li:first-child {
* Stretch alternative wrapper to full height
* make it flex to allow vertical spacing.
*/
.alternative-wrapper {
align-self: center;
display: flex;
position: absolute;
top: 0;
bottom: 0;
background: repeating-linear-gradient(to bottom, var(--background-color) 0, var(--background-color) .25em, transparent .25em, transparent .5em);
}
/*
Dashed line
.alternative-wrapper {
align-self: center;
display: flex;
position: absolute;
top: 0;
bottom: 0;
background: repeating-linear-gradient(to bottom, var(--background-color) 0, var(--background-color) .25em, transparent .25em, transparent .5em);
left: -2px;
width: 3px;
z-index: 1;
}
.tag {
z-index: 2;
}
*/
/**
* Vertically center the alternative
*/
......@@ -955,11 +986,12 @@ ol ol > li:first-child {
position: sticky;
top: 3em;
right: calc(100% - .75em);
font-size: 80%;
background-color: var(--row-color);
font-size: var(--font-size--small);
line-height: 1.5;
border: 2px solid var(--row-color);
background-color: var(--background-color);
font-weight: bold;
padding: 0.1em .8em 0 .8em;
z-index: 9;
padding: 0em .8em 0 .8em;
color: white;
border-radius: .8em/50%;
z-index: 1;
......@@ -1019,7 +1051,7 @@ li + li .alt-symbol { display: none; }
}
.axis-title {
font-size: 150%;
font-size: 140%;
text-transform: lowercase;
/* vertical-align: bottom; */
position: relative;
......@@ -1033,14 +1065,8 @@ li + li .alt-symbol { display: none; }
border: none !important;
}
.attachment--view {
position: absolute;
z-index: 3;
}
/* axe modulé : le nom de l’axe est souligné */
[data-module="true"]>.axis-row>.axis-row--body>.axis-title {
[data-module="true"] > .axis-row > .axis-row--background-wrapper > .axis-row--body > .axis-title {
/* text-decoration: underline; */
font-style: italic;
/* color: var(--background-color); */
......@@ -1050,7 +1076,7 @@ li + li .alt-symbol { display: none; }
text-decoration: line-through;
} */
[data-contingent="true"]>.axis-row>.axis-row--body {
[data-contingent="true"] > .axis-row > .axis-row--background-wrapper > .axis-row--body {
/* position: absolute;
top: 50%;
left: 0;
......@@ -1091,11 +1117,27 @@ li + li .alt-symbol { display: none; }
.option { margin-left: .5em; }
/**
* Boucle
*/
.boucle {
border: 2px solid var(--row-color);
border-radius: .75em/50%;
padding: .4em .75em;
padding: .4em .75em .4em 1em;
background: var(--background-color);
font-size: var(--font-size--small);
font-weight: bold;
position: relative;
}
.boucle::before {
content: "<";
position: absolute;
top: -.8em;
color: var(--row-color);
font-size: 125%;
font-weight: bold;
left: .25em;
}
/* .options { font-size: .85em; } */
......@@ -1117,8 +1159,6 @@ li + li .alt-symbol { display: none; }
/* .terme .full { font-size: 133%; } */
/* .condition, */
.indications { position: relative; }
/* .condition .full, */
.indications .full {
......@@ -1128,9 +1168,9 @@ li + li .alt-symbol { display: none; }
max-width: 500px;
padding: .75em;
color: white;
border: 2px solid var(--background-color);
background: var(--row-color);
transform: translate(-50%, -.5em);
top: 0;
padding-left: 1em;
}
/* .condition:hover .full, */
......@@ -1188,6 +1228,72 @@ li + li .alt-symbol { display: none; }
right: 0;
}
/**
* Attachment
*/
.attachment--view {
display: inline-block;
}
.attachment--view a {
text-decoration: none;
}
.attachment--view--audio .audio--timecode {
font-size: 80%;
}
.attachment--view--audio audio,
.attachment--view--audio button,
.attachment--view--audio .audio--timecode {
display: none
}
.attachment--view--audio[data-playing="true"] .button--pause,
.attachment--view--audio[data-playing="false"] .button--play,
.attachment--view--audio[data-playing="true"] .audio--timecode {
display: inline-block;
}
.attachment--view--image .attachment--image--full {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
display: none;
flex-direction: column;
z-index: 4;
background-color: var(--background-color);
}
.attachment--image--full--title {
flex: 0 0 auto;
padding: .5em;
}
.attachment--image--full--image {
flex: 1 0 auto;
text-align: center;
}
.attachment--view--image[data-opened="true"] .attachment--image--full {
display: flex;
}
.attachment--image--full--image img {
max-width: 100%;
max-height: 100%;
vertical-align: middle;
}
.attachment--image--full .btn--close {
position: absolute;
top: 0;
right: 0;
}
/* Exemptions for the first axis
========================================================================== */
......@@ -1340,14 +1446,28 @@ li .icon--tag { color: white } */
* Vertical lines are drawn by the .axis li's, last child draws also a vertical gradient to cover
* the tail of the axis
*/
.axis {
/* .axis {
background: linear-gradient(to right,
transparent var(--axis-vertical-line-start),
var(--row-color) var(--axis-vertical-line-start),
var(--row-color) var(--axis-vertical-line-end),
transparent var(--axis-vertical-line-end));
}
} */
/*
background: repeating-linear-gradient(to bottom, var(--background-color) 0, var(--background-color) .25em, transparent .25em, transparent .5em);
*/
/*
[data-alternative]:not([data-alternative=""]) .axis {
background: repeating-linear-gradient(to bottom, var(--background-color) 0, var(--background-color) .25em, transparent .25em, transparent .5em),
linear-gradient(to right,
transparent var(--axis-vertical-line-start),
var(--row-color) var(--axis-vertical-line-start),
var(--row-color) var(--axis-vertical-line-end),
transparent var(--axis-vertical-line-end));
} */
/*
.axis:last-child {
background: linear-gradient(to bottom,
transparent var(--axis-horizontal-line-middle),
......@@ -1358,14 +1478,41 @@ li .icon--tag { color: white } */
var(--row-color) var(--axis-vertical-line-start),
var(--row-color) var(--axis-vertical-line-end),
transparent var(--axis-vertical-line-end));
} */
.axis:last-child {
background: linear-gradient(to bottom,
transparent var(--axis-horizontal-line-middle),
var(--background-color) var(--axis-horizontal-line-middle)
);
}
ol {
background: linear-gradient(to right,
transparent var(--axis-vertical-line-start),
var(--row-color) var(--axis-vertical-line-start),
var(--row-color) var(--axis-vertical-line-end),
transparent var(--axis-vertical-line-end));
}
[data-alternative]:not([data-alternative=""]) > ol {
background: repeating-linear-gradient(to bottom,
var(--background-color) 0,
var(--background-color) var(--dash-size),
transparent var(--dash-size),
transparent calc(2 * var(--dash-size))),
linear-gradient(to right,
transparent var(--axis-vertical-line-start),
var(--row-color) var(--axis-vertical-line-start),
var(--row-color) var(--axis-vertical-line-end),
transparent var(--axis-vertical-line-end));
}
#axis-wrapper > ol > li > header .axis-row--header{
background: none;
}
.axis:nth-child(n + 2) .icon--tag { display: none; }
.axis.placeholder:before {
content: "▶";
color: var(--row-color);
......
......@@ -191,6 +191,28 @@ window.W = window.W || {};
+ ':' + d.getMinutes();
}
/**
* Transform time in seconds to timecode: (hh:?)mm:ss
* @param {number} t time
*/
function toTimecode (t) {
t = Math.round(t)
var hours = Math.floor(t / 3600),
minutes = Math.floor((t % 3600) / 60),
seconds = t % 60,
timecode = '';
if (hours > 0) {
timecode += (hours > 9) ? hours.toString(10) : '0' + hours.toString(10)
timecode += ':';
}
timecode += (minutes > 9) ? minutes.toString(10) : '0' + minutes.toString(10)
timecode += ':' + ((seconds > 9) ? seconds.toString(10) : '0' + seconds.toString(10))
return timecode;
}
// https://stackoverflow.com/a/18197511
function download(filename, text) {
var a = document.createElement('a');
......@@ -437,6 +459,7 @@ window.W = window.W || {};
// Otherwise a more simple object
// How to differentiate between synced model and file? Marker?
W.AttachmentField = Marionette.View.extend({
tagName: 'section',
ui: {
'title': 'input[name="title"]',
'file': 'input[name="file"]'
......@@ -465,33 +488,197 @@ window.W = window.W || {};
},
value: function () {
var title = this.ui.title.val().trim();
if (this.model) {
var title = this.ui.title.val();
return {
'new': false,
'title': title,
'changed': (this.model.get('title') !== title)
};
} else {
if (title == '') {
// If no title was provided tack filename
title = this.ui.file.get(0).files[0].name;
}
return {
new: true,
title: this.ui.title.val(),
title: title,
file: this.ui.file.get(0).files[0]
};
}
}
});
/** Unknown file types, let browser deal with it or download */
W.AttachmentDownload = Marionette.View.extend({
tagName: 'section',
template: '#attachment-download-template',
className: 'attachment--view attachment--view--download',
})
/** Image files, display in lightbox */
W.AttachmentImage = Marionette.View.extend({
tagName: 'section',
template: '#attachment-image-template',
className: 'attachment--view attachment--view--image',
ui: {
'close': '.btn--close'
},
events: {
'click @ui.close': 'close',
'click': 'open'
},
open: function (e) {
if (e) {
e.preventDefault();
}
this.el.dataset.opened = true;
},
close: function (e) {
if (e) {
e.stopImmediatePropagation();
}
this.el.dataset.opened = false;
}
})
/** Audio file, show discrete player in view */
W.AttachmentAudio = Marionette.View.extend({
template: '#attachment-audio-template',
className: 'attachment--view attachment--view--audio',
ui: {
'player': 'audio',
'currentTime': '.audio--timecode--current',
'duration': '.audio--timecode--duration'
},
events: {
'click .button--pause': 'pause',
'click .button--play': 'play',
},
attributes: {
'data-playing': 'false'
},
W.Attachment = Marionette.View.extend({
template: '#attachment-template',
pause: function (e) {
if (e) {
e.preventDefault();
}
this.el.dataset.playing = this.playing = false;
this.ui.player.get(0).pause();
},
play: function (e) {
if (e) {
e.preventDefault();
}
this.el.dataset.playing = this.playing = true;
this.updateCurrenttime();
this.ui.player.get(0).play();
},
ended: function () {
this.el.dataset.playing = false;
},
className: 'attachment--view',
updateCurrenttime: function () {
var last,
player = this.ui.player,
currenttimeLabel = this.ui.currentTime,
updater = function () {
if (this.playing) {
var current = toTimecode(player.get(0).currentTime);
if (current != last) {
currenttimeLabel.text(current);
last = current;
}
window.requestAnimationFrame(updater);
}
}.bind(this);
updater();
},
updateDuration: function () {
var player = this.ui.player,
durationLabel = this.ui.duration,
duration = player.get(0).duration;
if (duration) {
durationLabel.text(toTimecode(duration));
} else {
player.one('durationchange', this.updateDuration.bind(this));
}
},
onRender: function () {
this.ui.player.on('ended', this.ended.bind(this));
this.updateCurrenttime();
this.updateDuration();
}
})
// W.Attachment = Marionette.View.extend({
// template: '#attachment-template',
// className: 'attachment--view',
triggers: {
'click .btn-close': 'attachment:close'
// triggers: {
// 'click .btn-close': 'attachment:close'
// }
// });
function getAttachmentViewByMimeType(attachment) {
var data = attachment.get('attachment');
if (/^data:image\//.test(data)) {
return new W.AttachmentImage({ model: attachment })
} else if (/^data:audio\//.test(data)) {
return new W.AttachmentAudio({ model: attachment })
}
});
return new W.AttachmentDownload({ model: attachment })
}
function getAttachmentViewByExtension(attachment) {
console.log(attachment, attachment.get('attachment'));
var patt = new RegExp(/\.(\w+)$/),
m = patt.exec(attachment.get('attachment')),
ext = (m && m[1]) ? m[1].toLowerCase() : '';
console.log(m, ext)
switch(ext) {
case 'mp3':
case 'flac':
case 'ogg':
case 'oga':
case 'wav':
case 'opus':
case 'aac':
return new W.AttachmentAudio({ model: attachment })
case 'jpg':
case 'jpeg':
case 'png':
case 'gif':
return new W.AttachmentImage({ model: attachment })
default:
return new W.AttachmentDownload({ model: attachment })
}
}
function getAttachmentView (attachment) {
if (/^data:/.test(attachment.get('attachment'))) {
/* Read from file system. We have the mime type in the datastring */
return getAttachmentViewByMimeType(attachment);
}
return getAttachmentViewByExtension(attachment);
}
/**
* Node editing form
......@@ -755,8 +942,7 @@ window.W = window.W || {};
rangeType: rangeType,
isLink: (this.link) ? true : false,
hasSublines: hasSublines,
hasAttachment: hasAttachment,
attachmentTitle: (hasAttachment) ? this.model.get('attachment').get('title') : ''
hasAttachment: hasAttachment
}
},
......@@ -789,12 +975,9 @@ window.W = window.W || {};
'click @ui.title': 'toggleClick',
'click @ui.titleSpan': 'titleClick',
'click [name="toggle"]': 'toggle',
'click [name="edit"]': 'toggleForm',
'click [name="add"]': 'addLine',
'click [name="delete"]': 'delete',
// 'click [name="link"]': 'makeLink',
'click [name="unlink"]': 'unlink',
'click .attachment--btn': 'showAttachment',
'change #tag': 'editSiblings',
'relocate': 'relocate',
'relocateChild': 'relocateChild'
......@@ -805,8 +988,7 @@ window.W = window.W || {};
'form:close': 'toggleForm',
'form:makeLink': 'makeLink',
'inlinetitleform:submit': 'updateTitle',
'inlinetitleform:close': 'closeInlineForm',
'attachment:close': 'hideAttachment'
'inlinetitleform:close': 'closeInlineForm'
},
childViewTriggers: {
......@@ -990,7 +1172,7 @@ window.W = window.W || {};
There is a new one -> drop old: queuedRemove, add new one: queuedUpload
The title changed -> update the attachment model, queued save
It did not change -> do nothing
There was no attachement on the old model
There was no attachment on the old model
There is a new one -> upload new one: queuedUpload
There is still none -> do nothing
*/
......@@ -1139,8 +1321,10 @@ window.W = window.W || {};
e.stopImmediatePropagation();
e.preventDefault();
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) {
if (e.shiftKey) {
this.showInlineForm();
} else if (e.ctrlKey || e.metaKey || e.altKey) {
this.toggleForm();
} else {
this.toggle();
}
......@@ -1218,6 +1402,22 @@ window.W = window.W || {};
}
},
renderAttachmentView: function () {
var attachment = this.model.get('attachment');
if (attachment) {
this.stopListening(attachment, 'change', this.renderAttachmentView);
if (!attachment.get('attachment')) {
// No data in attachment, possible when a user selected a file
// in the score editing form and it is still being read.
// Retry on a change in the model.
this.listenToOnce(attachment, 'change', this.renderAttachmentView)
}
else {
this.showChildView('attachment', getAttachmentView(attachment))
}
}
},
onRender: function () {
// attribut des attributs html pour styler en css
this.$el.attr("data-collapsed", state.collapsed[this.model.cid]);
......@@ -1233,18 +1433,8 @@ window.W = window.W || {};
this.$el.attr("data-sublines", "true");
this.showChildView('tree', sublinesView);
}
},
showAttachment: function (e) {
if (e) {
e.preventDefault();
e.stopImmediatePropagation();
}
this.showChildView('attachment', (new W.Attachment({model: this.model.get('attachment')} )));
},
hideAttachment: function() {
this.detachChildView('attachment');
this.renderAttachmentView();
}
});
......@@ -1546,7 +1736,7 @@ window.W = window.W || {};
initialize: function () {
this.hasChanges = false;
this.metaToggled = false;
this.metaExpanded = true;
this.mainlineView = null;
this.sliderView = null;
// this.listenTo(this.model, 'change', this.render);
......
......@@ -22,8 +22,16 @@
{% include "playground/underscore/applied-filters.mtpl" %}
</script>
<script id="attachment-template" type="text/template">
{% include "playground/underscore/attachment.mtpl" %}
<script id="attachment-audio-template" type="text/template">
{% include "playground/underscore/node-attachment-audio.mtpl" %}
</script>
<script id="attachment-download-template" type="text/template">
{% include "playground/underscore/node-attachment-download.mtpl" %}
</script>
<script id="attachment-image-template" type="text/template">
{% include "playground/underscore/node-attachment-image.mtpl" %}
</script>
<script id="base-template" type="text/template">
......
<label><span class="label-text"><%- t('Titre') %></span><input type="text" name="title" ></label>
<label><span class="label-text"><%- t('Fiche') %></span><input type="file" name="file" ></label>
\ No newline at end of file