Merge pull request #297 from nextcloud/design/remove-forms-emptycontent
Further design improvements after review
This commit is contained in:
commit
aa9fa028e3
|
@ -37,16 +37,21 @@
|
|||
{{ t('forms', 'Loading forms …') }}
|
||||
</EmptyContent>
|
||||
<EmptyContent v-else-if="noForms">
|
||||
{{ t('forms', 'No forms in here') }}
|
||||
{{ t('forms', 'No forms created yet') }}
|
||||
<template #desc>
|
||||
<button class="primary" @click="onNewForm">
|
||||
{{ t('forms', 'Create a new one') }}
|
||||
{{ t('forms', 'Create a form') }}
|
||||
</button>
|
||||
</template>
|
||||
</EmptyContent>
|
||||
|
||||
<EmptyContent v-else>
|
||||
{{ t('forms', 'Please select a form') }}
|
||||
{{ t('forms', 'Select a form or create a new one') }}
|
||||
<template #desc>
|
||||
<button class="primary" @click="onNewForm">
|
||||
{{ t('forms', 'Create new form') }}
|
||||
</button>
|
||||
</template>
|
||||
</EmptyContent>
|
||||
</AppContent>
|
||||
|
||||
|
|
|
@ -38,9 +38,9 @@
|
|||
</ActionLink>
|
||||
<ActionRouter :close-after-click="true"
|
||||
:exact="true"
|
||||
icon="icon-forms"
|
||||
icon="icon-comment"
|
||||
:to="{ name: 'results', params: { hash: form.hash } }">
|
||||
{{ t('forms', 'Show results') }}
|
||||
{{ t('forms', 'Responses') }}
|
||||
</ActionRouter>
|
||||
<!-- <ActionRouter :close-after-click="true"
|
||||
:exact="true"
|
||||
|
@ -131,7 +131,7 @@ export default {
|
|||
? t('forms', 'Form link copied')
|
||||
: t('forms', 'Cannot copy, please copy the link manually')
|
||||
}
|
||||
return t('forms', 'Copy to clipboard')
|
||||
return t('forms', 'Copy share link')
|
||||
},
|
||||
},
|
||||
|
||||
|
|
|
@ -155,8 +155,6 @@ export default {
|
|||
justify-content: stretch;
|
||||
margin-bottom: 22px;
|
||||
padding-left: 44px;
|
||||
// room for the new question menu
|
||||
padding-right: 44px;
|
||||
user-select: none;
|
||||
background-color: var(--color-main-background);
|
||||
|
||||
|
@ -169,7 +167,15 @@ export default {
|
|||
left: 0;
|
||||
width: 44px;
|
||||
height: 100%;
|
||||
opacity: .5;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
cursor: grab;
|
||||
|
||||
&:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
@ -179,7 +185,7 @@ export default {
|
|||
&__content {
|
||||
flex: 1 1 100%;
|
||||
max-width: 100%;
|
||||
margin: 20px;
|
||||
margin-bottom: 20px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
|
@ -189,8 +195,7 @@ export default {
|
|||
flex: 1 1 100%;
|
||||
justify-content: space-between;
|
||||
width: auto;
|
||||
margin: 20px;
|
||||
margin-bottom: 0;
|
||||
margin-top: 20px;
|
||||
|
||||
// Using type to have a higher order than the input styling of server
|
||||
&-title,
|
||||
|
@ -205,6 +210,7 @@ export default {
|
|||
border-bottom: 1px dotted transparent;
|
||||
border-radius: 0;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
|
|
|
@ -51,7 +51,7 @@ $top-bar-height: 60px;
|
|||
|
||||
button {
|
||||
cursor: pointer;
|
||||
&:not(.primary) {
|
||||
&:not(:first-child) {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border: none;
|
||||
|
|
|
@ -21,9 +21,7 @@
|
|||
-->
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h2> {{ t('forms', 'Share with') }}</h2>
|
||||
|
||||
<div class="sharing">
|
||||
<Multiselect id="ajax"
|
||||
v-model="shares"
|
||||
:options="users"
|
||||
|
@ -151,7 +149,11 @@ export default {
|
|||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
<style lang="scss" scoped>
|
||||
.sharing {
|
||||
margin: 8px 8px 8px 36px;
|
||||
}
|
||||
|
||||
.shared-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
|
|
@ -29,21 +29,21 @@
|
|||
<template>
|
||||
<AppContent v-if="isLoadingForm">
|
||||
<EmptyContent icon="icon-loading">
|
||||
{{ t('forms', 'Loading form “{title}”', { title: form.title }) }}
|
||||
{{ t('forms', 'Loading {title} …', { title: form.title }) }}
|
||||
</EmptyContent>
|
||||
</AppContent>
|
||||
|
||||
<AppContent v-else>
|
||||
<!-- Show results & sidebar button -->
|
||||
<TopBar>
|
||||
<button class="primary" @click="showResults">
|
||||
<span class="icon-forms-white" role="img" />
|
||||
{{ t('forms', 'Show results') }}
|
||||
<button @click="showResults">
|
||||
<span class="icon-comment" role="img" />
|
||||
{{ t('forms', 'Responses') }}
|
||||
</button>
|
||||
<button v-tooltip="t('forms', 'Toggle settings')"
|
||||
:aria-label="t('forms', 'Toggle settings')"
|
||||
@click="toggleSidebar">
|
||||
<span class="icon-settings" role="img" />
|
||||
<span class="icon-menu-sidebar" role="img" />
|
||||
</button>
|
||||
</TopBar>
|
||||
|
||||
|
@ -91,21 +91,11 @@
|
|||
</Actions>
|
||||
</div>
|
||||
|
||||
<!-- No questions -->
|
||||
<EmptyContent v-if="hasQuestions">
|
||||
{{ t('forms', 'This form does not have any questions') }}
|
||||
<template #desc>
|
||||
<button class="empty-content__button primary" @click="openQuestionMenu">
|
||||
<span class="icon-add-white" />
|
||||
{{ t('forms', 'Add a new one') }}
|
||||
</button>
|
||||
</template>
|
||||
</EmptyContent>
|
||||
|
||||
<!-- Questions list -->
|
||||
<Draggable v-model="form.questions"
|
||||
:animation="200"
|
||||
tag="ul"
|
||||
handle=".question__drag-handle"
|
||||
@change="onQuestionOrderChange"
|
||||
@start="isDragging = true"
|
||||
@end="isDragging = false">
|
||||
|
@ -382,7 +372,7 @@ export default {
|
|||
header,
|
||||
section {
|
||||
width: 100%;
|
||||
max-width: 900px;
|
||||
max-width: 750px;
|
||||
}
|
||||
|
||||
// Title & description header
|
||||
|
@ -394,19 +384,22 @@ export default {
|
|||
#form-title,
|
||||
#form-desc {
|
||||
width: 100%;
|
||||
margin: 10px; // aerate the header
|
||||
padding: 0; // makes alignment and desc height calc easier
|
||||
margin: 16px 0; // aerate the header
|
||||
padding: 0 16px;
|
||||
border: none;
|
||||
}
|
||||
#form-title {
|
||||
font-size: 2em;
|
||||
font-weight: bold;
|
||||
padding-left: 14px; // align with description (compensate font size diff)
|
||||
overflow-x: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
#form-desc {
|
||||
// make sure height calculations are correct
|
||||
box-sizing: content-box !important;
|
||||
min-height: 60px;
|
||||
max-height: 200px;
|
||||
padding-left: 2px; // align with title (compensate font size diff)
|
||||
margin-top: 0;
|
||||
resize: none;
|
||||
}
|
||||
}
|
||||
|
@ -434,7 +427,7 @@ export default {
|
|||
top: var(--header-height);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-self: flex-end;
|
||||
align-self: flex-start;
|
||||
width: 44px;
|
||||
height: var(--top-bar-height);
|
||||
// make sure this doesn't take any space and appear floating
|
||||
|
|
|
@ -23,17 +23,43 @@
|
|||
-->
|
||||
|
||||
<template>
|
||||
<AppContent>
|
||||
<div>
|
||||
<button class="button btn primary" @click="download">
|
||||
<span>{{ "Export to CSV" }}</span>
|
||||
<AppContent v-if="loadingResults">
|
||||
<EmptyContent icon="icon-loading">
|
||||
{{ t('forms', 'Loading responses …') }}
|
||||
</EmptyContent>
|
||||
</AppContent>
|
||||
|
||||
<AppContent v-else>
|
||||
<TopBar>
|
||||
<button @click="showEdit">
|
||||
<span class="icon-forms" role="img" />
|
||||
{{ t('forms', 'Back to form') }}
|
||||
</button>
|
||||
</TopBar>
|
||||
|
||||
<header v-if="!noSubmissions">
|
||||
<h2>{{ t('forms', 'Responses for {title}', { title: form.title }) }}</h2>
|
||||
<div v-for="sum in stats" :key="sum">
|
||||
{{ sum }}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- No submissions -->
|
||||
<section v-if="noSubmissions">
|
||||
<EmptyContent icon="icon-comment">
|
||||
{{ t('forms', 'No responses yet') }}
|
||||
<template #desc>
|
||||
{{ t('forms', 'Results of submitted forms will show up here') }}
|
||||
</template>
|
||||
<!-- Button to copy Share-Link? -->
|
||||
</EmptyContent>
|
||||
</section>
|
||||
|
||||
<section v-else>
|
||||
<button id="exportButton" class="primary" @click="download">
|
||||
<span class="icon-download-white" role="img" />
|
||||
{{ t('forms', 'Export to CSV') }}
|
||||
</button>
|
||||
</div>
|
||||
<h1>{{ "Statistics" }}</h1>
|
||||
<div v-for="sum in stats" :key="sum">
|
||||
{{ sum }}
|
||||
</div>
|
||||
<div id="app-content" :class="{'icon-loading': loading}">
|
||||
<transition-group
|
||||
name="list"
|
||||
tag="div"
|
||||
|
@ -41,41 +67,44 @@
|
|||
<ResultItem
|
||||
key="0"
|
||||
:header="true" />
|
||||
<li
|
||||
is="resultItem"
|
||||
v-for="(answer, index) in answers"
|
||||
<ResultItem
|
||||
v-for="answer in answers"
|
||||
:key="answer.id"
|
||||
:answer="answer"
|
||||
@viewResults="viewFormResults(index, form.form, 'results')" />
|
||||
:answer="answer" />
|
||||
</transition-group>
|
||||
<modal-dialog />
|
||||
</div>
|
||||
</section>
|
||||
</AppContent>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
import { showError } from '@nextcloud/dialogs'
|
||||
import AppContent from '@nextcloud/vue/dist/Components/AppContent'
|
||||
import axios from '@nextcloud/axios'
|
||||
|
||||
import EmptyContent from '../components/EmptyContent'
|
||||
import TopBar from '../components/TopBar'
|
||||
import ViewsMixin from '../mixins/ViewsMixin'
|
||||
|
||||
import ResultItem from '../components/resultItem'
|
||||
import json2csvParser from 'json2csv'
|
||||
import axios from '@nextcloud/axios'
|
||||
import ViewsMixin from '../mixins/ViewsMixin'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
|
||||
import AppContent from '@nextcloud/vue/dist/Components/AppContent'
|
||||
export default {
|
||||
name: 'Results',
|
||||
|
||||
components: {
|
||||
AppContent,
|
||||
EmptyContent,
|
||||
ResultItem,
|
||||
TopBar,
|
||||
},
|
||||
|
||||
mixins: [ViewsMixin],
|
||||
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
loadingResults: true,
|
||||
answers: [],
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -111,41 +140,46 @@ export default {
|
|||
|
||||
return sums.sort()
|
||||
},
|
||||
|
||||
noSubmissions() {
|
||||
return this.answers && this.answers.length === 0
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
this.indexPage = OC.generateUrl('apps/forms/')
|
||||
this.loadForms()
|
||||
beforeMount() {
|
||||
this.loadFormResults()
|
||||
},
|
||||
|
||||
methods: {
|
||||
loadForms() {
|
||||
this.loading = true
|
||||
axios.get(generateUrl('apps/forms/api/v1/submissions/{hash}', { hash: this.$route.params.hash }))
|
||||
.then((response) => {
|
||||
if (response.data == null) {
|
||||
this.answers = null
|
||||
OC.Notification.showTemporary('Access Denied')
|
||||
} else {
|
||||
this.answers = response.data
|
||||
}
|
||||
this.loading = false
|
||||
}, (error) => {
|
||||
/* eslint-disable-next-line no-console */
|
||||
console.log(error.response)
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
viewFormResults(index, form, name) {
|
||||
|
||||
showEdit() {
|
||||
this.$router.push({
|
||||
name: name,
|
||||
name: 'edit',
|
||||
params: {
|
||||
hash: form.id,
|
||||
hash: this.form.hash,
|
||||
},
|
||||
})
|
||||
},
|
||||
download() {
|
||||
|
||||
async loadFormResults() {
|
||||
this.loadingResults = true
|
||||
console.debug('Loading Results')
|
||||
|
||||
try {
|
||||
const response = await axios.get(generateUrl('/apps/forms/api/v1/submissions/{hash}', {
|
||||
hash: this.form.hash,
|
||||
}))
|
||||
this.answers = response.data
|
||||
console.debug(this.answers)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
showError(t('forms', 'There was an error while loading results'))
|
||||
} finally {
|
||||
this.loadingResults = false
|
||||
}
|
||||
},
|
||||
|
||||
download() {
|
||||
this.loading = true
|
||||
axios.get(OC.generateUrl('apps/forms/get/form/' + this.$route.params.hash))
|
||||
.then((response) => {
|
||||
|
@ -178,8 +212,7 @@ export default {
|
|||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.table {
|
||||
width: 100%;
|
||||
margin-top: 45px;
|
||||
|
@ -189,12 +222,7 @@ export default {
|
|||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
#emptycontent {
|
||||
.icon-forms {
|
||||
background-color: black;
|
||||
-webkit-mask: url('./img/app.svg') no-repeat 50% 50%;
|
||||
mask: url('./img/app.svg') no-repeat 50% 50%;
|
||||
}
|
||||
#exportButton {
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -22,92 +22,94 @@
|
|||
|
||||
<template>
|
||||
<AppSidebar v-show="opened" :title="form.title" @close="onClose">
|
||||
<div class="configBox ">
|
||||
<label class="title icon-settings">
|
||||
{{ t('forms', 'Form configurations') }}
|
||||
</label>
|
||||
<h3>{{ t('forms', 'Settings') }}</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<input id="isAnonymous"
|
||||
v-model="form.isAnonymous"
|
||||
|
||||
<input id="isAnonymous"
|
||||
v-model="form.isAnonymous"
|
||||
type="checkbox"
|
||||
class="checkbox"
|
||||
@change="onAnonChange">
|
||||
<label for="isAnonymous">
|
||||
{{ t('forms', 'Anonymous responses') }}
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<input id="submitOnce"
|
||||
v-model="form.submitOnce"
|
||||
:disabled="form.access.type === 'public' || form.isAnonymous"
|
||||
type="checkbox"
|
||||
class="checkbox"
|
||||
@change="onSubmOnceChange">
|
||||
<label for="submitOnce">
|
||||
{{ t('forms', 'Only allow one response per user') }}
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<input id="expires"
|
||||
v-model="formExpires"
|
||||
type="checkbox"
|
||||
class="checkbox">
|
||||
<label for="expires">
|
||||
{{ t('forms', 'Set expiration date') }}
|
||||
</label>
|
||||
<DatetimePicker v-show="formExpires"
|
||||
id="expiresDatetimePicker"
|
||||
v-model="form.expires"
|
||||
v-bind="expirationDatePicker"
|
||||
@change="onExpiresChange" />
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
type="checkbox"
|
||||
class="checkbox"
|
||||
@change="onAnonChange">
|
||||
<label for="isAnonymous" class="title">
|
||||
{{ t('forms', 'Anonymous form') }}
|
||||
</label>
|
||||
|
||||
<input id="submitOnce"
|
||||
v-model="form.submitOnce"
|
||||
:disabled="form.access.type === 'public' || form.isAnonymous"
|
||||
type="checkbox"
|
||||
class="checkbox"
|
||||
@change="onSubmOnceChange">
|
||||
<label for="submitOnce" class="title">
|
||||
<span>{{ t('forms', 'Only allow one submission per user') }}</span>
|
||||
</label>
|
||||
|
||||
<input id="expires"
|
||||
v-model="formExpires"
|
||||
|
||||
type="checkbox"
|
||||
class="checkbox">
|
||||
<label class="title" for="expires">
|
||||
{{ t('forms', 'Expires') }}
|
||||
</label>
|
||||
|
||||
<DatetimePicker v-show="formExpires"
|
||||
id="expiresDatetimePicker"
|
||||
v-model="form.expires"
|
||||
v-bind="expirationDatePicker"
|
||||
@change="onExpiresChange" />
|
||||
</div>
|
||||
|
||||
<div class="configBox">
|
||||
<label class="title icon-user">
|
||||
{{ t('forms', 'Access') }}
|
||||
</label>
|
||||
|
||||
<input id="registered"
|
||||
v-model="form.access.type"
|
||||
type="radio"
|
||||
value="registered"
|
||||
class="radio"
|
||||
@change="onAccessChange">
|
||||
<label for="registered" class="title">
|
||||
<div class="title icon-group" />
|
||||
<span>{{ t('forms', 'Registered users only') }}</span>
|
||||
</label>
|
||||
|
||||
<input id="public"
|
||||
v-model="form.access.type"
|
||||
type="radio"
|
||||
value="public"
|
||||
class="radio"
|
||||
@change="onAccessChange">
|
||||
<label for="public" class="title">
|
||||
<div class="title icon-link" />
|
||||
<span>{{ t('forms', 'Public access') }}</span>
|
||||
</label>
|
||||
|
||||
<input id="selected"
|
||||
v-model="form.access.type"
|
||||
type="radio"
|
||||
value="selected"
|
||||
class="radio"
|
||||
@change="onAccessChange">
|
||||
<label for="selected" class="title">
|
||||
<div class="title icon-shared" />
|
||||
<span>{{ t('forms', 'Only shared') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<ShareDiv v-show="form.access.type === 'selected'"
|
||||
:active-shares="form.shares"
|
||||
:placeholder="t('forms', 'Name of user or group')"
|
||||
:hide-names="true"
|
||||
@update-shares="updateShares"
|
||||
@remove-share="removeShare" />
|
||||
<h3>{{ t('forms', 'Sharing') }}</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<input id="registered"
|
||||
v-model="form.access.type"
|
||||
type="radio"
|
||||
value="registered"
|
||||
class="radio"
|
||||
@change="onAccessChange">
|
||||
<label for="registered">
|
||||
<span class="icon-group">
|
||||
{{ t('forms', 'Show to all users of this instance') }}
|
||||
</span>
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<input id="public"
|
||||
v-model="form.access.type"
|
||||
type="radio"
|
||||
value="public"
|
||||
class="radio"
|
||||
@change="onAccessChange">
|
||||
<label for="public">
|
||||
<span class="icon-link">
|
||||
{{ t('forms', 'Share link') }}
|
||||
</span>
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<input id="selected"
|
||||
v-model="form.access.type"
|
||||
type="radio"
|
||||
value="selected"
|
||||
class="radio"
|
||||
@change="onAccessChange">
|
||||
<label for="selected">
|
||||
<span class="icon-shared">
|
||||
{{ t('forms', 'Choose users to share with') }}
|
||||
</span>
|
||||
</label>
|
||||
<ShareDiv v-show="form.access.type === 'selected'"
|
||||
:active-shares="form.shares"
|
||||
:placeholder="t('forms', 'Name of user or group')"
|
||||
:hide-names="true"
|
||||
@update-shares="updateShares"
|
||||
@remove-share="removeShare" />
|
||||
</li>
|
||||
</ul>
|
||||
</AppSidebar>
|
||||
</template>
|
||||
|
||||
|
@ -132,7 +134,7 @@ export default {
|
|||
|
||||
data() {
|
||||
return {
|
||||
opened: true,
|
||||
opened: false,
|
||||
lang: '',
|
||||
locale: '',
|
||||
longDateFormat: '',
|
||||
|
@ -247,21 +249,22 @@ export default {
|
|||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
h3 {
|
||||
margin-left: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.configBox {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 8px;
|
||||
& > * {
|
||||
padding-left: 21px;
|
||||
}
|
||||
& > .title {
|
||||
display: flex;
|
||||
background-position: 0 2px;
|
||||
padding-left: 24px;
|
||||
margin-bottom: 4px;
|
||||
& > span {
|
||||
padding-left: 4px;
|
||||
ul {
|
||||
margin-bottom: 24px;
|
||||
|
||||
label {
|
||||
padding: 8px;
|
||||
display: block;
|
||||
|
||||
span[class^="icon-"],
|
||||
span[class*=" icon-"] {
|
||||
background-position: 4px;
|
||||
padding-left: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -275,6 +278,7 @@ textarea {
|
|||
}
|
||||
|
||||
#expiresDatetimePicker {
|
||||
width: 170px;
|
||||
left: 36px;
|
||||
width: calc(100% - 44px);
|
||||
}
|
||||
</style>
|
||||
|
|
Loading…
Reference in a new issue