diff --git a/src/components/admin_modal/admin_modal.js b/src/components/admin_modal/admin_modal.js new file mode 100644 index 0000000000..525f09aa68 --- /dev/null +++ b/src/components/admin_modal/admin_modal.js @@ -0,0 +1,68 @@ +import Modal from 'src/components/modal/modal.vue' +import PanelLoading from 'src/components/panel_loading/panel_loading.vue' +import AsyncComponentError from 'src/components/async_component_error/async_component_error.vue' +import getResettableAsyncComponent from 'src/services/resettable_async_component.js' +import Popover from '../popover/popover.vue' +import Checkbox from 'src/components/checkbox/checkbox.vue' +import { library } from '@fortawesome/fontawesome-svg-core' +import { + newImporter, + newExporter +} from 'src/services/export_import/export_import.js' +import { + faTimes, + faFileUpload, + faFileDownload, + faChevronDown +} from '@fortawesome/free-solid-svg-icons' +import { + faWindowMinimize +} from '@fortawesome/free-regular-svg-icons' + +library.add( + faTimes, + faWindowMinimize, + faFileUpload, + faFileDownload, + faChevronDown +) + +const AdminModal = { + data () { + return {} + }, + components: { + Modal, + Popover, + Checkbox, + AdminModalContent: getResettableAsyncComponent( + () => import('./admin_modal_content.vue'), + { + loadingComponent: PanelLoading, + errorComponent: AsyncComponentError, + delay: 0 + } + ) + }, + methods: { + closeModal () { + this.$store.dispatch('closeAdminModal') + }, + peekModal () { + this.$store.dispatch('togglePeekAdminModal') + } + }, + computed: { + modalActivated () { + return this.$store.state.interface.adminModalState !== 'hidden' + }, + modalOpenedOnce () { + return this.$store.state.interface.adminModalLoaded + }, + modalPeeked () { + return this.$store.state.interface.adminModalState === 'minimized' + } + } +} + +export default AdminModal diff --git a/src/components/admin_modal/admin_modal.scss b/src/components/admin_modal/admin_modal.scss new file mode 100644 index 0000000000..0d916f32f8 --- /dev/null +++ b/src/components/admin_modal/admin_modal.scss @@ -0,0 +1,80 @@ +@import "src/variables"; + +.admin-modal { + overflow: hidden; + + .setting-list, + .option-list { + list-style-type: none; + padding-left: 2em; + + li { + margin-bottom: 0.5em; + } + + .suboptions { + margin-top: 0.3em; + } + } + + .admin-modal-panel { + overflow: hidden; + transition: transform; + transition-timing-function: ease-in-out; + transition-duration: 300ms; + width: 1000px; + max-width: 90vw; + height: 90vh; + + @media all and (max-width: 800px) { + max-width: 100vw; + height: 100%; + } + + >.panel-body { + height: 100%; + overflow-y: hidden; + + .btn { + min-height: 2em; + min-width: 10em; + padding: 0 2em; + } + } + } + + .admin-footer { + display: flex; + + >* { + margin-right: 0.5em; + } + + .extra-content { + display: flex; + flex-grow: 1; + } + } + + &.peek { + .admin-modal-panel { + /* Explanation: + * Modal is positioned vertically centered. + * 100vh - 100% = Distance between modal's top+bottom boundaries and screen + * (100vh - 100%) / 2 = Distance between bottom (or top) boundary and screen + * + 100% - we move modal completely off-screen, it's top boundary touches + * bottom of the screen + * - 50px - leaving tiny amount of space so that titlebar + tiny amount of modal is visible + */ + transform: translateY(calc(((100vh - 100%) / 2 + 100%) - 50px)); + + @media all and (max-width: 800px) { + /* For mobile, the modal takes 100% of the available screen. + This ensures the minimized modal is always 50px above the browser bottom + bar regardless of whether or not it is visible. + */ + transform: translateY(calc(100% - 50px)); + } + } + } +} diff --git a/src/components/admin_modal/admin_modal.vue b/src/components/admin_modal/admin_modal.vue new file mode 100644 index 0000000000..d7e5a80f3b --- /dev/null +++ b/src/components/admin_modal/admin_modal.vue @@ -0,0 +1,121 @@ + + + + + diff --git a/src/components/admin_modal/admin_modal_content.js b/src/components/admin_modal/admin_modal_content.js new file mode 100644 index 0000000000..897cc163fa --- /dev/null +++ b/src/components/admin_modal/admin_modal_content.js @@ -0,0 +1,88 @@ +import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx' + +import DataImportExportTab from './tabs/data_import_export_tab.vue' +import MutesAndBlocksTab from './tabs/mutes_and_blocks_tab.vue' +import NotificationsTab from './tabs/notifications_tab.vue' +import FilteringTab from './tabs/filtering_tab.vue' +import SecurityTab from './tabs/security_tab/security_tab.vue' +import ProfileTab from './tabs/profile_tab.vue' +import GeneralTab from './tabs/general_tab.vue' +import VersionTab from './tabs/version_tab.vue' +import ThemeTab from './tabs/theme_tab/theme_tab.vue' + +import { library } from '@fortawesome/fontawesome-svg-core' +import { + faWrench, + faUser, + faFilter, + faPaintBrush, + faBell, + faDownload, + faEyeSlash, + faInfo +} from '@fortawesome/free-solid-svg-icons' + +library.add( + faWrench, + faUser, + faFilter, + faPaintBrush, + faBell, + faDownload, + faEyeSlash, + faInfo +) + +const AdminModalContent = { + components: { + TabSwitcher, + + DataImportExportTab, + MutesAndBlocksTab, + NotificationsTab, + FilteringTab, + SecurityTab, + ProfileTab, + GeneralTab, + VersionTab, + ThemeTab + }, + computed: { + isLoggedIn () { + return !!this.$store.state.users.currentUser + }, + open () { + return this.$store.state.interface.AdminModalState !== 'hidden' + }, + bodyLock () { + return this.$store.state.interface.AdminModalState === 'visible' + } + }, + methods: { + onOpen () { + const targetTab = this.$store.state.interface.AdminModalTargetTab + // We're being told to open in specific tab + if (targetTab) { + const tabIndex = this.$refs.tabSwitcher.$slots.default().findIndex(elm => { + return elm.props && elm.props['data-tab-name'] === targetTab + }) + if (tabIndex >= 0) { + this.$refs.tabSwitcher.setTab(tabIndex) + } + } + // Clear the state of target tab, so that next time Admin is opened + // it doesn't force it. + this.$store.dispatch('clearAdminModalTargetTab') + } + }, + mounted () { + this.onOpen() + }, + watch: { + open: function (value) { + if (value) this.onOpen() + } + } +} + +export default AdminModalContent diff --git a/src/components/admin_modal/admin_modal_content.scss b/src/components/admin_modal/admin_modal_content.scss new file mode 100644 index 0000000000..2db7b2f86f --- /dev/null +++ b/src/components/admin_modal/admin_modal_content.scss @@ -0,0 +1,56 @@ +@import "src/variables"; + +.admin_tab-switcher { + height: 100%; + + .setting-item { + border-bottom: 2px solid var(--fg, $fallback--fg); + margin: 1em 1em 1.4em; + padding-bottom: 1.4em; + + > div, + > label { + display: block; + margin-bottom: 0.5em; + + &:last-child { + margin-bottom: 0; + } + } + + .select-multiple { + display: flex; + + .option-list { + margin: 0; + padding-left: 0.5em; + } + } + + &:last-child { + border-bottom: none; + padding-bottom: 0; + margin-bottom: 1em; + } + + select { + min-width: 10em; + } + + textarea { + width: 100%; + max-width: 100%; + height: 100px; + } + + .unavailable, + .unavailable svg { + color: var(--cRed, $fallback--cRed); + color: $fallback--cRed; + } + + .number-input { + max-width: 6em; + } + } +} diff --git a/src/components/admin_modal/tabs/general_tab.js b/src/components/admin_modal/tabs/general_tab.js new file mode 100644 index 0000000000..8c166f19af --- /dev/null +++ b/src/components/admin_modal/tabs/general_tab.js @@ -0,0 +1,33 @@ +import BooleanSetting from '../settings_modal/helpers/boolean_setting.vue' +import ChoiceSetting from '../settings_modal/helpers/choice_setting.vue' +import IntegerSetting from '../settings_modal/helpers/integer_setting.vue' + +import { library } from '@fortawesome/fontawesome-svg-core' +import { + faGlobe +} from '@fortawesome/free-solid-svg-icons' + +library.add( + faGlobe +) + +const GeneralTab = { + components: { + BooleanSetting, + ChoiceSetting, + IntegerSetting, + }, + computed: { + mergedConfig () { + console.log(this.$store.state) + return this.$store.state + } + }, + methods: { + changeDefaultScope (value) { + this.$store.dispatch('setProfileOption', { name: 'defaultScope', value }) + } + } +} + +export default GeneralTab diff --git a/src/components/desktop_nav/desktop_nav.js b/src/components/desktop_nav/desktop_nav.js index 745b1a8158..f6a2e29471 100644 --- a/src/components/desktop_nav/desktop_nav.js +++ b/src/components/desktop_nav/desktop_nav.js @@ -107,7 +107,10 @@ export default { this.searchBarHidden = hidden }, openSettingsModal () { - this.$store.dispatch('openSettingsModal') + this.$store.dispatch('openSettingsModal', 'user') + }, + openAdminModal () { + this.$store.dispatch('openSettingsModal', 'admin') } } } diff --git a/src/components/desktop_nav/desktop_nav.vue b/src/components/desktop_nav/desktop_nav.vue index dc8bbfd386..49382f8ee9 100644 --- a/src/components/desktop_nav/desktop_nav.vue +++ b/src/components/desktop_nav/desktop_nav.vue @@ -48,20 +48,19 @@ icon="cog" /> - - +
- + +