diff --git a/appinfo/routes.php b/appinfo/routes.php index 764a3b7..b973073 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -24,12 +24,14 @@ return [ 'routes' => [ ['name' => 'page#index', 'url' => '/', 'verb' => 'GET'], - ['name' => 'page#goto_form', 'url' => '/form/{hash}', 'verb' => 'GET'], - ['name' => 'page#create_form', 'url' => '/new', 'verb' => 'GET'], - ['name' => 'page#edit_form', 'url' => '/edit/{hash}', 'verb' => 'GET'], - ['name' => 'page#clone_form', 'url' => '/clone/{hash}', 'verb' => 'GET'], - ['name' => 'page#getResult', 'url' => '/results/{id}', 'verb' => 'GET'], + // Before /{hash} to avoid conflict + ['name' => 'page#createForm', 'url' => '/new', 'verb' => 'GET'], + ['name' => 'page#editForm', 'url' => '/{hash}/edit/', 'verb' => 'GET'], + ['name' => 'page#cloneForm', 'url' => '/{hash}/clone/', 'verb' => 'GET'], + ['name' => 'page#getResult', 'url' => '/{hash}/results/', 'verb' => 'GET'], + + ['name' => 'page#goto_form', 'url' => '/{hash}', 'verb' => 'GET'], ['name' => 'page#delete_form', 'url' => '/delete', 'verb' => 'POST'], ['name' => 'page#insert_vote', 'url' => '/insert/vote', 'verb' => 'POST'], diff --git a/css/colors.scss b/css/colors.scss deleted file mode 100644 index 6495a93..0000000 --- a/css/colors.scss +++ /dev/null @@ -1,33 +0,0 @@ -$bg-no: #ffede9; -$bg-maybe: #fcf7e1; -$bg-unvoted: #fff4c8; -$bg-yes: #ebf5d6; -$bg-information: #b19c3e; - -$fg-no: #f45573; -$fg-maybe: #f0db98; -$fg-unvoted: #f0db98; -$fg-yes: #49bc49; - -// Icon definitions -@include icon-black-white('app', 'forms', 2); - -.icon-yes { - @include icon-color('checkmark', 'actions', $fg-yes, 1, true); -} - -.icon-comment-yes { - @include icon-color('comment', 'actions', $fg-yes, 1, true); -} - -.icon-comment-no { - @include icon-color('comment', 'actions', $fg-no, 1, true); -} - -.icon-no { - @include icon-color('close', 'actions', $fg-no, 1, true); -} - -.icon-maybe { - @include icon-color('maybe-vote-variant', 'forms', $fg-maybe); -} diff --git a/css/forms.scss b/css/forms.scss new file mode 100644 index 0000000..93649ee --- /dev/null +++ b/css/forms.scss @@ -0,0 +1,23 @@ +/** + * @copyright Copyright (c) 2020 John Molakvoæ + * + * @author John Molakvoæ + * + * @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 . + * + */ + +@import 'icons' diff --git a/css/icons.scss b/css/icons.scss new file mode 100644 index 0000000..a799705 --- /dev/null +++ b/css/icons.scss @@ -0,0 +1,24 @@ + +// Icon definitions +@include icon-black-white('forms', 'forms', 3); +@include icon-black-white('clone', 'forms', 1); + +.icon-yes { + @include icon-color('checkmark', 'actions', $color-success, 1, true); +} + +.icon-comment-yes { + @include icon-color('comment', 'actions', $color-success, 1, true); +} + +.icon-comment-no { + @include icon-color('comment', 'actions', $color-error, 1, true); +} + +.icon-no { + @include icon-color('close', 'actions', $color-error, 1, true); +} + +.icon-maybe { + @include icon-color('maybe-vote-variant', 'forms', $color-warning); +} diff --git a/css/sidebar.scss b/css/sidebar.scss deleted file mode 100644 index eb8cf38..0000000 --- a/css/sidebar.scss +++ /dev/null @@ -1,214 +0,0 @@ -@import 'colors.scss'; - -$border_current_user: 2px solid; -$border_user: 1px solid var(--color-border-dark); -$user-column-width: 265px; - -#forms-sidebar { - width: 520px; - flex-grow: 0; - flex-shrink: 1; - min-width: 300px; - border-left: 1px solid var(--color-border); - transition: margin-right 300ms; - z-index: 500; - > div, - > ul { - padding: 8px; - } -} - -.authorRow { - align-items: center; - .author { - margin-left: 8px; - opacity: 0.5; - flex-grow: 1; - &.external { - margin-right: 33px; - opacity: 1; - > input { - width: 100%; - } - } - } -} - -.detailsView { - z-index: 1000 !important; - .close.flex-row { - justify-content: flex-end; - margin: 8px 8px 0 0; - } - - .header.flex-row { - flex-direction: row; - flex-grow: 0; - align-items: flex-start; - margin-left: 0; - margin-top: 0; - padding: 0 17px; - } - - .formInformation { - width: 220px; - flex-grow: 1; - flex-shrink: 1; - padding-right: 15px; - - .authorRow { - .leftLabel { - margin-right: 4px; - } - } - - .cloud { - margin: 4px 0; - - > span { - color: var(--color-primary-text); - margin: 2px; - padding: 2px 4px; - border-radius: var(--border-radius); - float: left; - text-shadow: 1px 1px var(--color-box-shadow); - background-color: var(--color-loading-light); - } - .open { - background-color: $fg-yes; - } - .expired { - background-color: $fg-no; - } - .information { - background-color: $bg-information; - } - } - } - #expired_info { - margin: 0 15px; - } - - .formActions { - display: flex; - flex-direction: column; - margin-right: 15px; - - .close { - margin: 15px; - background-position: right top; - height: 30px; - } - - > ul > li { - &:focus, - &:hover, - &.active, - a.selected { - &, - > a { - opacity: 1; - box-shadow: inset 4px 0 var(--color-primary); - } - } - - > a[class*='icon-'], - > ul > li > a[class*='icon-'], - > a[style*='background-image'], - > ul > li > a[style*='background-image'] { - padding-left: 44px; - } - - > a, - > ul > li > a { - background-size: 16px 16px; - background-position: 14px center; - background-repeat: no-repeat; - display: block; - justify-content: space-between; - line-height: 44px; - min-height: 44px; - padding: 0 12px; - overflow: hidden; - box-sizing: border-box; - white-space: nowrap; - text-overflow: ellipsis; - color: var(--color-main-text); - opacity: 0.57; - flex: 1 1 0; - z-index: 100; - } - a, - .app-navigation-entry-deleted { - padding-left: 44px !important; - } - } - } - #configurationsTabView { - .configBox { - padding: 8px 8px; - - > .title { - font-weight: bold; - margin-bottom: 4px; - } - - > div { - padding-left: 4px; - } - - input.hasDatepicker { - margin-left: 17px; - } - &.oneline { - width: 100%; - } - } - } - #commentsTabView { - .newCommentForm div.message:empty:before { - content: attr(data-placeholder); - color: grey; - } - #commentBox { - border: 1px solid var(--color-border-dark); - border-radius: var(--border-radius); - padding: 7px 6px; - margin: 3px 3px 3px 40px; - cursor: text; - } - .comment { - margin-bottom: 30px; - - .date { - right: 0; - top: 5px; - opacity: 0.5; - } - } - - .message { - margin-left: 40px; - flex-grow: 1; - flex-shrink: 1; - } - - .new-comment { - .submitComment { - align-self: last baseline; - width: 30px; - margin: 0; - padding: 7px 9px; - background-color: transparent; - border: none; - opacity: 0.3; - } - - .icon-loading-small { - float: left; - margin-top: 10px; - display: none; - } - } - } -} diff --git a/img/app.png b/img/app.png deleted file mode 100644 index cf801c0..0000000 Binary files a/img/app.png and /dev/null differ diff --git a/img/app.svg b/img/app.svg index d85219c..63cfdc3 100644 --- a/img/app.svg +++ b/img/app.svg @@ -1 +1,8 @@ - + + + + + + + + diff --git a/img/clone.svg b/img/clone.svg new file mode 100644 index 0000000..469fd1b --- /dev/null +++ b/img/clone.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/img/favicon-mask.svg b/img/favicon-mask.svg deleted file mode 100644 index 094e556..0000000 --- a/img/favicon-mask.svg +++ /dev/null @@ -1,45 +0,0 @@ - -image/svg+xml \ No newline at end of file diff --git a/img/favicon-touch.png b/img/favicon-touch.png deleted file mode 100644 index dec255f..0000000 Binary files a/img/favicon-touch.png and /dev/null differ diff --git a/img/favicon-touch.svg b/img/favicon-touch.svg deleted file mode 100644 index 6503143..0000000 --- a/img/favicon-touch.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/img/favicon.ico b/img/favicon.ico deleted file mode 100644 index b5c8ac6..0000000 Binary files a/img/favicon.ico and /dev/null differ diff --git a/img/favicon.png b/img/favicon.png deleted file mode 100644 index 75f5c15..0000000 Binary files a/img/favicon.png and /dev/null differ diff --git a/img/favicon.svg b/img/favicon.svg deleted file mode 100644 index 80b12d0..0000000 --- a/img/favicon.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/img/forms.svg b/img/forms.svg new file mode 100644 index 0000000..63cfdc3 --- /dev/null +++ b/img/forms.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/lib/Controller/PageController.php b/lib/Controller/PageController.php index 1e2956b..982ffb0 100644 --- a/lib/Controller/PageController.php +++ b/lib/Controller/PageController.php @@ -7,6 +7,7 @@ * @author Inigo Jiron * @author Natalie Gilbert * @author Affan Hussain + * @author John Molakvoæ * * @license GNU AGPL version 3 or any later version * @@ -86,40 +87,63 @@ class PageController extends Controller { } /** - * @NoAdminRequired - * @NoCSRFRequired - */ + * @NoAdminRequired + * @NoCSRFRequired + * + * @return TemplateResponse + */ public function index(): TemplateResponse { - return new TemplateResponse('forms', 'forms.tmpl', - ['urlGenerator' => $this->urlGenerator]); - } - - /** - * @NoAdminRequired - */ - public function createForm(): TemplateResponse { - return new TemplateResponse('forms', 'forms.tmpl', - ['urlGenerator' => $this->urlGenerator]); - } - - /** - * @NoAdminRequired - */ - public function cloneForm(): TemplateResponse { - return new TemplateResponse('forms', 'forms.tmpl', - ['urlGenerator' => $this->urlGenerator]); + Util::addScript($this->appName, 'forms'); + Util::addStyle($this->appName, 'icons'); + return new TemplateResponse($this->appName, 'main'); } /** * @NoAdminRequired - * @param string $hash + * @NoCSRFRequired + * * @return TemplateResponse */ - public function editForm($hash): TemplateResponse { - return new TemplateResponse('forms', 'forms.tmpl', [ - 'urlGenerator' => $this->urlGenerator, - 'hash' => $hash - ]); + public function createForm(): TemplateResponse { + Util::addScript($this->appName, 'forms'); + Util::addStyle($this->appName, 'icons'); + return new TemplateResponse($this->appName, 'main'); + } + + /** + * @NoAdminRequired + * @NoCSRFRequired + * + * @return TemplateResponse + */ + public function cloneForm(): TemplateResponse { + Util::addScript($this->appName, 'forms'); + Util::addStyle($this->appName, 'icons'); + return new TemplateResponse($this->appName, 'main'); + } + + /** + * @NoAdminRequired + * @NoCSRFRequired + * + * @return TemplateResponse + */ + public function editForm(): TemplateResponse { + Util::addScript($this->appName, 'forms'); + Util::addStyle($this->appName, 'icons'); + return new TemplateResponse($this->appName, 'main'); + } + + /** + * @NoAdminRequired + * @NoCSRFRequired + * + * @return TemplateResponse + */ + public function getResult(): TemplateResponse { + Util::addScript($this->appName, 'forms'); + Util::addStyle($this->appName, 'icons'); + return new TemplateResponse($this->appName, 'main'); } /** @@ -402,13 +426,4 @@ class PageController extends Controller { } return false; } - - /** - * @NoAdminRequired - * @param int $id - * @return TemplateResponse - */ - public function getResult(int $id): TemplateResponse { - return new TemplateResponse('forms', 'forms.tmpl'); - } } diff --git a/package-lock.json b/package-lock.json index 44a68f3..00a8486 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1668,6 +1668,22 @@ "integrity": "sha512-f+sKpdLZXkODV+OY39K1M+Spmd4RgxmtEXmNn4Bviv4R7uBFHXuw+JX9ZdfDeOryfHjJ/TRQxQEp0GMpBwZFUw==", "dev": true }, + "@nextcloud/dialogs": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@nextcloud/dialogs/-/dialogs-1.2.2.tgz", + "integrity": "sha512-N8A8J8UKSvz/hqNcm7gwpm70uAAsx0wurjhdYZ989jaMho+H/Hinjd2jkbV8UnsYYw0x/vWvEX5t6Lwbv08K0g==", + "requires": { + "core-js": "3.6.4", + "toastify-js": "^1.7.0" + }, + "dependencies": { + "core-js": { + "version": "3.6.4", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.4.tgz", + "integrity": "sha512-4paDGScNgZP2IXXilaffL9X7968RuvwlkK3xWtZRVqgd8SYNiVKRJvkFd1aqqEuPfN7E68ZHEp9hDj6lHj4Hyw==" + } + } + }, "@nextcloud/eslint-config": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@nextcloud/eslint-config/-/eslint-config-1.0.0.tgz", @@ -10977,6 +10993,11 @@ } } }, + "toastify-js": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/toastify-js/-/toastify-js-1.7.0.tgz", + "integrity": "sha512-GmPy4zJ/ulCfmCHlfCtgcB+K2xhx2AXW3T/ZZOSjyjaIGevhz+uvR8HSCTay/wBq4tt2mUnBqlObP1sSWGlsnQ==" + }, "tough-cookie": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", diff --git a/package.json b/package.json index 039863e..e1e553e 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,9 @@ }, "dependencies": { "@nextcloud/axios": "^1.3.2", + "@nextcloud/dialogs": "^1.2.2", "@nextcloud/moment": "^1.1.0", + "@nextcloud/router": "^1.0.2", "@nextcloud/vue": "^1.4.1", "json2csv": "5.0.0", "vue": "^2.6.11", diff --git a/src/Forms.vue b/src/Forms.vue new file mode 100644 index 0000000..98007c5 --- /dev/null +++ b/src/Forms.vue @@ -0,0 +1,131 @@ + + + + + diff --git a/src/components/AppNavigationForm.vue b/src/components/AppNavigationForm.vue new file mode 100644 index 0000000..8305cad --- /dev/null +++ b/src/components/AppNavigationForm.vue @@ -0,0 +1,79 @@ + + + + + diff --git a/src/App.vue b/src/components/EmptyContent.vue similarity index 56% rename from src/App.vue rename to src/components/EmptyContent.vue index 7a042c7..7047d18 100644 --- a/src/App.vue +++ b/src/components/EmptyContent.vue @@ -1,7 +1,7 @@ diff --git a/src/main.js b/src/main.js index 8d09de4..e662a15 100644 --- a/src/main.js +++ b/src/main.js @@ -1,4 +1,3 @@ -/* jshint esversion: 6 */ /** * @copyright Copyright (c) 2018 René Gieling * @@ -23,7 +22,7 @@ import Vue from 'vue' import router from './router' -import App from './App' +import Forms from './Forms' import VueClipboard from 'vue-clipboard2' import Tooltip from '@nextcloud/vue/dist/Directives/Tooltip' @@ -51,7 +50,7 @@ __webpack_public_path__ = OC.linkTo('forms', 'js/') /* eslint-disable-next-line no-new */ new Vue({ - el: '#app-forms', + el: '#content', router: router, - render: h => h(App), + render: h => h(Forms), }) diff --git a/src/mixins/ViewsMixin.js b/src/mixins/ViewsMixin.js new file mode 100644 index 0000000..d7e8307 --- /dev/null +++ b/src/mixins/ViewsMixin.js @@ -0,0 +1,29 @@ +/** + * @copyright Copyright (c) 2020 John Molakvoæ + * + * @author John Molakvoæ + * + * @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 . + */ + +export default { + props: { + hash: { + type: String, + default: null, + }, + }, +} diff --git a/src/plugins/plugin.js b/src/plugins/plugin.js index 04c76a7..ad83016 100644 --- a/src/plugins/plugin.js +++ b/src/plugins/plugin.js @@ -1,4 +1,3 @@ -/* jshint esversion: 6 */ // we need our modal component import ModalDialog from './ModalDialog' diff --git a/src/router.js b/src/router.js index db40d9d..9554db7 100644 --- a/src/router.js +++ b/src/router.js @@ -1,4 +1,3 @@ -/* jshint esversion: 6 */ /** * @copyright Copyright (c) 2018 Julius Härtl * @copyright Copyright (c) 2018 John Molakvoæ @@ -22,59 +21,56 @@ * along with this program. If not, see . * */ + import Vue from 'vue' import Router from 'vue-router' +import { generateUrl } from '@nextcloud/router' + +import Create from './views/Create' +import Results from './views/Results' -// Dynamic loading -const Create = () => import('./views/Create') -const List = () => import('./views/List') -const Results = () => import('./views/Results') Vue.use(Router) export default new Router({ mode: 'history', - base: OC.generateUrl(''), + + // if index.php is in the url AND we got this far, then it's working: + // let's keep using index.php in the url + base: generateUrl('/apps/forms', ''), linkActiveClass: 'active', + routes: [ { - path: '/:index(index.php/)?apps/forms/', - components: { - default: List, - }, - props: false, - name: 'list', + path: '/', + name: 'root', }, { - path: '/:index(index.php/)?apps/forms/edit/:hash', - components: { - default: Create, - }, - props: true, - name: 'edit', - }, - { - path: '/:index(index.php/)?apps/forms/results/:hash', - components: { - default: Results, - }, - props: false, - name: 'results', - }, - { - path: '/:index(index.php/)?apps/forms/clone/:hash', - components: { - default: Create, - }, - props: true, - name: 'clone', - }, - { - path: '/:index(index.php/)?apps/forms/new', - components: { - default: Create, - }, - props: false, + path: '/new', + component: Create, name: 'create', }, + { + path: '/:hash', + name: 'fill', + props: true, + }, + { + path: '/:hash/edit', + component: Create, + name: 'edit', + props: true, + }, + { + path: '/:hash/results', + component: Results, + name: 'results', + props: true, + }, + { + path: '/:hash/clone', + component: Create, + name: 'clone', + props: true, + }, ], }) diff --git a/src/services/FormsService.js b/src/services/FormsService.js new file mode 100644 index 0000000..95a8bd4 --- /dev/null +++ b/src/services/FormsService.js @@ -0,0 +1,53 @@ +/** + * @copyright Copyright (c) 2019 John Molakvoæ + * + * @author John Molakvoæ + * + * @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 . + */ + +import axios from '@nextcloud/axios' + +/** + * Get the forms list + * + * @returns {Array} + */ +const getForms = async function() { + try { + const response = await axios.get(OC.generateUrl('apps/forms/get/forms')) + return response.data + } catch (error) { + console.error(error) + throw Error(t('forms', 'Unable to fetch the forms list')) + } +} + +/** + * Delete a form + * + * @param {int} id the form id to delete + */ +const deleteForm = async function(id) { + try { + axios.delete(OC.generateUrl('apps/forms/forms/{id}', { id })) + } catch (error) { + console.error(error) + throw Error(t('forms', 'Unable to delete the form')) + } +} + +export { deleteForm, getForms } diff --git a/src/utils/FormsUtils.js b/src/utils/FormsUtils.js new file mode 100644 index 0000000..4d102b1 --- /dev/null +++ b/src/utils/FormsUtils.js @@ -0,0 +1,44 @@ +/** + * @copyright Copyright (c) 2020 John Molakvoæ + * + * @author John Molakvoæ + * + * @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 . + */ + +/** + * Format a form object prior to forms v2.0 + * + * @param {Object} form the form raw object + * @returns {Object} properly formatted form object + */ +const formatForm = function(form) { + // clone form + const newForm = Object.assign({}, form, form.event) + + // migrate object architecture + Object.assign(newForm, { + questions: form.options.formQuizQuestions, + }) + + // cleanup + delete newForm.options + delete newForm.event + + return newForm +} + +export { formatForm } diff --git a/src/views/Create.vue b/src/views/Create.vue index 8af5bc8..b807838 100644 --- a/src/views/Create.vue +++ b/src/views/Create.vue @@ -26,7 +26,7 @@ -->