diff --git a/CHANGELOG.md b/CHANGELOG.md index 39f2b4c..16fe154 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ * Move the infinity_one_pages dependency into the one_pages plugin * Added new Users Guide * Added new Administration Guide +* Add site avatar to admin for customizing the desktop app icon +* Add site_client_name to admin for customizing the desktop app tooltip ### Bug Fixes diff --git a/assets/css/app.scss b/assets/css/app.scss index 17080d2..86d9287 100644 --- a/assets/css/app.scss +++ b/assets/css/app.scss @@ -485,6 +485,14 @@ aside.side-nav nav.options { } } } +.dropzone.site-avatar-dropzone { + height: 125px; + width: calc(100% - 178px); +} +.dropzone.site-avatar-dropzone span { + top: calc(50% - 8px); +} +.site-avatar-dropzone, .avatar-dropzone { border: 1px dashed #ddd; border-radius: 5px; @@ -494,6 +502,7 @@ aside.side-nav nav.options { display: inline-block; text-align: center; z-index: 9999; + position: relative; &.over { border: 1px dashed #666; diff --git a/assets/js/admin.js b/assets/js/admin.js index d2d65cf..552a96d 100644 --- a/assets/js/admin.js +++ b/assets/js/admin.js @@ -6,52 +6,55 @@ console.log('loading admin'); const reset_i = '' OneChat.on_load(function(one_chat) { - one_chat.admin = new Admin(one_chat) -}) + one_chat.admin = new Admin(one_chat); +}); class Admin { constructor(one_chat) { - this.one_chat = one_chat - this.modifed = false - this.register_events(this) + this.one_chat = one_chat; + this.modifed = false; + this.register_events(this); } enable_save_button() { - let save = $('button.save') + let save = $('button.save'); if (save.attr('disabled') == 'disabled') { - save.removeAttr('disabled') - save.parent().prepend(``) - this.modified = true + save.removeAttr('disabled'); + save.parent().prepend(``); + this.modified = true; } } disable_save_button() { - let save = $('button.save') - this.modified = false - save.attr('disabled', 'disabled') - $('button.discard').remove() + let save = $('button.save'); + this.modified = false; + save.attr('disabled', 'disabled'); + $('button.discard').remove(); } add_reset_setting_button(target, input_line) { - let data_settings = target.getAttribute('name').replace('[', '__').replace(']', '') - let reset = `` - if (input_line.find('button.reset-settings').length === 0) { - input_line.append(reset); + let name = target.getAttribute('name'); + if (name) { + let data_settings = name.replace('[', '__').replace(']', ''); + let reset = ``; + if (input_line.find('button.reset-settings').length === 0) { + input_line.append(reset); - Rebel.set_event_handlers($(target).selector); + Rebel.set_event_handlers($(target).selector); + } } } handle_change(e) { let target = e.currentTarget; let input_line = $(target).closest('.input-line'); - this.enable_save_button() - input_line.addClass('setting-changed') + this.enable_save_button(); + input_line.addClass('setting-changed'); this.add_reset_setting_button(target, input_line); } register_events(admin) { $('body') .on('click', 'button.discard', () => { // admin.disable_save_button() - $('a.admin-link[data-id="admin_info"]').click() + $('a.admin-link[data-id="admin_info"]').click(); }) .on('change', '.admin-settings form input:not(.search)', (e) => { this.handle_change(e); @@ -66,36 +69,36 @@ class Admin { this.handle_change(e); }) .on('change', '.permissions-manager [type="checkbox"]', function(e, t) { - e.preventDefault() - console.log('checkbox change t', $(this)) - let name = $(this).attr('name') - let value = $(this).is(':checked') + e.preventDefault(); + console.log('checkbox change t', $(this)); + let name = $(this).attr('name'); + let value = $(this).is(':checked'); if (!value) { value = "false" } OneChat.userchan.push('admin:permissions:change:' + name, {value: value}) .receive("ok", resp => { // stop_loading_animation() - toastr.success('Room ' + name + ' updated successfully.') + toastr.success('Room ' + name + ' updated successfully.'); }) }) .on('click', '.page-settings .section button.expand', function(e) { - e.preventDefault() + e.preventDefault(); $(this) .addClass('collapse') .removeClass('expand') .first().html('Collapse') .closest('.section-collapsed') - .removeClass('section-collapsed') + .removeClass('section-collapsed'); }) .on('click', '.page-settings .section button.collapse', function(e) { - e.preventDefault() + e.preventDefault(); $(this) .removeClass('collapse') .addClass('expand') .first().html('Expand') .closest('.section-title') .parent() - .addClass('section-collapsed') + .addClass('section-collapsed'); }) .on('click', '.admin-settings button.save', function(e) { //console.log('saving form....', $('form').data('id')) @@ -103,37 +106,37 @@ class Admin { OneChat.userchan.push('admin:save:' + $('form').data('id'), $('form').serializeArray()) .receive("ok", resp => { if (resp.success) { - admin.disable_save_button() - toastr.success(resp.success) + admin.disable_save_button(); + toastr.success(resp.success); } else if (resp.error) { - toastr.error(resp.error) + toastr.error(resp.error); } }) }) .on('click', 'button.refresh', function(e) { - let page = $(this).closest('section').data('page') - $('a.admin-link[data-id="' + page + '"]').click() + let page = $(this).closest('section').data('page'); + $('a.admin-link[data-id="' + page + '"]').click(); }) .on('click', 'section.admin .list-view.channel-settings span[data-edit]', (e) => { - let channel_id = $(e.currentTarget).closest('[data-id]').data('id') - this.userchan_push('edit', {channel_id: channel_id, field: $(e.currentTarget).data('edit')}) + let channel_id = $(e.currentTarget).closest('[data-id]').data('id'); + this.userchan_push('edit', {channel_id: channel_id, field: $(e.currentTarget).data('edit')}); }) .on('click', 'section.admin .channel-settings button.save', e => { - let channel_id = $(e.currentTarget).closest('[data-id]').data('id') - let params = $('.channel-settings form').serializeArray() - this.userchan_push('save', {channel_id: channel_id, params: params}) + let channel_id = $(e.currentTarget).closest('[data-id]').data('id'); + let params = $('.channel-settings form').serializeArray(); + this.userchan_push('save', {channel_id: channel_id, params: params}); }) .on('click', 'section.admin .channel-settings button.cancel', e => { - let channel_id = $(e.currentTarget).closest('[data-id]').data('id') - this.userchan_push('cancel', {channel_id: channel_id}) + let channel_id = $(e.currentTarget).closest('[data-id]').data('id'); + this.userchan_push('cancel', {channel_id: channel_id}); }) .on('click', '#showPassword', e => { let prefix = ""; if ($('#user_password').length > 0) { prefix = "user_"; } - let password_name = `#${prefix}password` - let password_confirmation_name = `#${prefix}password_confirmation` + let password_name = `#${prefix}password`; + let password_confirmation_name = `#${prefix}password_confirmation`; $(e.currentTarget).hide(); $('#hidePassword').show(); $(password_name).attr('type','text'); @@ -144,26 +147,26 @@ class Admin { if ($('#user_password').length > 0) { prefix = "user_"; } - let password_name = `#${prefix}password` - let password_confirmation_name = `#${prefix}password_confirmation` + let password_name = `#${prefix}password`; + let password_confirmation_name = `#${prefix}password_confirmation`; $(e.currentTarget).hide(); $('#showPassword').show(); $(password_name).attr('type','password'); $(password_confirmation_name).attr('type','password'); }) .on('click', '#randomPassword', e => { - let new_password = OneChat.randomString(12) + let new_password = OneChat.randomString(12); let prefix = ""; if ($('#user_password').length > 0) { prefix = "user_"; } - let password_name = `#${prefix}password` - let password_confirmation_name = `#${prefix}password_confirmation` + let password_name = `#${prefix}password`; + let password_confirmation_name = `#${prefix}password_confirmation`; - e.preventDefault() - e.stopPropagation() - $(password_name).attr('type', 'password').val(new_password) - $(password_confirmation_name).attr('type', 'password').val(new_password) + e.preventDefault(); + e.stopPropagation(); + $(password_name).attr('type', 'password').val(new_password); + $(password_confirmation_name).attr('type', 'password').val(new_password); $('#showPassword').show(); $('#hidePassword').hide(); }) @@ -171,36 +174,35 @@ class Admin { OneChat.userchan.push('admin:save:user', $('form.user').serializeArray()) .receive("ok", resp => { if (resp.success) { - toastr.success(resp.success) - this.close_edit_form($('form.user').data('username')) + toastr.success(resp.success); + this.close_edit_form($('form.user').data('username')); } else if (resp.error) { - toastr.error(resp.error) + toastr.error(resp.error); } }) .receive("error", resp => { - console.log('error resp', resp) + console.log('error resp', resp); if (resp.error) { - toastr.error(resp.error) + toastr.error(resp.error); } if (resp.errors) { - this.show_form_errors(resp.errors) + this.show_form_errors(resp.errors); } }) }) .on('click', 'section.admin form.user button.cancel', e => { - this.close_edit_form($('form.user').data('username')) + this.close_edit_form($('form.user').data('username')); }) .on('click', 'a.new-role', e => { - OneChat.userchan.push('admin:permissions:role:new', {}) + OneChat.userchan.push('admin:permissions:role:new', {}); }) .on('click', 'a[href="#admin-permissions-edit"]', e => { - let name = $(e.currentTarget).attr('name') - console.log('permissions edit', name) - OneChat.userchan.push('admin:permissions:role:edit', {name: name}) + let name = $(e.currentTarget).attr('name'); + OneChat.userchan.push('admin:permissions:role:edit', {name: name}); }) .on('click', '.admin-role.delete', e => { - let name = $(e.currentTarget).attr('data-name') - OneChat.userchan.push('admin:permissions:role:delete', {name: name}) + let name = $(e.currentTarget).attr('data-name'); + OneChat.userchan.push('admin:permissions:role:delete', {name: name}); }) .on('click', 'a[href="/admin/permissions"]', e => { e.preventDefault(); diff --git a/assets/js/chat_dropzone.js b/assets/js/chat_dropzone.js index ebdb447..c37d45b 100644 --- a/assets/js/chat_dropzone.js +++ b/assets/js/chat_dropzone.js @@ -6,40 +6,47 @@ $(document).ready(() => { return (('draggable' in div) || ('ondragstart' in div && 'ondrop' in div)) && 'FormData' in window && 'FileReader' in window; }(); if (isAdvancedUpload) { - let droppedFiles = false + let droppedFiles = false; let enterTarget = null; - let obj = $('.dropzone') + let obj = $('.dropzone'); $('body').on('drag dragstart dragend dragover dragenter dragleave drop', '.dropzone', e => { - e.preventDefault() - e.stopPropagation() + e.preventDefault(); + e.stopPropagation(); }) .on('dragover dragenter', '.dropzone', (e) => { if (chat_settings.allow_upload) { - enterTarget = e.target - $('.dropzone').addClass('over') + enterTarget = e.target; + $('.dropzone').addClass('over'); } }) .on('dragleave dragend drop', '.dropzone', (e) => { if (enterTarget == e.target) { - $('.dropzone').removeClass('over') + $('.dropzone').removeClass('over'); } }) .on('drop', '.avatar-dropzone', event => { // handle the avatar image file drop if (chat_settings.allow_upload) { - let e = event.originalEvent || event - let files = e.dataTransfer.files || [] - OneChat.fileUpload.handleAvatarUpload(files, obj) + let e = event.originalEvent || event; + let files = e.dataTransfer.files || []; + OneChat.fileUpload.handleAvatarUpload(files, obj); } }) - .on('drop', '.dropzone:not(.avatar-dropzone)', event => { - // handle the attachment file drop + .on('drop', '.site-avatar-dropzone', event => { if (chat_settings.allow_upload) { - let e = event.originalEvent || event - let files = e.dataTransfer.files || [] - OneChat.fileUpload.handleFileUpload(files, obj) + let e = event.originalEvent || event; + let files = e.dataTransfer.files || []; + OneChat.fileUpload.handleSiteAvatarUpload(files, obj); } }) + .on('drop', '.attachment-dropzone', event => { + // handle the attachment file drop + if (chat_settings.allow_upload) { + let e = event.originalEvent || event; + let files = e.dataTransfer.files || []; + OneChat.fileUpload.handleFileUpload(files, obj); + } + }); } -}) +}); diff --git a/assets/js/fileUpload.coffee.bak b/assets/js/fileUpload.coffee.bak deleted file mode 100644 index 0751564..0000000 --- a/assets/js/fileUpload.coffee.bak +++ /dev/null @@ -1,122 +0,0 @@ -readAsDataURL = (file, callback) -> - reader = new FileReader() - reader.onload = (ev) -> - callback ev.target.result, file - - reader.readAsDataURL file - -readAsArrayBuffer = (file, callback) -> - reader = new FileReader() - reader.onload = (ev) -> - callback ev.target.result, file - - reader.readAsArrayBuffer file - -fileUploadIsValidContentType = (file) -> - return true - -@fileUpload = (files) -> - roomId = "" - files = [].concat files - - consume = -> - file = files.pop() - if not file? - swal.close() - return - - readAsDataURL file.file, (fileContent) -> - if not fileUploadIsValidContentType file.file.type - swal - title: t('FileUpload_MediaType_NotAccepted') - text: file.file.type || "*.#{s.strRightBack(file.file.name, '.')}" - type: 'error' - timer: 3000 - return - - if file.file.size is 0 - swal - title: 'File Empty') - type: 'error' - timer: 1000 - return - - text = '' - - if file.type is 'audio' - text = """ -
- -
-
- - -
- """ - else if file.type is 'video' - text = """ -
- -
-
- - -
- """ - else - text = """ -
-
-
-
- - -
- """ - - swal - title: 'Upload file?' - text: text - showCancelButton: true - closeOnConfirm: false - closeOnCancel: false - html: true - , (isConfirm) -> - consume() - if isConfirm isnt true - return - - record = - name: document.getElementById('file-name').value or file.name or file.file.name - size: file.file.size - type: file.file.type - rid: roomId - description: document.getElementById('file-description').value - - upload = fileUploadHandler record, file.file - - # uploading = Session.get('uploading') or [] - # uploading.push - # id: upload.id - # name: upload.getFileName() - # percentage: 0 - - # Session.set 'uploading', uploading - - # upload.onProgress = (progress) -> - # uploading = Session.get('uploading') - - # item = _.findWhere(uploading, {id: upload.id}) - # if item? - # item.percentage = Math.round(progress * 100) or 0 - # Session.set 'uploading', uploading - - # upload.start() - - consume() diff --git a/assets/js/file_upload.js b/assets/js/file_upload.js index c627ec8..b1cb423 100644 --- a/assets/js/file_upload.js +++ b/assets/js/file_upload.js @@ -1,53 +1,53 @@ -import UploadStatusBar from './upload_status_bar' -import toastr from 'toastr' -require('./file_upload_restrictions.js') +import UploadStatusBar from './upload_status_bar'; +import toastr from 'toastr'; +require('./file_upload_restrictions.js'); -const debug = true +const debug = true; console.log('loading file_upload'); OneChat.on_load(function(one_chat) { - one_chat.fileUpload = new FileUpload(one_chat) + one_chat.fileUpload = new FileUpload(one_chat); }) class FileUpload { constructor(one_chat) { this.whiteList = undefined; - this.one_chat = one_chat - this.register_events() + this.one_chat = one_chat; + this.register_events(); this.useConfirmation = true; } readAsDataURL(file, callback) { - let reader = new FileReader() + let reader = new FileReader(); reader.onload = (ev) => { - callback(ev.target.result, file) + callback(ev.target.result, file); } - reader.readAsDataURL(file) + reader.readAsDataURL(file); } readAsArrayBuffer(file, callback) { - let reader = new FileReader() + let reader = new FileReader(); reader.onload = (ev) => { - callback(ev.target.result, file) + callback(ev.target.result, file); } - reader.readAsArrayBuffer(file) + reader.readAsArrayBuffer(file); } fileUploadIsValidContentType(file) { - return true + return true; } upload_files(files) { files.forEach((file, i) => { - this.upload_file(file) + this.upload_file(file); }) } upload_file(file, fd) { } fileAlertText(file, filename, fileContent) { - let text = '' + let text = ''; if (file.type.startsWith('video')) { text = ` @@ -60,7 +60,7 @@ class FileUpload {
-
` + `; } else if (file.type.startsWith('audio')) { text = `
@@ -72,7 +72,7 @@ class FileUpload {
-
` +
`; } else { text = `
@@ -81,7 +81,7 @@ class FileUpload {
-
` +
`; } return text; @@ -91,20 +91,27 @@ class FileUpload { return `
-
` + `; + } + + siteAvatarAlertText(file, filename, fileContent) { + return ` +
+
+
`; } consume() { - let file = this.files.pop() + let file = this.files.pop(); if (!file) { - swal.close() - return + swal.close(); + return; } if (!this.validate_upload(file)) { - swal.close() - return + swal.close(); + return; } this.readAsDataURL(file, (fileContent) => { @@ -114,8 +121,8 @@ class FileUpload { text: file.type, type: 'error', timer: 3000 - }) - return + }); + return; } if (file.file == 0) { @@ -123,11 +130,11 @@ class FileUpload { title: 'File Empty', type: 'error', timer: 1000 - }) - return + }); + return; } - let filename = file.name + let filename = file.name; if (this.useConfirmation) { sweetAlert({ @@ -141,62 +148,63 @@ class FileUpload { isConfirm => { setTimeout(() => { this.consume() - }, 400) + }, 400); if (!isConfirm) { - return + return; } - let fd = new FormData() - fd.append('type', file.type) - fd.append('user_id', ucxchat.user_id) + let fd = new FormData(); + fd.append('type', file.type); + fd.append('user_id', ucxchat.user_id); if (this.extraFields) { - console.log('getting extra fields', this.extraFields) this.extraFields(fd, file); } let status = new UploadStatusBar(file.name, 10000); - this.sendFileToServer(fd, status) - }) - $('#file-description').focus() + this.sendFileToServer(fd, status); + }); + $('#file-description').focus(); } else { - - let fd = new FormData() - fd.append('type', file.type) - fd.append('user_id', ucxchat.user_id) + let fd = new FormData(); + fd.append('type', file.type); + fd.append('user_id', ucxchat.user_id); if (this.extraFields) { this.extraFields(fd, file); } let status = new UploadStatusBar(file.name, 10000); - this.sendFileToServer(fd, status) + this.sendFileToServer(fd, status); } - }) + }); } fileUploadExtraFields(fd, file) { - fd.append('file_name', document.getElementById('file-name').value) - fd.append('description', document.getElementById('file-description').value) - fd.append('channel_id', ucxchat.channel_id) - fd.append('room', ucxchat.room) - fd.append('file', file) + fd.append('file_name', document.getElementById('file-name').value); + fd.append('description', document.getElementById('file-description').value); + fd.append('channel_id', ucxchat.channel_id); + fd.append('room', ucxchat.room); + fd.append('file', file); } avatarUploadExtraFields(fd, file) { - fd.append('avatar', file) + fd.append('avatar', file); + } + siteAvatarUploadExtraFields(fd, file) { + fd.append('site_avatar', file); } validate_upload(file) { - let size = chat_settings.maximum_file_upload_size_kb * 1024 + let size = chat_settings.maximum_file_upload_size_kb * 1024; if (file.size > size) { // console.log('file sizes', file.size, size) - toastr.error('File size exceeds the ' + chat_settings.maximum_file_upload_size_kb + 'KB maximum!') - return false + toastr.error('File size exceeds the ' + chat_settings.maximum_file_upload_size_kb + 'KB maximum!'); + return false; } if (!OneChat.fileUploadIsValidContentType(file.type, this.whiteList)) { // console.log('file.type', file.type) - toastr.error('Restricted file type') - return false + toastr.error('Restricted file type'); + return false; } - return true + return true; } initFileUpload(files) { OneChat.fileUpload.alertText = OneChat.fileUpload.fileAlertText; - OneChat.fileUpload.uploadURL = "/attachments/create" + OneChat.fileUpload.uploadURL = "/attachments/create"; OneChat.fileUpload.whiteList = undefined; OneChat.fileUpload.extraFields = OneChat.fileUpload.fileUploadExtraFields; OneChat.fileUpload.useConfirmation = true; @@ -210,36 +218,51 @@ class FileUpload { OneChat.fileUpload.useConfirmation = false; } + initSiteAvatarUpload(files) { + OneChat.fileUpload.alertText = OneChat.fileUpload.siteAvatarAlertText; + OneChat.fileUpload.uploadURL = "/site_avatar"; + OneChat.fileUpload.whiteList = ["image/*"]; + OneChat.fileUpload.extraFields = OneChat.fileUpload.siteAvatarUploadExtraFields; + OneChat.fileUpload.useConfirmation = false; + } + register_events() { $('body').on('change', '.message-form input[type=file]', function(event) { - console.log('file change') - let e = event.originalEvent || event - let files = e.target.files + let e = event.originalEvent || event; + let files = e.target.files; if (!files || files.length == 0) { - files = e.dataTransfer.files || [] + files = e.dataTransfer.files || []; } - OneChat.fileUpload.handleFileUpload(files) + OneChat.fileUpload.handleFileUpload(files); }) .on('change', '#account-profile-form input[type=file]', function(event) { - let e = event.originalEvent || event - let files = e.target.files + let e = event.originalEvent || event; + let files = e.target.files; if (!files || files.length == 0) { - files = e.dataTransfer.files || [] + files = e.dataTransfer.files || []; } - OneChat.fileUpload.handleAvatarUpload(files) + OneChat.fileUpload.handleAvatarUpload(files); }) - $('body').on('click', '.attachment .collapse-switch.icon-right-dir', e => { - $(e.currentTarget).removeData('collapsed').removeClass('icon-right-dir').addClass('icon-down-dir') - $(e.currentTarget).closest('.attachment-block').find('.media-container').show() + .on('change', '#admin-general-form input[type=file]', function(event) { + let e = event.originalEvent || event; + let files = e.target.files; + if (!files || files.length == 0) { + files = e.dataTransfer.files || []; + } + OneChat.fileUpload.handleSiteAvatarUpload(files); }) + $('body').on('click', '.attachment .collapse-switch.icon-right-dir', e => { + $(e.currentTarget).removeData('collapsed').removeClass('icon-right-dir').addClass('icon-down-dir'); + $(e.currentTarget).closest('.attachment-block').find('.media-container').show(); + }); $('body').on('click', '.attachment .collapse-switch.icon-down-dir', e => { - $(e.currentTarget).data('collapsed', 'true').addClass('icon-right-dir').removeClass('icon-down-dir') - $(e.currentTarget).closest('.attachment-block').find('.media-container').hide() - }) + $(e.currentTarget).data('collapsed', 'true').addClass('icon-right-dir').removeClass('icon-down-dir'); + $(e.currentTarget).closest('.attachment-block').find('.media-container').hide(); + }); } sendFileToServer(formData,status) { var uploadURL = OneChat.fileUpload.uploadURL; //Upload URL - var extraData ={}; //Extra Data. + var extraData = {}; //Extra Data. var jqXHR=$.ajax({ xhr: function() { var xhrobj = $.ajaxSettings.xhr(); @@ -269,12 +292,12 @@ class FileUpload { } status.updateProgress(100); setTimeout(() => { - status.close() - }, 2000) + status.close(); + }, 2000); }, error: function (xhr, textStatus, error) { - status.close() - toastr.error('There was a problem uploading your file') + status.close(); + toastr.error('There was a problem uploading your file'); } }); status.setCancel(jqXHR); @@ -282,31 +305,45 @@ class FileUpload { handleFileUpload(fileList,obj) { - this.obj = obj - let files = [] + this.obj = obj; + let files = []; for (var i = 0; i < fileList.length; i++) { - files.push(fileList[i]) + files.push(fileList[i]); } - this.files = files + this.files = files; this.initFileUpload(files); - this.consume() + this.consume(); } handleAvatarUpload(fileList,obj) { - this.obj = obj - let files = [] + this.obj = obj; + let files = []; for (var i = 0; i < fileList.length; i++) { - files.push(fileList[i]) + files.push(fileList[i]); } - this.files = files + this.files = files; this.initAvatarUpload(files); - this.consume() + this.consume(); + } + + handleSiteAvatarUpload(fileList,obj) + { + this.obj = obj; + let files = []; + for (var i = 0; i < fileList.length; i++) + { + files.push(fileList[i]); + } + + this.files = files; + this.initSiteAvatarUpload(files); + this.consume(); } } -export default FileUpload +export default FileUpload; diff --git a/lib/infinity_one/settings/general.ex b/lib/infinity_one/settings/general.ex index 07d4776..5e678bd 100644 --- a/lib/infinity_one/settings/general.ex +++ b/lib/infinity_one/settings/general.ex @@ -1,5 +1,52 @@ defmodule InfinityOne.Settings.General do + @moduledoc """ + The General Settings context module. + + Provides access to the General Settings schema. + """ use OneSettings.Settings, schema: InfinityOne.Settings.Schema.General + alias InfinityOne.SiteAvatar + alias OneChatWeb.SharedView + + @doc """ + The the site_avatar path from the schema and return the url. + + Get the url and trim leading `/priv/static`. Return the internal formated + url by default. If the `external: true` option is passed, prefixes the url + with the sites url schema, hostname, and port number. + """ + def site_avatar_url(general \\ nil, opts \\ []) + + def site_avatar_url(nil, opts) do + site_avatar_url(__MODULE__.get(), opts) + end + + def site_avatar_url(opts, _) when is_list(opts) do + site_avatar_url(__MODULE__.get(), opts) + end + + def site_avatar_url(%{site_avatar: site_avatar}, opts) do + site_avatar + |> SiteAvatar.url() + |> SharedView.view_url() + |> add_external(opts[:external]) + end + + def add_external(url, true), do: InfinityOneWeb.root_url() <> url + + def add_external(url, _), do: url + + def get_site_client_name do + get_site_client_name(__MODULE__.get()) + end + + def get_site_client_name(%{site_client_name: "use-host-name"}) do + InfinityOneWeb.root_url() + end + + def get_site_client_name(%{site_client_name: name}) do + name + end end diff --git a/lib/infinity_one/settings/schema/general.ex b/lib/infinity_one/settings/schema/general.ex index ba5a71a..13bda13 100644 --- a/lib/infinity_one/settings/schema/general.ex +++ b/lib/infinity_one/settings/schema/general.ex @@ -1,5 +1,6 @@ defmodule InfinityOne.Settings.Schema.General do use OneSettings.Settings.Schema + use Arc.Ecto.Schema @sitename InfinityOne.brandname() @primary_key {:id, :binary_id, autogenerate: true} @@ -10,17 +11,20 @@ defmodule InfinityOne.Settings.Schema.General do field :site_name, :string, default: @sitename field :enable_desktop_notifications, :boolean, default: true field :desktop_notification_duration, :integer, default: 8 + field :site_avatar, InfinityOne.SiteAvatar.Type + field :site_client_name, :string, default: "use-host-name" end @fields [ :site_url, :site_name, :enable_desktop_notifications, - :desktop_notification_duration + :desktop_notification_duration, :site_client_name ] def changeset(struct, params \\ %{}) do struct |> cast(params, @fields) |> validate_required(@fields) + |> cast_attachments(params, [:site_avatar]) end end diff --git a/lib/infinity_one_web/controllers/api/public_controller.ex b/lib/infinity_one_web/controllers/api/public_controller.ex index 97f659e..6dcb831 100644 --- a/lib/infinity_one_web/controllers/api/public_controller.ex +++ b/lib/infinity_one_web/controllers/api/public_controller.ex @@ -1,13 +1,18 @@ defmodule InfinityOneWeb.API.PublicController do use InfinityOneWeb, :controller + alias InfinityOne.Settings.General + require Logger plug(:put_layout, false) def server_settings(conn, _params) do + general = General.get() + site_avatar_url = General.site_avatar_url(general, external: true) + data = %{ - realm_name: "InfinityOne", + realm_name: General.get_site_client_name(general), require_email_format_usernames: false, push_notifications_enabled: true, authentication_methods: %{ @@ -22,11 +27,15 @@ defmodule InfinityOneWeb.API.PublicController do realm_uri: InfinityOneWeb.root_url(), email_auth_enabled: true, msg: "", - # realm_icon: "https:\/\/secure.gravatar.com\/avatar\/6fa1013f5e7cb449d3d96b36327566da?d=identicon", infinityone_version: InfinityOne.version(), realm_description: "

The coolest place in the universe.<\/p>" } + |> add_realm_icon(site_avatar_url) render(conn, "server_settings.json", data: data) end + + defp add_realm_icon(data, nil), do: data + + defp add_realm_icon(data, url), do: Map.put(data, :realm_icon, url) end diff --git a/lib/infinity_one_web/controllers/site_avatar_controller.ex b/lib/infinity_one_web/controllers/site_avatar_controller.ex new file mode 100644 index 0000000..ccdfbc2 --- /dev/null +++ b/lib/infinity_one_web/controllers/site_avatar_controller.ex @@ -0,0 +1,35 @@ +defmodule InfinityOneWeb.SiteAvatarController do + @moduledoc """ + Handles SiteAvatar Uploads + + """ + use InfinityOneWeb, :controller + + alias InfinityOne.Accounts + alias InfinityOne.Settings.General + alias InfinityOne.SiteAvatar + + require Logger + + @doc """ + Create a newly uploaded SiteAvatar and saves if on the General settings schema + + Requested through Ajax only. Creates the uploaded file and assigns it + to the schema. Returns the file URL to the client for display in the + admin page. + """ + def create(conn, params) do + + general = General.get() + old_site_avatar = general.site_avatar + + case General.update(general, params) do + {:ok, general} -> + SiteAvatar.delete({old_site_avatar, nil}) + render conn, "success.json", url: SiteAvatar.url(general.site_avatar) + |> OneChatWeb.SharedView.view_url() + {:error, _} -> + render conn, "error.json", %{} + end + end +end diff --git a/lib/infinity_one_web/router.ex b/lib/infinity_one_web/router.ex index 69dcccf..6172b56 100644 --- a/lib/infinity_one_web/router.ex +++ b/lib/infinity_one_web/router.ex @@ -62,6 +62,11 @@ defmodule InfinityOneWeb.Router do post("/upload_backup", UploadController, :create) end + scope "/", InfinityOneWeb do + pipe_through(:api) + + post("/site_avatar", SiteAvatarController, :create) + end # The following is a prototype of an API implementation. It is basically # working, without authentication. Need updates in Coherence to get it # working diff --git a/lib/infinity_one_web/uploaders/site_avatar.ex b/lib/infinity_one_web/uploaders/site_avatar.ex new file mode 100644 index 0000000..35ae674 --- /dev/null +++ b/lib/infinity_one_web/uploaders/site_avatar.ex @@ -0,0 +1,47 @@ +defmodule InfinityOne.SiteAvatar do + @moduledoc """ + Arc File Upload descriptor module for SiteAvatar uploads. + """ + use Arc.Definition + use Arc.Ecto.Definition + require Logger + + def __storage, do: Arc.Storage.Local + + @versions [:default] + + @acl :public_read + + @doc """ + White list Avatar file types + + Avatars must be image files, so only image files are supported. + """ + def validate({file, _}) do + extname = file.file_name |> Path.extname |> String.downcase + ~w(.jpg .jpeg .gif .png .ico) |> Enum.member?(extname) + end + + + def transform(:default, {_, %{type: "image" <> _}} = _params) do + {:convert, "-strip -resize @1600 -format png", :png} + end + + def filename(:default, {%{file_name: name}, _}) do + name + end + + def storage_dir(_version, _) do + storage_dir() + end + + def storage_dir() do + path = "priv/static/uploads/site_avatar" + if InfinityOne.env == :prod do + Path.join(Application.app_dir(:infinity_one), path) + else + path + end + end + +end diff --git a/lib/infinity_one_web/views/site_avatar_view.ex b/lib/infinity_one_web/views/site_avatar_view.ex new file mode 100644 index 0000000..f8beceb --- /dev/null +++ b/lib/infinity_one_web/views/site_avatar_view.ex @@ -0,0 +1,18 @@ +defmodule InfinityOneWeb.SiteAvatarView do + @moduledoc """ + View related concerns for the SiteAvatar Controller. + """ + use InfinityOneWeb, :view + + @doc """ + Render json responses for the Upload avatar feature. + """ + def render("success.json", opts) do + %{success: true, url: opts[:url]} + end + + def render("error.json", _opts) do + %{error: true} + end + +end diff --git a/plugins/one_admin/lib/one_admin_web/view/utils.ex b/plugins/one_admin/lib/one_admin_web/view/utils.ex index b8612a1..43655cf 100644 --- a/plugins/one_admin/lib/one_admin_web/view/utils.ex +++ b/plugins/one_admin/lib/one_admin_web/view/utils.ex @@ -41,6 +41,37 @@ defmodule OneAdminWeb.View.Utils do end end + def file_upload_line(_f, item, field, _title, opts \\ []) do + # description = opts[:description] + dropzone = opts[:dropzone] + title = opts[:title] + + content_tag :div, class: "input-line double-col set-avatar" do + [ + content_tag :label, class: "setting-label" do + title + end, + content_tag :div, class: "setting-field" do + [ + content_tag :div, class: "avatar-full", title: title do + tag :img, src: InfinityOne.SiteAvatar.url(Map.get(item, field, "")) |> OneChatWeb.SharedView.view_url() + end, + content_tag :div, class: "file", style: "position: relative;", title: ~g"Upload File" do + content_tag :i, class: "icon-upload" do + tag :input, accept: "", type: "file" + end + end, + content_tag :div, class: "dropzone #{dropzone}-dropzone", style: "margin-top: 10px;", title: ~g"Drop file here to upload" do + content_tag :span, [], do: ~g"Drop file to upload" + end, + # hidden_input(f, field), + # |> do_description(description) + ] + end + ] + end + end + def text_input_line(f, item, field, title, opts \\ []) do type = opts[:type] || :text description = opts[:description] diff --git a/plugins/one_chat/lib/one_chat/services/channel_service.ex b/plugins/one_chat/lib/one_chat/services/channel_service.ex index dbc3e6f..5ff504e 100644 --- a/plugins/one_chat/lib/one_chat/services/channel_service.ex +++ b/plugins/one_chat/lib/one_chat/services/channel_service.ex @@ -150,7 +150,7 @@ defmodule OneChat.ChannelService do Subscription.get_by_user_id user_id, opts end - def insert_channel!(%{user_id: user_id} = params) do + def insert_channel!(%{"user_id" => user_id} = params) do user_id |> Accounts.get_user(default_preload: true) |> insert_channel!(params) diff --git a/plugins/one_chat/lib/one_chat_web/templates/admin/general.html.slime b/plugins/one_chat/lib/one_chat_web/templates/admin/general.html.slime index e99a140..1d27765 100644 --- a/plugins/one_chat/lib/one_chat_web/templates/admin/general.html.slime +++ b/plugins/one_chat/lib/one_chat_web/templates/admin/general.html.slime @@ -13,13 +13,15 @@ section.page-container.page-home.page-static.page-settings.admin-settings = unauthorized_message() - else .content.background-transparent-dark - = form_for @changeset, "#", [id: "admin-general-form", "data-id": "general"], fn f -> + = form_for @changeset, "#", [multipart: true, id: "admin-general-form", "data-id": "general"], fn f -> = hidden_input f, :id, value: item.id .rocket-form .section .section-content.border-component-color = text_input_line f, item, :site_name, ~g(Site Name), changed: @changed = text_input_line f, item, :site_url, ~g(Site Url), changed: @changed + = text_input_line f, item, :site_client_name, ~g(Site Client Name), changed: @changed, description: ~g(The name given to this site in the desktop application.) + = file_upload_line f, item, :site_avatar, ~g(Site Avatar), changed: @changed, description: ~g(Override the default icon for this site in the desktop application.), title: ~g(Upload Site Avatar), dropzone: "site-avatar" .input-line.double-col label.setting-label title="#{~g(Restart)}" = ~g(Restart) .setting-field diff --git a/plugins/one_chat/lib/one_chat_web/templates/home/index.html.slime b/plugins/one_chat/lib/one_chat_web/templates/home/index.html.slime index c1d486c..ebd294e 100644 --- a/plugins/one_chat/lib/one_chat_web/templates/home/index.html.slime +++ b/plugins/one_chat/lib/one_chat_web/templates/home/index.html.slime @@ -34,7 +34,8 @@ javascript: window.chat_settings = { link_preview: false, - use_emojis: true + use_emojis: true, + allow_upload: true }; let myPanel = $('.page-container') diff --git a/plugins/one_chat/lib/one_chat_web/templates/master/room.html.slime b/plugins/one_chat/lib/one_chat_web/templates/master/room.html.slime index 46f67f4..5fe6441 100644 --- a/plugins/one_chat/lib/one_chat_web/templates/master/room.html.slime +++ b/plugins/one_chat/lib/one_chat_web/templates/master/room.html.slime @@ -1,5 +1,5 @@ .room-container - .dropzone + .dropzone.attachment-dropzone .dropzone-overlay.background-transparent-darkest.color-content-background-color .background-transparent-darkest = ~g"Drop to upload file" diff --git a/priv/repo/migrations/20180330014650_add_site_avatar_to_settings_general.exs b/priv/repo/migrations/20180330014650_add_site_avatar_to_settings_general.exs new file mode 100644 index 0000000..0270857 --- /dev/null +++ b/priv/repo/migrations/20180330014650_add_site_avatar_to_settings_general.exs @@ -0,0 +1,10 @@ +defmodule InfinityOnePages.Repo.Migrations.AddSiteAvatarToSettingsGeneral do + use Ecto.Migration + + def change do + alter table(:settings_general) do + add :site_avatar, :string + add :site_client_name, :string, default: "use-host-name" + end + end +end