mirror of
https://gitnet.fr/deblan/side_menu.git
synced 2025-12-17 21:02:25 +01:00
refactor of menus
begin work on settings
This commit is contained in:
parent
ecbe2f7d72
commit
52b2d18a03
26 changed files with 1613 additions and 1667 deletions
|
|
@ -123,14 +123,14 @@ class Application extends App implements IBootstrap
|
||||||
//Util::addStyle(self::APP_ID, 'sideMenu');
|
//Util::addStyle(self::APP_ID, 'sideMenu');
|
||||||
|
|
||||||
$assets = [
|
$assets = [
|
||||||
// 'stylesheet' => [
|
'stylesheet' => [
|
||||||
// 'route' => 'side_menu.Css.stylesheet',
|
'route' => 'side_menu.Css.stylesheet',
|
||||||
// 'type' => 'link',
|
'type' => 'link',
|
||||||
// 'route_attr' => 'href',
|
'route_attr' => 'href',
|
||||||
// 'attr' => [
|
'attr' => [
|
||||||
// 'rel' => 'stylesheet',
|
'rel' => 'stylesheet',
|
||||||
// ],
|
],
|
||||||
// ],
|
],
|
||||||
// 'script' => [
|
// 'script' => [
|
||||||
// 'route' => 'side_menu.Js.script',
|
// 'route' => 'side_menu.Js.script',
|
||||||
// 'type' => 'script',
|
// 'type' => 'script',
|
||||||
|
|
|
||||||
|
|
@ -145,6 +145,8 @@ class JsController extends Controller
|
||||||
'opener-hover' => $this->config->getAppValueBool('opener-hover', '0'),
|
'opener-hover' => $this->config->getAppValueBool('opener-hover', '0'),
|
||||||
'external-sites-in-top-menu' => $this->config->getAppValueBool('external-sites-in-top-menu', '0'),
|
'external-sites-in-top-menu' => $this->config->getAppValueBool('external-sites-in-top-menu', '0'),
|
||||||
'force-light-icon' => $this->config->getAppValueBool('force-light-icon', '0'),
|
'force-light-icon' => $this->config->getAppValueBool('force-light-icon', '0'),
|
||||||
|
'display-logo' => $this->config->getAppValueBool('display-logo', '1'),
|
||||||
|
'use-avatar' => $this->config->getAppValueBool('use-avatar', '0'),
|
||||||
'hide-when-no-apps' => $this->config->getAppValueBool('hide-when-no-apps', '0'),
|
'hide-when-no-apps' => $this->config->getAppValueBool('hide-when-no-apps', '0'),
|
||||||
'loader-enabled' => $this->config->getAppValueBool('loader-enabled', '1'),
|
'loader-enabled' => $this->config->getAppValueBool('loader-enabled', '1'),
|
||||||
'always-displayed' => $this->config->getAppValueBool('always-displayed', '0'),
|
'always-displayed' => $this->config->getAppValueBool('always-displayed', '0'),
|
||||||
|
|
|
||||||
|
|
@ -115,6 +115,7 @@ class NavController extends Controller
|
||||||
$appsCategories[$app['id']][] = $category;
|
$appsCategories[$app['id']][] = $category;
|
||||||
|
|
||||||
$items[$category]['apps'][$app['id']] = [
|
$items[$category]['apps'][$app['id']] = [
|
||||||
|
'id' => $app['id'],
|
||||||
'name' => $app['name'],
|
'name' => $app['name'],
|
||||||
'href' => $app['href'],
|
'href' => $app['href'],
|
||||||
'icon' => $app['icon'],
|
'icon' => $app['icon'],
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,12 @@
|
||||||
"format": "./node_modules/.bin/prettier src --write"
|
"format": "./node_modules/.bin/prettier src --write"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@babel/core": ">=7.12.0 <8.0.0",
|
||||||
"@nextcloud/router": "^3.0.1",
|
"@nextcloud/router": "^3.0.1",
|
||||||
"@nextcloud/vue": "^9.0.0-alpha.8",
|
"@nextcloud/vue": "^9.0.0-alpha.8",
|
||||||
"node-polyfill-webpack-plugin": "^4.1.0",
|
"node-polyfill-webpack-plugin": "^4.1.0",
|
||||||
"pinia": "^3.0.1",
|
"pinia": "^3.0.1",
|
||||||
|
"postcss": "^7.0.0 || ^8.0.1",
|
||||||
"vue": "^3.5.13"
|
"vue": "^3.5.13"
|
||||||
},
|
},
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
|
|
|
||||||
269
src/admin.js
269
src/admin.js
|
|
@ -8,268 +8,25 @@
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful,
|
* This program is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU Affero General Public License for more details.
|
* GNU Affero General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import './scss/admin.scss'
|
||||||
|
|
||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import AdminCategoriesCustom from './components/AdminCategoriesCustom.vue'
|
import { createPinia } from 'pinia'
|
||||||
|
import { waitContainer } from './lib/dom.js'
|
||||||
|
|
||||||
let elements = []
|
import AdminSettings from './pages/AdminSettings.vue'
|
||||||
|
|
||||||
const selector = '#side-menu-message'
|
waitContainer('#side-menu-admin-settings').then((selector) => {
|
||||||
|
const pinia = createPinia()
|
||||||
const userConfig = (name, value, callbacks) => {
|
const app = createApp(AdminSettings)
|
||||||
const url = OC.generateUrl('/apps/side_menu/personalSetting/valueSet')
|
app.use(pinia)
|
||||||
const formData = []
|
app.mixin({ methods: { t, n }})
|
||||||
|
app.mount(selector)
|
||||||
formData.push('name=' + encodeURIComponent(name))
|
|
||||||
formData.push('value=' + encodeURIComponent(value))
|
|
||||||
|
|
||||||
fetch(url, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
|
||||||
},
|
|
||||||
body: formData.join('&'),
|
|
||||||
})
|
|
||||||
.then(callbacks.success)
|
|
||||||
.catch(callbacks.error)
|
|
||||||
}
|
|
||||||
|
|
||||||
const appConfig = (name, value, callbacks) => {
|
|
||||||
OCP.AppConfig.setValue('side_menu', name, value, callbacks)
|
|
||||||
}
|
|
||||||
|
|
||||||
const saveSettings = (key) => {
|
|
||||||
const element = elements[key]
|
|
||||||
|
|
||||||
if (!element) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let value
|
|
||||||
let name
|
|
||||||
|
|
||||||
if (element.hasAttribute('data-checkbox')) {
|
|
||||||
name = element.getAttribute('data-name')
|
|
||||||
value = []
|
|
||||||
|
|
||||||
const inputs = document.querySelectorAll('input[name="' + name + '[]"]:checked')
|
|
||||||
|
|
||||||
for (let input of inputs) {
|
|
||||||
value.push(input.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
value = JSON.stringify(value)
|
|
||||||
} else {
|
|
||||||
name = element.getAttribute('name')
|
|
||||||
value = element.value
|
|
||||||
}
|
|
||||||
|
|
||||||
const size = elements.length
|
|
||||||
|
|
||||||
if (name === 'cache') {
|
|
||||||
++value
|
|
||||||
}
|
|
||||||
|
|
||||||
const progress = document.querySelector('#side-menu-save-progress')
|
|
||||||
|
|
||||||
progress.style.width = '40px'
|
|
||||||
progress.style.marginLeft = '5px'
|
|
||||||
|
|
||||||
const callbacks = {
|
|
||||||
success: () => {
|
|
||||||
const percent = parseInt(((key + 1) * 100) / size)
|
|
||||||
|
|
||||||
progress.setAttribute('value', percent)
|
|
||||||
|
|
||||||
if (key < size - 1) {
|
|
||||||
saveSettings(key + 1)
|
|
||||||
} else {
|
|
||||||
location.reload()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
error: () => {
|
|
||||||
OC.msg.finishedError(selector, t('side_menu', 'Error while saving "' + element + '"'))
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if (element.hasAttribute('data-personal')) {
|
|
||||||
userConfig(name, value, callbacks)
|
|
||||||
} else {
|
|
||||||
appConfig(name, value, callbacks)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const elementToggler = (element) => {
|
|
||||||
let display = 'none'
|
|
||||||
|
|
||||||
if (window.getComputedStyle(element).display === 'none') {
|
|
||||||
display = 'block'
|
|
||||||
}
|
|
||||||
|
|
||||||
element.style.display = display
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateAppsCategoriesCustom = () => {
|
|
||||||
let values = {}
|
|
||||||
|
|
||||||
for (let item of document.querySelectorAll('.apps-categories-custom')) {
|
|
||||||
let app = item.getAttribute('data-app')
|
|
||||||
let value = item.value
|
|
||||||
|
|
||||||
if (value) {
|
|
||||||
values[app] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
document.querySelector('#apps-categories-custom').value = JSON.stringify(values)
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
$('*[data-toggle="tooltip"]').tooltip()
|
|
||||||
|
|
||||||
if (document.querySelector('#side-menu-categories-custom')) {
|
|
||||||
const app = createApp(AdminCategoriesCustom)
|
|
||||||
app.mount('#side-menu-categories-custom')
|
|
||||||
}
|
|
||||||
|
|
||||||
elements = document.querySelectorAll('.side-menu-setting')
|
|
||||||
|
|
||||||
document.querySelector('#side-menu-save').addEventListener('click', (event) => {
|
|
||||||
event.preventDefault()
|
|
||||||
OC.msg.startSaving(selector)
|
|
||||||
|
|
||||||
saveSettings(0)
|
|
||||||
})
|
|
||||||
|
|
||||||
const resets = document.querySelectorAll('.btn-reset')
|
|
||||||
|
|
||||||
for (let btn of resets) {
|
|
||||||
btn.addEventListener('click', (event) => {
|
|
||||||
const target = event.target
|
|
||||||
const values = JSON.parse(target.getAttribute('data-reset'))
|
|
||||||
|
|
||||||
target.classList.toggle('btn-reset--progress', true)
|
|
||||||
|
|
||||||
for (let i in values) {
|
|
||||||
document.querySelector(`#${i}`).value = values[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
window.setTimeout(() => {
|
|
||||||
target.classList.toggle('btn-reset--progress', false)
|
|
||||||
}, 800)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const displays = document.querySelectorAll('.side-menu-display')
|
|
||||||
|
|
||||||
for (let display of displays) {
|
|
||||||
display.addEventListener('click', (event) => {
|
|
||||||
const target = event.target
|
|
||||||
|
|
||||||
for (let d of displays) {
|
|
||||||
d.classList.toggle('is-active', d === display)
|
|
||||||
}
|
|
||||||
|
|
||||||
document.querySelector('#side-menu-always-displayed').value = target.getAttribute('data-alwaysdiplayed')
|
|
||||||
document.querySelector('#side-menu-big-menu').value = target.getAttribute('data-bigmenu')
|
|
||||||
document.querySelector('#side-menu-side-with-categories').value = target.getAttribute('data-sidewithcategories')
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let item of document.querySelectorAll('.apps-categories-custom')) {
|
|
||||||
item.addEventListener('change', (event) => {
|
|
||||||
updateAppsCategoriesCustom()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let item of document.querySelectorAll('.side-menu-setting-live')) {
|
|
||||||
item.addEventListener('change', (event) => {
|
|
||||||
const target = event.target
|
|
||||||
const name = target.getAttribute('name')
|
|
||||||
|
|
||||||
let value = target.value
|
|
||||||
let id = null
|
|
||||||
|
|
||||||
if (name === 'background-color-opacity') {
|
|
||||||
id = '#side-menu-background-color, #side-menu-background-color-to'
|
|
||||||
} else if (name === 'dark-mode-background-color-opacity') {
|
|
||||||
id = '#side-menu-dark-mode-background-color, #side-menu-dark-mode-background-color-to'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (id) {
|
|
||||||
document.querySelector(id).dispatchEvent(new CustomEvent('change'))
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name === 'opener') {
|
|
||||||
const url = OC.generateUrl(`/apps/side_menu/img/${value}.svg`).replace('/index.php', '')
|
|
||||||
|
|
||||||
value = `url(${url})`
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name === 'icon-invert-filter' || name === 'icon-opacity') {
|
|
||||||
value /= 100
|
|
||||||
}
|
|
||||||
|
|
||||||
if (['dark-mode-background-color', 'dark-mode-background-color-to'].indexOf(name) > -1) {
|
|
||||||
const opacity = parseInt((document.querySelector('#side-menu-dark-mode-background-color-opacity').value * 255) / 100)
|
|
||||||
|
|
||||||
value = [value, opacity.toString(16)].join('')
|
|
||||||
} else if (['background-color', 'background-color-to'].indexOf(name) > -1) {
|
|
||||||
const opacity = parseInt((document.querySelector('#side-menu-background-color-opacity').value * 255) / 100)
|
|
||||||
|
|
||||||
value = [value, opacity.toString(16)].join('')
|
|
||||||
}
|
|
||||||
|
|
||||||
document.documentElement.style.setProperty('--side-menu-' + name, value)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let toggler of document.querySelectorAll('.side-menu-toggler')) {
|
|
||||||
toggler.addEventListener('click', (event) => {
|
|
||||||
const target = event.target
|
|
||||||
const element = document.querySelector(target.getAttribute('data-target'))
|
|
||||||
|
|
||||||
elementToggler(element)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
sortable('#categories-list .side-menu-setting-list', {
|
|
||||||
placeholderClass: 'side-menu-setting-list-drop',
|
|
||||||
})
|
|
||||||
|
|
||||||
try {
|
|
||||||
sortable('#categories-list .side-menu-setting-list')[0].addEventListener('sortstop', (e) => {
|
|
||||||
let value = []
|
|
||||||
|
|
||||||
for (let item of document.querySelectorAll('#categories-list .side-menu-setting-list-item')) {
|
|
||||||
value.push(item.getAttribute('data-id'))
|
|
||||||
}
|
|
||||||
|
|
||||||
document.querySelector('input[name="categories-order"]').value = JSON.stringify(value)
|
|
||||||
})
|
|
||||||
} catch (e) {}
|
|
||||||
|
|
||||||
sortable('#apps-order-list .side-menu-setting-list', {
|
|
||||||
placeholderClass: 'side-menu-setting-list-drop',
|
|
||||||
})
|
|
||||||
|
|
||||||
try {
|
|
||||||
sortable('#apps-order-list .side-menu-setting-list')[0].addEventListener('sortstop', (e) => {
|
|
||||||
let value = []
|
|
||||||
|
|
||||||
for (let item of document.querySelectorAll('#apps-order-list .side-menu-setting-list-item')) {
|
|
||||||
value.push(item.getAttribute('data-id'))
|
|
||||||
}
|
|
||||||
|
|
||||||
document.querySelector('input[name="apps-order"]').value = JSON.stringify(value)
|
|
||||||
})
|
|
||||||
} catch (e) {}
|
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -102,101 +102,92 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import NcModal from '@nextcloud/vue/dist/Components/NcModal.js'
|
import { NcModal, NcActions, NcActionButton } from '@nextcloud/vue'
|
||||||
import NcActions from '@nextcloud/vue/dist/Components/NcActions.js'
|
import { ref } from 'vue'
|
||||||
import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js'
|
|
||||||
|
|
||||||
export default {
|
const { langs, values } = defineProps(['lang', 'values'])
|
||||||
name: 'AdminCategoriesCustom',
|
|
||||||
components: {
|
|
||||||
NcModal,
|
|
||||||
NcActions,
|
|
||||||
NcActionButton,
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
input: null,
|
|
||||||
values: [],
|
|
||||||
langs: [],
|
|
||||||
addForm: false,
|
|
||||||
editForm: false,
|
|
||||||
newValue: {},
|
|
||||||
editValue: {},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.input = document.querySelector('input[name="categories-custom"]')
|
|
||||||
this.init()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
init() {
|
|
||||||
this.values = JSON.parse(this.input.value)
|
|
||||||
this.langs = JSON.parse(this.input.getAttribute('data-langs'))
|
|
||||||
},
|
|
||||||
update() {
|
|
||||||
this.input.value = JSON.stringify(this.values)
|
|
||||||
},
|
|
||||||
showAddForm() {
|
|
||||||
this.newValue = { id: 'cat' + Math.random().toString().replace('0.', '') }
|
|
||||||
|
|
||||||
this.addForm = true
|
const input = ref(null)
|
||||||
},
|
const values = ref([])
|
||||||
showEditForm(value) {
|
const langs = ref([])
|
||||||
this.editValue = { id: value.id }
|
const addForm = ref(false)
|
||||||
|
const editForm = ref(false)
|
||||||
|
const newValue = ref({})
|
||||||
|
const editValue = ref({})
|
||||||
|
|
||||||
for (let i of this.langs) {
|
// mounted() {
|
||||||
this.editValue[i] = typeof value[i] !== 'undefined' ? value[i] : ''
|
// this.input = document.querySelector('input[name="categories-custom"]')
|
||||||
}
|
// this.init()
|
||||||
|
// },
|
||||||
this.editForm = true
|
// methods: {
|
||||||
},
|
// init() {
|
||||||
saveAdd() {
|
// this.values = JSON.parse(this.input.value)
|
||||||
for (let i of this.langs) {
|
// this.langs = JSON.parse(this.input.getAttribute('data-langs'))
|
||||||
if (!this.newValue[i] || /^\s*$/.test(this.newValue[i])) {
|
// },
|
||||||
return
|
// update() {
|
||||||
}
|
// this.input.value = JSON.stringify(this.values)
|
||||||
}
|
// },
|
||||||
|
// showAddForm() {
|
||||||
this.values.push(this.newValue)
|
// this.newValue = { id: 'cat' + Math.random().toString().replace('0.', '') }
|
||||||
this.update()
|
//
|
||||||
this.hideAddForm()
|
// this.addForm = true
|
||||||
this.newValue = {}
|
// },
|
||||||
},
|
// showEditForm(value) {
|
||||||
saveEdit() {
|
// this.editValue = { id: value.id }
|
||||||
for (let i of this.langs) {
|
//
|
||||||
if (!this.editValue[i] || /^\s*$/.test(this.editValue[i])) {
|
// for (let i of this.langs) {
|
||||||
return
|
// this.editValue[i] = typeof value[i] !== 'undefined' ? value[i] : ''
|
||||||
}
|
// }
|
||||||
}
|
//
|
||||||
|
// this.editForm = true
|
||||||
for (let i in this.values) {
|
// },
|
||||||
if (this.values[i].id === this.editValue.id) {
|
// saveAdd() {
|
||||||
this.values[i] = this.editValue
|
// for (let i of this.langs) {
|
||||||
}
|
// if (!this.newValue[i] || /^\s*$/.test(this.newValue[i])) {
|
||||||
}
|
// return
|
||||||
|
// }
|
||||||
this.update()
|
// }
|
||||||
this.hideEditForm()
|
//
|
||||||
},
|
// this.values.push(this.newValue)
|
||||||
removeEdit() {
|
// this.update()
|
||||||
for (let i in this.values) {
|
// this.hideAddForm()
|
||||||
if (this.values[i].id === this.editValue.id) {
|
// this.newValue = {}
|
||||||
this.values.splice(i, 1)
|
// },
|
||||||
}
|
// saveEdit() {
|
||||||
}
|
// for (let i of this.langs) {
|
||||||
|
// if (!this.editValue[i] || /^\s*$/.test(this.editValue[i])) {
|
||||||
this.update()
|
// return
|
||||||
this.hideEditForm()
|
// }
|
||||||
},
|
// }
|
||||||
hideAddForm() {
|
//
|
||||||
this.addForm = false
|
// for (let i in this.values) {
|
||||||
},
|
// if (this.values[i].id === this.editValue.id) {
|
||||||
hideEditForm() {
|
// this.values[i] = this.editValue
|
||||||
this.editForm = false
|
// }
|
||||||
},
|
// }
|
||||||
},
|
//
|
||||||
}
|
// this.update()
|
||||||
|
// this.hideEditForm()
|
||||||
|
// },
|
||||||
|
// removeEdit() {
|
||||||
|
// for (let i in this.values) {
|
||||||
|
// if (this.values[i].id === this.editValue.id) {
|
||||||
|
// this.values.splice(i, 1)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// this.update()
|
||||||
|
// this.hideEditForm()
|
||||||
|
// },
|
||||||
|
// hideAddForm() {
|
||||||
|
// this.addForm = false
|
||||||
|
// },
|
||||||
|
// hideEditForm() {
|
||||||
|
// this.editForm = false
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// }
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
||||||
33
src/components/settings/MenuDisplay.vue
Normal file
33
src/components/settings/MenuDisplay.vue
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
<template>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
// import AlwaysDisplayImg from '../../img/admin/layout-always-displayed.svg'
|
||||||
|
// import WideMenuImg from '../../img/admin/layout-big-menu.svg'
|
||||||
|
// import DefaultImg from '../../img/admin/layout-default.svg'
|
||||||
|
// import WithCategoriesImg from '../../img/admin/layout-width-categories.svg'
|
||||||
|
|
||||||
|
const choices = [
|
||||||
|
// {id: 1, url: AlwaysDisplayImg},
|
||||||
|
// {id: 2, url: WideMenuImg},
|
||||||
|
// {id: 3, url: DefaultImg},
|
||||||
|
// {id: 4, url: WithCategoriesImg},
|
||||||
|
]
|
||||||
|
|
||||||
|
const carouselConfig = {
|
||||||
|
itemsToShow: 1.3,
|
||||||
|
wrapAround: true
|
||||||
|
}
|
||||||
|
|
||||||
|
const { mode, alwaysDisplayed } = defineProps({
|
||||||
|
mode: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
alwaysDisplayed: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
5
src/components/settings/TableContainer.vue
Normal file
5
src/components/settings/TableContainer.vue
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<template>
|
||||||
|
<div class="side-menu-setting-table">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
33
src/components/settings/TableLabel.vue
Normal file
33
src/components/settings/TableLabel.vue
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
<template>
|
||||||
|
<div class="side-menu-setting-label" :class="{
|
||||||
|
'side-menu-setting-label-short': short,
|
||||||
|
'side-menu-setting-label--top': top,
|
||||||
|
'side-menu-setting-label--middle': middle,
|
||||||
|
}">
|
||||||
|
{{ t('side_menu', label) }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
const { short, label } = defineProps({
|
||||||
|
short: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
middle: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
top: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
5
src/components/settings/TableRow.vue
Normal file
5
src/components/settings/TableRow.vue
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<template>
|
||||||
|
<div class="side-menu-setting-row">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
15
src/components/settings/TableValue.vue
Normal file
15
src/components/settings/TableValue.vue
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
<template>
|
||||||
|
<div class="side-menu-setting-form" :class="{'side-menu-setting-form-long': long}">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
const { long } = defineProps({
|
||||||
|
long: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
@ -5,7 +5,7 @@ const getActiveAppId = () => {
|
||||||
|
|
||||||
for (let id in apps) {
|
for (let id in apps) {
|
||||||
if (apps[id].active) {
|
if (apps[id].active) {
|
||||||
return id
|
return apps[id].id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
12
src/lib/menu.js
Normal file
12
src/lib/menu.js
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
const focusActiveApp = (menu) => {
|
||||||
|
window.setTimeout(() => {
|
||||||
|
const a = menu.querySelector('.side-menu-app.active a')
|
||||||
|
|| menu.querySelector('.side-menu-app a')
|
||||||
|
|
||||||
|
if (a) {
|
||||||
|
a.focus()
|
||||||
|
}
|
||||||
|
}, 500)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { focusActiveApp }
|
||||||
|
|
@ -40,6 +40,7 @@ const display = ref(null)
|
||||||
const hasPageLoader = ref(false)
|
const hasPageLoader = ref(false)
|
||||||
const isOpen = ref(false)
|
const isOpen = ref(false)
|
||||||
const hasApps = ref(false)
|
const hasApps = ref(false)
|
||||||
|
const openerHover = ref(false)
|
||||||
|
|
||||||
const toggleMenu = (value) => {
|
const toggleMenu = (value) => {
|
||||||
isOpen.value = value
|
isOpen.value = value
|
||||||
|
|
@ -78,6 +79,10 @@ const createOpener = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
opener.addEventListener('click', () => toggleMenu(true), true)
|
opener.addEventListener('click', () => toggleMenu(true), true)
|
||||||
|
|
||||||
|
if (openerHover.value) {
|
||||||
|
opener.addEventListener('mouseenter', () => toggleMenu(true), true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const createLoader = () => {
|
const createLoader = () => {
|
||||||
|
|
@ -111,6 +116,7 @@ onMounted(async () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
hasPageLoader.value = config.value['loader-enabled']
|
hasPageLoader.value = config.value['loader-enabled']
|
||||||
|
openerHover.value = config.value['opener-hover']
|
||||||
|
|
||||||
if (hasApps.value) {
|
if (hasApps.value) {
|
||||||
createOpener()
|
createOpener()
|
||||||
|
|
|
||||||
|
|
@ -77,10 +77,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, useTemplateRef, onMounted } from 'vue'
|
import { ref, useTemplateRef, onMounted, watch } from 'vue'
|
||||||
import { useNavStore } from '../store/nav.js'
|
import { useNavStore } from '../store/nav.js'
|
||||||
import { useConfigStore } from '../store/config.js'
|
import { useConfigStore } from '../store/config.js'
|
||||||
import { getActiveAppId } from '../lib/app.js'
|
import { getActiveAppId } from '../lib/app.js'
|
||||||
|
import { focusActiveApp } from '../lib/menu.js'
|
||||||
import { containsAppsMatchingSearch, isAppMatchingSearch } from '../lib/search.js'
|
import { containsAppsMatchingSearch, isAppMatchingSearch } from '../lib/search.js'
|
||||||
|
|
||||||
import OpenerButton from '../components/OpenerButton'
|
import OpenerButton from '../components/OpenerButton'
|
||||||
|
|
@ -108,6 +109,12 @@ const openerHover = ref(false)
|
||||||
const menu = useTemplateRef('menu')
|
const menu = useTemplateRef('menu')
|
||||||
const isTouchDevice = window.matchMedia('(pointer: coarse)').matches
|
const isTouchDevice = window.matchMedia('(pointer: coarse)').matches
|
||||||
|
|
||||||
|
watch(() => open, (val) => {
|
||||||
|
if (val) {
|
||||||
|
focusActiveApp(menu.value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const config = await configStore.getConfig()
|
const config = await configStore.getConfig()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
ref="menu"
|
ref="menu"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="settings || !openerHover || (!avatar && !alwaysDisplayed && logo) || avatar"
|
|
||||||
class="side-menu-header"
|
class="side-menu-header"
|
||||||
|
v-if="settings || displayLogo || open || !openerHover"
|
||||||
>
|
>
|
||||||
<SettingsButton
|
<SettingsButton
|
||||||
v-if="settings && open"
|
v-if="settings && open"
|
||||||
|
|
@ -32,26 +32,20 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
/>
|
/>
|
||||||
<AppSearch v-model="search" v-if="open" />
|
<AppSearch v-model="search" v-if="open" />
|
||||||
<OpenerButton
|
<OpenerButton
|
||||||
v-if="(!alwaysDisplayed && !openerHover) || isTouchDevice"
|
v-if="!openerHover || isTouchDevice"
|
||||||
@click="$emit('toggle')"
|
@click="$emit('toggle')"
|
||||||
/>
|
/>
|
||||||
<Logo
|
<Logo
|
||||||
v-if="!avatar && !alwaysDisplayed && logo"
|
v-if="displayLogo"
|
||||||
:classes="{ 'side-menu-logo': true, avatardiv: false }"
|
:classes="{ 'side-menu-logo': true, avatardiv: false }"
|
||||||
:image="logo"
|
:image="useAvatarAsLogo ? avatar : logo"
|
||||||
:link="logoLink"
|
|
||||||
/>
|
|
||||||
<Logo
|
|
||||||
v-if="avatar && !alwaysDisplayed"
|
|
||||||
:classes="{ 'side-menu-logo': true, avatardiv: true }"
|
|
||||||
:image="avatar"
|
|
||||||
:link="logoLink"
|
:link="logoLink"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul
|
<ul
|
||||||
class="side-menu-apps-list"
|
class="side-menu-apps-list"
|
||||||
:class="{ 'side-menu-apps-list--with-settings': !!settings }"
|
:class="{ 'side-menu-apps-list--with-logo': displayLogo }"
|
||||||
>
|
>
|
||||||
<template
|
<template
|
||||||
v-for="(app, key) in apps"
|
v-for="(app, key) in apps"
|
||||||
|
|
@ -59,7 +53,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
>
|
>
|
||||||
<SideMenuApp
|
<SideMenuApp
|
||||||
v-if="isAppMatchingSearch(app, search)"
|
v-if="isAppMatchingSearch(app, search)"
|
||||||
:classes="{ 'side-menu-app': true, active: app.active }"
|
:classes="{ 'side-menu-app': true, active: app.id === activeApp }"
|
||||||
:icon="app.icon"
|
:icon="app.icon"
|
||||||
:label="app.name"
|
:label="app.name"
|
||||||
:href="app.href"
|
:href="app.href"
|
||||||
|
|
@ -74,7 +68,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
import { ref, useTemplateRef, onMounted, watch } from 'vue'
|
import { ref, useTemplateRef, onMounted, watch } from 'vue'
|
||||||
import { useConfigStore } from '../store/config.js'
|
import { useConfigStore } from '../store/config.js'
|
||||||
import { useNavStore } from '../store/nav.js'
|
import { useNavStore } from '../store/nav.js'
|
||||||
|
import { focusActiveApp } from '../lib/menu.js'
|
||||||
import { isAppMatchingSearch } from '../lib/search.js'
|
import { isAppMatchingSearch } from '../lib/search.js'
|
||||||
|
import { getActiveAppId } from '../lib/app.js'
|
||||||
|
|
||||||
import OpenerButton from '../components/OpenerButton'
|
import OpenerButton from '../components/OpenerButton'
|
||||||
import SettingsButton from '../components/SettingsButton'
|
import SettingsButton from '../components/SettingsButton'
|
||||||
|
|
@ -94,12 +90,15 @@ const navStore = useNavStore()
|
||||||
const configStore = useConfigStore()
|
const configStore = useConfigStore()
|
||||||
const targetBlankApps = ref(null)
|
const targetBlankApps = ref(null)
|
||||||
const forceLightIcon = ref(null)
|
const forceLightIcon = ref(null)
|
||||||
|
const activeApp = ref(null)
|
||||||
const avatar = ref(null)
|
const avatar = ref(null)
|
||||||
const logo = ref(null)
|
const logo = ref(null)
|
||||||
const logoLink = ref(null)
|
const logoLink = ref(null)
|
||||||
const settings = ref(null)
|
const settings = ref(null)
|
||||||
const openerHover = ref(false)
|
const openerHover = ref(false)
|
||||||
const alwaysDisplayed = ref(false)
|
const alwaysDisplayed = ref(false)
|
||||||
|
const displayLogo = ref(false)
|
||||||
|
const useAvatarAsLogo = ref(false)
|
||||||
const search = ref('')
|
const search = ref('')
|
||||||
const apps = ref([])
|
const apps = ref([])
|
||||||
const menu = useTemplateRef('menu')
|
const menu = useTemplateRef('menu')
|
||||||
|
|
@ -109,6 +108,12 @@ watch(apps, (val) => {
|
||||||
document.querySelector('html').classList.toggle('side-menu-always-displayed', alwaysDisplayed.value && val.length)
|
document.querySelector('html').classList.toggle('side-menu-always-displayed', alwaysDisplayed.value && val.length)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
watch(() => open, (val) => {
|
||||||
|
if (val) {
|
||||||
|
focusActiveApp(menu.value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
function getFiltredAndSortedApps(items, order, topMenuApps, topSideMenuApps) {
|
function getFiltredAndSortedApps(items, order, topMenuApps, topSideMenuApps) {
|
||||||
const data = []
|
const data = []
|
||||||
|
|
||||||
|
|
@ -136,14 +141,22 @@ function getFiltredAndSortedApps(items, order, topMenuApps, topSideMenuApps) {
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const config = await configStore.getConfig()
|
const config = await configStore.getConfig()
|
||||||
|
|
||||||
|
alwaysDisplayed.value = config['always-displayed']
|
||||||
targetBlankApps.value = config['target-blank-apps']
|
targetBlankApps.value = config['target-blank-apps']
|
||||||
forceLightIcon.value = config['force-light-icon']
|
forceLightIcon.value = config['force-light-icon']
|
||||||
|
|
||||||
avatar.value = config['avatar']
|
avatar.value = config['avatar']
|
||||||
logo.value = config['logo']
|
logo.value = config['logo']
|
||||||
|
useAvatarAsLogo.value = config['use-avatar']
|
||||||
|
displayLogo.value = config['display-logo'] && !alwaysDisplayed.value && (
|
||||||
|
(!useAvatarAsLogo.value && logo.value) ||
|
||||||
|
(useAvatarAsLogo.value && avatar.value)
|
||||||
|
)
|
||||||
|
|
||||||
logoLink.value = config['logo-link']
|
logoLink.value = config['logo-link']
|
||||||
settings.value = config['settings']
|
settings.value = config['settings']
|
||||||
openerHover.value = config['opener-hover'] && !isTouchDevice
|
openerHover.value = config['opener-hover'] && !isTouchDevice
|
||||||
alwaysDisplayed.value = config['always-displayed']
|
activeApp.value = getActiveAppId()
|
||||||
|
|
||||||
apps.value = getFiltredAndSortedApps(await navStore.getApps(), config['apps-order'], config['top-menu-apps'], config['top-side-menu-apps'])
|
apps.value = getFiltredAndSortedApps(await navStore.getApps(), config['apps-order'], config['top-menu-apps'], config['top-side-menu-apps'])
|
||||||
|
|
||||||
|
|
@ -151,7 +164,7 @@ onMounted(async () => {
|
||||||
menu.value.addEventListener('mouseleave', () => emit('close'))
|
menu.value.addEventListener('mouseleave', () => emit('close'))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (alwaysDisplayed.value) {
|
if (alwaysDisplayed.value && openerHover.value) {
|
||||||
menu.value.addEventListener('mouseenter', () => emit('open'))
|
menu.value.addEventListener('mouseenter', () => emit('open'))
|
||||||
menu.value.addEventListener('mouseleave', () => emit('close'))
|
menu.value.addEventListener('mouseleave', () => emit('close'))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -81,10 +81,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, useTemplateRef, onMounted } from 'vue'
|
import { ref, useTemplateRef, onMounted, watch } from 'vue'
|
||||||
import { useNavStore } from '../store/nav.js'
|
import { useNavStore } from '../store/nav.js'
|
||||||
import { useConfigStore } from '../store/config.js'
|
import { useConfigStore } from '../store/config.js'
|
||||||
import { getActiveAppId } from '../lib/app.js'
|
import { getActiveAppId } from '../lib/app.js'
|
||||||
|
import { focusActiveApp } from '../lib/menu.js'
|
||||||
import { containsAppsMatchingSearch, isAppMatchingSearch } from '../lib/search.js'
|
import { containsAppsMatchingSearch, isAppMatchingSearch } from '../lib/search.js'
|
||||||
|
|
||||||
import OpenerButton from '../components/OpenerButton'
|
import OpenerButton from '../components/OpenerButton'
|
||||||
|
|
@ -113,6 +114,12 @@ const openerHover = ref(false)
|
||||||
const menu = useTemplateRef('menu')
|
const menu = useTemplateRef('menu')
|
||||||
const isTouchDevice = window.matchMedia('(pointer: coarse)').matches
|
const isTouchDevice = window.matchMedia('(pointer: coarse)').matches
|
||||||
|
|
||||||
|
watch(() => open, (val) => {
|
||||||
|
if (val) {
|
||||||
|
focusActiveApp(menu.value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const config = await configStore.getConfig()
|
const config = await configStore.getConfig()
|
||||||
|
|
||||||
|
|
|
||||||
104
src/pages/AdminSettings.vue
Normal file
104
src/pages/AdminSettings.vue
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
<template>
|
||||||
|
<NcContent app-name="side_menu">
|
||||||
|
<NcAppContent>
|
||||||
|
<div class="side-menu-setting">
|
||||||
|
<NcButton
|
||||||
|
v-for="item in menu"
|
||||||
|
@click="setSection(item.section)"
|
||||||
|
:variant="item.section === section ? 'primary' : 'secondary'"
|
||||||
|
>{{ trans(item.label) }}</NcButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<TableContainer :class="sectionClass('panel')">
|
||||||
|
</TableContainer>
|
||||||
|
|
||||||
|
<TableContainer :class="sectionClass('color')">
|
||||||
|
<TableRow>
|
||||||
|
<TableLabel label="Background color" :short="true" :middle="true"></TableLabel>
|
||||||
|
<TableValue>
|
||||||
|
<NcColorPicker v-model="color" class="side-menu-setting-color-picker">
|
||||||
|
<div :style="{'background-color': color}" class="side-menu-setting-color-picker-value" />
|
||||||
|
</NcColorPicker>
|
||||||
|
<NcColorPicker v-model="color" class="side-menu-setting-color-picker">
|
||||||
|
<div :style="{'background-color': color}" class="side-menu-setting-color-picker-value" />
|
||||||
|
</NcColorPicker>
|
||||||
|
</TableValue>
|
||||||
|
</TableRow>
|
||||||
|
|
||||||
|
<TableRow>
|
||||||
|
<TableLabel label="Background color of current app" :short="true" :middle="true"></TableLabel>
|
||||||
|
<TableValue>
|
||||||
|
<NcColorPicker v-model="color" class="side-menu-setting-color-picker">
|
||||||
|
<div :style="{'background-color': color}" class="side-menu-setting-color-picker-value" />
|
||||||
|
</NcColorPicker>
|
||||||
|
</TableValue>
|
||||||
|
</TableRow>
|
||||||
|
|
||||||
|
<TableRow>
|
||||||
|
<TableLabel label="Text color" :short="true" :middle="true"></TableLabel>
|
||||||
|
<TableValue>
|
||||||
|
<NcColorPicker v-model="color" class="side-menu-setting-color-picker">
|
||||||
|
<div :style="{'background-color': color}" class="side-menu-setting-color-picker-value" />
|
||||||
|
</NcColorPicker>
|
||||||
|
</TableValue>
|
||||||
|
</TableRow>
|
||||||
|
</TableContainer>
|
||||||
|
</NcAppContent>
|
||||||
|
</NcContent>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.wrapper {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
padding-top: calc(var(--default-grid-baseline) * 5);
|
||||||
|
padding-left: calc(var(--default-grid-baseline) * 7);
|
||||||
|
padding-right: calc(var(--default-grid-baseline) * 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-picker {
|
||||||
|
width: 100px;
|
||||||
|
height: 34px;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { NcContent, NcAppContent, NcColorPicker, NcButton } from '@nextcloud/vue'
|
||||||
|
import MenuDisplay from '../components/settings/MenuDisplay'
|
||||||
|
import TableContainer from '../components/settings/TableContainer'
|
||||||
|
import TableRow from '../components/settings/TableRow'
|
||||||
|
import TableLabel from '../components/settings/TableLabel'
|
||||||
|
import TableValue from '../components/settings/TableValue'
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
|
||||||
|
const menu = [
|
||||||
|
{label: 'Panel', section: 'panel'},
|
||||||
|
{label: 'Colors', section: 'color'},
|
||||||
|
]
|
||||||
|
|
||||||
|
const section = ref('panel')
|
||||||
|
const color = ref('#ff9')
|
||||||
|
|
||||||
|
const setSection = (value) => {
|
||||||
|
section.value = value
|
||||||
|
}
|
||||||
|
|
||||||
|
const trans = (value) => {
|
||||||
|
return t('side_menu', value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const sectionClass = (value) => {
|
||||||
|
return {
|
||||||
|
hidden: value !== section.value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
@ -114,6 +114,7 @@
|
||||||
.side-menu-setting-table {
|
.side-menu-setting-table {
|
||||||
display: table;
|
display: table;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.side-menu-setting-row {
|
.side-menu-setting-row {
|
||||||
|
|
@ -141,6 +142,10 @@
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.side-menu-setting-label--middle {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
.side-menu-setting-form {
|
.side-menu-setting-form {
|
||||||
display: table-cell;
|
display: table-cell;
|
||||||
min-width: 300px;
|
min-width: 300px;
|
||||||
|
|
@ -218,3 +223,23 @@
|
||||||
border-color: #c681d4;
|
border-color: #c681d4;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.side-menu-setting {
|
||||||
|
padding: 10px;
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.side-menu-setting-color-picker {
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 12px;
|
||||||
|
width: 60px;
|
||||||
|
height: 30px;
|
||||||
|
|
||||||
|
&-value {
|
||||||
|
cursor: pointer;
|
||||||
|
width: 60px;
|
||||||
|
height: 30px;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,12 +41,40 @@
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.side-menu-opener {
|
||||||
|
margin-top: 1px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.side-menu-big {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.side-menu-with-categories {
|
||||||
|
max-width: 290px;
|
||||||
|
height: 100vh;
|
||||||
|
|
||||||
|
.side-menu-categories {
|
||||||
|
display: block;
|
||||||
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.side-menu-category {
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.side-menu-big, &.side-menu-with-categories {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#header {
|
#header {
|
||||||
.side-menu-opener {
|
.side-menu-opener {
|
||||||
margin-left: 0px;
|
margin-left: 0px;
|
||||||
margin-top: -1px;
|
margin-top: 0px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -111,20 +139,19 @@
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#side-menu.hide-opener .side-menu-opener,
|
|
||||||
.side-menu-opener.hide,
|
|
||||||
#side-menu.hide {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.side-menu-apps-list {
|
.side-menu-apps-list {
|
||||||
height: calc(100vh - 150px);
|
height: calc(100vh - 49px);
|
||||||
|
top: 49px;
|
||||||
z-index: 2200;
|
z-index: 2200;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 150px;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 290px;
|
max-width: 290px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
|
||||||
|
&.side-menu-apps-list--with-logo {
|
||||||
|
height: calc(100vh - 160px);
|
||||||
|
top: 160px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.side-menu-app-icon {
|
.side-menu-app-icon {
|
||||||
|
|
@ -149,7 +176,8 @@
|
||||||
|
|
||||||
a:hover,
|
a:hover,
|
||||||
a:focus,
|
a:focus,
|
||||||
&:active {
|
&:active,
|
||||||
|
&.active {
|
||||||
background: var(--side-menu-current-app-background-color, #444);
|
background: var(--side-menu-current-app-background-color, #444);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -165,7 +193,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.side-menu-header {
|
.side-menu-header {
|
||||||
height: 150px;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
z-index: 2300;
|
z-index: 2300;
|
||||||
max-width: 290px;
|
max-width: 290px;
|
||||||
|
|
@ -183,10 +210,6 @@
|
||||||
max-width: 295px;
|
max-width: 295px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#side-menu.hide-opener .side-menu-logo {
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#side-menu-loader {
|
#side-menu-loader {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|
@ -202,28 +225,24 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#side-menu.side-menu-big,
|
.side-menu-big, .side-menu-with-categories {
|
||||||
#side-menu.side-menu-with-categories {
|
.side-menu-apps-list {
|
||||||
max-width: 100%;
|
height: auto;
|
||||||
height: auto;
|
position: static;
|
||||||
}
|
max-width: 100vw;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.side-menu-big .side-menu-header,
|
.side-menu-app {
|
||||||
.side-menu-with-categories .side-menu-header {
|
a {
|
||||||
height: auto;
|
padding: 7px 0 7px 7px;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.side-menu-big .side-menu-apps-list,
|
.side-menu-app-icon {
|
||||||
.side-menu-with-categories .side-menu-apps-list {
|
vertical-align: middle;
|
||||||
height: auto;
|
margin-top: -2px;
|
||||||
position: static;
|
}
|
||||||
max-width: 100vw;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.side-menu-big .side-menu-app a,
|
|
||||||
.side-menu-with-categories .side-menu-app a {
|
|
||||||
padding: 7px 0 7px 7px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.side-menu-categories-wrapper {
|
.side-menu-categories-wrapper {
|
||||||
|
|
@ -265,11 +284,6 @@
|
||||||
stroke: var(--side-menu-text-color, #fff);
|
stroke: var(--side-menu-text-color, #fff);
|
||||||
}
|
}
|
||||||
|
|
||||||
.side-menu-with-categories .side-menu-app-icon,
|
|
||||||
.side-menu-big .side-menu-app-icon {
|
|
||||||
vertical-align: middle;
|
|
||||||
margin-top: -2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.side-menu-always-displayed {
|
.side-menu-always-displayed {
|
||||||
body {
|
body {
|
||||||
|
|
@ -287,12 +301,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.side-menu-apps-list {
|
.side-menu-apps-list {
|
||||||
height: 100vh;
|
|
||||||
top: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.side-menu-apps-list--with-settings {
|
|
||||||
height: calc(100vh - 49px);
|
height: calc(100vh - 49px);
|
||||||
top: 49px;
|
top: 49px;
|
||||||
}
|
}
|
||||||
|
|
@ -331,32 +339,6 @@
|
||||||
right: 0 !important;
|
right: 0 !important;
|
||||||
margin-left: 0 !important;
|
margin-left: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.side-menu-search {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#body-settings,
|
|
||||||
#body-settings.body-settings-side-menu {
|
|
||||||
overflow-x: visible;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#side-menu.side-menu-with-categories {
|
|
||||||
max-width: 290px;
|
|
||||||
height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.side-menu-with-categories {
|
|
||||||
.side-menu-categories {
|
|
||||||
display: block;
|
|
||||||
padding: 0;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.side-menu-category {
|
|
||||||
padding: 10px 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-menu {
|
.app-menu {
|
||||||
|
|
@ -390,10 +372,6 @@
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
#side-menu.hide-opener.side-menu-big .side-menu-search {
|
|
||||||
float: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.side-menu-categories {
|
.side-menu-categories {
|
||||||
display: block;
|
display: block;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
|
||||||
|
|
@ -8,65 +8,28 @@
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
}
|
}
|
||||||
|
|
||||||
<?php if (empty($_['top-menu-apps']) && empty($_['top-side-menu-apps'])): ?>
|
|
||||||
#appmenu {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#appmenu + nav {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
<?php else: ?>
|
|
||||||
.app-hidden {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<?php if ($_['opener-only']): ?>
|
<?php if ($_['opener-only']): ?>
|
||||||
#nextcloud {
|
#nextcloud {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if (!$_['display-logo']): ?>
|
<?php if ($_['size-text'] === 'hidden'): ?>
|
||||||
.side-menu-logo {
|
#side-menu, .side-menu-apps-list {
|
||||||
display: none;
|
<?php if ($_['size-icon'] === 'big'): ?>
|
||||||
|
width: 55px;
|
||||||
|
<?php else: ?>
|
||||||
|
width: 52px;
|
||||||
|
<?php endif; ?>
|
||||||
}
|
}
|
||||||
|
|
||||||
.side-menu-header {
|
#side-menu .side-menu-opener {
|
||||||
height: 50px;
|
<?php if ($_['size-icon'] === 'big'): ?>
|
||||||
|
margin-left: 1px;
|
||||||
|
<?php else: ?>
|
||||||
|
margin-left: 0px;
|
||||||
|
<?php endif; ?>
|
||||||
}
|
}
|
||||||
|
|
||||||
.side-menu-apps-list {
|
|
||||||
height: calc(100vh - 49px);
|
|
||||||
top: 49px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#side-menu.hide-opener .side-menu-header .side-menu-opener.side-menu-closer {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
#side-menu.hide-opener.side-menu-with-categories .side-menu-search {
|
|
||||||
float: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
<?php if ($_['size-text'] === 'hidden'): ?>
|
|
||||||
#side-menu, .side-menu-apps-list {
|
|
||||||
<?php if ($_['size-icon'] === 'big'): ?>
|
|
||||||
width: 55px;
|
|
||||||
<?php else: ?>
|
|
||||||
width: 52px;
|
|
||||||
<?php endif; ?>
|
|
||||||
}
|
|
||||||
|
|
||||||
#side-menu .side-menu-opener {
|
|
||||||
<?php if ($_['size-icon'] === 'big'): ?>
|
|
||||||
margin-left: 1px;
|
|
||||||
<?php else: ?>
|
|
||||||
margin-left: 0px;
|
|
||||||
<?php endif; ?>
|
|
||||||
}
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if ($_['size-icon'] === 'hidden'): ?>
|
<?php if ($_['size-icon'] === 'hidden'): ?>
|
||||||
|
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
(function() {
|
|
||||||
const body = document.querySelector('body')
|
|
||||||
const html = document.querySelector('html')
|
|
||||||
const nextcloud = document.querySelector('#nextcloud')
|
|
||||||
const logo = document.querySelector('.header-left .logo')
|
|
||||||
const isTouchDevice = window.matchMedia("(pointer: coarse)").matches
|
|
||||||
|
|
||||||
const sideMenuFocus = () => {
|
|
||||||
let a = document.querySelector('#side-menu .side-menu-app.active a')
|
|
||||||
|| document.querySelector('#side-menu .side-menu-app a')
|
|
||||||
|
|
||||||
if (a) {
|
|
||||||
a.focus()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
document.querySelector('body').addEventListener('side-menu.apps', (e) => {
|
|
||||||
const apps = e.detail.apps;
|
|
||||||
})
|
|
||||||
|
|
||||||
body.addEventListener('side-menu.ready', () => {
|
|
||||||
const sideMenu = document.querySelector('#side-menu')
|
|
||||||
|
|
||||||
|
|
||||||
const sideMenuObserver = new MutationObserver((e) => {
|
|
||||||
if (body.getAttribute('id') !== 'body-settings') {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
body.classList.toggle('body-settings-side-menu', sideMenu.classList.contains('open'))
|
|
||||||
})
|
|
||||||
|
|
||||||
sideMenuObserver.observe(sideMenu, {
|
|
||||||
attributes: true,
|
|
||||||
attributeFilter: ['class'],
|
|
||||||
childList: false,
|
|
||||||
characterData: false
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})();
|
|
||||||
File diff suppressed because it is too large
Load diff
1148
templates/settings/admin-form.php.bk
Normal file
1148
templates/settings/admin-form.php.bk
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -16,7 +16,7 @@ module.exports = {
|
||||||
devtool: "inline-source-map",
|
devtool: "inline-source-map",
|
||||||
entry: {
|
entry: {
|
||||||
menu: path.resolve(path.join('src', 'menu.js')),
|
menu: path.resolve(path.join('src', 'menu.js')),
|
||||||
// admin: path.resolve(path.join('src', 'admin.js')),
|
admin: path.resolve(path.join('src', 'admin.js')),
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
path: path.resolve('./js'),
|
path: path.resolve('./js'),
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,10 @@ module.exports = {
|
||||||
test: /\.scss$/,
|
test: /\.scss$/,
|
||||||
use: ['style-loader', 'css-loader', 'sass-loader'],
|
use: ['style-loader', 'css-loader', 'sass-loader'],
|
||||||
},
|
},
|
||||||
|
css: {
|
||||||
|
test: /\.css$/,
|
||||||
|
use: ['style-loader', 'css-loader'],
|
||||||
|
},
|
||||||
vue: {
|
vue: {
|
||||||
test: /\.vue$/,
|
test: /\.vue$/,
|
||||||
loader: 'vue-loader',
|
loader: 'vue-loader',
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue