Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Forms.vue
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@
:form="selectedForm"
:sidebarOpened="sidebarOpened"
:active="sidebarActive"
@update:form="updateSelectedForm"
@update:sidebarOpened="sidebarOpened = $event"
@update:active="sidebarActive = $event" />
</template>
Expand Down
216 changes: 111 additions & 105 deletions src/components/AppNavigationForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,8 @@ import { showConfirmation, showError } from '@nextcloud/dialogs'
import { t } from '@nextcloud/l10n'
import moment from '@nextcloud/moment'
import { generateOcsUrl } from '@nextcloud/router'
import { defineComponent } from 'vue'
import { computed, defineComponent, ref } from 'vue'
import { useRoute } from 'vue-router'
import NcActionButton from '@nextcloud/vue/components/NcActionButton'
import NcActionRouter from '@nextcloud/vue/components/NcActionRouter'
import NcActionSeparator from '@nextcloud/vue/components/NcActionSeparator'
Expand Down Expand Up @@ -161,176 +162,175 @@ export default defineComponent({

emits: ['mobileCloseNavigation', 'openSharing', 'clone', 'delete'],

setup() {
return {
t,
FormsIcon,
IconArchive,
IconArchiveOff,
IconCheck,
IconContentCopy,
IconDelete,
IconPencil,
IconPoll,
IconShareVariant,
}
},

data() {
return {
loading: false as boolean,
}
},
setup(props, { emit }) {
const route = useRoute()
const loading = ref(false)

computed: {
canEdit(): boolean {
return this.form.permissions.includes(PERMISSION_TYPES.PERMISSION_EDIT)
},

canSeeResults(): boolean {
return (
this.form.permissions.includes(PERMISSION_TYPES.PERMISSION_RESULTS)
|| (this.form.submissionCount ?? 0) > 0
)
},
const canEdit = computed(() => {
return props.form.permissions.includes(PERMISSION_TYPES.PERMISSION_EDIT)
})
const canSeeResults = computed(
() =>
props.form.permissions.includes(PERMISSION_TYPES.PERMISSION_RESULTS)
|| (props.form.submissionCount ?? 0) > 0,
)

/**
* Check if form is current form and set active
*/
isActive(): boolean {
return this.form.hash === String(this.$route.params.hash)
},
const isActive = computed(
() => props.form.hash === String(route.params.hash),
)

/**
* Check if the form is archived
*/
isArchived(): boolean {
return this.form.state === FormState.FormArchived
},
const isArchived = computed(
() => props.form.state === FormState.FormArchived,
)

/**
* Check if form is expired
*/
isExpired(): boolean {
return Boolean(this.form.expires && moment().unix() > this.form.expires)
},
const isExpired = computed(() =>
Boolean(props.form.expires && moment().unix() > props.form.expires),
)

/**
* Check if form is locked
*/
isFormLocked(): boolean {
const isFormLocked = computed(() => {
const currentUserUid = getCurrentUser()?.uid ?? ''
const lockedUntil = this.form.lockedUntil ?? -1
const lockedUntil = props.form.lockedUntil ?? -1
return (
lockedUntil === 0
|| (lockedUntil > moment().unix()
&& this.form.lockedBy !== currentUserUid)
&& props.form.lockedBy !== currentUserUid)
)
},
})

/**
* Return form title, or placeholder if not set
*
* @return
*/
formTitle(): string {
if (this.form.title) {
return this.form.title
const formTitle = computed(() => {
if (props.form.title) {
return props.form.title
}
return t('forms', 'New form')
},
})

/**
* Return expiration details for subtitle
*/
formSubtitle(): string {
if (this.form.state === FormState.FormClosed) {
// TRANSLATORS: The form was closed manually so it does not take new submissions
const formSubtitle = computed(() => {
if (props.form.state === FormState.FormClosed) {
return t('forms', 'Form closed')
}
if (this.form.expires) {
const relativeDate = moment(this.form.expires, 'X')
if (props.form.expires) {
const relativeDate = moment(props.form.expires, 'X')
.locale(window.OC.getLanguage())
.fromNow()
if (this.isExpired) {
if (isExpired.value) {
return t('forms', 'Expired {relativeDate}', {
relativeDate,
})
}
return t('forms', 'Expires {relativeDate}', { relativeDate })
}
return ''
},
})

/**
* Return, if form has Subtitle
*/
hasSubtitle(): boolean {
return this.formSubtitle !== ''
},
const hasSubtitle = computed(() => formSubtitle.value !== '')

/**
* Route to use, depending on readOnly
*
* @return Route to 'submit' or 'formRoot'
*/
routerTarget(): NavigationTarget {
if (this.readOnly) {
const routerTarget = computed<NavigationTarget>(() => {
if (props.readOnly) {
return 'submit'
}

return 'formRoot'
},
},
})

methods: {
/**
* Closes the App-Navigation on mobile-devices
*/
mobileCloseNavigation(): void {
this.$emit('mobileCloseNavigation')
},
const mobileCloseNavigation = (): void => {
emit('mobileCloseNavigation')
}

onShareForm(): void {
this.$emit('openSharing', this.form.hash)
},
const onShareForm = (): void => {
emit('openSharing', props.form.hash)
}

onCloneForm(): void {
this.$emit('clone', this.form.id)
},
const onCloneForm = (): void => {
emit('clone', props.form.id)
}

const onDeleteForm = async (): Promise<void> => {
loading.value = true
try {
await axios.delete(
generateOcsUrl('apps/forms/api/v3/forms/{id}', {
id: props.form.id,
}),
)
emit('delete', props.form.id)
} catch (error) {
const response = (error as { response?: unknown }).response
logger.error(`Error while deleting ${formTitle.value}`, {
error: response,
})
showError(
t('forms', 'Error while deleting {title}', {
title: formTitle.value,
}),
)
} finally {
loading.value = false
}
}

async onConfirmDelete(): Promise<void> {
const onConfirmDelete = async (): Promise<void> => {
const shouldDelete = await showConfirmation({
name: t('forms', 'Delete form'),
text: t('forms', 'Are you sure you want to delete {title}?', {
title: this.formTitle,
title: formTitle.value,
}),
labelConfirm: t('forms', 'Delete form'),
labelReject: t('forms', 'Cancel'),
})

if (shouldDelete) {
await this.onDeleteForm()
await onDeleteForm()
}
},
}

async onToggleArchive(): Promise<void> {
const onToggleArchive = async (): Promise<void> => {
try {
// TODO: add loading status feedback ?
await axios.patch(
generateOcsUrl('apps/forms/api/v3/forms/{id}', {
id: this.form.id,
id: props.form.id,
}),
{
keyValuePairs: {
state: this.isArchived
state: isArchived.value
? FormState.FormClosed
: FormState.FormArchived,
},
},
)

;(this.form as FormsForm).state = this.isArchived
;(props.form as FormsForm).state = isArchived.value
? FormState.FormClosed
: FormState.FormArchived
} catch (error) {
Expand All @@ -339,31 +339,37 @@ export default defineComponent({
})
showError(t('forms', 'Error changing archived state of form'))
}
},
}

async onDeleteForm(): Promise<void> {
this.loading = true
try {
await axios.delete(
generateOcsUrl('apps/forms/api/v3/forms/{id}', {
id: this.form.id,
}),
)
this.$emit('delete', this.form.id)
} catch (error) {
const response = (error as { response?: unknown }).response
logger.error(`Error while deleting ${this.formTitle}`, {
error: response,
})
showError(
t('forms', 'Error while deleting {title}', {
title: this.formTitle,
}),
)
} finally {
this.loading = false
}
},
return {
t,
FormsIcon,
IconArchive,
IconArchiveOff,
IconCheck,
IconContentCopy,
IconDelete,
IconPencil,
IconPoll,
IconShareVariant,
loading,
canEdit,
canSeeResults,
isActive,
isArchived,
isExpired,
isFormLocked,
formTitle,
formSubtitle,
hasSubtitle,
routerTarget,
mobileCloseNavigation,
onShareForm,
onCloneForm,
onConfirmDelete,
onToggleArchive,
onDeleteForm,
}
},
})
</script>
Loading
Loading