Skip to content

Commit

Permalink
feat: Add form state handling (e.g. archived) to the frontend
Browse files Browse the repository at this point in the history
Co-authored-by: Chartman123 <chris-hartmann@gmx.de>
Co-authored-by: Ferdinand Thiessen <opensource@fthiessen.de>
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
  • Loading branch information
susnux and Chartman123 committed Mar 20, 2024
1 parent 69a68c4 commit 262666b
Show file tree
Hide file tree
Showing 13 changed files with 604 additions and 241 deletions.
7 changes: 3 additions & 4 deletions lib/Controller/ApiController.php
Original file line number Diff line number Diff line change
Expand Up @@ -114,15 +114,14 @@ public function getForms(): DataResponse {
* @return DataResponse
*/
public function getSharedForms(): DataResponse {
$forms = $this->formMapper->findAll();
$forms = $this->formMapper->findActiveForms();

$result = [];
foreach ($forms as $form) {
// Check if the form should be shown on sidebar
if (!$this->formsService->isSharedFormShown($form)) {
continue;
if ($this->formsService->isSharedFormShown($form)) {
$result[] = $this->formsService->getPartialFormArray($form);
}
$result[] = $this->formsService->getPartialFormArray($form);
}

return new DataResponse($result);
Expand Down
24 changes: 24 additions & 0 deletions lib/Db/FormMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,30 @@ public function findAllByOwnerId(string $ownerId): array {
return $this->findEntities($qb);
}

/**
* Get only active forms - thus not archived, closed or expired
* @return Form[]
*/
public function findActiveForms(): array {
$qb = $this->db->getQueryBuilder();

$expires = $qb->expr()->orX();
$expires->add($qb->expr()->eq('expires', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)));
$expires->add($qb->expr()->gt('expires', $qb->createNamedParameter(time(), IQueryBuilder::PARAM_INT)));

$state = $qb->expr()->eq('state', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT));

$qb->select('*')
->from($this->getTableName())
->where($expires)
->andWhere($state)
//Last updated forms first, then newest forms first
->addOrderBy('last_updated', 'DESC')
->addOrderBy('created', 'DESC');

return $this->findEntities($qb);
}

/**
* Delete a Form including connected Questions, Submissions and shares.
* @param Form $form The form instance to delete
Expand Down
3 changes: 1 addition & 2 deletions lib/Service/FormsService.php
Original file line number Diff line number Diff line change
Expand Up @@ -389,8 +389,6 @@ public function hasUserAccess(Form $form): bool {
* @return bool
*/
public function isSharedFormShown(Form $form): bool {
$access = $form->getAccess();

// Dont show here to owner, as its in the owned list anyways.
if ($form->getOwnerId() === $this->currentUser->getUID()) {
return false;
Expand All @@ -401,6 +399,7 @@ public function isSharedFormShown(Form $form): bool {
return false;
}

$access = $form->getAccess();
// Shown if permitall and showntoall are both set.
if ($access['permitAllUsers'] &&
$access['showToAllUsers'] &&
Expand Down
130 changes: 99 additions & 31 deletions src/Forms.vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@
</NcAppNavigationNew>
<template #list>
<!-- Form-Owner-->
<NcAppNavigationCaption v-if="!noOwnedForms" :name="t('forms', 'Your Forms')" />
<AppNavigationForm v-for="form in forms"
<NcAppNavigationCaption v-if="ownedForms.length > 0" :name="t('forms', 'Your Forms')" />
<AppNavigationForm v-for="form in ownedForms"
:key="form.id"
:form="form"
:read-only="false"
Expand All @@ -44,14 +44,28 @@
@delete="onDeleteForm" />

<!-- Shared Forms-->
<NcAppNavigationCaption v-if="!noSharedForms" :name="t('forms', 'Shared with you')" />
<NcAppNavigationCaption v-if="sharedForms.length > 0" :name="t('forms', 'Shared with you')" />
<AppNavigationForm v-for="form in sharedForms"
:key="form.id"
:form="form"
:read-only="true"
@open-sharing="openSharing"
@mobile-close-navigation="mobileCloseNavigation" />
</template>
<template #footer>
<div v-if="archivedForms.length > 0" class="forms-navigation-footer">
<NcButton alignment="start"
class="forms__archived-forms-toggle"
type="tertiary"
wide
@click="showArchivedForms = true">
<template #icon>
<IconArchive :size="20" />
</template>
{{ t('forms', 'Archived forms') }}
</NcButton>
</div>
</template>
</NcAppNavigation>

<!-- No forms & loading emptycontents -->
Expand Down Expand Up @@ -102,6 +116,9 @@
:active.sync="sidebarActive"
name="sidebar" />
</template>

<!-- Archived forms modal -->
<ArchivedFormsModal :open.sync="showArchivedForms" :forms="archivedForms" />
</NcContent>
</template>

Expand All @@ -123,20 +140,25 @@ import NcContent from '@nextcloud/vue/dist/Components/NcContent.js'
import NcEmptyContent from '@nextcloud/vue/dist/Components/NcEmptyContent.js'
import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js'
import IconArchive from 'vue-material-design-icons/Archive.vue'
import IconPlus from 'vue-material-design-icons/Plus.vue'
import FormsIcon from './components/Icons/FormsIcon.vue'
import ArchivedFormsModal from './components/ArchivedFormsModal.vue'
import AppNavigationForm from './components/AppNavigationForm.vue'
import FormsIcon from './components/Icons/FormsIcon.vue'
import PermissionTypes from './mixins/PermissionTypes.js'
import OcsResponse2Data from './utils/OcsResponse2Data.js'
import logger from './utils/Logger.js'
import { FormState } from './models/FormStates.ts'
export default {
name: 'Forms',
components: {
AppNavigationForm,
ArchivedFormsModal,
FormsIcon,
IconArchive,
IconPlus,
NcAppContent,
NcAppNavigation,
Expand All @@ -162,7 +184,9 @@ export default {
sidebarOpened: false,
sidebarActive: 'forms-sharing',
forms: [],
sharedForms: [],
allSharedForms: [],
showArchivedForms: false,
canCreateForms: loadState(appName, 'appConfig').canCreateForms,
}
Expand All @@ -172,14 +196,39 @@ export default {
canEdit() {
return this.selectedForm.permissions.includes(this.PERMISSION_TYPES.PERMISSION_EDIT)
},
hasForms() {
return !this.noOwnedForms || !this.noSharedForms
return this.allSharedForms.length > 0 || this.forms.length > 0
},
noOwnedForms() {
return this.forms?.length === 0
/**
* All own active forms
*/
ownedForms() {
const now = Date.now() / 1000
return this.forms
.filter((form) => form.state !== FormState.FormArchived)
.filter((form) => form.expires === 0 || form.expires >= now)
},
/**
* All active shared forms
*/
sharedForms() {
const now = Date.now() / 1000
return this.allSharedForms.filter((form) => form.expires === 0 || form.expires >= now)
},
noSharedForms() {
return this.sharedForms?.length === 0
/**
* All forms that have been archived
*/
archivedForms() {
return [
...this.forms,
...this.allSharedForms,
].filter((form) => form.state === FormState.FormArchived)
},
routeHash() {
Expand All @@ -194,7 +243,7 @@ export default {
}
// Try to find form in owned & shared list
const form = [...this.forms, ...this.sharedForms]
const form = [...this.forms, ...this.allSharedForms]
.find(form => form.hash === this.routeHash)
// If no form found, load it from server. Route will be automatically re-evaluated.
Expand All @@ -210,7 +259,7 @@ export default {
selectedForm: {
get() {
if (this.routeAllowed) {
return this.forms.concat(this.sharedForms).find(form => form.hash === this.routeHash)
return this.forms.concat(this.allSharedForms).find(form => form.hash === this.routeHash)
}
return {}
},
Expand All @@ -222,9 +271,9 @@ export default {
return
}
// Otherwise a shared form
index = this.sharedForms.findIndex(search => search.hash === this.routeHash)
index = this.allSharedForms.findIndex(search => search.hash === this.routeHash)
if (index > -1) {
this.$set(this.sharedForms, index, form)
this.$set(this.allSharedForms, index, form)
}
},
},
Expand Down Expand Up @@ -285,7 +334,7 @@ export default {
// Load shared forms
try {
const response = await axios.get(generateOcsUrl('apps/forms/api/v2.4/shared_forms'))
this.sharedForms = OcsResponse2Data(response)
this.allSharedForms = OcsResponse2Data(response)
} catch (error) {
logger.error('Error while loading shared forms list', { error })
showError(t('forms', 'An error occurred while loading the forms list'))
Expand All @@ -300,22 +349,34 @@ export default {
* @param {string} hash the hash of the form to load
*/
async fetchPartialForm(hash) {
this.loading = true
try {
const response = await axios.get(generateOcsUrl('apps/forms/api/v2.4/partial_form/{hash}', { hash }))
const form = OcsResponse2Data(response)
await new Promise((resolve) => {
const wait = () => {
if (this.loading) {
window.setTimeout(wait, 250)
} else {
resolve()
}
}
wait()
})
// If the user has (at least) submission-permissions, add it to the shared forms
if (form.permissions.includes(this.PERMISSION_TYPES.PERMISSION_SUBMIT)) {
this.sharedForms.push(form)
this.loading = true
if ([...this.forms, ...this.allSharedForms].find((form) => form.hash === hash) === undefined) {
try {
const response = await axios.get(generateOcsUrl('apps/forms/api/v2.4/partial_form/{hash}', { hash }))
const form = OcsResponse2Data(response)
// If the user has (at least) submission-permissions, add it to the shared forms
if (form.permissions.includes(this.PERMISSION_TYPES.PERMISSION_SUBMIT)) {
this.allSharedForms.push(form)
}
} catch (error) {
logger.error(`Form ${hash} not found`, { error })
showError(t('forms', 'Form not found'))
}
} catch (error) {
logger.error(`Form ${hash} not found`, { error })
showError(t('forms', 'Form not found'))
} finally {
this.loading = false
}
this.loading = false
},
/**
Expand Down Expand Up @@ -381,16 +442,23 @@ export default {
this.forms[formIndex].lastUpdated = moment().unix()
this.forms.sort((b, a) => a.lastUpdated - b.lastUpdated)
} else {
const sharedFormIndex = this.sharedForms.findIndex(form => form.id === id)
this.sharedForms[sharedFormIndex].lastUpdated = moment().unix()
this.sharedForms.sort((b, a) => a.lastUpdated - b.lastUpdated)
const sharedFormIndex = this.allSharedForms.findIndex(form => form.id === id)
this.allSharedForms[sharedFormIndex].lastUpdated = moment().unix()
this.allSharedForms.sort((b, a) => a.lastUpdated - b.lastUpdated)
}
},
},
}
</script>
<style scoped lang="scss">
.forms-navigation-footer {
display: flex;
flex-direction: column;
padding: var(--app-navigation-padding);
}
.forms-emptycontent {
height: 100%;
}
Expand Down
Loading

0 comments on commit 262666b

Please sign in to comment.