Browse Source

Add support for Talk sidebar in public share pages

When the public share page is loaded "publicshare.js" is initialized,
which modifies the page to add a Talk sidebar. The default layout has
the header, content and footer in a flex column; when the sidebar is
added the layout id modified to still have the header and content in a
flex column, but the content is now a flex row that includes
"#app-content" and the sidebar, and the footer is moved inside
"#app-content" so it does not affect the sidebar.

The Talk sidebar includes a call container at the top, which is only
shown during calls, and below it a call button and a chat view which are
always shown.

The CSS styles are a mix of the styles for the public share auth page
and the Files app, as well as some rules copied from the main
"style.scss" file.

Signed-off-by: Daniel Calviño Sánchez <danxuliu@gmail.com>
pull/2107/head
Daniel Calviño Sánchez 6 years ago
parent
commit
1ba0f4fd45
  1. 4
      css/merged-public-share.scss
  2. 413
      css/publicshare.scss
  3. 4
      js/filesplugin.js
  4. 31
      js/merged-public-share.json
  5. 254
      js/publicshare.js
  6. 2
      lib/AppInfo/Application.php
  7. 57
      lib/PublicShare/TemplateLoader.php

4
css/merged-public-share.scss

@ -0,0 +1,4 @@
@import 'chatview.scss';
@import 'autocomplete.scss';
@import 'video.scss';
@import 'publicshare.scss';

413
css/publicshare.scss

@ -0,0 +1,413 @@
/* Special layout to include the Talk sidebar */
/* The standard layout defined in the server includes a fixed header with a
* sticky sidebar. This causes the scroll bar for the main area to appear to the
* right of the sidebar, which looks confusing for the chat. Thus that layout is
* overridden with a full set of flex containers to cascade parent element
* height to the main view to limit the vertical scroll bar only to it (same
* thing done for the sidebar and the chat view). */
#body-user,
#body-public {
display: flex;
flex-direction: column;
}
#body-user #header,
#body-public #header {
/* Override fixed position from server to include it in the flex layout */
position: static;
flex-shrink: 0;
}
#content {
display: flex;
flex-direction: row;
overflow: hidden;
flex-grow: 1;
/* Override "min-height: 100%" and "padding-top: 50px" set in server, as the
* header is part of the flex layout and thus the whole body is not
* available for the content. */
min-height: 0;
padding-top: 0;
/* Does not change anything in normal mode, but ensures that the element
* will stretch to the full width in full screen mode. */
width: 100%;
/* Override margin used in server, as the header is part of the flex layout
* and thus the content does not need to be pushed down. */
margin-top: 0;
}
#app-content {
display: flex;
flex-direction: column;
overflow-y: auto;
overflow-x: hidden;
flex-grow: 1;
margin-right: 0;
}
#files-public-content {
flex-grow: 1;
}
/* Properties based on the app-sidebar */
#talk-sidebar {
position: relative;
flex-shrink: 0;
width: 27vw;
min-width: 300px;
background: var(--color-main-background);
border-left: 1px solid var(--color-border);
overflow-x: hidden;
overflow-y: auto;
z-index: 500;
transition: 300ms width ease-in-out,
300ms min-width ease-in-out;
}
#talk-sidebar.disappear {
width: 0;
min-width: 0;
border-left-width: 0;
}
/* Talk sidebar */
#talk-sidebar {
display: flex;
flex-direction: column;
justify-content: center;
}
#talk-sidebar #emptycontent {
position: relative;
margin-top: 10px;
}
#talk-sidebar #call-container-wrapper {
/* Overlap the padding from the sidebar itself to maximize the area of the
* video as much as possible */
margin-left: -15px;
margin-right: -15px;
}
#talk-sidebar #call-container-wrapper #emptycontent {
/* Compensate for the removed margins above. */
margin-left: 30px;
margin-right: 30px;
/* Override "width: 100%" set in server so margins are respected. */
width: auto;
}
#talk-sidebar #call-container-wrapper #emptycontent-icon {
width: 128px;
margin: 0 auto;
padding-bottom: 20px;
}
#talk-sidebar #call-container-wrapper #call-container.incall ~ #emptycontent {
display: none;
}
#talk-sidebar #call-container-wrapper #call-container:not(.incall) {
display: none;
}
#talk-sidebar #call-container-wrapper #call-container {
position: relative;
flex-grow: 1;
/* Prevent shadows of videos from leaking on other elements. */
overflow: hidden;
/* Show the call container in a 16/9 proportion based on the sidebar
* width. This is the same proportion used for previews of images by the
* SidebarPreviewManager. */
padding-bottom: 56.25%;
max-height: 56.25%;
/* Ensure that the background will be black also in voice only calls. */
background-color: #000;
}
/* Video in Talk sidebar */
#talk-sidebar #call-container-wrapper #videos {
flex-grow: 1;
}
#talk-sidebar #call-container-wrapper .videoContainer.promoted video {
/* Base the size of the video on its width instead of on its height;
* otherwise the video could appear in full height but cropped on the sides
* due to the space available in the sidebar being typically larger in
* vertical than in horizontal. */
width: 100%;
height: auto;
}
/* Screensharing in Talk sidebar */
#talk-sidebar #call-container-wrapper #screens {
display: none;
}
#talk-sidebar #videos .videoContainer:not(.promoted) video {
/* Make the unpromoted videos smaller to not overlap too much the promoted
* video */
max-height: 100px;
}
/* The avatars are requested with a size of 128px, so reduce it to not overlap
* too much the promoted video */
#talk-sidebar #videos .videoContainer:not(.promoted) .avatar,
#talk-sidebar #videos .videoContainer:not(.promoted) .avatar img {
width: 64px !important;
height: 64px !important;
}
#talk-sidebar .participants-1 .videoView,
#talk-sidebar .participants-2 .videoView {
/* Do not force the width to 200px, as otherwise the video is too tall and
* overlaps too much with the promoted video. */
min-width: initial;
/* z-index of 10 would put the video on top of the close button. */
z-index: 1;
}
#talk-sidebar .nameIndicator {
/* Reduce padding to bring the name closer to the bottom */
padding: 3px;
/* Use default font size, as it takes too much space otherwise */
font-size: initial;
}
#talk-sidebar .participants-2 .videoContainer.promoted + .videoContainer-dummy .nameIndicator {
/* Reduce padding to bring the name closer to the bottom */
padding: 3px 35%;
}
#talk-sidebar .mediaIndicator {
/* Move the media indicator closer to the bottom */
bottom: 16px;
}
#talk-sidebar .call-button {
text-align: center;
margin-top: 20px;
margin-bottom: 20px;
button {
padding-left: 26px;
padding-right: 26px;
}
.icon-loading-small {
/* Prevent the text from being moved when the icon is shown. */
position: absolute;
margin-left: 5px;
margin-top: 1px;
/* Unset the background image set by the server for loading icons inside
* buttons, as in this case the pure CSS icon can be used instead of the
* image. */
background-image: unset;
&.hidden {
display: none;
}
}
}
/**
* Cascade parent element height to the chat view in the sidebar to limit the
* vertical scroll bar only to the list of messages. Otherwise, the vertical
* scroll bar would be shown for the whole sidebar and everything would be
* moved when scrolling to see overflown messages.
*
* The list of messages should stretch to fill the available space at the bottom
* of the right sidebar, so the height is cascaded using flex boxes.
*/
#talk-sidebar #chatView {
display: flex;
flex-direction: column;
overflow: hidden;
flex-grow: 1;
/* Distribute available height between call container and chat view. */
height: 50%;
}
#talk-sidebar .comments {
overflow-y: auto;
/* Needed for proper calculation of comment positions in the scrolling
container (as otherwise the comment position is calculated with respect
to the closest ancestor with a relative position) */
position: relative;
}
#talk-sidebar #chatView .newCommentRow,
#talk-sidebar #chatView .comments {
padding-left: 15px;
padding-right: 15px;
}
#talk-sidebar #chatView .comments .wrapper-background,
#talk-sidebar #chatView .comments .wrapper {
/* Padding is not respected in the comment wrapper due to its absolute
* positioning, so it must be set through its position. */
left: 15px;
right: 15px;
}
#talk-sidebar #chatView .newCommentForm.with-add-button {
/* Make room to show the "Add" button if needed. */
margin-right: 44px;
}
/**
* Confirm icon inside input field.
*
* The input and the icon should be direct children of a wrapper with a relative
* position. The input is expected to be as wide as its wrapper.
*
* It is assumed that the icon will have the standard width for buttons in
* inputs of 34px. However, further adjustments may be needed for the input and
* the padding depending on the context where they are used.
*
* The confirm icon can have a sibling loading icon to switch to (by hiding one
* icon and showing the other) while the operation is in progress.
*/
input[type="text"],
input[type="password"] {
padding-right: 34px;
/* When the input is focused it is expected that pressing enter will confirm
* the input just like clicking on the icon would do. To hint this behaviour
* the opacity of the confirm icon is slightly increased in this case.
*/
&:focus + .icon-confirm:not(:disabled) {
opacity: .6;
}
& + .icon-confirm {
position: absolute;
top: 0;
/* Compensate for right margin of inputs set in the server. */
right: 3px;
/* Border and background color are removed to show only the icon inside
* the input. */
border: none;
background-color: transparent;
opacity: .3;
&:hover:not(:disabled),
&:focus:not(:disabled),
&:active:not(:disabled) {
opacity: 1;
}
+ .icon-loading-small {
/* Mimic size set in server for confirm button. */
width: 34px;
height: 34px;
padding: 7px 6px;
margin-top: 3px;
margin-bottom: 3px;
position: absolute;
top: 0;
right: 3px;
}
}
}
.authorRow {
.editable-text-label {
.label-wrapper {
display: flex;
align-items: center;
.edit-button .icon {
background-color: transparent;
border: none;
padding: 13px 22px;
margin: 0;
opacity: .3;
&:hover,
&:focus,
&:active {
opacity: 1;
}
}
}
.input-wrapper {
position: relative;
.icon-confirm {
/* Needed to override an important rule set in the
* server. */
background-color: transparent !important;
}
}
.label {
margin-left: 5px;
}
}
.guest-name p {
display: inline-block;
padding: 9px 0;
}
/* The specific locator is needed to have higher priority than other
* important rules set in the server. */
input.checkbox + label.link-checkbox-label,
input.radio + label {
/* If ".icon-loading-small" is set hide the checkbox and show a
* loading icon instead. */
&.icon-loading-small:before {
background-image: none !important;
background-color: transparent !important;
border-color: transparent !important;
}
&.icon-loading-small:after {
top: 22px;
left: 21px;
}
}
}
.hidden-important {
display: none !important;
}

4
js/filesplugin.js

@ -591,7 +591,9 @@
return shareType === OC.Share.SHARE_TYPE_USER ||
shareType === OC.Share.SHARE_TYPE_GROUP ||
shareType === OC.Share.SHARE_TYPE_CIRCLE ||
shareType === OC.Share.SHARE_TYPE_ROOM;
shareType === OC.Share.SHARE_TYPE_ROOM ||
shareType === OC.Share.SHARE_TYPE_LINK ||
shareType === OC.Share.SHARE_TYPE_EMAIL;
});
if (shareTypes.length === 0) {

31
js/merged-public-share.json

@ -0,0 +1,31 @@
[
"vendor/backbone/backbone-min.js",
"vendor/backbone.radio/build/backbone.radio.min.js",
"vendor/backbone.marionette/lib/backbone.marionette.min.js",
"vendor/jshashes/hashes.min.js",
"vendor/Caret.js/dist/jquery.caret.min.js",
"vendor/At.js/dist/js/jquery.atwho.min.js",
"simplewebrtc/bundled.js",
"models/chatmessage.js",
"models/chatmessagecollection.js",
"models/localstoragemodel.js",
"models/room.js",
"models/roomcollection.js",
"views/callbutton.js",
"views/chatview.js",
"views/editabletextlabel.js",
"views/emptycontentview.js",
"views/localvideoview.js",
"views/mediacontrolsview.js",
"views/richobjectstringparser.js",
"views/screenview.js",
"views/speakingwhilemutedwarner.js",
"views/templates.js",
"views/videoview.js",
"views/virtuallist.js",
"webrtc.js",
"signaling.js",
"connection.js",
"embedded.js",
"publicshare.js"
]

254
js/publicshare.js

@ -0,0 +1,254 @@
/**
*
* @copyright Copyright (c) 2018, Daniel Calviño Sánchez (danxuliu@gmail.com)
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
(function(OCA) {
'use strict';
OCA.Talk = OCA.Talk || {};
OCA.Talk.PublicShare = {
init: function() {
this._boundHideCallUi = this._hideCallUi.bind(this);
this.setupLayoutForTalkSidebar();
this.setupSignalingEventHandlers();
this.enableTalkSidebar();
},
setupLayoutForTalkSidebar: function() {
$('#app-content').append($('footer'));
this._$callContainerWrapper = $('<div id="call-container-wrapper" class="hidden"></div>');
$('#content').append('<div id="talk-sidebar" class="disappear"></div>');
$('#talk-sidebar').append(this._$callContainerWrapper);
$('#call-container-wrapper').append('<div id="call-container"></div>');
$('#call-container-wrapper').append('<div id="emptycontent"><div id="emptycontent-icon" class="icon-loading"></div><h2></h2><p class="emptycontent-additional"></p></div>');
$('#call-container').append('<div id="videos"></div>');
$('#call-container').append('<div id="screens"></div>');
OCA.SpreedMe.app._emptyContentView = new OCA.SpreedMe.Views.EmptyContentView({
el: '#call-container-wrapper > #emptycontent'
});
OCA.SpreedMe.app._localVideoView.render();
$('#videos').append(OCA.SpreedMe.app._localVideoView.$el);
},
enableTalkSidebar: function() {
var self = this;
var shareToken = $('#sharingToken').val();
if (this.hideTalkSidebarTimeout) {
clearTimeout(this.hideTalkSidebarTimeout);
delete this.hideTalkSidebarTimeout;
}
$.ajax({
url: OC.linkToOCS('apps/spreed/api/v1', 2) + 'publicshare/' + shareToken,
type: 'GET',
beforeSend: function(request) {
request.setRequestHeader('Accept', 'application/json');
},
success: function(ocsResponse) {
self.setupRoom(ocsResponse.ocs.data.token);
},
error: function() {
// Just keep sidebar hidden
}
});
},
setupSignalingEventHandlers: function() {
var self = this;
OCA.SpreedMe.app.signaling.on('joinRoom', function(joinedRoomToken) {
if (OCA.SpreedMe.app.token !== joinedRoomToken) {
return;
}
function setPageTitle(title) {
if (title) {
title += ' - ';
} else {
title = '';
}
title += t('spreed', 'Talk');
title += ' - ' + oc_defaults.title;
window.document.title = title;
}
OCA.SpreedMe.app.signaling.syncRooms().then(function() {
OCA.SpreedMe.app._chatView.$el.appendTo('#talk-sidebar');
OCA.SpreedMe.app._chatView.setTooltipContainer($('body'));
// "joinRoom" will be called again in a forced reconnection
// during a call with the MCU, so the previous button needs
// to be removed before adding a new one.
if (self._callButton) {
self._callButton.remove();
}
self._callButton = new OCA.SpreedMe.Views.CallButton({
model: OCA.SpreedMe.app.activeRoom,
connection: OCA.SpreedMe.app.connection,
});
// Force initial rendering; changes in the room state will
// automatically render the button again from now on.
self._callButton.render();
self._callButton.$el.insertBefore(OCA.SpreedMe.app._chatView.$el);
self.stopListening(OCA.SpreedMe.app.activeRoom, 'change:participantFlags', self._updateCallContainer);
// Signaling uses its own event system, so Backbone methods can not
// be used.
OCA.SpreedMe.app.signaling.off('leaveCall', self._boundHideCallUi);
if (OCA.SpreedMe.app.activeRoom) {
self.listenTo(OCA.SpreedMe.app.activeRoom, 'change:participantFlags', self._updateCallContainer);
// Signaling uses its own event system, so Backbone methods can
// not be used.
OCA.SpreedMe.app.signaling.on('leaveCall', self._boundHideCallUi);
}
OCA.SpreedMe.app._emptyContentView.setActiveRoom(OCA.SpreedMe.app.activeRoom);
setPageTitle(OCA.SpreedMe.app.activeRoom.get('displayName'));
OCA.SpreedMe.app._chatView.setRoom(OCA.SpreedMe.app.activeRoom);
OCA.SpreedMe.app._messageCollection.setRoomToken(OCA.SpreedMe.app.activeRoom.get('token'));
OCA.SpreedMe.app._messageCollection.receiveMessages();
self.showTalkSidebar().then(function() {
// Once the sidebar is shown its size has changed, so
// the chat view needs to handle a size change.
OCA.SpreedMe.app._chatView.handleSizeChanged();
});
});
});
},
setupRoom: function(token) {
OCA.SpreedMe.app.activeRoom = new OCA.SpreedMe.Models.Room({token: token});
OCA.SpreedMe.app.signaling.setRoom(OCA.SpreedMe.app.activeRoom);
OCA.SpreedMe.app.token = token;
OCA.SpreedMe.app.signaling.joinRoom(token);
},
_updateCallContainer: function() {
var flags = OCA.SpreedMe.app.activeRoom.get('participantFlags') || 0;
var inCall = flags & OCA.SpreedMe.app.FLAG_IN_CALL !== 0;
if (inCall) {
this._showCallUi();
} else {
this._hideCallUi();
}
},
_showCallUi: function() {
if (!this._$callContainerWrapper || !this._$callContainerWrapper.hasClass('hidden')) {
return;
}
this._$callContainerWrapper.removeClass('hidden');
},
_hideCallUi: function() {
if (!this._$callContainerWrapper || this._$callContainerWrapper.hasClass('hidden')) {
return;
}
this._$callContainerWrapper.addClass('hidden');
},
leaveRoom: function() {
this.hideTalkSidebarTimeout = setTimeout(this.hideTalkSidebar, 5000);
},
/**
* Shows the Talk sidebar.
*
* The sidebar is shown with an animation; this method returns a promise
* that is resolved once the sidebar has been fully shown.
*/
showTalkSidebar: function() {
var deferred = $.Deferred();
if (!$('#talk-sidebar').hasClass('disappear')) {
deferred.resolve();
return deferred.promise();
}
if ('ontransitionend' in $('#talk-sidebar').get(0)) {
var resolveOnceSidebarIsOpen = function(event) {
if (event.propertyName !== 'min-width' && event.propertyName !== 'width') {
return;
}
$('#talk-sidebar').get(0).removeEventListener('transitionend', resolveOnceSidebarIsOpen);
deferred.resolve();
};
$('#talk-sidebar').get(0).addEventListener('transitionend', resolveOnceSidebarIsOpen);
} else {
// The browser does not support the "ontransitionend" event, so
// just wait a few milliseconds more than the duration of the
// transition (300ms).
setTimeout(function() {
console.log('ontransitionend is not supported; the sidebar should have been fully shown by now');
deferred.resolve();
}, 500);
}
$('#talk-sidebar').removeClass('disappear');
return deferred.promise();
},
hideTalkSidebar: function() {
$('#talk-sidebar').addClass('disappear');
delete this.hideTalkSidebarTimeout;
},
};
_.extend(OCA.Talk.PublicShare, Backbone.Events);
OCA.SpreedMe.app = new OCA.Talk.Embedded();
OCA.SpreedMe.app.on('start', function() {
OCA.Talk.PublicShare.init();
});
// Unlike in the regular Talk app when Talk is embedded the signaling
// settings are not initially included in the HTML, so they need to be
// explicitly loaded before starting the app.
OCA.Talk.Signaling.loadSettings().then(function() {
OCA.SpreedMe.app.start();
});
})(OCA);

2
lib/AppInfo/Application.php

@ -43,6 +43,7 @@ use OCA\Talk\Notification\Listener as NotificationListener;
use OCA\Talk\Notification\Notifier;
use OCA\Talk\PublicShareAuth\Listener as PublicShareAuthListener;
use OCA\Talk\PublicShareAuth\TemplateLoader as PublicShareAuthTemplateLoader;
use OCA\Talk\PublicShare\TemplateLoader as PublicShareTemplateLoader;
use OCA\Talk\Room;
use OCA\Talk\Settings\Personal;
use OCA\Talk\Share\RoomShareProvider;
@ -110,6 +111,7 @@ class Application extends App {
ParserListener::register($dispatcher);
PublicShareAuthListener::register($dispatcher);
PublicShareAuthTemplateLoader::register($dispatcher);
PublicShareTemplateLoader::register($dispatcher);
FilesListener::register($dispatcher);
FilesTemplateLoader::register($dispatcher);
RestrictStartingCallsListener::register($dispatcher);

57
lib/PublicShare/TemplateLoader.php

@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
/**
*
* @copyright Copyright (c) 2018, Daniel Calviño Sánchez (danxuliu@gmail.com)
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Talk\PublicShare;
use OCP\Util;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
/**
* Helper class to extend the "publicshare" template from the server.
*
* The "loadTalkSidebarUi" method loads additional scripts that, when run on the
* browser, adjust the page generated by the server to inject the Talk UI as
* needed.
*/
class TemplateLoader {
public static function register(EventDispatcherInterface $dispatcher): void {
$listener = function() {
self::loadTalkSidebarUi();
};
$dispatcher->addListener('OCA\Files_Sharing::loadAdditionalScripts', $listener);
}
/**
* Load the "Talk sidebar" UI in the public share page for the given share.
*
* This method should be called when loading additional scripts for the
* public share page of the server.
*/
public static function loadTalkSidebarUi() {
Util::addStyle('spreed', 'merged-public-share');
Util::addScript('spreed', 'merged-public-share');
}
}
Loading…
Cancel
Save