New question ui

Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
This commit is contained in:
John Molakvoæ (skjnldsv) 2020-03-31 18:20:55 +02:00 committed by Jonas Rittershofer
parent 2a14697a06
commit 0961fbfdd7
25 changed files with 853 additions and 811 deletions

View File

@ -1,25 +1,9 @@
# EditorConfig is awesome: http://EditorConfig.org
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true
# Set default charset
charset = utf-8
# 4 space tab indentation
indent_style = tab
indent_size = 4
# Line length form NC coding guidelines
trim_trailing_whitespace = true
max_line_length = 80
# 2 space indentation for .yml files
[.*.yml]
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

View File

@ -2,8 +2,11 @@ module.exports = {
plugins: ['@babel/plugin-syntax-dynamic-import'],
presets: [
[
'@babel/preset-env'
]
]
'@babel/preset-env',
{
corejs: 3,
useBuiltIns: 'entry',
},
],
],
}

View File

@ -20,4 +20,11 @@
*
*/
@import 'icons'
// Various variables used by this app
:root {
--header-height: $header-height;
--top-bar-height: 60px;
}
@import 'variables';
@import 'icons';

View File

@ -5,6 +5,8 @@
@include icon-black-white('answer-short', 'forms', 1);
@include icon-black-white('answer-long', 'forms', 1);
@include icon-black-white('answer-checkbox', 'forms', 1);
@include icon-black-white('answer-multiple', 'forms', 1);
@include icon-black-white('drag-handle', 'forms', 1);
.icon-yes {
@include icon-color('checkmark', 'actions', $color-success, 1, true);

View File

@ -1,4 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16">
<path d="M2.43 2a.43.43 0 00-.43.43V4.3l.86.86v-2.3h8.73l.86-.86H2.43zM14 5.86l-.86.86v6.42H2.86v-2.6L2 9.68v3.9a.43.43 0 00.43.42h11.14a.43.43 0 00.43-.43v-7.7z"/>
<path d="M6.09 12.5L1.14 7.55l1.41-1.41L6.1 9.67l6.34-6.38 1.44 1.43z"/>
<path d="M1.5 1c-.277 0-.5.223-.5.5v13c0 .277.223.5.5.5h13c.277 0 .5-.223.5-.5v-13c0-.277-.223-.5-.5-.5h-13zm10.756 3L13.5 5.242 6.773 12 2.5 7.701l1.217-1.226L6.783 9.54 12.256 4z"/>
</svg>

Before

Width:  |  Height:  |  Size: 313 B

After

Width:  |  Height:  |  Size: 257 B

3
img/answer-multiple.svg Normal file
View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16">
<path d="M2 3v2h2V3zM6 3v2h8V3zM2 7v2h2V7zM6 7v2h8V7zM2 11v2h2v-2zM6 11v2h8v-2z"/>
</svg>

After

Width:  |  Height:  |  Size: 156 B

3
img/drag-handle.svg Normal file
View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10.7 10.7" width="16" height="16">
<path d="M1.34 3.34h8.02v1.34H1.34zM1.34 6.02h8.02v1.34H1.34z"/>
</svg>

After

Width:  |  Height:  |  Size: 162 B

View File

@ -107,7 +107,7 @@ class PageController extends Controller {
*/
public function index(): TemplateResponse {
Util::addScript($this->appName, 'forms');
Util::addStyle($this->appName, 'icons');
Util::addStyle($this->appName, 'forms');
return new TemplateResponse($this->appName, 'main');
}
@ -119,7 +119,7 @@ class PageController extends Controller {
*/
public function createForm(): TemplateResponse {
Util::addScript($this->appName, 'forms');
Util::addStyle($this->appName, 'icons');
Util::addStyle($this->appName, 'forms');
return new TemplateResponse($this->appName, 'main');
}
@ -131,7 +131,7 @@ class PageController extends Controller {
*/
public function cloneForm(): TemplateResponse {
Util::addScript($this->appName, 'forms');
Util::addStyle($this->appName, 'icons');
Util::addStyle($this->appName, 'forms');
return new TemplateResponse($this->appName, 'main');
}
@ -143,7 +143,7 @@ class PageController extends Controller {
*/
public function editForm(): TemplateResponse {
Util::addScript($this->appName, 'forms');
Util::addStyle($this->appName, 'icons');
Util::addStyle($this->appName, 'forms');
return new TemplateResponse($this->appName, 'main');
}
@ -155,7 +155,7 @@ class PageController extends Controller {
*/
public function getResult(): TemplateResponse {
Util::addScript($this->appName, 'forms');
Util::addStyle($this->appName, 'icons');
Util::addStyle($this->appName, 'forms');
return new TemplateResponse($this->appName, 'main');
}

36
package-lock.json generated
View File

@ -1658,9 +1658,15 @@
}
},
"core-js": {
<<<<<<< HEAD
"version": "3.6.4",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.4.tgz",
"integrity": "sha512-4paDGScNgZP2IXXilaffL9X7968RuvwlkK3xWtZRVqgd8SYNiVKRJvkFd1aqqEuPfN7E68ZHEp9hDj6lHj4Hyw=="
=======
"version": "3.6.1",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.1.tgz",
"integrity": "sha512-186WjSik2iTGfDjfdCZAxv2ormxtKgemjC3SI6PL31qOA0j5LhTDVjHChccoc7brwLvpvLPiMyRlcO88C4l1QQ=="
>>>>>>> f89f534... fixup! New question ui
}
}
},
@ -3469,9 +3475,15 @@
"integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40="
},
"core-js": {
<<<<<<< HEAD
"version": "3.6.5",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz",
"integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA=="
=======
"version": "3.6.4",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.4.tgz",
"integrity": "sha512-4paDGScNgZP2IXXilaffL9X7968RuvwlkK3xWtZRVqgd8SYNiVKRJvkFd1aqqEuPfN7E68ZHEp9hDj6lHj4Hyw=="
>>>>>>> f89f534... fixup! New question ui
},
"core-js-compat": {
"version": "3.6.5",
@ -3591,6 +3603,11 @@
"randomfill": "^1.0.3"
}
},
"crypto-js": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.0.0.tgz",
"integrity": "sha512-bzHZN8Pn+gS7DQA6n+iUmBfl0hO5DJq++QP3U6uTucDtk/0iGpXd/Gg7CGR0p8tJhofJyaKoWBuJI4eAO00BBg=="
},
"css-loader": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/css-loader/-/css-loader-3.5.2.tgz",
@ -9374,9 +9391,9 @@
}
},
"regenerator-runtime": {
"version": "0.13.3",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz",
"integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw=="
"version": "0.13.5",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz",
"integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA=="
},
"regenerator-transform": {
"version": "0.14.4",
@ -10001,6 +10018,11 @@
}
}
},
"sortablejs": {
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.10.2.tgz",
"integrity": "sha512-YkPGufevysvfwn5rfdlGyrGjt7/CRHwvRPogD/lC+TnvcN29jDpCifKP+rBqf+LRldfXSTh+0CGLcSg0VIxq3A=="
},
"source-list-map": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz",
@ -11866,6 +11888,14 @@
"date-format-parse": "^0.2.5"
}
},
"vuedraggable": {
"version": "2.23.2",
"resolved": "https://registry.npmjs.org/vuedraggable/-/vuedraggable-2.23.2.tgz",
"integrity": "sha512-PgHCjUpxEAEZJq36ys49HfQmXglattf/7ofOzUrW2/rRdG7tu6fK84ir14t1jYv4kdXewTEa2ieKEAhhEMdwkQ==",
"requires": {
"sortablejs": "^1.10.1"
}
},
"watchpack": {
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.1.tgz",

View File

@ -77,11 +77,16 @@
"@nextcloud/moment": "^1.1.1",
"@nextcloud/router": "^1.0.2",
"@nextcloud/vue": "^1.5.0",
"core-js": "^3.6.4",
"crypto-js": "^4.0.0",
"debounce": "^1.2.0",
"json2csv": "5.0.0",
"regenerator-runtime": "^0.13.5",
"v-click-outside": "^3.0.1",
"vue": "^2.6.11",
"vue-clipboard2": "^0.3.1",
"vue-router": "^3.1.6"
"vue-router": "^3.1.6",
"vuedraggable": "^2.23.2"
},
"browserslist": [
"extends @nextcloud/browserslist-config"

View File

@ -21,9 +21,11 @@
-->
<template>
<div class="emptycontent" role="note">
<div :class="icon" role="img" />
<h2><slot /></h2>
<div class="empty-content" role="note">
<div class="empty-content__icon" :class="icon" role="img" />
<h2 class="empty-content__title">
<slot />
</h2>
<p v-show="$slots.desc">
<slot name="desc" />
</p>
@ -44,10 +46,24 @@ export default {
</script>
<style lang="scss">
.emptycontent {
.empty-content {
margin-top: 20vh;
button {
margin: 3px;
display: flex;
flex-direction: column;
align-items: center;
&__icon {
width: 64px;
height: 64px;
margin: 0 auto 15px;
opacity: .4;
background-size: 64px;
background-repeat: no-repeat;
background-position: center;
}
&__title {
margin-bottom: 10px;
}
}

View File

@ -0,0 +1,144 @@
<!--
- @copyright Copyright (c) 2020 John Molakvoæ <skjnldsv@protonmail.com>
-
- @author John Molakvoæ <skjnldsv@protonmail.com>
-
- @license GNU AGPL version 3 or any later version
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-->
<template>
<li v-click-outside="disableEdit"
:class="{ 'question--edit': edit }"
class="question"
@click="enableEdit">
<!-- TODO: implement arrow key mapping to reorder question -->
<div class="question__drag-handle icon-drag-handle"
:aria-label="t('forms', 'Drag to re-order the questions')" />
<input v-if="edit"
:value="title"
class="question__title"
type="text"
minlength="1"
maxlength="256"
@input="onInput">
<h3 v-else class="question__title" v-text="title" />
<slot />
</li>
</template>
<script>
import { directive as ClickOutside } from 'v-click-outside'
export default {
name: 'Question',
directives: {
ClickOutside,
},
props: {
title: {
type: String,
required: true,
},
edit: {
type: Boolean,
required: true,
},
},
methods: {
onInput({ target }) {
this.$emit('update:title', target.value)
},
/**
* Enable the edit mode
*/
enableEdit() {
this.$emit('update:edit', true)
},
/**
* Disable the edit mode
*/
disableEdit() {
this.$emit('update:edit', false)
},
},
}
</script>
<style lang="scss">
.question {
position: relative;
display: flex;
align-items: stretch;
flex-direction: column;
justify-content: stretch;
margin-bottom: 22px;
padding-left: 44px;
user-select: none;
background-color: var(--color-main-background);
> * {
cursor: pointer;
}
&__drag-handle {
position: absolute;
left: 0;
width: 44px;
height: 100%;
cursor: grab;
&:active {
cursor: grabbing;
}
}
&__title,
&__content {
flex: 1 1 100%;
max-width: 100%;
margin: 20px;
padding: 0;
}
// Using type to have a higher order than the input styling of server
&__title,
&__title[type=text] {
flex: 1 1 100%;
width: auto;
max-width: calc(100% - 44px);
min-height: 22px;
margin: 20px;
margin-bottom: 0;
padding: 0;
padding-bottom: 6px;
color: var(--color-text-light);
border: 0;
border-radius: 0;
font-size: 16px;
line-height: 22px;
}
&__title[type=text] {
border-bottom: 1px dotted var(--color-border-dark);
}
}
</style>

View File

@ -0,0 +1,91 @@
<!--
- @copyright Copyright (c) 2020 John Molakvoæ <skjnldsv@protonmail.com>
-
- @author John Molakvoæ <skjnldsv@protonmail.com>
-
- @license GNU AGPL version 3 or any later version
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-->
<template>
<Question :title="title" :edit.sync="edit" @update:title="onTitleChange">
<div class="question__content">
<!-- TODO: properly choose max length -->
<textarea ref="textarea"
:aria-label="t('forms', 'A long answer for the question “{title}”', { title })"
:placeholder="t('forms', 'Long answer text')"
:readonly="edit"
:value="values[0]"
class="question__text"
maxlength="1024"
minlength="1"
@input="onInput"
@keydown="autoSizeText" />
</div>
</Question>
</template>
<script>
import QuestionMixin from '../../mixins/QuestionMixin'
export default {
name: 'QuestionLong',
mixins: [QuestionMixin],
data() {
return {
height: 1,
}
},
mounted() {
this.autoSizeText()
},
methods: {
onInput() {
const textarea = this.$refs.textarea
this.$emit('update:values', [textarea.value])
this.autoSizeText()
},
autoSizeText() {
const textarea = this.$refs.textarea
textarea.style.cssText = 'height:auto; padding:0'
textarea.style.cssText = `height: ${textarea.scrollHeight + 20}px`
},
},
}
</script>
<style lang="scss">
// Using type to have a higher order than the input styling of server
.question__text {
// make sure height calculations are correct
box-sizing: content-box !important;
width: 100%;
min-width: 100%;
max-width: 100%;
min-height: 44px;
max-height: 10rem;
margin: 0;
padding: 6px 0;
border: 0;
border-bottom: 1px dotted var(--color-border-dark);
border-radius: 0;
}
</style>

View File

@ -0,0 +1,203 @@
<!--
- @copyright Copyright (c) 2020 John Molakvoæ <skjnldsv@protonmail.com>
-
- @author John Molakvoæ <skjnldsv@protonmail.com>
-
- @license GNU AGPL version 3 or any later version
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-->
<template>
<Question :title="title" :edit.sync="edit" @update:title="onTitleChange">
<ul class="question__content">
<template v-for="(answer, index) in values">
<li :key="index" class="question__item">
<input :id="`${id}-check-${index}`"
ref="checkbox"
:checked="false"
:readonly="true"
type="checkbox"
class="checkbox question__checkbox">
<label v-if="!edit"
ref="label"
:for="`${id}-check-${index}`"
class="question__label">{{ answer }}</label>
<!-- TODO: properly choose max length -->
<input v-else
ref="input"
:aria-label="t('forms', 'An answer for checkbox {index}', { index: index + 1 })"
:placeholder="t('forms', 'Answer for checkbox {index}', { index: index + 1 })"
:value="answer"
class="question__input"
maxlength="256"
minlength="1"
type="text"
@input="onInput(index)"
@keydown.enter.prevent="addNewEntry"
@keydown.delete="deleteEntry($event, index)">
<!-- Delete answer -->
<Actions v-if="edit">
<ActionButton icon="icon-close" @click="deleteEntry($event, index)">
{{ t('forms', 'Delete answer') }}
</ActionButton>
</Actions>
</li>
</template>
<li v-if="edit && !isLastEmpty" class="question__item">
<!-- TODO: properly choose max length -->
<input
:aria-label="t('forms', 'Add a new checkbox')"
:placeholder="t('forms', 'Add a new checkbox')"
class="question__input"
maxlength="256"
minlength="1"
type="text"
@click="addNewEntry">
</li>
</ul>
</Question>
</template>
<script>
import Actions from '@nextcloud/vue/dist/Components/Actions'
import ActionButton from '@nextcloud/vue/dist/Components/ActionButton'
import QuestionMixin from '../../mixins/QuestionMixin'
import GenRandomId from '../../utils/GenRandomId'
export default {
name: 'QuestionMultiple',
components: {
Actions,
ActionButton,
},
mixins: [QuestionMixin],
data() {
return {
id: GenRandomId(),
}
},
computed: {
isLastEmpty() {
const value = this.values[this.values.length - 1]
return value && value.trim().length === 0
},
},
watch: {
edit(edit) {
if (!edit) {
// Filter and update questions
this.$emit('update:values', this.values.filter(answer => !!answer))
}
},
},
methods: {
onInput(index) {
// Update values
const input = this.$refs.input[index]
const values = this.values.slice()
values[index] = input.value
// Update question
this.$emit('update:values', values)
},
addNewEntry() {
// Add entry
const values = this.values.slice()
values.push('')
// Update question
this.$emit('update:values', values)
this.$nextTick(() => {
this.focusIndex(values.length - 1)
})
},
deleteEntry(e, index) {
const input = this.$refs.input[index]
if (input.value.length === 0) {
// Dismiss delete action
e.preventDefault()
// Remove entry
const values = this.values.slice()
values.splice(index, 1)
// Update question
this.$emit('update:values', values)
this.$nextTick(() => {
this.focusNext(index)
})
}
},
/**
* Focus the input matching the index
*
* @param {Number} index the value index
*/
focusIndex(index) {
const input = this.$refs.input[index]
if (input) {
input.focus()
}
},
},
}
</script>
<style lang="scss">
.question__content {
display: flex;
flex-direction: column;
}
.question__item {
display: inline-flex;
align-items: center;
height: 44px;
.question__label {
flex: 1 1 100%;
&::before {
margin: 14px !important;
}
}
}
// Using type to have a higher order than the input styling of server
.question__input[type=text] {
width: 100%;
min-height: 44px;
margin: 0;
padding: 6px 0;
border: 0;
border-bottom: 1px dotted var(--color-border-dark);
border-radius: 0;
}
</style>

View File

@ -0,0 +1,70 @@
<!--
- @copyright Copyright (c) 2020 John Molakvoæ <skjnldsv@protonmail.com>
-
- @author John Molakvoæ <skjnldsv@protonmail.com>
-
- @license GNU AGPL version 3 or any later version
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-->
<template>
<Question :title="title" :edit.sync="edit" @update:title="onTitleChange">
<div class="question__content">
<!-- TODO: properly choose max length -->
<input ref="input"
:aria-label="t('forms', 'A short answer for the question “{title}”', { title })"
:placeholder="t('forms', 'Short answer text')"
:readonly="edit"
:value="values[0]"
class="question__input"
maxlength="256"
minlength="1"
type="text"
@input="onInput">
</div>
</Question>
</template>
<script>
import QuestionMixin from '../../mixins/QuestionMixin'
export default {
name: 'QuestionShort',
mixins: [QuestionMixin],
methods: {
onInput() {
const input = this.$refs.input
this.$emit('update:values', [input.value])
},
},
}
</script>
<style lang="scss">
// Using type to have a higher order than the input styling of server
.question__input[type=text] {
width: 100%;
min-height: 44px;
margin: 0;
padding: 6px 0;
border: 0;
border-bottom: 1px dotted var(--color-border-dark);
border-radius: 0;
}
</style>

View File

@ -35,15 +35,18 @@ export default {
</script>
<style lang="scss" scoped>
$top-bar-height: 60px;
.top-bar {
position: absolute;
position: sticky;
z-index: 10;
top: 0;
right: 0;
top: var(--header-height);
display: flex;
align-items: center;
align-self: flex-end;
justify-content: flex-end;
height: 60px;
height: var(--top-bar-height);
margin-top: calc(var(--top-bar-height) * -1);
padding: 0 6px;
button {
@ -51,8 +54,8 @@ export default {
&:not(.primary) {
width: 44px;
height: 44px;
background-color: transparent;
border: none;
background-color: transparent;
&:hover, a:active, a:focus {
background-color: var(--color-background-darker);
}

View File

@ -1,67 +0,0 @@
<!--
- @copyright Copyright (c) 2018 René Gieling <github@dartcafe.de>
-
- @author René Gieling <github@dartcafe.de>
-
- @license GNU AGPL version 3 or any later version
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-->
/* global Vue, oc_userconfig */
<template>
<div class="cloud">
<span v-if="options.expired" class="expired">
{{ t('forms', 'Expired') }}
</span>
<span v-if="options.expires" class="open">
{{ t('forms', 'Expires %n', 1, expirationDate) }}
</span>
<span v-else class="open">
{{ t('forms', 'Expires never') }}
</span>
<span class="information">
{{ options.access }}
</span>
<span v-if="options.isAnonymous" class="information">
{{ t('forms', 'Anonymous form') }}
</span>
</div>
</template>
<script>
import moment from '@nextcloud/moment'
export default {
props: {
options: {
type: Object,
default: undefined,
},
},
computed: {
expirationDate() {
const date = moment(this.options.expirationDate, moment.localeData().longDateFormat('L')).fromNow()
return date
},
},
}
</script>
<style scoped>
</style>

View File

@ -1,114 +0,0 @@
<!--
- @copyright Copyright (c) 2018 René Gieling <github@dartcafe.de>
-
- @author René Gieling <github@dartcafe.de>
-
- @license GNU AGPL version 3 or any later version
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-->
<template>
<div class="controls">
<div class="breadcrumb">
<button class="button btn primary" @click="helpPage">
{{ "Help" }}
</button>
<div class="crumb svg crumbhome">
<router-link :to="{ name: 'list'}" class="icon-home">
Home
</router-link>
</div>
<div v-show="intitle ===''" class="crumb svg last">
<span v-text="intitle" />
</div>
<div class="action">
<slot />
</div>
</div>
<slot name="after" class="after" />
</div>
</template>
<script>
export default {
props: {
intitle: {
type: String,
default: undefined,
},
},
data() {
return {
imagePath: OC.imagePath('core', 'places/home.svg'),
}
},
methods: {
helpPage() {
window.open('https://github.com/nextcloud/forms/blob/master/Forms_Support.md')
},
},
}
</script>
<style lang="scss">
.controls {
display: flex;
position: fixed;
border-bottom: 1px solid var(--color-border);
background: var(--color-main-background);
width: 100%;
height: 45px;
z-index: 1001;
.action {
order: 999;
}
.button, button {
box-sizing: border-box;
display: inline-block;
display: flex;
height: 36px;
padding: 9px;
align-items: center;
justify-content: center;
margin-left: 7px;
&.symbol {
width: 36px;
}
&.primary {
background: var(--color-primary);
color: var(--color-primary-text);
}
}
.breadcrumb {
flex-grow: 1;
overflow: hidden;
min-width: 35px;
// div.crumb:last-child {
// flex-shrink: 1;
// overflow: hidden;
// > span {
// flex-shrink: 1;
// text-overflow: ellipsis;
// }
// }
}
}
</style>

View File

@ -1,31 +0,0 @@
<template>
<div class="loading-overlay">
<span class="icon-loading" />
</div>
</template>
<style lang="scss">
.loading-overlay {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: #fff;
opacity: 0.9;
z-index: 1001;
.icon-loading {
position: fixed;
left: 50%;
top: 50%;
margin-left: -35px;
margin-top: -10px;
&::after {
border: 10px solid var(--color-loading-light);
border-top-color: var(--color-primary-element);
height: 70px;
width: 70px;
}
}
}
</style>

View File

@ -1,495 +0,0 @@
<!--
- @copyright Copyright (c) 2018 René Gieling <github@dartcafe.de>
-
- @author René Gieling <github@dartcafe.de>
- @author Natalie Gilbert
- @author Nick Gallo
-
- @license GNU AGPL version 3 or any later version
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-->
<template>
<div v-if="header" class="wrapper group-master table-row table-header">
<div class="wrapper group-1">
<div class="wrapper group-1-1">
<div class="name">
{{ t('forms', 'Title') }}
</div>
</div>
</div>
<div class="wrapper group-2">
<div class="wrapper-group-2-5">
<div class="deletetwo">
{{ t('forms',"Delete form") }}
</div>
</div>
<div class="wrapper-group-2-5">
<div class="copyL">
{{ t('forms', "Copy link") }}
</div>
</div>
<div class="wrapper-group-2-5">
<div class="result">
{{ t('forms', "View results") }}
</div>
</div>
<div class="wrapper group-2-1">
<div class="access">
{{ t('forms', 'Access') }}
</div>
</div>
<div class="owner">
{{ t('forms', 'Owner') }}
</div>
<div class="wrapper group-2-2">
<div class="created">
{{ t('forms', 'Created') }}
</div>
<div class="expiry">
{{ t('forms', 'Expires') }}
</div>
</div>
</div>
</div>
<div v-else class="wrapper table-row table-body group-master">
<div class="wrapper group-1">
<div>
<img class="icontwo">
</div>
<div class="symbol icon-voted" />
<a :href="submitUrl" class="wrapper group-1-1">
<div class="name">
{{ form.form.title }}
</div>
<div class="description">
{{ form.form.description }}
</div>
</a>
</div>
<div class="wrapper group-2">
<div class="wrapper group-2-8">
<a class="icon icon-delete svg delete-form" @click="deleteForm" />
<span class="hidden-visually">
{{ t('forms', 'Delete form') }}
</span>
</div>
<div class="wrapper group-2-9">
<a class="icon icon-clippy svg delete-form" @click="copyLink" />
<span class="hidden-visually">
{{ t('forms', 'Copy link') }}
</span>
</div>
<div class="list-results wrapper">
<a class="icon icon-toggle svg delete-form" @click="viewResults" />
<span class="hidden-visually">
{{ t('forms', 'View results') }}
</span>
</div>
<div class="wrapper group-2-1">
<div v-tooltip="accessType" class="thumbnail access" :class="form.form.access">
{{ accessType }}
</div>
</div>
<div class="owner">
<UserDiv :user-id="form.form.ownerId" :display-name="form.form.ownerDisplayName" />
</div>
<div class="wrapper group-2-2">
<div class="created ">
{{ timeSpanCreated }}
</div>
<div class="expiry" :class="{ expired : form.form.expired }">
{{ timeSpanExpiration }}
</div>
</div>
</div>
</div>
</template>
<script>
import moment from '@nextcloud/moment'
import UserDiv from '../components/_base-UserDiv'
export default {
components: {
UserDiv,
},
props: {
header: {
type: Boolean,
default: false,
},
form: {
type: Object,
default: undefined,
},
},
data() {
return {
openedMenu: false,
hostName: this.$route.query.page,
}
},
computed: {
accessType() {
if (this.form.form.access === 'public') {
return t('forms', 'Public access')
} else if (this.form.form.access === 'select') {
return t('forms', 'Only shared')
} else if (this.form.form.access === 'registered') {
return t('forms', 'Registered users only')
} else if (this.form.form.access === 'hidden') {
return t('forms', 'Hidden form')
} else {
return ''
}
},
timeSpanCreated() {
return moment(this.form.form.created, 'YYYY-MM-DD HH:mm')
},
timeSpanExpiration() {
if (this.form.form.expires) {
return moment(this.form.form.expirationDate)
} else {
return t('forms', 'never')
}
},
countShares() {
return this.form.shares.length
},
submitUrl() {
return OC.generateUrl('apps/forms/form/') + this.form.form.hash
},
},
methods: {
toggleMenu() {
this.openedMenu = !this.openedMenu
},
hideMenu() {
this.openedMenu = false
},
copyLink() {
// this.$emit('copyLink')
this.$copyText(window.location.origin + this.submitUrl).then(
function(e) {
OC.Notification.showTemporary(t('forms', 'Link copied to clipboard'))
},
function(e) {
OC.Notification.showTemporary(t('forms', 'Error, while copying link to clipboard'))
}
)
this.hideMenu()
},
deleteForm() {
this.$emit('deleteForm')
},
viewResults() {
this.$emit('viewResults')
},
},
}
</script>
<style lang="scss">
$row-padding: 15px;
$table-padding: 4px;
$date-width: 130px;
$participants-width: 95px;
$group-2-2-width: max($date-width, $participants-width);
$owner-width: 140px;
$access-width: 44px;
$group-2-1-width: max($access-width, $date-width);
$group-2-width: $owner-width + $group-2-1-width + $group-2-2-width;
$action-width: 44px;
$thumbnail-width: 44px;
$thumbnail-icon-width: 32px;
$name-width: 150px;
$description-width: 150px;
$group-1-1-width: max($name-width, $description-width);
$group-1-width: $thumbnail-width + $group-1-1-width + $action-width;
$group-master-width: max($group-1-width, $group-2-width);
$mediabreak-1: ($group-1-width + $owner-width + $access-width + $date-width + $date-width + $participants-width + $row-padding * 2);
$mediabreak-2: ($group-1-width + $group-2-width + $row-padding * 2);
$mediabreak-3: $group-1-width + $owner-width + max($group-2-1-width, $group-2-2-width) + $row-padding *2 ;
.table-row {
width: 100%;
padding-left: $row-padding;
padding-right: $row-padding;
line-height: 2em;
transition: background-color 0.3s ease;
background-color: var(--color-main-background);
min-height: 4em;
border-bottom: 1px solid var(--color-border);
&.table-header {
.name, .description {
padding-left: ($thumbnail-width + $table-padding *2);
}
.owner {
padding-left: 6px;
}
}
&.table-body {
&:hover, &:focus, &:active, &.mouseOver {
transition: background-color 0.3s ease;
background-color: var(--color-background-dark);
}
.icon-more {
right: 14px;
opacity: 0.3;
cursor: pointer;
height: 44px;
width: 44px;
}
.symbol {
padding: 2px;
}
}
&.table-header {
opacity: 0.5;
}
}
.wrapper {
display: flex;
align-items: center;
position: relative;
flex-grow: 0;
}
.name {
width: $name-width;
}
.description {
width: $description-width;
opacity: 0.5;
}
.name, .description {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.actions {
width: $action-width;
position: relative;
overflow: initial;
}
.result {
width: 60px;
padding-right: 15px;
}
.deletetwo {
width: 60px;
padding-right: 15px;
}
.copyL {
width: 60px;
padding-right: 15px;
}
.access {
width: $access-width;
}
.owner {
width: $owner-width;
}
.created {
width: $date-width;
}
.expiry {
width: $date-width;
&.expired {
color: red;
}
}
.delete-form {
padding-right:70px;
}
.list-results {
width: 65px;
}
.group-1, .group-1-1 {
flex-grow: 1;
}
.group-1-1 {
flex-direction: column;
width: $group-1-1-width;
> div {
width: 100%;
}
}
@media all and (max-width: ($mediabreak-1) ) {
.group-1 {
width: $group-1-width;
}
.group-2-1, .group-2-2 {
flex-direction: column;
}
.created {
width: $group-2-1-width;;
}
.expiry, .participants {
width: $group-2-2-width;;
}
}
@media all and (max-width: ($mediabreak-2) ) {
.table-row {
padding: 0;
}
.group-2-1 {
display: none;
}
}
@media all and (max-width: ($mediabreak-3) ) {
.group-2 {
display: none;
}
}
.icontwo {
width: 44px;
height: 44px;
padding-right: 4px;
font-size: 0;
background-color: var(--color-text-light);
mask-image: var(--icon-organization-000) no-repeat 50% 50%;
-webkit-mask: var(--icon-organization-000) no-repeat 50% 50%;
mask-size: 16px;
}
.thumbnail {
width: 44px;
height: 44px;
padding-right: 4px;
font-size: 0;
background-color: var(--color-text-light);
&.dateForm {
mask-image: var(--icon-calendar-000) no-repeat 50% 50%;
-webkit-mask: var(--icon-calendar-000) no-repeat 50% 50%;
mask-size: 16px;
}
&.textForm {
mask-image: var(--icon-organization-000) no-repeat 50% 50%;
-webkit-mask: var(--icon-organization-000) no-repeat 50% 50%;
mask-size: 16px;
}
&.expired {
background-color: var(--color-background-darker);
}
&.access {
display: inherit;
&.hidden {
mask-image: var(--icon-password-000) no-repeat 50% 50%;
-webkit-mask: var(--icon-password-000) no-repeat 50% 50%;
mask-size: 16px;
}
&.public {
mask-image: var(--icon-link-000) no-repeat 50% 50%;
-webkit-mask: var(--icon-link-000) no-repeat 50% 50%;
mask-size: 16px;
}
&.select {
mask-image: var(--icon-share-000) no-repeat 50% 50%;
-webkit-mask: var(--icon-share-000) no-repeat 50% 50%;
mask-size: 16px;
}
&.registered {
mask-image: var(--icon-group-000) no-repeat 50% 50%;
-webkit-mask: var(--icon-group-000) no-repeat 50% 50%;
mask-size: 16px;
}
}
}
.icon-voted {
background-image: var(--icon-checkmark-fff);
}
.app-navigation-entry-utils-counter {
padding-right: 0 !important;
overflow: hidden;
text-align: right;
font-size: 9pt;
line-height: 44px;
padding: 0 12px;
// min-width: 25px;
&.highlighted {
padding: 0;
text-align: center;
span {
padding: 2px 5px;
border-radius: 10px;
background-color: var(--color-primary);
color: var(--color-primary-text);
}
}
}
.symbol.icon-voted {
position: absolute;
left: 11px;
top: 16px;
background-size: 0;
min-width: 8px;
min-height: 8px;
background-color: var(--color-success);
border-radius: 50%;
}
</style>

View File

@ -0,0 +1,54 @@
/**
* @copyright Copyright (c) 2020 John Molakvoæ <skjnldsv@protonmail.com>
*
* @author John Molakvoæ <skjnldsv@protonmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Question from '../components/Questions/Question'
export default {
inheritAttrs: false,
props: {
title: {
type: String,
required: true,
},
values: {
type: Array,
required: true,
},
},
components: {
Question,
},
data() {
return {
edit: false,
}
},
methods: {
onTitleChange(title) {
this.$emit('update:title', title)
},
onValuesChange(values) {
this.$emit('update:values', values)
},
},
}

View File

@ -24,7 +24,7 @@ export default [
{
label: t('forms', 'Multiple choice'),
value: 'radiogroup',
icon: 'icon-forms',
icon: 'icon-answer-multiple',
},
{
label: t('forms', 'Checkboxes'),

31
src/utils/GenRandomId.js Normal file
View File

@ -0,0 +1,31 @@
/**
* @copyright Copyright (c) 2018 John Molakvoæ <skjnldsv@protonmail.com>
*
* @author John Molakvoæ <skjnldsv@protonmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
const GenRandomId = (length) => {
return Math.random()
.toString(36)
.replace(/[^a-z]+/g, '')
.substr(0, length || 5)
}
export default GenRandomId

View File

@ -3,6 +3,7 @@
-
- @author René Gieling <github@dartcafe.de>
- @author Nick Gallo
- @author John Molakvoæ <skjnldsv@protonmail.com>
-
- @license GNU AGPL version 3 or any later version
-
@ -33,7 +34,9 @@
<span class="icon-forms-white" role="img" />
{{ t('forms', 'Show results') }}
</button>
<button v-tooltip="t('forms', 'Toggle settings')" @click="toggleSidebar">
<button v-tooltip="t('forms', 'Toggle settings')"
:aria-label="t('forms', 'Toggle settings')"
@click="toggleSidebar">
<span class="icon-settings" role="img" />
</button>
</TopBar>
@ -48,30 +51,37 @@
:placeholder="t('forms', 'Title')"
:required="true"
autofocus
type="text">
type="text"
@click="selectIfUnchanged">
<label class="hidden-visually" for="form-desc">{{ t('forms', 'Description') }}</label>
<textarea
id="form-desc"
ref="description"
v-model="form.form.description"
:placeholder="t('forms', 'Description')"
@change="autoSizeDescription"
@keydown="autoSizeDescription" />
</header>
<section>
<!-- Add new questions toolbar -->
<!-- <div class="question-toolbar" role="toolbar">
<button v-for="type in answerTypes"
:key="type.label"
class="question-toolbar__question"
@click="addQuestion">
<span class="question-toolbar__icon" :class="type.icon" />
{{ type.label }}
</button>
</div> -->
<div class="question-toolbar" role="toolbar">
<Actions ref="questionMenu"
v-tooltip="t('forms', 'Add a question to this form')"
:aria-label="t('forms', 'Add a question to this form')"
:open.sync="questionMenuOpened"
default-icon="icon-add-white">
<ActionButton v-for="type in answerTypes"
:key="type.label"
class="question-toolbar__question"
:icon="type.icon"
@click="addQuestion">
{{ type.label }}
</ActionButton>
</Actions>
</div>
<div id="quiz-form-selector-text">
<!--shows inputs for question types: drop down box to select the type, text box for question, and button to add-->
<!-- <div id="quiz-form-selector-text">
<label for="ans-type">Answer Type: </label>
<select v-model="selected">
<option value="" disabled>
@ -90,20 +100,21 @@
@click="addQuestion()">
{{ t('forms', 'Add Question') }}
</button>
</div>
</div> -->
<!-- No questions -->
<EmptyContent v-if="form.questions.length === 0">
{{ t('forms', 'This form does not have any questions') }}
<template #desc>
<button class="primary" @click="openQuestionMenu">
<button class="empty-content__button primary" @click="openQuestionMenu">
<span class="icon-add-white" />
{{ t('forms', 'Add a new one') }}
</button>
</template>
</EmptyContent>
<!-- Questions list -->
<transitionGroup
<!-- <transitionGroup
v-else
id="form-list"
name="list"
@ -117,18 +128,29 @@
@addOption="addOption"
@deleteOption="deleteOption"
@deleteQuestion="deleteQuestion(question, index)" />
</transitionGroup>
</transitionGroup> -->
<Draggable v-model="questions"
:animation="200"
tag="ul"
@start="dragging = true"
@end="dragging = false">
<Questions :is="question.type"
v-for="question in questions"
:key="question.id"
v-bind.sync="question" />
</Draggable>
</section>
</AppContent>
</template>
<script>
import { generateUrl } from '@nextcloud/router'
import axios from '@nextcloud/axios'
import moment from '@nextcloud/moment'
import { emit } from '@nextcloud/event-bus'
import { showError } from '@nextcloud/dialogs'
import { generateUrl } from '@nextcloud/router'
import { showError, showSuccess } from '@nextcloud/dialogs'
import axios from '@nextcloud/axios'
import debounce from 'debounce'
import Draggable from 'vuedraggable'
import ActionButton from '@nextcloud/vue/dist/Components/ActionButton'
import Actions from '@nextcloud/vue/dist/Components/Actions'
@ -136,9 +158,12 @@ import AppContent from '@nextcloud/vue/dist/Components/AppContent'
import answerTypes from '../models/AnswerTypes'
import EmptyContent from '../components/EmptyContent'
import Question from '../components/Questions/Question'
import QuestionLong from '../components/Questions/QuestionLong'
import QuestionShort from '../components/Questions/QuestionShort'
import QuestionMultiple from '../components/Questions/QuestionMultiple'
import QuizFormItem from '../components/quizFormItem'
import TopBar from '../components/TopBar'
import ViewsMixin from '../mixins/ViewsMixin'
export default {
@ -147,7 +172,12 @@ export default {
ActionButton,
Actions,
AppContent,
Draggable,
EmptyContent,
Question,
QuestionLong,
QuestionShort,
QuestionMultiple,
QuizFormItem,
TopBar,
},
@ -156,6 +186,7 @@ export default {
data() {
return {
questionMenuOpened: false,
placeholder: '',
newOption: '',
newQuestion: '',
@ -167,14 +198,28 @@ export default {
uniqueQuestionText: false,
uniqueOptionText: false,
allHaveOpt: false,
questionTypes: [
{ text: 'Radio Buttons', value: 'radiogroup' },
{ text: 'Checkboxes', value: 'checkbox' },
{ text: 'Short Response', value: 'text' },
{ text: 'Long Response', value: 'comment' },
{ text: 'Drop Down', value: 'dropdown' },
],
answerTypes,
questions: [
{
id: 1,
type: QuestionShort,
title: 'How old are you ?',
values: ['I\'m 48 years old'],
},
{
id: 2,
type: QuestionLong,
title: 'Your latest best memory ?',
values: ['One day I was at the beach.\nIt was fun. The sun was shinning.\nThe water was warm'],
},
{
id: 3,
type: QuestionMultiple,
title: 'Choose an answer ?',
values: ['Answer 1', 'Answer 2', 'Answer 3', 'Answer 4'],
},
],
dragging: false,
}
},
@ -197,6 +242,7 @@ export default {
return t('forms', 'Done')
}
},
},
watch: {
@ -221,6 +267,10 @@ export default {
}
},
mounted() {
this.autoSizeDescription()
},
methods: {
switchSidebar() {
@ -252,10 +302,10 @@ export default {
order: respData.order,
text: this.newQuestion,
type: this.selected,
options: [],
answers: [],
})
}
this.newQuestion = ''
this.newQuizQuestion = ''
}
},
@ -366,7 +416,21 @@ export default {
* Add question methods
*/
openQuestionMenu() {
this.$refs.questionMenu.opened = true
// TODO: fix the vue components to allow external click triggers without
// conflicting with the click outside directive
setTimeout(() => {
this.questionMenuOpened = true
}, 100)
},
/**
* Select the text in the input if it is still set to 'New form'
* @param {Event} e the click event
*/
selectIfUnchanged(e) {
if (e.target && e.target.value === t('forms', 'New form')) {
e.target.select()
}
},
},
}
@ -375,14 +439,16 @@ export default {
<style lang="scss">
#app-content {
display: flex;
flex-direction: column;
align-items: center;
flex-direction: column;
header,
section {
width: 100%;
max-width: 900px;
}
// Title & description header
header {
display: flex;
flex-direction: column;
@ -391,30 +457,69 @@ export default {
#form-title,
#form-desc {
width: 100%;
border: none;
margin: 10px; // aerate the header
padding: 0; // makes alignment and desc height calc easier
border: none;
}
#form-title {
font-size: 2em;
}
#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)
resize: none
resize: none;
}
}
.empty-content__button {
margin: 5px;
> span {
margin-right: 5px;
cursor: pointer;
opacity: 1;
}
}
// Questions container
section {
position: relative;
display: flex;
flex-direction: column;
margin-bottom: 250px;
.question-toolbar {
position: sticky;
z-index: 50;
top: var(--header-height);
display: flex;
align-items: center;
align-self: flex-end;
width: 44px;
height: var(--top-bar-height);
// make sure this doesn't take any space and appear floating
margin-top: -44px;
.icon-add-white {
opacity: 1;
border-radius: 50%;
// TODO: standardize on components
background-color: var(--color-primary-element);
&:hover,
&:focus,
&:active {
background-color: var(--color-primary-element-light) !important;
}
}
}
}
}
/* Transitions for inserting and removing list items */
.list-enter-active,
.list-leave-active {
transition: all 0.5s ease;
transition: all .5s ease;
}
.list-enter,
@ -423,9 +528,8 @@ export default {
}
.list-move {
transition: transform 0.5s;
transition: transform .5s;
}
/* */
#form-item-selector-text {
> input {
@ -436,38 +540,37 @@ export default {
.form-table {
> li {
display: flex;
align-items: baseline;
padding-left: 8px;
padding-right: 8px;
line-height: 24px;
min-height: 24px;
border-bottom: 1px solid var(--color-border);
overflow: hidden;
align-items: baseline;
min-height: 24px;
padding-right: 8px;
padding-left: 8px;
white-space: nowrap;
border-bottom: 1px solid var(--color-border);
line-height: 24px;
&:active,
&:hover {
transition: var(--background-dark) 0.3s ease;
transition: var(--background-dark) .3s ease;
background-color: var(--color-background-dark); //$hover-color;
}
> div {
display: flex;
flex-grow: 1;
font-size: 1.2em;
opacity: 0.7;
white-space: normal;
padding-right: 4px;
white-space: normal;
opacity: .7;
font-size: 1.2em;
&.avatar {
flex-grow: 0;
}
}
> div:nth-last-child(1) {
justify-content: center;
flex-grow: 0;
flex-shrink: 0;
justify-content: center;
}
}
}
@ -493,13 +596,14 @@ button {
}
#shiftDates {
background-repeat: no-repeat;
background-position: 10px center;
min-width: 16px;
min-height: 16px;
margin: 0;
padding: 10px;
padding-left: 34px;
text-align: left;
margin: 0;
background-repeat: no-repeat;
background-position: 10px center;
}
</style>

View File

@ -33,7 +33,7 @@
<div v-for="sum in stats" :key="sum">
{{ sum }}
</div>
<div id="app-content">
<div id="app-content" :class="{'icon-loading': loading}">
<transition-group
name="list"
tag="div"
@ -48,7 +48,6 @@
:answer="answer"
@viewResults="viewFormResults(index, form.form, 'results')" />
</transition-group>
<LoadingOverlay v-if="loading" />
<modal-dialog />
</div>
</AppContent>
@ -58,7 +57,6 @@
import ResultItem from '../components/resultItem'
import json2csvParser from 'json2csv'
import axios from '@nextcloud/axios'
import LoadingOverlay from '../components/_base-LoadingOverlay'
import ViewsMixin from '../mixins/ViewsMixin'
import { generateUrl } from '@nextcloud/router'
@ -69,7 +67,6 @@ export default {
components: {
AppContent,
ResultItem,
LoadingOverlay,
},
mixins: [ViewsMixin],