mirror of
https://github.com/Macrame-App/Macrame
synced 2025-12-29 07:19:26 +00:00
Compare commits
6 commits
3193127809
...
c77233f07d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c77233f07d | ||
|
|
11a1c4a6e7 | ||
|
|
782dacb822 | ||
|
|
7da4cf0ad1 | ||
|
|
401eb0b905 | ||
|
|
57fa6cf2a2 |
16 changed files with 533 additions and 27 deletions
|
|
@ -10,7 +10,7 @@ bin = "tmp/main.exe"
|
|||
[build]
|
||||
cmd = "go build -o ./tmp/main.exe main.go"
|
||||
include_ext = ["go"]
|
||||
exclude_dir = ["fe", "panels", "builds"]
|
||||
exclude_dir = ["ui", "panels", "builds"]
|
||||
|
||||
# Restart on file changes
|
||||
[log]
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ func configFileExists() bool {
|
|||
}
|
||||
|
||||
func CheckUIDevDir() {
|
||||
log.Println("Checking FE dev directory...")
|
||||
log.Println("Checking UI dev directory...")
|
||||
_, err := os.Stat("ui")
|
||||
|
||||
if err != nil {
|
||||
|
|
@ -72,10 +72,10 @@ func CheckUIDevDir() {
|
|||
return
|
||||
}
|
||||
|
||||
copyConfigToFe()
|
||||
copyConfigToUi()
|
||||
}
|
||||
|
||||
func copyConfigToFe() {
|
||||
func copyConfigToUi() {
|
||||
data, err := os.ReadFile(configPath)
|
||||
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -35,19 +35,25 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
<h4>Not authorized!</h4>
|
||||
<p>Click here to start authorization and open the "Devices" page on your PC.</p>
|
||||
</AlertComp>
|
||||
<NotificationsComp />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import MainMenu from '@/components/base/MainMenu.vue'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { RouterView, useRoute } from 'vue-router'
|
||||
import { onBeforeUnmount, onMounted, onUnmounted, onUpdated, ref } from 'vue'
|
||||
import { RouterView, useRoute, useRouter } from 'vue-router'
|
||||
import { useDeviceStore } from './stores/device'
|
||||
import { useSettingStore } from './stores/settings'
|
||||
import { isLocal } from './services/ApiService'
|
||||
import AlertComp from './components/base/AlertComp.vue'
|
||||
import NotificationsComp from './components/base/NotificationsComp.vue'
|
||||
import { GetLocalPanel } from './services/PanelService'
|
||||
|
||||
const device = useDeviceStore()
|
||||
const settings = useSettingStore()
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const handshake = ref(false)
|
||||
|
||||
onMounted(() => {
|
||||
|
|
@ -55,17 +61,38 @@ onMounted(() => {
|
|||
// If not present in LocalStorage a new uuidV4 will be generated
|
||||
device.uuid()
|
||||
|
||||
if (!isLocal) appHandshake()
|
||||
settings.loadSettings()
|
||||
|
||||
if (!isLocal) {
|
||||
appHandshake()
|
||||
loadLastPanel()
|
||||
}
|
||||
|
||||
device.$subscribe(() => {
|
||||
if (device.key()) handshake.value = true
|
||||
})
|
||||
})
|
||||
|
||||
onUpdated(() => {
|
||||
console.log('App updated')
|
||||
|
||||
// loadLastPanel()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
settings.saveSettings()
|
||||
})
|
||||
|
||||
async function appHandshake() {
|
||||
const hsReq = await device.remoteHandshake()
|
||||
handshake.value = hsReq
|
||||
}
|
||||
|
||||
function loadLastPanel() {
|
||||
if (route.fullPath != GetLocalPanel()) {
|
||||
router.push(GetLocalPanel())
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
|||
|
|
@ -45,6 +45,9 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
<IconDevices />{{ isLocal() ? 'Devices' : 'Server' }}
|
||||
</RouterLink>
|
||||
</li>
|
||||
<li>
|
||||
<RouterLink @click="menuOpen = false" to="/settings"> <IconSettings />Settings </RouterLink>
|
||||
</li>
|
||||
<!-- <li>
|
||||
<RouterLink @click="menuOpen = false" to="/settings">
|
||||
<IconSettings />Settings
|
||||
|
|
|
|||
46
ui/src/components/base/NotificationsComp.vue
Normal file
46
ui/src/components/base/NotificationsComp.vue
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
<template>
|
||||
<div class="mcrm-notifications">
|
||||
<ToastComp
|
||||
v-for="(notification, id) in notificationList"
|
||||
:key="id"
|
||||
:id="id"
|
||||
:title="notification.title"
|
||||
:message="notification.message"
|
||||
:variant="notification.variant"
|
||||
:time="notification.time"
|
||||
:closable="notification.closable"
|
||||
:notification="id"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useNoticationStore } from '@/stores/notifications'
|
||||
import { computed, onMounted } from 'vue'
|
||||
import ToastComp from './ToastComp.vue'
|
||||
|
||||
const notifications = useNoticationStore()
|
||||
|
||||
const notificationList = computed(() => notifications.list)
|
||||
|
||||
// onMounted(() => {
|
||||
// notifications.$subscribe((mutation, state) => {
|
||||
// console.log(mutation, state)
|
||||
// })
|
||||
// })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@reference "@/assets/main.css";
|
||||
|
||||
.mcrm-notifications {
|
||||
@apply grid
|
||||
gap-4
|
||||
fixed
|
||||
bottom-0
|
||||
right-0
|
||||
p-4
|
||||
z-50
|
||||
w-[30ch];
|
||||
}
|
||||
</style>
|
||||
82
ui/src/components/base/ToastComp.vue
Normal file
82
ui/src/components/base/ToastComp.vue
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
<template>
|
||||
<div :class="`mcrm-toast mcrm-block block__${toastOptions.variant}`" ref="toast">
|
||||
<ButtonComp v-if="closable" variant="subtle" size="sm" @click="closeToast()">
|
||||
<IconX />
|
||||
</ButtonComp>
|
||||
<h4>{{ title }}</h4>
|
||||
<p>{{ message }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useNoticationStore } from '@/stores/notifications'
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import ButtonComp from './ButtonComp.vue'
|
||||
import { IconX } from '@tabler/icons-vue'
|
||||
|
||||
const props = defineProps({
|
||||
title: String,
|
||||
message: String,
|
||||
variant: String,
|
||||
time: Number,
|
||||
closable: Boolean,
|
||||
notification: [String, Number],
|
||||
})
|
||||
|
||||
const notifications = useNoticationStore()
|
||||
|
||||
const toast = ref(null)
|
||||
|
||||
const toastOptions = reactive({
|
||||
variant: props.variant,
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if (toastOptions.variant == 'info') toastOptions.variant = 'primary'
|
||||
|
||||
setTimeout(() => {
|
||||
closeToast()
|
||||
}, props.time)
|
||||
})
|
||||
|
||||
const closeToast = () => {
|
||||
toast.value.classList.add('closing')
|
||||
|
||||
setTimeout(() => {
|
||||
toast.value.remove()
|
||||
if (props.notification) notifications.remove(props.notification)
|
||||
}, 500)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@reference "@/assets/main.css";
|
||||
|
||||
.mcrm-toast {
|
||||
@apply relative
|
||||
grid
|
||||
gap-2
|
||||
p-4
|
||||
transition-opacity
|
||||
duration-400;
|
||||
|
||||
h4 {
|
||||
@apply pr-6
|
||||
text-base;
|
||||
}
|
||||
|
||||
p {
|
||||
@apply text-sm opacity-80;
|
||||
}
|
||||
|
||||
&.closing {
|
||||
@apply opacity-0;
|
||||
}
|
||||
|
||||
button.btn {
|
||||
@apply absolute
|
||||
top-2 right-2
|
||||
p-2;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -93,8 +93,6 @@ const server = reactive({
|
|||
onMounted(async () => {
|
||||
const serverIP = await device.serverGetIP()
|
||||
server.ip = serverIP
|
||||
// server.port = window.__CONFIG__.MCRM__PORT
|
||||
// server.fullPath = `http://${server.ip}:${server.port}`
|
||||
|
||||
const remoteCount = await device.serverGetRemotes(true)
|
||||
server.remoteCount = remoteCount
|
||||
|
|
@ -104,8 +102,6 @@ onMounted(async () => {
|
|||
|
||||
const panelCount = await panel.getList(true)
|
||||
server.panelCount = panelCount
|
||||
|
||||
console.log(server)
|
||||
})
|
||||
</script>
|
||||
|
||||
|
|
|
|||
114
ui/src/components/form/FormInput.vue
Normal file
114
ui/src/components/form/FormInput.vue
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
<template>
|
||||
<div :class="`form-input ${horizontal ? 'horizontal' : ''}`">
|
||||
<template v-if="label">
|
||||
<label :for="name">
|
||||
{{ label }}
|
||||
</label>
|
||||
</template>
|
||||
<template v-if="type != 'radio' && type != 'checkbox'">
|
||||
<input
|
||||
:type="type"
|
||||
:name="name"
|
||||
:id="name"
|
||||
:value="value"
|
||||
:placeholder="placeholder"
|
||||
:disabled="disabled"
|
||||
:readonly="readonly"
|
||||
:required="required"
|
||||
:autofocus="autofocus"
|
||||
@change="$emit('onChange', $event.target.value)"
|
||||
@input="$emit('onInput', $event.target.value)"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="options">
|
||||
<div class="boolean-group">
|
||||
<template v-for="option in options" :key="option.value">
|
||||
<label :for="`${name}-${option.value}`">
|
||||
{{ option.label }}
|
||||
</label>
|
||||
<input
|
||||
:type="type"
|
||||
:name="name"
|
||||
:id="`${name}-${option.value}`"
|
||||
:disabled="disabled"
|
||||
:readonly="readonly"
|
||||
:required="required"
|
||||
:checked="value == option.value"
|
||||
:value="option.value"
|
||||
@change="$emit('onChange', $event.target.value)"
|
||||
@input="$emit('onInput', $event.target.value)"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<input
|
||||
:type="type"
|
||||
:name="name"
|
||||
:id="name"
|
||||
:value="value"
|
||||
:disabled="disabled"
|
||||
:readonly="readonly"
|
||||
:required="required"
|
||||
:checked="value === true || value === 'true'"
|
||||
@change="$emit('onChange', $event.target)"
|
||||
@input="$emit('onInput', $event.target)"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
defineProps({
|
||||
type: String,
|
||||
name: String,
|
||||
value: [String, Number, Boolean],
|
||||
options: Array,
|
||||
label: String,
|
||||
placeholder: String,
|
||||
disabled: Boolean,
|
||||
readonly: Boolean,
|
||||
required: Boolean,
|
||||
autofocus: Boolean,
|
||||
horizontal: Boolean,
|
||||
})
|
||||
|
||||
defineEmits(['onChange', 'onInput'])
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@reference "@/assets/main.css";
|
||||
|
||||
.form-input {
|
||||
@apply flex
|
||||
flex-col
|
||||
items-center
|
||||
gap-2;
|
||||
|
||||
&.horizontal {
|
||||
@apply flex-row
|
||||
justify-between;
|
||||
|
||||
input {
|
||||
@apply max-w-2/3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.boolean-group {
|
||||
@apply flex
|
||||
items-center
|
||||
gap-2;
|
||||
}
|
||||
|
||||
input[type='checkbox'],
|
||||
input[type='radio'] {
|
||||
@apply size-5
|
||||
cursor-pointer;
|
||||
}
|
||||
|
||||
input[disabled] {
|
||||
@apply opacity-60
|
||||
cursor-not-allowed;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -21,37 +21,48 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
|
||||
<template>
|
||||
<div id="panel-view">
|
||||
<div class="panel-preview__content" ref="panelView" v-html="viewPanel.html"></div>
|
||||
<div class="panel-container" ref="panelContainer" v-html="viewPanel.html"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { isLocal } from '@/services/ApiService'
|
||||
import { RunMacro } from '@/services/MacroService'
|
||||
import {
|
||||
CheckLocalPanel,
|
||||
PanelButtonListeners,
|
||||
PanelDialogListeners,
|
||||
RemovePanelScripts,
|
||||
RemovePanelStyle,
|
||||
SavePanelToLocal,
|
||||
SetPanelStyle,
|
||||
StripPanelHTML,
|
||||
} from '@/services/PanelService'
|
||||
import { usePanelStore } from '@/stores/panel'
|
||||
import { useSettingStore } from '@/stores/settings'
|
||||
import { onMounted, onUnmounted, ref } from 'vue'
|
||||
|
||||
const panel = usePanelStore()
|
||||
const settings = useSettingStore()
|
||||
|
||||
const props = defineProps({
|
||||
dirname: String,
|
||||
})
|
||||
|
||||
const panelView = ref(null)
|
||||
const panelContainer = ref(null)
|
||||
|
||||
const viewPanel = ref({})
|
||||
|
||||
const wakeLock = ref(null)
|
||||
|
||||
onMounted(async () => {
|
||||
requestWakeLock()
|
||||
|
||||
const currentPanel = await panel.get(props.dirname)
|
||||
viewPanel.value = currentPanel
|
||||
|
||||
if (!isLocal() && settings.get('openLastPanel') && !CheckLocalPanel()) SavePanelToLocal()
|
||||
|
||||
viewPanel.value.html = StripPanelHTML(viewPanel.value.html, viewPanel.value.aspectRatio)
|
||||
SetPanelStyle(viewPanel.value.style)
|
||||
|
||||
|
|
@ -67,6 +78,8 @@ onMounted(async () => {
|
|||
onUnmounted(() => {
|
||||
RemovePanelStyle()
|
||||
RemovePanelScripts()
|
||||
|
||||
wakeLock.value.release()
|
||||
})
|
||||
|
||||
const viewPanelListeners = () => {
|
||||
|
|
@ -74,8 +87,20 @@ const viewPanelListeners = () => {
|
|||
RunMacro(viewPanel.value.macros[button.id])
|
||||
}
|
||||
|
||||
PanelButtonListeners(panelView.value, callback)
|
||||
PanelDialogListeners(panelView.value)
|
||||
PanelButtonListeners(panelContainer.value, callback)
|
||||
PanelDialogListeners(panelContainer.value)
|
||||
}
|
||||
|
||||
const requestWakeLock = async () => {
|
||||
try {
|
||||
if ('wakeLock' in navigator) {
|
||||
wakeLock.value = await navigator.wakeLock.request('screen')
|
||||
} else {
|
||||
console.warn('Wake Lock API not supported')
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`${err.name}, ${err.message}`)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
@ -88,7 +113,7 @@ const viewPanelListeners = () => {
|
|||
size-full
|
||||
bg-black;
|
||||
|
||||
.panel-preview__content {
|
||||
.panel-container {
|
||||
@apply relative
|
||||
grid
|
||||
justify-center
|
||||
|
|
|
|||
|
|
@ -31,7 +31,9 @@ import router from '@/router'
|
|||
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(createPinia())
|
||||
app.use(router)
|
||||
app.use(createPinia())
|
||||
|
||||
app.mount('#app')
|
||||
router.isReady().then(() => {
|
||||
app.mount('#app')
|
||||
})
|
||||
|
|
|
|||
|
|
@ -60,11 +60,11 @@ const router = createRouter({
|
|||
name: 'devices',
|
||||
component: () => import('../views/DevicesView.vue'),
|
||||
},
|
||||
// {
|
||||
// path: '/settings',
|
||||
// name: 'settings',
|
||||
// component: () => import('../views/SettingsView.vue'),
|
||||
// },
|
||||
{
|
||||
path: '/settings',
|
||||
name: 'settings',
|
||||
component: () => import('../views/SettingsView.vue'),
|
||||
},
|
||||
// {
|
||||
// path: '/about',
|
||||
// name: 'about',
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ You should have received a copy of the GNU General Public License
|
|||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { useNoticationStore } from '@/stores/notifications'
|
||||
|
||||
export const SetPanelStyle = (styleStr) => {
|
||||
const styleEl = document.createElement('style')
|
||||
styleEl.setAttribute('custom_panel_style', true)
|
||||
|
|
@ -118,3 +120,32 @@ export const PanelDialogListeners = (panelEl) => {
|
|||
})
|
||||
})
|
||||
}
|
||||
|
||||
const getPanelUrl = () => {
|
||||
const url = new URL(window.location.href)
|
||||
return url.pathname
|
||||
}
|
||||
|
||||
export const SavePanelToLocal = () => {
|
||||
localStorage.setItem('last_opened_panel', getPanelUrl())
|
||||
|
||||
const notificationStore = useNoticationStore()
|
||||
|
||||
notificationStore.add({
|
||||
message: 'Panel will be opened next launch',
|
||||
variant: 'success',
|
||||
time: 1000,
|
||||
})
|
||||
}
|
||||
|
||||
export const CheckLocalPanel = () => {
|
||||
const localPanel = localStorage.getItem('last_opened_panel')
|
||||
|
||||
if (localPanel) return localPanel == getPanelUrl()
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
export const GetLocalPanel = () => {
|
||||
return localStorage.getItem('last_opened_panel')
|
||||
}
|
||||
|
|
|
|||
20
ui/src/stores/notifications.js
Normal file
20
ui/src/stores/notifications.js
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
|
||||
export const useNoticationStore = defineStore('notications', () => {
|
||||
const list = ref({})
|
||||
|
||||
const add = (notification) => {
|
||||
list.value[Date.now()] = notification
|
||||
}
|
||||
|
||||
const remove = (id) => {
|
||||
delete list.value[id]
|
||||
}
|
||||
|
||||
return {
|
||||
list,
|
||||
add,
|
||||
remove,
|
||||
}
|
||||
})
|
||||
74
ui/src/stores/settings.js
Normal file
74
ui/src/stores/settings.js
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
|
||||
export const useSettingStore = defineStore('settings', () => {
|
||||
const list = ref({})
|
||||
|
||||
const get = (key, all = false) => {
|
||||
if (key === undefined) return list.value
|
||||
|
||||
if (!all) return list.value[key].value
|
||||
else return list.value[key]
|
||||
}
|
||||
|
||||
const set = (key, value) => {
|
||||
if (value === 'true' || value === 'false') value = value === 'true'
|
||||
|
||||
list.value[key].value = value
|
||||
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
const loadSettings = (returnObj = false) => {
|
||||
const settings = localStorage.getItem('settings')
|
||||
|
||||
if (settings) list.value = JSON.parse(settings)
|
||||
else list.value = loadDefaultSettings()
|
||||
|
||||
if (returnObj) return list.value
|
||||
}
|
||||
|
||||
const loadDefaultSettings = () => {
|
||||
const defaultSettings = {
|
||||
openLastPanel: {
|
||||
title: 'Open last panel',
|
||||
label: 'Open last panel',
|
||||
description: 'Open the last panel on startup',
|
||||
type: 'checkbox',
|
||||
value: true,
|
||||
remote: true,
|
||||
},
|
||||
mcrmPort: {
|
||||
title: 'Macrame port',
|
||||
label: 'Port',
|
||||
description:
|
||||
'The port that is used by Macrame, changing this will require a restart of the application.',
|
||||
type: 'number',
|
||||
value: window.__CONFIG__.MCRM__PORT,
|
||||
remote: false,
|
||||
disabled: true,
|
||||
},
|
||||
}
|
||||
|
||||
return defaultSettings
|
||||
}
|
||||
|
||||
const saveSettings = () => {
|
||||
localStorage.setItem('settings', JSON.stringify(list.value))
|
||||
}
|
||||
|
||||
const resetSettings = () => {
|
||||
localStorage.removeItem('settings')
|
||||
list.value = loadDefaultSettings()
|
||||
}
|
||||
|
||||
return {
|
||||
list,
|
||||
get,
|
||||
set,
|
||||
loadSettings,
|
||||
loadDefaultSettings,
|
||||
saveSettings,
|
||||
resetSettings,
|
||||
}
|
||||
})
|
||||
|
|
@ -45,7 +45,6 @@ import ButtonComp from '@/components/base/ButtonComp.vue'
|
|||
import PanelEdit from '@/components/panels/PanelEdit.vue'
|
||||
import PanelView from '@/components/panels/PanelView.vue'
|
||||
import PanelsOverview from '@/components/panels/PanelsOverview.vue'
|
||||
import { isLocal } from '@/services/ApiService'
|
||||
import { IconArrowLeft } from '@tabler/icons-vue'
|
||||
import { onMounted, onUpdated, reactive } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
|
|
|
|||
|
|
@ -20,9 +20,96 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
-->
|
||||
|
||||
<template>
|
||||
<div></div>
|
||||
<div id="settings" class="panel">
|
||||
<h1 class="flex items-end justify-between !w-full panel__title">
|
||||
<div>Settings</div>
|
||||
<ButtonComp variant="subtle" size="sm" @click="settings.resetSettings()">
|
||||
<IconRestore /> Reset
|
||||
</ButtonComp>
|
||||
</h1>
|
||||
|
||||
<div class="panel__content">
|
||||
<div class="settings-container">
|
||||
<template
|
||||
class="setting-block mcrm-block block__dark"
|
||||
v-for="(setting, name) in settingList"
|
||||
:key="setting"
|
||||
>
|
||||
<div
|
||||
v-if="(setting.remote && !isLocal()) || (!setting.remote && isLocal())"
|
||||
class="setting-block mcrm-block block__dark"
|
||||
>
|
||||
<h4>{{ setting.title }}</h4>
|
||||
<p class="text-sm">
|
||||
<em>{{ setting.description }}</em>
|
||||
</p>
|
||||
<FormInput
|
||||
:type="setting.type"
|
||||
:label="setting.label"
|
||||
:name="name"
|
||||
:value="setting.value"
|
||||
:options="setting.options"
|
||||
:disabled="setting.disabled"
|
||||
:horizontal="true"
|
||||
@onChange="updateSetting(name, setting.type, $event)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup></script>
|
||||
<script setup>
|
||||
import ButtonComp from '@/components/base/ButtonComp.vue'
|
||||
import { IconRestore } from '@tabler/icons-vue'
|
||||
import FormInput from '@/components/form/FormInput.vue'
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
import { isLocal } from '@/services/ApiService'
|
||||
|
||||
import { useSettingStore } from '@/stores/settings'
|
||||
import { useNoticationStore } from '@/stores/notifications'
|
||||
|
||||
import { computed, onMounted } from 'vue'
|
||||
|
||||
const settings = useSettingStore()
|
||||
const notifications = useNoticationStore()
|
||||
|
||||
const settingList = computed(() => settings.list)
|
||||
|
||||
onMounted(() => {})
|
||||
|
||||
function updateSetting(name, type, target) {
|
||||
if (type == 'checkbox' || type == 'radio') target.value = target.checked
|
||||
|
||||
settings.set(name, target.value)
|
||||
|
||||
notifications.add({
|
||||
title: 'Settings updated',
|
||||
message: 'Settings have been updated',
|
||||
variant: 'success',
|
||||
time: 5000,
|
||||
closable: true,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@reference "@/assets/main.css";
|
||||
|
||||
.settings-container {
|
||||
@apply grid
|
||||
grid-cols-1
|
||||
sm:grid-cols-2
|
||||
lg:grid-cols-3
|
||||
items-start
|
||||
gap-4
|
||||
pt-8;
|
||||
|
||||
.setting-block {
|
||||
@apply grid
|
||||
gap-3
|
||||
content-start;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue