Skip to content
Draft
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
54 changes: 53 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
"lint:fix": "eslint --ext .js,.ts,.vue src --fix",
"start:nextcloud": "node playwright/start-nextcloud-server.mjs",
"stylelint": "stylelint \"src/**/*.scss\" \"src/**/*.vue\"",
"stylelint:fix": "stylelint \"src/**/*.scss\" \"src/**/*.vue\" --fix"
"stylelint:fix": "stylelint \"src/**/*.scss\" \"src/**/*.vue\" --fix",
"typecheck": "vue-tsc --noEmit -p tsconfig.json"
},
"browserslist": [
"extends @nextcloud/browserslist-config"
Expand Down Expand Up @@ -59,11 +60,13 @@
"@nextcloud/vite-config": "^2.5.2",
"@playwright/test": "^1.61.1",
"@types/node": "^26.1.1",
"@types/qrcode": "^1.5.6",
"@vue/tsconfig": "^0.9.1",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.6",
"prettier": "^3.9.5",
"vite": "^7.3.6"
"vite": "^7.3.6",
"vue-tsc": "^3.3.7"
},
"engines": {
"node": "^24.0.0",
Expand Down
79 changes: 47 additions & 32 deletions src/components/AppNavigationForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,10 @@
</NcListItem>
</template>

<script>
<script lang="ts">
import type { PropType } from 'vue'
import type { FormsForm } from '../models/Entities.d.ts'

import IconArchive from '@material-symbols/svg-400/outlined/archive.svg?raw'
import IconPoll from '@material-symbols/svg-400/outlined/bar_chart.svg?raw'
import IconCheck from '@material-symbols/svg-400/outlined/check.svg?raw'
Expand All @@ -109,8 +112,10 @@ import IconArchiveOff from '@material-symbols/svg-400/outlined/unarchive.svg?raw
import { getCurrentUser } from '@nextcloud/auth'
import axios from '@nextcloud/axios'
import { showConfirmation, showError } from '@nextcloud/dialogs'
import { translate as t } from '@nextcloud/l10n'
import moment from '@nextcloud/moment'
import { generateOcsUrl } from '@nextcloud/router'
import { defineComponent } from 'vue'
import NcActionButton from '@nextcloud/vue/components/NcActionButton'
import NcActionRouter from '@nextcloud/vue/components/NcActionRouter'
import NcActionSeparator from '@nextcloud/vue/components/NcActionSeparator'
Expand All @@ -122,7 +127,13 @@ import PermissionTypes from '../mixins/PermissionTypes.ts'
import { FormState } from '../models/Constants.ts'
import logger from '../utils/Logger.ts'

export default {
type NavigationTarget = 'submit' | 'formRoot'

interface AppNavigationFormData {
loading: boolean
}

export default defineComponent({
name: 'AppNavigationForm',

components: {
Expand All @@ -138,7 +149,7 @@ export default {

props: {
form: {
type: Object,
type: Object as PropType<FormsForm>,
required: true,
},

Expand All @@ -158,6 +169,7 @@ export default {

setup() {
return {
t,
FormsIcon,
IconArchive,
IconArchiveOff,
Expand All @@ -170,65 +182,67 @@ export default {
}
},

data() {
data(): AppNavigationFormData {
return {
loading: false,
}
},

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

canSeeResults() {
canSeeResults(): boolean {
return (
this.form.permissions.includes(
this.PERMISSION_TYPES.PERMISSION_RESULTS,
) || this.form.submissionCount > 0
) || (this.form.submissionCount ?? 0) > 0
)
},

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

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

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

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

/**
* Return form title, or placeholder if not set
*
* @return {string}
* @return
*/
formTitle() {
formTitle(): string {
if (this.form.title) {
return this.form.title
}
Expand All @@ -238,7 +252,7 @@ export default {
/**
* Return expiration details for subtitle
*/
formSubtitle() {
formSubtitle(): string {
if (this.form.state === FormState.FormClosed) {
// TRANSLATORS: The form was closed manually so it does not take new submissions
return t('forms', 'Form closed')
Expand All @@ -260,16 +274,16 @@ export default {
/**
* Return, if form has Subtitle
*/
hasSubtitle() {
hasSubtitle(): boolean {
return this.formSubtitle !== ''
},

/**
* Route to use, depending on readOnly
*
* @return {string} Route to 'submit' or 'formRoot'
* @return Route to 'submit' or 'formRoot'
*/
routerTarget() {
routerTarget(): NavigationTarget {
if (this.readOnly) {
return 'submit'
}
Expand All @@ -282,19 +296,19 @@ export default {
/**
* Closes the App-Navigation on mobile-devices
*/
mobileCloseNavigation() {
mobileCloseNavigation(): void {
this.$emit('mobileCloseNavigation')
},

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

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

async onConfirmDelete() {
async onConfirmDelete(): Promise<void> {
const shouldDelete = await showConfirmation({
name: t('forms', 'Delete form'),
text: t('forms', 'Are you sure you want to delete {title}?', {
Expand All @@ -309,7 +323,7 @@ export default {
}
},

async onToggleArchive() {
async onToggleArchive(): Promise<void> {
try {
// TODO: add loading status feedback ?
await axios.patch(
Expand All @@ -324,8 +338,8 @@ export default {
},
},
)
// eslint-disable-next-line vue/no-mutating-props
this.form.state = this.isArchived

;(this.form as FormsForm).state = this.isArchived
? FormState.FormClosed
: FormState.FormArchived
} catch (error) {
Expand All @@ -336,7 +350,7 @@ export default {
}
},

async onDeleteForm() {
async onDeleteForm(): Promise<void> {
this.loading = true
try {
await axios.delete(
Expand All @@ -346,8 +360,9 @@ export default {
)
this.$emit('delete', this.form.id)
} catch (error) {
const response = (error as { response?: unknown }).response
logger.error(`Error while deleting ${this.formTitle}`, {
error: error.response,
error: response,
})
showError(
t('forms', 'Error while deleting {title}', {
Expand All @@ -359,5 +374,5 @@ export default {
}
},
},
}
})
</script>
Loading
Loading