diff --git a/README.md b/README.md index 744c4b7b..645fc254 100644 --- a/README.md +++ b/README.md @@ -23,9 +23,14 @@ When multichannel is enabled, here is what it looks like: ![overall-screenshot](./doc/screenshot.png) -### Synchronized Youtube / Twitch / Embedded player +### Synchronized media player + +Users can play Youtube, Twitch and self-hosted videos in a shared synchronized players. + +The player has two modes: +- A 'free' mode, selected by default, where any user can add a media. Orchestration is implemented through a public queue of videos to play, and decision-making (for instance to skip videos) is done with polls. +- A 'schedule' mode, when users with privilege schedule a media to play at a specific time. These medias can not be skipped, and users can view the schedule to see what medias will play in the upcoming days. When the scheduled media finishes, the player goes back in 'free' mode. -Users can play medias from Youtube, Twitch or their local computer in a shared synchronized player. Orchestration is implemented through a public queue of videos to play, and decision-making (for instance to skip videos) is done with polls. Democracy 💯. If you want it, setup multiple video channels to allow concurrent screenings. ![youtube-short](./doc/youtube-short.gif) ### Live cursor visualization and mini-games @@ -119,15 +124,18 @@ The `config/preferences.json` file specifies application preferences. The availa | field | type | default | description | |-------|------|---------|-------------| -| minRightForPrivateMessages | number | -1 | Min. right to send private messages | -| minRightForMessageHistory | number | -1 | Min. right to access room message history | -| minRightForAudioRecording | number | -1 | Min. right to share and play audio recordings | -| minRightForConnectedList | number | -1 | Min. right to access the list of currently active users | -| minRightForPolls | number | -1 | Min. right to create polls | -| minRightForGallery | number | -1 | Min. right to access the gallery | -| maxReplacedImagesPerMessage | number | 50 | Max. number of replaced images per message | -| maxReplacedStickersPerMessage | number | 50 | Max. number of replaced stickers per message | -| maxNewlinesPerMessage | number | 20 | Max. number of newlines per message | +| minRightForPrivateMessages | number | -1 | Min. right to send private messages | +| minRightForMessageHistory | number | -1 | Min. right to access room message history | +| minRightForAudioRecording | number | -1 | Min. right to share and play audio recordings | +| minRightForConnectedList | number | -1 | Min. right to access the list of currently active users | +| minRightForPolls | number | -1 | Min. right to create polls | +| minRightForGalleryRead | number \| 'op' | 'op' | Min. right to access the gallery | +| minRightForGalleryWrite | number \| 'op' | 'op' | Min. right to add and remove gallery documents | +| minRightForPlayerAddMedia | number \| 'op' | 'op' | Min. right to add medias to the player | +| minRightForPlayerManageSchedule | number \| 'op' | 'op' | Min. right to manage the player schedules | +| maxReplacedImagesPerMessage | number | 50 | Max. number of replaced images per message | +| maxReplacedStickersPerMessage | number | 50 | Max. number of replaced stickers per message | +| maxNewlinesPerMessage | number | 20 | Max. number of newlines per message | ### Customize plugins diff --git a/app/client/css/styles.scss b/app/client/css/styles.scss index 436ecfeb..20839992 100644 --- a/app/client/css/styles.scss +++ b/app/client/css/styles.scss @@ -78,7 +78,9 @@ body { } } -.scrollbar { +.scrollbar, +.tui-full-calendar-timegrid-container, +.tui-full-calendar-dayname-container { /** Firefox */ scrollbar-color: #555 #343434; diff --git a/app/client/src/SkyChatClient.js b/app/client/src/SkyChatClient.js index 59b036d5..478f84a0 100644 --- a/app/client/src/SkyChatClient.js +++ b/app/client/src/SkyChatClient.js @@ -346,7 +346,7 @@ export class SkyChatClient extends EventEmitter { */ onMessage(message) { this.store.commit('Main/NEW_MESSAGE', message); - if (this.store.state.Main.user.right >= 0 && this.store.state.focused) { + if (this.store.state.Main.user.right >= 0 && this.store.state.Main.focused) { this.notifySeenMessage(message.id); } } @@ -357,7 +357,7 @@ export class SkyChatClient extends EventEmitter { */ onMessages(messages) { this.store.commit('Main/NEW_MESSAGES', messages); - if (this.store.state.Main.user.right >= 0 && this.store.state.focused && messages.length > 0) { + if (this.store.state.Main.user.right >= 0 && this.store.state.Main.focused && messages.length > 0) { this.notifySeenMessage(messages[messages.length - 1].id); } } @@ -388,10 +388,10 @@ export class SkyChatClient extends EventEmitter { /** * - * @param data + * @param messageSeen */ - onMessageSeen(data) { - this.store.commit('Main/MESSAGE_SEEN', data); + onMessageSeen(messageSeen) { + this.store.dispatch('Main/messageSeen', messageSeen); } /** @@ -412,10 +412,10 @@ export class SkyChatClient extends EventEmitter { /** * - * @param users + * @param connectedList */ - onConnectedList(users) { - this.store.commit('Main/SET_CONNECTED_LIST', users); + onConnectedList(connectedList) { + this.store.dispatch('Main/setConnectedList', connectedList); } /** diff --git a/app/client/src/index.js b/app/client/src/index.js index 0c34b5be..7edee4d7 100644 --- a/app/client/src/index.js +++ b/app/client/src/index.js @@ -61,15 +61,15 @@ window.addEventListener('blur', () => { }); window.addEventListener('focus', () => { - if (store.state.lastMissedMessage) { - client.notifySeenMessage(store.state.lastMissedMessage.id); + if (store.state.Main.lastMissedMessage) { + client.notifySeenMessage(store.state.Main.lastMissedMessage.id); } store.commit('Main/FOCUS'); }); setInterval(() => { // In case the title is not currently blinking, just update it - if (! store.state.documentTitleBlinking) { + if (! store.state.Main.documentTitleBlinking) { if (document.title !== store.state.Main.documentTitle) { document.title = store.state.Main.documentTitle; } @@ -79,6 +79,6 @@ setInterval(() => { const chars = "┤┘┴└├┌┬┐"; const indexOf = chars.indexOf(document.title[0]); const newPosition = (indexOf + 1) % chars.length; - document.title = chars[newPosition] + ' ' + store.state.documentTitle; + document.title = chars[newPosition] + ' ' + store.state.Main.documentTitle; }, 1000); diff --git a/app/client/src/vue/components/channel/PlayerChannelList.vue b/app/client/src/vue/components/channel/PlayerChannelList.vue index 67bccba4..907c4a45 100644 --- a/app/client/src/vue/components/channel/PlayerChannelList.vue +++ b/app/client/src/vue/components/channel/PlayerChannelList.vue @@ -9,7 +9,8 @@
-
close
- -
-
{{channel.currentOwner}}
- movie + +
+ +
+ +
+ more_horiz
-
- -
- -
- -
- {{playerChannelUsers[channel.id].length > 1 ? 'group' : 'person'}} - {{ playerChannelUsers[channel.id].length }} +
+ +
+ +
{{ channel.currentMedia.title }}
+ movie
@@ -69,7 +72,7 @@ props: { }, methods: { joinChannel: function(id) { - if (this.playerChannel === id) { + if (this.playerChannelId === id) { this.$client.leavePlayerChannel(); } else { this.$client.joinPlayerChannel(id); @@ -86,7 +89,7 @@ ...mapState('Main', [ 'playerChannelUsers', 'playerChannels', - 'playerChannel', + 'playerChannelId', 'user', 'op', ]), @@ -120,7 +123,7 @@ .channel { min-height: 35px; - margin-top: 4px; + margin-top: 2px; .channel-content { display: flex; @@ -128,6 +131,8 @@ .channel-content-info { display: flex; + height: 35px; + flex-basis: 35px; .channel-icon { flex-basis: 20px; @@ -138,67 +143,69 @@ .channel-name { flex-grow: 1; - margin-top: 10px; margin-left: 2px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; - height: 20px; + display: flex; + flex-direction: column; + justify-content: center; } .channel-meta { - flex-basis: 100px; + flex-basis: 105px; margin-top: 10px; + padding-right: 10px; display: flex; flex-direction: row-reverse; + flex-wrap: nowrap; + overflow: hidden; - .channel-player { - color: #ff8f8f; - - .channel-player-owner { - vertical-align: top; - font-size: 11px; - max-width: 70px; - overflow: hidden; - display: inline-block; - text-overflow: ellipsis; - } - } - - .channel-player.disabled { - color: #8c8c8c; + >.avatar { + min-width: 15px; + width: 15px; + height: 15px; + margin-right: 4px; } - + .channel-delete { color: #ff8e8e; + margin-right: 3px; } } } - .channel-content-users { + .channel-content-detail { display: flex; overflow: hidden; justify-content: flex-end; margin-bottom: 5px; - margin-left: 12px; + padding-right: 14px; + padding-left: 10px; + + .channel-player { + color: #ff8f8f; + flex-grow: 1; + width: 0; + display: flex; - .channel-users { - color: #8ecfff; - margin-top: 3px; - margin-left: 6px; - white-space: nowrap; - text-align: right; - span { vertical-align: top; } + .channel-player-owner { + vertical-align: top; + font-size: 11px; + flex-grow: 1; + padding-right: 10px; + overflow: hidden; + display: inline-block; + text-overflow: ellipsis; + white-space: nowrap; + } } - >.avatar { - min-width: 20px; - width: 20px; - height: 20px; - margin: 1px; + .channel-player.disabled { + color: #8c8c8c; } } } } } - + \ No newline at end of file diff --git a/app/client/src/vue/components/channel/TextChannelList.vue b/app/client/src/vue/components/channel/TextChannelList.vue index 09cb331d..9bd81445 100644 --- a/app/client/src/vue/components/channel/TextChannelList.vue +++ b/app/client/src/vue/components/channel/TextChannelList.vue @@ -8,6 +8,7 @@
collections +
+ event +
@@ -53,6 +56,7 @@ import Vue from "vue"; import { mapState } from 'vuex'; import GalleryModal from "../modal/GalleryModal.vue"; + import PlayerSchedule from "../modal/PlayerSchedule.vue"; const MESSAGE_HISTORY_LENGTH = 100; @@ -114,6 +118,10 @@ this.$modal.show(GalleryModal); }, + openPlayerSchedule: function() { + this.$modal.show(PlayerSchedule); + }, + uploadAudio: async function() { if (this.recordingAudio) { // Stop recording @@ -322,7 +330,12 @@ .open-gallery { margin-bottom: 12px; - margin-right: 20px; + margin-right: 10px; + } + + .open-player-schedule { + margin-bottom: 12px; + margin-right: 10px; } .goto-left-col, diff --git a/app/client/src/vue/components/gallery/GalleryPreview.vue b/app/client/src/vue/components/gallery/GalleryPreview.vue index b454c855..4a9afce4 100644 --- a/app/client/src/vue/components/gallery/GalleryPreview.vue +++ b/app/client/src/vue/components/gallery/GalleryPreview.vue @@ -17,46 +17,45 @@
+ :key="folder.id" + class="gallery-folder">

{{folder.name}} - close + close

-
-
- -
-
-
- {{ tag }} -
+
+ +
+
+
+ {{ tag }}
-
- .{{ media.location.match(/\.([a-z0-9]+)$/)[1] }} +
+
+ .{{ media.location.match(/\.([a-z0-9]+)$/)[1] }} +
+
+ +
+ content_copy +
+ +
+ play_arrow
-
- -
- content_copy -
- -
- play_arrow -
- -
- close -
+ +
+ close
@@ -90,7 +89,7 @@ watch: { 'tab': function() { if (this.tab === 'preview') { - this.shownFolders = this.gallery.folders; + this.shownFolders = this.gallery.data.folders; } else { this.shownFolders = this.gallerySearchResults.folders; } @@ -109,7 +108,7 @@ if (this.tab !== 'preview') { return; } - this.shownFolders = this.gallery.folders; + this.shownFolders = this.gallery.data.folders; } }, 'gallerySearchResults': { @@ -124,7 +123,7 @@ }, mounted: function() { if (this.gallery) { - this.shownFolders = this.gallery.folders; + this.shownFolders = this.gallery.data.folders; } }, methods: { @@ -157,7 +156,10 @@ }, deleteFolder: function(folderId) { this.$client.sendMessage(`/galleryfolderremove ${folderId}`); - } + }, + canWrite: function() { + return this.gallery && this.gallery.canWrite; + }, }, computed: { ...mapState('Main', [ @@ -173,15 +175,17 @@ .gallery-preview { width: 100%; + height: 100%; display: flex; flex-direction: column; - padding-left: 6px; color: white; - margin-bottom: 6px; - margin-top: 10px; - padding-right: 20px; + padding-bottom: 10px; + padding-top: 10px; + padding-left: 6px; + padding-right: 6px; .gallery-content { + height: 100%; background-color: #242427; overflow: hidden; display: flex; @@ -210,40 +214,48 @@ .gallery-results { flex-grow: 1; overflow-y: auto; + display: flex; + flex-direction: column; - .section-title { - color: grey; - margin-left: 8px; - margin-top: 8px; - text-align: center; - - .folder-delete { - cursor: pointer; - color: #ff8e8e; - } - } - - .media-list { + .gallery-folder { display: flex; - margin-top: 4px; - height: 110px; + flex-direction: column; + + .section-title { + color: grey; + margin-left: 8px; + margin-top: 8px; + text-align: center; + flex-basis: 22px; - .media-list-arrow { - flex-basis: 20px; + .folder-delete { + cursor: pointer; + color: #ff8e8e; + } } - .media-list-thumbs { + .media-list { + display: flex; + margin-top: 4px; + max-height: 320px; + margin-bottom: 20px; margin-left: 20px; margin-right: 20px; - display: flex; - overflow: hidden; + padding-bottom: 10px; + max-width: 100%; + flex-wrap: nowrap; + overflow-y: auto; + + .media-list-arrow { + flex-basis: 20px; + } .media { margin-left: 6px; margin-right: 6px; - flex-basis: 110px; - min-width: 110px; - height: 110px; + flex-basis: 120px; + min-width: 120px; + height: 160px; background-position: 50%; background-size: cover; position: relative; diff --git a/app/client/src/vue/components/layout/TchatMiddleColumn.vue b/app/client/src/vue/components/layout/TchatMiddleColumn.vue index e5562548..ca41eabc 100644 --- a/app/client/src/vue/components/layout/TchatMiddleColumn.vue +++ b/app/client/src/vue/components/layout/TchatMiddleColumn.vue @@ -58,7 +58,6 @@ diff --git a/app/client/src/vue/components/message/MessageList.vue b/app/client/src/vue/components/message/MessageList.vue index 8a95d492..07948b3c 100644 --- a/app/client/src/vue/components/message/MessageList.vue +++ b/app/client/src/vue/components/message/MessageList.vue @@ -3,14 +3,13 @@ ref="scroller" @scroll="onScroll" :style="smoothScroll ? 'scroll-behavior: smooth' : ''"> - + + :seen-users="lastMessageSeenIds[item.id] || []">
@@ -21,7 +20,7 @@ import VideoPlayer from "../video-player/VideoPlayer.vue"; export default Vue.extend({ - components: {VideoPlayer, SingleMessage}, + components: { VideoPlayer, SingleMessage }, data: function() { return { autoScroll: true, diff --git a/app/client/src/vue/components/message/SingleMessage.vue b/app/client/src/vue/components/message/SingleMessage.vue index 98018f7a..afd26b16 100644 --- a/app/client/src/vue/components/message/SingleMessage.vue +++ b/app/client/src/vue/components/message/SingleMessage.vue @@ -1,49 +1,56 @@ + + diff --git a/app/client/src/vue/components/user/UserListRow.vue b/app/client/src/vue/components/user/UserListRow.vue index d5dd1d39..0ae84c20 100644 --- a/app/client/src/vue/components/user/UserListRow.vue +++ b/app/client/src/vue/components/user/UserListRow.vue @@ -1,5 +1,6 @@