aboutsummaryrefslogtreecommitdiff
path: root/src/components/admin_modal
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/admin_modal')
-rw-r--r--src/components/admin_modal/admin_modal.js68
-rw-r--r--src/components/admin_modal/admin_modal.scss80
-rw-r--r--src/components/admin_modal/admin_modal.vue121
-rw-r--r--src/components/admin_modal/admin_modal_content.js88
-rw-r--r--src/components/admin_modal/admin_modal_content.scss56
-rw-r--r--src/components/admin_modal/tabs/general_tab.js33
6 files changed, 446 insertions, 0 deletions
diff --git a/src/components/admin_modal/admin_modal.js b/src/components/admin_modal/admin_modal.js
new file mode 100644
index 00000000..525f09aa
--- /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 00000000..0d916f32
--- /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 00000000..d7e5a80f
--- /dev/null
+++ b/src/components/admin_modal/admin_modal.vue
@@ -0,0 +1,121 @@
+<template>
+ <Modal
+ :is-open="modalActivated"
+ class="admin-modal"
+ :class="{ peek: modalPeeked }"
+ :no-background="modalPeeked"
+ >
+ <div class="admin-modal-panel panel">
+ <div class="panel-heading">
+ <span class="title">
+ {{ $t('admin.settings') }}
+ </span>
+ <transition name="fade">
+ <div
+ v-if="currentSaveStateNotice"
+ class="alert"
+ :class="{ transparent: !currentSaveStateNotice.error, error: currentSaveStateNotice.error}"
+ @click.prevent
+ >
+ {{ currentSaveStateNotice.error ? $t('admin.saving_err') : $t('settings.saving_ok') }}
+ </div>
+ </transition>
+ <button
+ class="btn button-default"
+ :title="$t('general.peek')"
+ @click="peekModal"
+ >
+ <FAIcon
+ :icon="['far', 'window-minimize']"
+ fixed-width
+ />
+ </button>
+ <button
+ class="btn button-default"
+ :title="$t('general.close')"
+ @click="closeModal"
+ >
+ <FAIcon
+ icon="times"
+ fixed-width
+ />
+ </button>
+ </div>
+ <div class="panel-body">
+ <AdminModalContent v-if="modalOpenedOnce" />
+ </div>
+ <div class="panel-footer admin-footer">
+ <Popover
+ class="export"
+ trigger="click"
+ placement="top"
+ :offset="{ y: 5, x: 5 }"
+ :bound-to="{ x: 'container' }"
+ remove-padding
+ >
+ <template #trigger>
+ <button
+ class="btn button-default"
+ :title="$t('general.close')"
+ >
+ <span>{{ $t("admin.file_export_import.backup_restore") }}</span>
+ {{ ' ' }}
+ <FAIcon
+ icon="chevron-down"
+ />
+ </button>
+ </template>
+ <template #content="{close}">
+ <div class="dropdown-menu">
+ <button
+ class="button-default dropdown-item dropdown-item-icon"
+ @click.prevent="backup"
+ @click="close"
+ >
+ <FAIcon
+ icon="file-download"
+ fixed-width
+ /><span>{{ $t("admin.file_export_import.backup_settings") }}</span>
+ </button>
+ <button
+ class="button-default dropdown-item dropdown-item-icon"
+ @click.prevent="backupWithTheme"
+ @click="close"
+ >
+ <FAIcon
+ icon="file-download"
+ fixed-width
+ /><span>{{ $t("admin.file_export_import.backup_settings_theme") }}</span>
+ </button>
+ <button
+ class="button-default dropdown-item dropdown-item-icon"
+ @click.prevent="restore"
+ @click="close"
+ >
+ <FAIcon
+ icon="file-upload"
+ fixed-width
+ /><span>{{ $t("admin.file_export_import.restore_settings") }}</span>
+ </button>
+ </div>
+ </template>
+ </Popover>
+
+ <Checkbox
+ :model-value="!!expertLevel"
+ @update:modelValue="expertLevel = Number($event)"
+ >
+ {{ $t("admin.expert_mode") }}
+ </Checkbox>
+ <span
+ id="unscrolled-content"
+ class="extra-content"
+ />
+ </div>
+ </div>
+ </Modal>
+</template>
+
+<script src="./admin_modal.js"></script>
+
+<style src="./admin_modal.scss" lang="scss"></style>
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 00000000..897cc163
--- /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 00000000..2db7b2f8
--- /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 00000000..8c166f19
--- /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