Added popout drag-n-drop

pull/9255/head
Gabriel Delavald 8 years ago
parent f08dc015ab
commit bfb924a160
  1. 2
      .meteor/versions
  2. 6
      packages/rocketchat-i18n/i18n/en.i18n.json
  3. 16
      packages/rocketchat-livestream/client/views/liveStreamTab.html
  4. 94
      packages/rocketchat-livestream/client/views/liveStreamTab.js
  5. 10
      packages/rocketchat-livestream/client/views/liveStreamView.html
  6. 11
      packages/rocketchat-livestream/client/views/liveStreamView.js
  7. 2
      packages/rocketchat-livestream/package.js
  8. 21
      packages/rocketchat-theme/client/imports/components/popout.css
  9. 11
      packages/rocketchat-ui/client/views/app/popout.html
  10. 83
      packages/rocketchat-ui/client/views/app/popout.js

@ -170,7 +170,7 @@ rocketchat:katex@0.0.1
rocketchat:ldap@0.0.1
rocketchat:lib@0.0.1
rocketchat:livechat@0.0.1
rocketchat:livestream@0.0.3
rocketchat:livestream@0.0.5
rocketchat:logger@0.0.1
rocketchat:login-token@1.0.0
rocketchat:mailer@0.0.1

@ -1127,10 +1127,12 @@
"Livechat_title_color": "Livechat Title Background Color",
"Livechat_Users": "Livechat Users",
"Livestream_title": "LiveStream",
"Livestream_not_found": "Livestream not found",
"Livestream_not_found": "Livestream not Available",
"Livestream_popout": "Open Livestream in Popout",
"Livestream_url": "Livestream url",
"Livestream_url": "Livestream source url",
"Livestream_source_changed_succesfully": "Livestream source changed successfully",
"Livestream_undocked": "Livestream is undocked",
"Livestream_switch_to_room": "Switch to current room's livestream",
"Load_more": "Load more",
"Loading...": "Loading...",
"Loading_more_from_history": "Loading more from history",

@ -9,8 +9,8 @@
{{#if editing}}
<form class="liveStreamTab__form">
<input type="text" name="streamingOptions" class="rc-input__element content-background-color editing" placeholder="{{_ "Livestream_url"}}" value="{{ streamingSource }}"/>
<button type="button" class="rc-button rc-button--nude js-cancel button-block" title="{{_ 'Cancel'}}" >{{_ "Cancel"}}</button>
<button type="button" class="rc-button rc-button--primary js-save button-block">{{_ "Save"}}</button>
<button type="button" class="rc-button rc-button--nude js-cancel button-block" title="{{_ 'Cancel'}}" >{{_ "Cancel"}}</button>
<button type="button" class="rc-button rc-button--primary js-save button-block">{{_ "Save"}}</button>
</form>
{{else}}
<div class="setting-block">
@ -19,14 +19,12 @@
{{/if}}
{{/if}}
{{#if hasSource}}
{{#unless isDocked}}
{{#if canDock}}
<div class="setting-block">
<span class="current-setting streamingSourceSetting"> {{_ "Livestream_undocked" }} </span>
</div>
{{else}}
{{#unless isPopoutOpen}}
<button type="button" class="rc-button rc-button--primary js-popout button-block" title="{{_ 'Livestream_popout'}}" >{{_ "Livestream_popout"}}</button>
{{else}}
{{#unless canDock}}
<button type="button" class="rc-button rc-button--primary js-close button-block" title="{{_ 'Livestream_switch_to_room'}}" >{{_ "Livestream_switch_to_room"}}</button>
{{/if}}
{{/unless}}
{{/unless}}
{{else}}
{{_ "Livestream_not_found" }}

@ -2,25 +2,33 @@
import toastr from 'toastr';
function parseUrl(url) {
const options = {};
const parsedUrl = url.match(/(http:|https:|)\/\/(clips.|player.|www.)?(twitch\.tv|vimeo\.com|youtu(be\.com|\.be|be\.googleapis\.com))\/(video\/|embed\/|watch\?v=|v\/|embed\?clip=)?([A-Za-z0-9._%-]*)(\&\S+)?/);
let source = url;
options.url = url;
if (parsedUrl != null) {
if (parsedUrl[3].includes('youtu')) {
source = `https://www.youtube.com/embed/${ parsedUrl[6] }?showinfo=0`;
options.url = `https://www.youtube.com/embed/${ parsedUrl[6] }?showinfo=0`;
options.thumbnail = `https://img.youtube.com/vi/${ parsedUrl[6] }/0.jpg`;
} else if (parsedUrl[3].includes('vimeo')) {
source = `https://player.vimeo.com/video/${ parsedUrl[6] }`;
options.url = `https://player.vimeo.com/video/${ parsedUrl[6] }`;
} else if (parsedUrl[3].includes('twitch')) {
source = `http://player.twitch.tv/?channel=${ parsedUrl[6] }`;
options.url = `http://player.twitch.tv/?channel=${ parsedUrl[6] }`;
}
// @TODO add support for other urls
return source;
}
return options;
}
Template.liveStreamTab.helpers({
streamingSource() {
return Template.instance().streamingOptions.get() ? Template.instance().streamingOptions.get().url : '';
},
thumbnailUrl() {
return Template.instance().streamingOptions.get() ? Template.instance().streamingOptions.get().thumbnail : '';
},
hasThumbnail() {
return !!Template.instance().streamingOptions.get() && !!Template.instance().streamingOptions.get().thumbnail && Template.instance().streamingOptions.get().thumbnail !== '';
},
hasSource() {
return !!Template.instance().streamingOptions.get() && !!Template.instance().streamingOptions.get().url && Template.instance().streamingOptions.get().url !== '';
},
@ -34,25 +42,28 @@ Template.liveStreamTab.helpers({
const livestreamTabSource = Template.instance().streamingOptions.get().url;
let popoutSource = null;
try {
popoutSource = Blaze.getData(popout.context).data && Blaze.getData(popout.context).data.streamingSource;
if (popout.context) {
popoutSource = Blaze.getData(popout.context).data && Blaze.getData(popout.context).data.streamingSource;
}
} catch (e) {
return false;
} finally {
if (livestreamTabSource === popoutSource) {
if (popoutSource != null && livestreamTabSource === popoutSource) {
return true;
} else {
return false;
}
}
},
isDocked() {
return popout.docked;
isPopoutOpen() {
return Template.instance().popoutOpen.get();
}
});
Template.liveStreamTab.onCreated(function() {
this.editing = new ReactiveVar(false);
this.streamingOptions = new ReactiveVar();
this.popoutOpen = new ReactiveVar(popout.context != null);
this.autorun(() => {
const room = RocketChat.models.Rooms.findOne(this.data.rid, { fields: { streamingOptions : 1 } });
@ -60,14 +71,15 @@ Template.liveStreamTab.onCreated(function() {
});
});
Template.liveStreamTab.onRendered(function() {
if (popout.context == null && (!!this.streamingOptions.get().url && this.streamingOptions.get().url !== '')) {
popout.open({
content: 'liveStreamView',
data: {
'streamingSource': this.streamingOptions.get().url
}
});
}
// console.log('rendered');
// if (popout.context == null && (!!this.streamingOptions.get().url && this.streamingOptions.get().url !== '')) {
// popout.open({
// content: 'liveStreamView',
// data: {
// 'streamingSource': this.streamingOptions.get().url
// }
// });
// }
});
Template.liveStreamTab.onDestroyed(function() {
@ -85,9 +97,7 @@ Template.liveStreamTab.events({
'click .js-save'(e, i) {
e.preventDefault();
const streamingOptions = {
url: parseUrl(i.find('[name=streamingOptions]').value)
};
const streamingOptions = parseUrl(i.find('[name=streamingOptions]').value);
Meteor.call('saveRoomSettings', this.rid, 'streamingOptions', streamingOptions, function(err) {
if (err) {
@ -115,23 +125,39 @@ Template.liveStreamTab.events({
'streamingSource': Template.instance().streamingOptions.get().url
}
});
},
'submit [name=streamingOptions]'(e) {
e.preventDefault();
},
'click .js-popout'(e, i) {
e.preventDefault();
popout.open({
content: 'liveStreamView',
data: {
'streamingSource': Template.instance().streamingOptions.get().url
}
});
i.popoutOpen.set(true);
}
});
RocketChat.callbacks.add('enter-room', function() {
RocketChat.callbacks.add('roomExit', function() {
if (popout.context != null && popout.docked) {
const room = RocketChat.models.Rooms.findOne(Session.get('openedRoom'), { fields: { streamingOptions : 1 } });
if (room.streamingOptions && room.streamingOptions.url !== popout.config.data.streamingSource) {
popout.close();
if (document.querySelector('.flex-tab-bar .tab-button.active') && document.querySelector('.flex-tab-bar .tab-button.active').title === 'Livestream') {
popout.open({
content: 'liveStreamView',
data: {
'streamingSource': room.streamingOptions.url
}
});
}
}
popout.close();
}
}, RocketChat.callbacks.priority.HIGH, 'close-docked-popout');
RocketChat.callbacks.add('enter-room', function() {
// console.log('enter-room');
// const room = RocketChat.models.Rooms.findOne(Session.get('openedRoom'), { fields: { streamingOptions : 1 } });
// if (popout.docked && (room.streamingOptions && room.streamingOptions.url !== popout.config.data.streamingSource)) {
// if (document.querySelector('.flex-tab-bar .tab-button.active') && document.querySelector('.flex-tab-bar .tab-button.active').title === 'Livestream') {
// popout.open({
// content: 'liveStreamView',
// data: {
// 'streamingSource': room.streamingOptions.url
// }
// });
// }
// }
}, RocketChat.callbacks.priority.HIGH, 'reopen-popout');

@ -3,10 +3,10 @@
<!-- <iframe width="356" height="350" src="{{ streamingSource }}"
frameborder="0" gesture="media" allow="encrypted-media" allowfullscreen>
</iframe> -->
<object width="356" height="350">
<param name="movie" value="{{ streamingSource }}"/>
<param name="allowscriptaccess" value="always"/>
<embed width="356" height="350" src="{{ streamingSource }}" class="youtube-player" type="text/html" allowscriptaccess="always" allowfullscreen="true"/>
</object>
<object width="356" height="350" class="streaming-object">
<param name="movie" value="{{ streamingSource }}" />
<param name="allowscriptaccess" value="always" />
<embed width="356" height="350" src="{{ streamingSource }}" class="youtube-player" type="text/html" allowscriptaccess="always" allowfullscreen="true" />
</object>
</div>
</template>

@ -0,0 +1,11 @@
Template.liveStreamView.events({
'click .streaming-object'(e) {
e.stopPropagation();
console.log('clicked video');
},
'click .youtube-player'(e) {
e.stopPropagation();
e.preventDefault();
console.log('youtube player ');
}
});

@ -1,6 +1,6 @@
Package.describe({
name: 'rocketchat:livestream',
version: '0.0.3',
version: '0.0.5',
summary: 'Embed livestream to Rocket.Chat channels',
git: ''
});

@ -12,24 +12,16 @@
position: absolute;
z-index: 10;
top: calc(var(--header-min-height) + 10px);
top: calc(var(--header-min-height) + 135px);
left: calc(var(--sidebar-width) + 10px);
display: flex;
align-items: center;
justify-content: center;
}
&--docked {
top: calc(var(--header-min-height) + 135px);
right: 55px;
left: auto;
& .contextual-bar__close {
display: none;
}
& .rc-popout {
box-shadow: none;
}
-webkit-user-drag: element;
-khtml-user-drag: element;
}
&__title {
@ -67,6 +59,11 @@
animation: dropdown-show 0.1s cubic-bezier(0.45, 0.05, 0.55, 0.95);
align-items: stretch;
& .youtube-player {
height: inherit;
width: inherit;
}
}
&__content-icon {

@ -1,6 +1,6 @@
<template name="popout">
<div class="rc-popout-wrapper rc-popout--{{ style }}">
<div class="rc-popout rc-popout--{{state}}" data-modal="modal">
<div class="rc-popout-wrapper rc-popout--{{ type }}" draggable="true">
<div class="rc-popout rc-popout--{{ state }}" data-modal="modal">
<header class="rc-popout__header">
<h1 class="rc-popout__title">{{title}}</h1>
<button class="contextual-bar__minimize js-minimize">
@ -16,9 +16,6 @@
{{#if content}}
{{> Template.dynamic template=content data=data}}
{{/if}}
{{#if type}}
{{> icon block="rc-popout__content-icon" icon=modalIcon}}
{{/if}}
{{#if text}}
<div class="rc-popout__content-text">
{{#if html}}
@ -29,9 +26,7 @@
</div>
{{/if}}
</main>
{{#if isDocked}}
<button type="button" name="livestream-popout" class="rc-button rc-button--primary js-dock"> {{_ "Livestream_popout" }} </button>
{{/if}}
</div>
</div>
</template>

@ -2,7 +2,9 @@
this.popout = {
context: null,
docked: true,
isAudioOnly: false,
x: 0,
y: 0,
open(config = {}, fn) {
this.close();
this.context = Blaze.renderWithData(Template.popout, config, document.body);
@ -12,8 +14,8 @@ this.popout = {
if (config.timer) {
this.timer = setTimeout(() => this.close(), config.timer);
}
if (config.docked != null) {
this.docked = config.docked;
if (config.isAudioOnly) {
this.isAudioOnly = config.isAudioOnly;
}
},
close() {
@ -25,6 +27,20 @@ this.popout = {
if (this.timer) {
clearTimeout(this.timer);
}
},
dragover(event) {
const e = event.originalEvent || event;
e.dataTransfer.dropEffect = 'move';
e.preventDefault();
},
drop(event) {
const e = event.originalEvent || event;
e.preventDefault();
const popoutElement = document.querySelector('.rc-popout-wrapper');
const positionTop = e.clientY - popout.y;
const positionLeft = e.clientX - popout.x;
popoutElement.style.left = `${ positionLeft >= 0 ? positionLeft : 0 }px`;
popoutElement.style.top = `${ positionTop >= 0 ? positionTop : 0 }px`;
}
};
@ -32,11 +48,8 @@ Template.popout.helpers({
state() {
return Template.instance().isMinimized.get() ? 'closed' : 'open';
},
style() {
return Template.instance().isDocked.get() ? 'docked' : 'undocked';
},
isDocked() {
return Template.instance().isDocked.get();
type() {
return 'video'; //or 'audio'
}
});
@ -46,10 +59,18 @@ Template.popout.onRendered(function() {
}
});
Template.popout.onCreated(function() {
this.isDocked = new ReactiveVar(popout.docked);
this.isMinimized = new ReactiveVar(false);
this.isAudioOnly = new ReactiveVar(popout.isAudioOnly);
document.body.addEventListener('dragover', popout.dragover, true);
document.body.addEventListener('drop', popout.drop, true);
});
Template.popout.onDestroyed(function() {
popout.context = null;
document.body.removeEventListener('dragover', popout.dragover, true);
document.body.removeEventListener('drop', popout.drop, true);
});
Template.popout.events({
@ -58,45 +79,31 @@ Template.popout.events({
e.stopPropagation();
popout.close();
},
'click .js-close'(e, i) {
'click .js-close'(e) {
e.stopPropagation();
popout.docked = true;
const livestreamTab = document.querySelector('.flex-tab--livestream');
let livestreamTabSource;
let popoutSource;
try {
livestreamTabSource = Blaze.getView(livestreamTab).templateInstance().streamingOptions.get().url;
popoutSource = Blaze.getData(popout.context).data && Blaze.getData(popout.context).data.streamingSource;
if (livestreamTab == null || livestreamTabSource !== popoutSource) {
popout.close();
popout.open({
content: 'liveStreamView',
data: {
'streamingSource': livestreamTabSource
}
});
} else {
i.isDocked.set(true);
}
} catch (e) {
console.log(e);
popout.close();
}
popout.close();
},
'click .js-minimize'(e, i) {
e.stopPropagation();
if (i.isMinimized.get()) {
i.isMinimized.set(false);
document.querySelector('.rc-popout object').height = '350px';
document.querySelector('.streaming-object').height = '350px';
} else {
i.isMinimized.set(true);
document.querySelector('.rc-popout object').height = '40px';
document.querySelector('.streaming-object').height = '40px';
}
},
'click .js-dock'(e, i) {
e.stopPropagation();
popout.docked = !i.isDocked.get();
i.isDocked.set(!i.isDocked.get());
'dragstart .rc-popout-wrapper'(event) {
const e = event.originalEvent || event;
const url = this.data.streamingSource || '.rc-popout-wrapper';
popout.x = e.offsetX;
popout.y = e.offsetY;
e.dataTransfer.setData('application/x-moz-node', e.currentTarget);
e.dataTransfer.setData('text/plain', url);
e.dataTransfer.effectAllowed = 'move';
},
'dragend .rc-popout-wrapper'(event) {
event.preventDefault();
}
});

Loading…
Cancel
Save