aboutsummaryrefslogtreecommitdiffstats
path: root/apps/dav/src
diff options
context:
space:
mode:
Diffstat (limited to 'apps/dav/src')
-rw-r--r--apps/dav/src/components/ExampleContactSettings.vue (renamed from apps/dav/src/views/ExampleContactSettings.vue)104
-rw-r--r--apps/dav/src/components/ExampleContentDownloadButton.vue57
-rw-r--r--apps/dav/src/components/ExampleEventSettings.vue217
-rw-r--r--apps/dav/src/service/ExampleEventService.js43
-rw-r--r--apps/dav/src/settings-example-content.js11
-rw-r--r--apps/dav/src/views/ExampleContentSettingsSection.vue38
6 files changed, 421 insertions, 49 deletions
diff --git a/apps/dav/src/views/ExampleContactSettings.vue b/apps/dav/src/components/ExampleContactSettings.vue
index d4d1c7d31d0..cdfdc130189 100644
--- a/apps/dav/src/views/ExampleContactSettings.vue
+++ b/apps/dav/src/components/ExampleContactSettings.vue
@@ -4,35 +4,34 @@
-->
<template>
- <NcSettingsSection id="exmaple-content"
- :name="$t('dav', 'Example Content')"
- class="example-content-setting"
- :description="$t('dav', 'Set example content to be created on new user first login.')">
- <div class="example-content-setting__contacts">
- <input id="enable-default-contact"
- v-model="enableDefaultContact"
- type="checkbox"
- class="checkbox"
- @change="updateEnableDefaultContact">
- <label for="enable-default-contact"> {{ $t('dav',"Default contact is added to the user's own address book on user's first login.") }} </label>
- <div v-if="enableDefaultContact" class="example-content-setting__contacts__buttons">
- <NcButton type="primary"
- class="example-content-setting__contacts__buttons__button"
- @click="toggleModal">
- <template #icon>
- <IconUpload :size="20" />
- </template>
- {{ $t('dav', 'Import contact') }}
- </NcButton>
- <NcButton type="secondary"
- class="example-content-setting__contacts__buttons__button"
- @click="resetContact">
- <template #icon>
- <IconRestore :size="20" />
- </template>
- {{ $t('dav', 'Reset to default contact') }}
- </NcButton>
- </div>
+ <div class="example-contact-settings">
+ <NcCheckboxRadioSwitch :checked="enableDefaultContact"
+ type="switch"
+ @update:model-value="updateEnableDefaultContact">
+ {{ $t('dav', "Add example contact to user's address book when they first log in") }}
+ </NcCheckboxRadioSwitch>
+ <div v-if="enableDefaultContact" class="example-contact-settings__buttons">
+ <ExampleContentDownloadButton :href="downloadUrl">
+ <template #icon>
+ <IconAccount :size="20" />
+ </template>
+ example_contact.vcf
+ </ExampleContentDownloadButton>
+ <NcButton type="secondary"
+ @click="toggleModal">
+ <template #icon>
+ <IconUpload :size="20" />
+ </template>
+ {{ $t('dav', 'Import contact') }}
+ </NcButton>
+ <NcButton v-if="hasCustomDefaultContact"
+ type="tertiary"
+ @click="resetContact">
+ <template #icon>
+ <IconRestore :size="20" />
+ </template>
+ {{ $t('dav', 'Reset to default') }}
+ </NcButton>
</div>
<NcDialog :open.sync="isModalOpen"
:name="$t('dav', 'Import contacts')"
@@ -48,33 +47,40 @@
accept=".vcf"
class="hidden-visually"
@change="processFile">
- </NcSettingsSection>
+ </div>
</template>
<script>
import axios from '@nextcloud/axios'
import { generateUrl } from '@nextcloud/router'
import { loadState } from '@nextcloud/initial-state'
-import { NcDialog, NcButton, NcSettingsSection } from '@nextcloud/vue'
+import { NcDialog, NcButton, NcCheckboxRadioSwitch } from '@nextcloud/vue'
import { showError, showSuccess } from '@nextcloud/dialogs'
import IconUpload from 'vue-material-design-icons/Upload.vue'
import IconRestore from 'vue-material-design-icons/Restore.vue'
+import IconAccount from 'vue-material-design-icons/Account.vue'
import IconCancel from '@mdi/svg/svg/cancel.svg?raw'
import IconCheck from '@mdi/svg/svg/check.svg?raw'
+import logger from '../service/logger.js'
+import ExampleContentDownloadButton from './ExampleContentDownloadButton.vue'
-const enableDefaultContact = loadState('dav', 'enableDefaultContact') === 'yes'
+const enableDefaultContact = loadState('dav', 'enableDefaultContact')
+const hasCustomDefaultContact = loadState('dav', 'hasCustomDefaultContact')
export default {
name: 'ExampleContactSettings',
components: {
NcDialog,
NcButton,
- NcSettingsSection,
+ NcCheckboxRadioSwitch,
IconUpload,
IconRestore,
+ IconAccount,
+ ExampleContentDownloadButton,
},
data() {
return {
enableDefaultContact,
+ hasCustomDefaultContact,
isModalOpen: false,
loading: false,
buttons: [
@@ -92,12 +98,18 @@ export default {
],
}
},
+ computed: {
+ downloadUrl() {
+ return generateUrl('/apps/dav/api/defaultcontact/contact')
+ },
+ },
methods: {
updateEnableDefaultContact() {
axios.put(generateUrl('apps/dav/api/defaultcontact/config'), {
- allow: this.enableDefaultContact ? 'yes' : 'no',
- }).catch(() => {
+ allow: !this.enableDefaultContact,
+ }).then(() => {
this.enableDefaultContact = !this.enableDefaultContact
+ }).catch(() => {
showError(this.$t('dav', 'Error while saving settings'))
})
},
@@ -111,10 +123,11 @@ export default {
this.loading = true
axios.put(generateUrl('/apps/dav/api/defaultcontact/contact'))
.then(() => {
+ this.hasCustomDefaultContact = false
showSuccess(this.$t('dav', 'Contact reset successfully'))
})
.catch((error) => {
- console.error('Error importing contact:', error)
+ logger.error('Error importing contact:', { error })
showError(this.$t('dav', 'Error while resetting contact'))
})
.finally(() => {
@@ -131,9 +144,10 @@ export default {
this.isModalOpen = false
try {
await axios.put(generateUrl('/apps/dav/api/defaultcontact/contact'), { contactData: reader.result })
+ this.hasCustomDefaultContact = true
showSuccess(this.$t('dav', 'Contact imported successfully'))
} catch (error) {
- console.error('Error importing contact:', error)
+ logger.error('Error importing contact:', { error })
showError(this.$t('dav', 'Error while importing contact'))
} finally {
this.loading = false
@@ -146,15 +160,13 @@ export default {
}
</script>
<style lang="scss" scoped>
-.example-content-setting{
- &__contacts{
- &__buttons{
- margin-top: 1rem;
- display: flex;
- &__button{
- margin-inline-end: 5px;
- }
- }
+.example-contact-settings {
+ margin-block-start: 2rem;
+
+ &__buttons {
+ display: flex;
+ gap: calc(var(--default-grid-baseline) * 2);
+ margin-top: calc(var(--default-grid-baseline) * 2);
}
}
</style>
diff --git a/apps/dav/src/components/ExampleContentDownloadButton.vue b/apps/dav/src/components/ExampleContentDownloadButton.vue
new file mode 100644
index 00000000000..d3ee793eddc
--- /dev/null
+++ b/apps/dav/src/components/ExampleContentDownloadButton.vue
@@ -0,0 +1,57 @@
+<!--
+ - SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ - SPDX-License-Identifier: AGPL-3.0-or-later
+ -->
+
+<template>
+ <NcButton type="tertiary" :href="href">
+ <template #icon>
+ <slot name="icon" />
+ </template>
+ <div class="download-button">
+ <span class="download-button__label">
+ <slot name="default" />
+ </span>
+ <IconDownload class="download-button__icon"
+ :size="20" />
+ </div>
+ </NcButton>
+</template>
+
+<script>
+import { NcButton } from '@nextcloud/vue'
+import IconDownload from 'vue-material-design-icons/Download.vue'
+
+export default {
+ name: 'ExampleContentDownloadButton',
+ components: {
+ NcButton,
+ IconDownload
+ },
+ props: {
+ href: {
+ type: String,
+ required: true,
+ }
+ },
+}
+</script>
+
+<style lang="scss" scoped>
+.download-button {
+ display: flex;
+ max-width: 200px;
+
+ &__label {
+ font-weight: initial;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
+ }
+
+ &__icon {
+ margin-top: 2px;
+ margin-inline-start: var(--default-grid-baseline);
+ }
+}
+</style>
diff --git a/apps/dav/src/components/ExampleEventSettings.vue b/apps/dav/src/components/ExampleEventSettings.vue
new file mode 100644
index 00000000000..c7a90b71a4a
--- /dev/null
+++ b/apps/dav/src/components/ExampleEventSettings.vue
@@ -0,0 +1,217 @@
+<!--
+ - SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ - SPDX-License-Identifier: AGPL-3.0-or-later
+ -->
+
+<template>
+ <div class="example-event-settings">
+ <NcCheckboxRadioSwitch :checked="createExampleEvent"
+ :disabled="savingConfig"
+ type="switch"
+ @update:model-value="updateCreateExampleEvent">
+ {{ t('dav', "Add example event to user's calendar when they first log in") }}
+ </NcCheckboxRadioSwitch>
+ <div v-if="createExampleEvent"
+ class="example-event-settings__buttons">
+ <ExampleContentDownloadButton :href="downloadUrl">
+ <template #icon>
+ <IconCalendarBlank :size="20" />
+ </template>
+ example_event.ics
+ </ExampleContentDownloadButton>
+ <NcButton type="secondary"
+ @click="showImportModal = true">
+ <template #icon>
+ <IconUpload :size="20" />
+ </template>
+ {{ t('dav', 'Import calendar event') }}
+ </NcButton>
+ <NcButton v-if="hasCustomEvent"
+ type="tertiary"
+ :disabled="deleting"
+ @click="deleteCustomEvent">
+ <template #icon>
+ <IconRestore :size="20" />
+ </template>
+ {{ t('dav', 'Reset to default') }}
+ </NcButton>
+ </div>
+ <NcDialog :open.sync="showImportModal"
+ :name="t('dav', 'Import calendar event')">
+ <div class="import-event-modal">
+ <p>
+ {{ t('dav', 'Uploading a new event will overwrite the existing one.') }}
+ </p>
+ <input ref="event-file"
+ :disabled="uploading"
+ type="file"
+ accept=".ics,text/calendar"
+ class="import-event-modal__file-picker"
+ @change="selectFile" />
+ <div class="import-event-modal__buttons">
+ <NcButton :disabled="uploading || !selectedFile"
+ type="primary"
+ @click="uploadCustomEvent()">
+ <template #icon>
+ <IconUpload :size="20" />
+ </template>
+ {{ t('dav', 'Upload event') }}
+ </NcButton>
+ </div>
+ </div>
+ </NcDialog>
+ </div>
+</template>
+
+<script>
+import { NcButton, NcCheckboxRadioSwitch, NcDialog } from '@nextcloud/vue'
+import { loadState } from '@nextcloud/initial-state'
+import IconCalendarBlank from 'vue-material-design-icons/CalendarBlank.vue'
+import IconUpload from 'vue-material-design-icons/Upload.vue'
+import IconRestore from 'vue-material-design-icons/Restore.vue'
+import * as ExampleEventService from '../service/ExampleEventService.js'
+import { showError, showSuccess } from '@nextcloud/dialogs'
+import logger from '../service/logger.js'
+import { generateUrl } from '@nextcloud/router'
+import ExampleContentDownloadButton from './ExampleContentDownloadButton.vue'
+
+export default {
+ name: 'ExampleEventSettings',
+ components: {
+ NcButton,
+ NcCheckboxRadioSwitch,
+ NcDialog,
+ IconCalendarBlank,
+ IconUpload,
+ IconRestore,
+ ExampleContentDownloadButton,
+ },
+ data() {
+ return {
+ createExampleEvent: loadState('dav', 'create_example_event', false),
+ hasCustomEvent: loadState('dav', 'has_custom_example_event', false),
+ showImportModal: false,
+ uploading: false,
+ deleting: false,
+ savingConfig: false,
+ selectedFile: undefined,
+ }
+ },
+ computed: {
+ downloadUrl() {
+ return generateUrl('/apps/dav/api/exampleEvent/event')
+ },
+ },
+ methods: {
+ selectFile() {
+ this.selectedFile = this.$refs['event-file']?.files[0]
+ },
+ async updateCreateExampleEvent() {
+ this.savingConfig = true
+
+ const enable = !this.createExampleEvent
+ try {
+ await ExampleEventService.setCreateExampleEvent(enable)
+ } catch (error) {
+ showError(t('dav', 'Failed to save example event creation setting'))
+ logger.error('Failed to save example event creation setting', {
+ error,
+ enable,
+ })
+ } finally {
+ this.savingConfig = false
+ }
+
+ this.createExampleEvent = enable
+ },
+ uploadCustomEvent() {
+ if (!this.selectedFile) {
+ return
+ }
+
+ this.uploading = true
+
+ const reader = new FileReader()
+ reader.addEventListener('load', async () => {
+ const ics = reader.result
+
+ try {
+ await ExampleEventService.uploadExampleEvent(ics)
+ } catch (error) {
+ showError(t('dav', 'Failed to upload the example event'))
+ logger.error('Failed to upload example ICS', {
+ error,
+ ics,
+ })
+ return
+ } finally {
+ this.uploading = false
+ }
+
+ showSuccess(t('dav', 'Custom example event was saved successfully'))
+ this.showImportModal = false
+ this.hasCustomEvent = true
+ })
+ reader.readAsText(this.selectedFile)
+ },
+ async deleteCustomEvent() {
+ this.deleting = true
+
+ try {
+ await ExampleEventService.deleteExampleEvent()
+ } catch (error) {
+ showError(t('dav', 'Failed to delete the custom example event'))
+ logger.error('Failed to delete the custom example event', {
+ error,
+ })
+ return
+ } finally {
+ this.deleting = false
+ }
+
+ showSuccess(t('dav', 'Custom example event was deleted successfully'))
+ this.hasCustomEvent = false
+ },
+ },
+}
+</script>
+
+<style lang="scss" scoped>
+.example-event-settings {
+ margin-block: 2rem;
+
+ &__buttons {
+ display: flex;
+ gap: calc(var(--default-grid-baseline) * 2);
+ margin-top: calc(var(--default-grid-baseline) * 2);
+
+ &__download-link {
+ display: flex;
+ max-width: 100px;
+
+ &__label {
+ text-decoration: underline;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
+ }
+ }
+ }
+}
+
+.import-event-modal {
+ display: flex;
+ flex-direction: column;
+ gap: calc(var(--default-grid-baseline) * 2);
+ padding: calc(var(--default-grid-baseline) * 2);
+
+ &__file-picker {
+ width: 100%;
+ }
+
+ &__buttons {
+ display: flex;
+ justify-content: flex-end;
+ }
+}
+</style>
diff --git a/apps/dav/src/service/ExampleEventService.js b/apps/dav/src/service/ExampleEventService.js
new file mode 100644
index 00000000000..a39e3641bd9
--- /dev/null
+++ b/apps/dav/src/service/ExampleEventService.js
@@ -0,0 +1,43 @@
+/**
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+import { generateUrl } from '@nextcloud/router'
+import axios from '@nextcloud/axios'
+
+/**
+ * Configure the creation of example events on a user's first login.
+ *
+ * @param {boolean} enable Whether to enable or disable the feature.
+ * @return {Promise<void>}
+ */
+export async function setCreateExampleEvent(enable) {
+ const url = generateUrl('/apps/dav/api/exampleEvent/enable')
+ await axios.post(url, {
+ enable,
+ })
+}
+
+/**
+ * Upload a custom example event.
+ *
+ * @param {string} ics The ICS data of the event.
+ * @return {Promise<void>}
+ */
+export async function uploadExampleEvent(ics) {
+ const url = generateUrl('/apps/dav/api/exampleEvent/event')
+ await axios.post(url, {
+ ics,
+ })
+}
+
+/**
+ * Delete a previously uploaded custom example event.
+ *
+ * @return {Promise<void>}
+ */
+export async function deleteExampleEvent() {
+ const url = generateUrl('/apps/dav/api/exampleEvent/event')
+ await axios.delete(url)
+}
diff --git a/apps/dav/src/settings-example-content.js b/apps/dav/src/settings-example-content.js
index f1374027bf3..ca0291ace4f 100644
--- a/apps/dav/src/settings-example-content.js
+++ b/apps/dav/src/settings-example-content.js
@@ -4,10 +4,15 @@
*/
import Vue from 'vue'
import { translate } from '@nextcloud/l10n'
-import ExampleContactSettings from './views/ExampleContactSettings.vue'
+import ExampleContentSettingsSection from './views/ExampleContentSettingsSection.vue'
-Vue.prototype.$t = translate
+Vue.mixin({
+ methods: {
+ t: translate,
+ $t: translate,
+ }
+})
-const View = Vue.extend(ExampleContactSettings);
+const View = Vue.extend(ExampleContentSettingsSection);
(new View({})).$mount('#settings-example-content')
diff --git a/apps/dav/src/views/ExampleContentSettingsSection.vue b/apps/dav/src/views/ExampleContentSettingsSection.vue
new file mode 100644
index 00000000000..5e65a1ba3b4
--- /dev/null
+++ b/apps/dav/src/views/ExampleContentSettingsSection.vue
@@ -0,0 +1,38 @@
+<!--
+ - SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ - SPDX-License-Identifier: AGPL-3.0-or-later
+-->
+
+<template>
+ <NcSettingsSection id="example-content"
+ :name="$t('dav', 'Example content')"
+ class="example-content-setting"
+ :description="$t('dav', 'Example content serves to showcase the features of Nextcloud. Default content is shipped with Nextcloud, and can be replaced by custom content.')">
+ <ExampleContactSettings v-if="hasContactsApp" />
+ <ExampleEventSettings v-if="hasCalendarApp" />
+ </NcSettingsSection>
+</template>
+
+<script>
+import { loadState } from '@nextcloud/initial-state'
+import { NcSettingsSection } from '@nextcloud/vue'
+import ExampleEventSettings from '../components/ExampleEventSettings.vue'
+import ExampleContactSettings from '../components/ExampleContactSettings.vue'
+
+export default {
+ name: 'ExampleContentSettingsSection',
+ components: {
+ NcSettingsSection,
+ ExampleContactSettings,
+ ExampleEventSettings,
+ },
+ computed: {
+ hasContactsApp() {
+ return loadState('dav', 'contactsEnabled')
+ },
+ hasCalendarApp() {
+ return loadState('dav', 'calendarEnabled')
+ },
+ }
+}
+</script>