diff --git a/.editorconfig b/.editorconfig index 059cd56..0689e41 100644 --- a/.editorconfig +++ b/.editorconfig @@ -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 \ No newline at end of file diff --git a/appinfo/routes.php b/appinfo/routes.php index 0373f2a..eaeb20d 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -27,25 +27,32 @@ return [ // 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#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#insert_submission', 'url' => '/insert/submission', 'verb' => 'POST'], - ['name' => 'api#getForms', 'url' => 'api/v1/forms', 'verb' => 'GET'], - ['name' => 'api#newForm', 'url' => 'api/v1/form', 'verb' => 'POST'], - ['name' => 'api#getForm', 'url' => 'api/v1/form/{id}', 'verb' => 'GET'], - ['name' => 'api#updateForm', 'url' => 'api/v1/form/update/', 'verb' => 'POST'], - ['name' => 'api#deleteForm', 'url' => 'api/v1/form/{id}', 'verb' => 'DELETE'], - ['name' => 'api#updateQuestion', 'url' => 'api/v1/question/update/', 'verb' => 'POST'], - ['name' => 'api#reorderQuestions', 'url' => 'api/v1/question/reorder/', 'verb' => 'POST'], - ['name' => 'api#newQuestion', 'url' => 'api/v1/question/', 'verb' => 'POST'], - ['name' => 'api#deleteQuestion', 'url' => 'api/v1/question/{id}', 'verb' => 'DELETE'], - ['name' => 'api#newOption', 'url' => 'api/v1/option/', 'verb' => 'POST'], - ['name' => 'api#deleteOption', 'url' => 'api/v1/option/{id}', 'verb' => 'DELETE'], - ['name' => 'api#getSubmissions', 'url' => 'api/v1/submissions/{hash}', 'verb' => 'GET'], + // Forms + ['name' => 'api#getForms', 'url' => '/api/v1/forms', 'verb' => 'GET'], + ['name' => 'api#newForm', 'url' => '/api/v1/form', 'verb' => 'POST'], + ['name' => 'api#getForm', 'url' => '/api/v1/form/{id}', 'verb' => 'GET'], + ['name' => 'api#updateForm', 'url' => '/api/v1/form/update', 'verb' => 'POST'], + ['name' => 'api#deleteForm', 'url' => '/api/v1/form/{id}', 'verb' => 'DELETE'], + + // Questions + ['name' => 'api#newQuestion', 'url' => '/api/v1/question', 'verb' => 'POST'], + ['name' => 'api#updateQuestion', 'url' => '/api/v1/question/update', 'verb' => 'POST'], + ['name' => 'api#reorderQuestions', 'url' => '/api/v1/question/reorder', 'verb' => 'POST'], + ['name' => 'api#deleteQuestion', 'url' => '/api/v1/question/{id}', 'verb' => 'DELETE'], + + // Answers + ['name' => 'api#newOption', 'url' => '/api/v1/option', 'verb' => 'POST'], + ['name' => 'api#updateOption', 'url' => '/api/v1/option/update', 'verb' => 'POST'], + ['name' => 'api#deleteOption', 'url' => '/api/v1/option/{id}', 'verb' => 'DELETE'], + + ['name' => 'api#getSubmissions', 'url' => '/api/v1/submissions/{hash}', 'verb' => 'GET'], ['name' => 'system#get_site_users_and_groups', 'url' => '/get/siteusers', 'verb' => 'POST'], ] diff --git a/babel.config.js b/babel.config.js index fb0cc3b..ff18494 100644 --- a/babel.config.js +++ b/babel.config.js @@ -2,8 +2,11 @@ module.exports = { plugins: ['@babel/plugin-syntax-dynamic-import'], presets: [ [ - '@babel/preset-env' - ] - ] + '@babel/preset-env', + { + corejs: 3, + useBuiltIns: 'entry', + }, + ], + ], } - diff --git a/css/forms.scss b/css/forms.scss index 93649ee..a9dedc5 100644 --- a/css/forms.scss +++ b/css/forms.scss @@ -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'; diff --git a/css/icons.scss b/css/icons.scss index a799705..9c651ba 100644 --- a/css/icons.scss +++ b/css/icons.scss @@ -2,6 +2,11 @@ // Icon definitions @include icon-black-white('forms', 'forms', 3); @include icon-black-white('clone', 'forms', 1); +@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); diff --git a/img/answer-checkbox.svg b/img/answer-checkbox.svg new file mode 100644 index 0000000..ccf0a48 --- /dev/null +++ b/img/answer-checkbox.svg @@ -0,0 +1,3 @@ + + + diff --git a/img/answer-long.svg b/img/answer-long.svg new file mode 100644 index 0000000..8303e29 --- /dev/null +++ b/img/answer-long.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/img/answer-multiple.svg b/img/answer-multiple.svg new file mode 100644 index 0000000..6b95854 --- /dev/null +++ b/img/answer-multiple.svg @@ -0,0 +1,3 @@ + + + diff --git a/img/answer-short.svg b/img/answer-short.svg new file mode 100644 index 0000000..f47c301 --- /dev/null +++ b/img/answer-short.svg @@ -0,0 +1,4 @@ + + + + diff --git a/img/drag-handle.svg b/img/drag-handle.svg new file mode 100644 index 0000000..c062d7d --- /dev/null +++ b/img/drag-handle.svg @@ -0,0 +1,3 @@ + + + diff --git a/lib/Controller/ApiController.php b/lib/Controller/ApiController.php index 9dd61c9..be8b82d 100644 --- a/lib/Controller/ApiController.php +++ b/lib/Controller/ApiController.php @@ -95,7 +95,7 @@ class ApiController extends Controller { } catch (DoesNotExistException $e) { //handle silently - }finally{ + } finally { return $optionList; } } @@ -119,6 +119,7 @@ class ApiController extends Controller { /** * @NoAdminRequired + * * Read Form-List only with necessary information for Listing. */ public function getForms(): Http\JSONResponse { @@ -130,7 +131,7 @@ class ApiController extends Controller { 'id' => $form->getId(), 'hash' => $form->getHash(), 'title' => $form->getTitle(), - 'expired' => $form->getExpired(), + 'expires' => $form->getExpires(), ]; } @@ -150,7 +151,7 @@ class ApiController extends Controller { } $result = $form->read(); - $result['questions'] = getQuestions(); + $result['questions'] = $this->getQuestions($id); return new Http\JSONResponse($result); } @@ -186,7 +187,9 @@ class ApiController extends Controller { /** * @NoAdminRequired + * * Writes the given key-value pairs into Database. + * * @param int $id FormId of form to update * @param array $keyValuePairs Array of key=>value pairs to update. */ @@ -241,7 +244,7 @@ class ApiController extends Controller { // Delete Submissions(incl. Answers), Questions(incl. Options) and Form. $this->submissionMapper->deleteByForm($id); $this->questionMapper->deleteByForm($id); - $this->formMapper->delete($formToDelete); + $this->formMapper->delete($form); return new Http\JSONResponse($id); } @@ -256,11 +259,16 @@ class ApiController extends Controller { 'text' => $text, ]); + if (array_search($type, Question::TYPES) === false) { + $this->logger->debug('Invalid type'); + return new Http\JSONResponse(['message' => 'Invalid type'], Http::STATUS_BAD_REQUEST); + } + try { $form = $this->formMapper->findById($formId); } catch (IMapperException $e) { $this->logger->debug('Could not find form'); - return new Http\JSONResponse([], Http::STATUS_BAD_REQUEST); + return new Http\JSONResponse(['message' => 'Could not find form'], Http::STATUS_BAD_REQUEST); } if ($form->getOwnerId() !== $this->userId) { @@ -286,10 +294,8 @@ class ApiController extends Controller { $question = $this->questionMapper->insert($question); - $response = [ - 'id' => $question->getId(), - 'order' => $question->getOrder() - ]; + $response = $question->read(); + $response['options'] = []; return new Http\JSONResponse($response); } @@ -298,7 +304,7 @@ class ApiController extends Controller { * @NoAdminRequired * Updates the Order of all Questions of a Form. * @param int $formId Id of the form to reorder - * @param int $newOrder Array of Question-Ids in new order. + * @param int[] $newOrder Array of Question-Ids in new order. */ public function reorderQuestions(int $formId, array $newOrder): Http\JSONResponse { $this->logger->debug('Reordering Questions on Form {formId} as Question-Ids {newOrder}', [ @@ -467,16 +473,15 @@ class ApiController extends Controller { /** * @NoAdminRequired */ - public function newOption(int $formId, int $questionId, string $text): Http\JSONResponse { - $this->logger->debug('Adding new option: formId: {formId}, questionId: {questionId}, text: {text}', [ - 'formId' => $formId, + public function newOption(int $questionId, string $text): Http\JSONResponse { + $this->logger->debug('Adding new option: questionId: {questionId}, text: {text}', [ 'questionId' => $questionId, 'text' => $text, ]); try { - $form = $this->formMapper->findById($formId); $question = $this->questionMapper->findById($questionId); + $form = $this->formMapper->findById($question->getFormId()); } catch (IMapperException $e) { $this->logger->debug('Could not find form or question so option can\'t be added'); return new Http\JSONResponse([], Http::STATUS_BAD_REQUEST); @@ -487,11 +492,6 @@ class ApiController extends Controller { return new Http\JSONResponse([], Http::STATUS_FORBIDDEN); } - if ($question->getFormId() !== $formId) { - $this->logger->debug('This question is not part of the current form'); - return new Http\JSONResponse([], Http::STATUS_FORBIDDEN); - } - $option = new Option(); $option->setQuestionId($questionId); @@ -499,6 +499,45 @@ class ApiController extends Controller { $option = $this->optionMapper->insert($option); + return new Http\JSONResponse([ + 'id' => $option->getId() + ]); + } + + /** + * @NoAdminRequired + * Writes the given key-value pairs into Database. + + * @param int $id OptionId of option to update + * @param array $keyValuePairs Array of key=>value pairs to update. + */ + public function updateOption(int $id, array $keyValuePairs): Http\JSONResponse { + $this->logger->debug('Updating option: option: {id}, values: {keyValuePairs}', [ + 'id' => $id, + 'keyValuePairs' => $keyValuePairs + ]); + + try { + $option = $this->optionMapper->findById($id); + $question = $this->questionMapper->findById($option->getQuestionId()); + $form = $this->formMapper->findById($question->getFormId()); + } catch (IMapperException $e) { + $this->logger->debug('Could not find option, question or form'); + return new Http\JSONResponse(['message' => 'Could not find option, question or form'], Http::STATUS_BAD_REQUEST); + } + + if ($form->getOwnerId() !== $this->userId) { + $this->logger->debug('This form is not owned by the current user'); + return new Http\JSONResponse([], Http::STATUS_FORBIDDEN); + } + + // Create OptionEntity with given Params & Id. + $option = Option::fromParams($keyValuePairs); + $option->setId($id); + + // Update changed Columns in Db. + $this->optionMapper->update($option); + return new Http\JSONResponse($option->getId()); } @@ -526,7 +565,6 @@ class ApiController extends Controller { $this->optionMapper->delete($option); - //TODO useful response return new Http\JSONResponse($id); } diff --git a/lib/Controller/PageController.php b/lib/Controller/PageController.php index 31a1d5d..9028145 100644 --- a/lib/Controller/PageController.php +++ b/lib/Controller/PageController.php @@ -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'); } @@ -173,13 +173,8 @@ class PageController extends Controller { return new TemplateResponse('forms', 'no.acc.tmpl', []); } - if ($form->getExpiresTimestamp() === 0) { - $expired = false; - } else { - $expired = time() > $form->getExpiresTimestamp(); - } - - if ($expired) { + // If form expired, return Expired-Template + if ( ($form->getExpires() !== 0) && (time() > $form->getExpires()) ) { return new TemplateResponse('forms', 'expired.tmpl'); } diff --git a/lib/Db/Form.php b/lib/Db/Form.php index 82fd931..9941bee 100644 --- a/lib/Db/Form.php +++ b/lib/Db/Form.php @@ -41,8 +41,8 @@ use OCP\AppFramework\Db\Entity; * @method void setAccess(array $value) * @method integer getCreated() * @method void setCreated(integer $value) - * @method integer getExpiresTimestamp() - * @method void setExpiresTimestamp(integer $value) + * @method integer getExpires() + * @method void setExpires(integer $value) * @method integer getIsAnonymous() * @method void setIsAnonymous(bool $value) * @method integer getSubmitOnce() @@ -56,7 +56,7 @@ class Form extends Entity { protected $ownerId; protected $accessJson; protected $created; - protected $expiresTimestamp; + protected $expires; protected $isAnonymous; protected $submitOnce; @@ -65,7 +65,7 @@ class Form extends Entity { */ public function __construct() { $this->addType('created', 'integer'); - $this->addType('expiresTimestamp', 'integer'); + $this->addType('expires', 'integer'); $this->addType('isAnonymous', 'bool'); $this->addType('submitOnce', 'bool'); } @@ -80,20 +80,6 @@ class Form extends Entity { $this->setAccessJson(json_encode($access)); } - // Get virtual column expires. Set should only be done by setExpiresTimestamp(). - public function getExpires(): bool { - return (bool) $this->getExpiresTimestamp(); - } - - // Get virtual column expired. Set should only be done by setExpiresTimestamp(). - public function getExpired(): bool { - if ($this->getExpires()) { - return time() > $this->getExpiresTimestamp(); - } - // else - does not expire - return false; - } - // Read full form public function read() { return [ @@ -102,12 +88,9 @@ class Form extends Entity { 'title' => $this->getTitle(), 'description' => $this->getDescription(), 'ownerId' => $this->getOwnerId(), - 'ownerDisplayName' => \OC_User::getDisplayName($this->getOwnerId()), 'created' => $this->getCreated(), 'access' => $this->getAccess(), 'expires' => $this->getExpires(), - 'expired' => $this->getExpired(), - 'expiresTimestamp' => $this->getExpiresTimestamp(), 'isAnonymous' => $this->getIsAnonymous(), 'submitOnce' => $this->getSubmitOnce() ]; diff --git a/lib/Db/Question.php b/lib/Db/Question.php index 426db1f..25f5199 100644 --- a/lib/Db/Question.php +++ b/lib/Db/Question.php @@ -43,6 +43,13 @@ class Question extends Entity { protected $mandatory; protected $text; + const TYPES = [ + 'short', + 'long', + 'multiple', + 'multiple_unique' + ]; + public function __construct() { $this->addType('formId', 'integer'); $this->addType('order', 'integer'); diff --git a/lib/Migration/Version010200Date2020323141300.php b/lib/Migration/Version010200Date20200323141300.php similarity index 96% rename from lib/Migration/Version010200Date2020323141300.php rename to lib/Migration/Version010200Date20200323141300.php index cc738b5..e236b02 100644 --- a/lib/Migration/Version010200Date2020323141300.php +++ b/lib/Migration/Version010200Date20200323141300.php @@ -39,7 +39,7 @@ use \DateTime; * Installation class for the forms app. * Initial db creation */ -class Version010200Date2020323141300 extends SimpleMigrationStep { +class Version010200Date20200323141300 extends SimpleMigrationStep { /** @var IDBConnection */ protected $connection; @@ -47,6 +47,15 @@ class Version010200Date2020323141300 extends SimpleMigrationStep { /** @var IConfig */ protected $config; + /** Map of questionTypes to change */ + private $questionTypeMap = [ + 'radiogroup' => 'multiple_unique', + 'checkbox' => 'multiple', + 'text' => 'short', + 'comment' => 'long', + 'dropdown' => 'multiple_unique' + ]; + /** * @param IDBConnection $connection * @param IConfig $config @@ -96,8 +105,9 @@ class Version010200Date2020323141300 extends SimpleMigrationStep { 'notnull' => false, 'comment' => 'unix-timestamp', ]); - $table->addColumn('expires_timestamp', Type::INTEGER, [ + $table->addColumn('expires', Type::INTEGER, [ 'notnull' => false, + 'default' => 0, 'comment' => 'unix-timestamp', ]); $table->addColumn('is_anonymous', Type::BOOLEAN, [ @@ -131,7 +141,7 @@ class Version010200Date2020323141300 extends SimpleMigrationStep { ]); $table->addColumn('mandatory', Type::BOOLEAN, [ 'notnull' => true, - 'default' => 1, + 'default' => 0, ]); $table->addColumn('text', Type::STRING, [ 'notnull' => true, @@ -227,7 +237,7 @@ class Version010200Date2020323141300 extends SimpleMigrationStep { 'owner_id' => $qb_restore->createNamedParameter($event['owner'], IQueryBuilder::PARAM_STR), 'access_json' => $qb_restore->createNamedParameter($newAccessJSON, IQueryBuilder::PARAM_STR), 'created' => $qb_restore->createNamedParameter($this->convertDateTime($event['created']), IQueryBuilder::PARAM_INT), - 'expires_timestamp' => $qb_restore->createNamedParameter($this->convertDateTime($event['expire']), IQueryBuilder::PARAM_INT), + 'expires' => $qb_restore->createNamedParameter($this->convertDateTime($event['expire']), IQueryBuilder::PARAM_INT), 'is_anonymous' => $qb_restore->createNamedParameter($event['is_anonymous'], IQueryBuilder::PARAM_BOOL), 'submit_once' => $qb_restore->createNamedParameter($event['unique'], IQueryBuilder::PARAM_BOOL) ]); @@ -257,7 +267,7 @@ class Version010200Date2020323141300 extends SimpleMigrationStep { ->values([ 'form_id' => $qb_restore->createNamedParameter($id_mapping['events'][$question['form_id']]['newId'], IQueryBuilder::PARAM_INT), 'order' => $qb_restore->createNamedParameter($id_mapping['events'][$question['form_id']]['nextQuestionOrder']++, IQueryBuilder::PARAM_INT), - 'type' => $qb_restore->createNamedParameter($question['form_question_type'], IQueryBuilder::PARAM_STR), + 'type' => $qb_restore->createNamedParameter($this->questionTypeMap[$question['form_question_type']], IQueryBuilder::PARAM_STR), 'text' => $qb_restore->createNamedParameter($question['form_question_text'], IQueryBuilder::PARAM_STR) ]); $qb_restore->execute(); diff --git a/package-lock.json b/package-lock.json index b87e4d5..6e487ae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1415,11 +1415,11 @@ } }, "@babel/runtime": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.4.tgz", - "integrity": "sha512-neAp3zt80trRVBI1x0azq6c57aNBqYZH8KhMm3TaB7wEI5Q4A2SHfBHE8w9gOhI/lrqxtEbXZgQIrHP+wvSGwQ==", + "version": "7.9.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.2.tgz", + "integrity": "sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==", "requires": { - "regenerator-runtime": "^0.13.2" + "regenerator-runtime": "^0.13.4" } }, "@babel/template": { @@ -1518,13 +1518,6 @@ "requires": { "@nextcloud/event-bus": "^1.1.3", "core-js": "^3.6.4" - }, - "dependencies": { - "core-js": { - "version": "3.6.5", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", - "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==" - } } }, "@nextcloud/axios": { @@ -1586,9 +1579,9 @@ } }, "@nextcloud/event-bus": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@nextcloud/event-bus/-/event-bus-1.1.4.tgz", - "integrity": "sha512-It27KzmUaSQ7w22nHFwOn8XgeVG0HYYOSNG9gs4UkP5VqcZ16m4ydt3GkMpWcyFec4OUjJc+yf7omRc3pNxsSw==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@nextcloud/event-bus/-/event-bus-1.1.3.tgz", + "integrity": "sha512-/f3OMh9Tu3bn17sCc1Sb5AaC/fjegP9bjFmlsPDFNcCAHrKKM5B2X+2eUDF2osLirYaBjVqypBmD87zyiE0WjQ==", "requires": { "@types/semver": "^6.2.1", "core-js": "^3.6.2", @@ -1596,9 +1589,9 @@ }, "dependencies": { "core-js": { - "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==" }, "semver": { "version": "6.3.0", @@ -1614,16 +1607,6 @@ "requires": { "core-js": "^3.6.4", "node-gettext": "^3.0.0" - }, - "dependencies": { - "node-gettext": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/node-gettext/-/node-gettext-3.0.0.tgz", - "integrity": "sha512-/VRYibXmVoN6tnSAY2JWhNRhWYJ8Cd844jrZU/DwLVoI4vBI6ceYbd8i42sYZ9uOgDH3S7vslIKOWV/ZrT2YBA==", - "requires": { - "lodash.get": "^4.4.2" - } - } } }, "@nextcloud/moment": { @@ -1657,10 +1640,13 @@ } } }, - "core-js": { - "version": "3.6.4", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.4.tgz", - "integrity": "sha512-4paDGScNgZP2IXXilaffL9X7968RuvwlkK3xWtZRVqgd8SYNiVKRJvkFd1aqqEuPfN7E68ZHEp9hDj6lHj4Hyw==" + "node-gettext": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/node-gettext/-/node-gettext-2.1.0.tgz", + "integrity": "sha512-vsHImHl+Py0vB7M2UXcFEJ5NJ3950gcja45YclBFtYxYeZiqdfQdcu+G9s4L7jpRFSh/J/7VoS3upR4JM1nS+g==", + "requires": { + "lodash.get": "^4.4.2" + } } } }, @@ -2867,14 +2853,14 @@ }, "dependencies": { "caniuse-lite": { - "version": "1.0.30001040", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001040.tgz", - "integrity": "sha512-Ep0tEPeI5wCvmJNrXjE3etgfI+lkl1fTDU6Y3ZH1mhrjkPlVI9W4pcKbMo+BQLpEWKVYYp2EmYaRsqpPC3k7lQ==" + "version": "1.0.30001042", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001042.tgz", + "integrity": "sha512-igMQ4dlqnf4tWv0xjaaE02op9AJ2oQzXKjWf4EuAHFN694Uo9/EfPVIPJcmn2WkU9RqozCxx5e2KPcVClHDbDw==" }, "electron-to-chromium": { - "version": "1.3.403", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.403.tgz", - "integrity": "sha512-JaoxV4RzdBAZOnsF4dAlZ2ijJW72MbqO5lNfOBHUWiBQl3Rwe+mk2RCUMrRI3rSClLJ8HSNQNqcry12H+0ZjFw==" + "version": "1.3.412", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.412.tgz", + "integrity": "sha512-4bVdSeJScR8fT7ERveLWbxemY5uXEHVseqMRyORosiKcTUSGtVwBkV8uLjXCqoFLeImA57Z9hbz3TOid01U4Hw==" } } }, @@ -3469,9 +3455,9 @@ "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" }, "core-js": { - "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==" }, "core-js-compat": { "version": "3.6.5", @@ -3591,6 +3577,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.3", "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-3.5.3.tgz", @@ -3641,12 +3632,6 @@ "supports-color": "^6.1.0" } }, - "postcss-value-parser": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.0.3.tgz", - "integrity": "sha512-N7h4pG+Nnu5BEIzyeaaIYWs0LI5XC40OrRh5L60z0QjFsqGWcHcbkBvpe1WYpcIS9yQ8sOi/vIPt1ejQCrMVrg==", - "dev": true - }, "schema-utils": { "version": "2.6.6", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.6.tgz", @@ -4638,6 +4623,11 @@ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" }, + "eventemitter3": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.0.tgz", + "integrity": "sha512-qerSRB0p+UDEssxTtm6EDKcE7W4OaoisfIMl4CngyEhjpYglocpNg6UEqCvemdGhosAsg4sO2dXJOdyBifPGCg==" + }, "events": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz", @@ -7815,9 +7805,9 @@ "dev": true }, "node-gettext": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/node-gettext/-/node-gettext-2.0.0.tgz", - "integrity": "sha1-8dwSN83FRvUVk9o0AwS4vrpbhSU=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/node-gettext/-/node-gettext-3.0.0.tgz", + "integrity": "sha512-/VRYibXmVoN6tnSAY2JWhNRhWYJ8Cd844jrZU/DwLVoI4vBI6ceYbd8i42sYZ9uOgDH3S7vslIKOWV/ZrT2YBA==", "requires": { "lodash.get": "^4.4.2" } @@ -8221,6 +8211,11 @@ "os-tmpdir": "^1.0.0" } }, + "p-debounce": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-debounce/-/p-debounce-2.1.0.tgz", + "integrity": "sha512-M9bMt62TTnozdZhqFgs+V7XD2MnuKCaz+7fZdlu2/T7xruI3uIE5CicQ0vx1hV7HIUYF0jF+4/R1AgfOkl74Qw==" + }, "p-defer": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", @@ -8230,8 +8225,7 @@ "p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" }, "p-is-promise": { "version": "2.1.0", @@ -8257,6 +8251,23 @@ "p-limit": "^2.0.0" } }, + "p-queue": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.3.0.tgz", + "integrity": "sha512-fg5dJlFpd5+3CgG3/0ogpVZUeJbjiyXFg0nu53hrOYsybqSiDyxyOpad0Rm6tAiGjgztAwkyvhlYHC53OiAJOA==", + "requires": { + "eventemitter3": "^4.0.0", + "p-timeout": "^3.1.0" + } + }, + "p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "requires": { + "p-finally": "^1.0.0" + } + }, "p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", @@ -8804,9 +8815,9 @@ } }, "postcss-value-parser": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.0.2.tgz", - "integrity": "sha512-LmeoohTpp/K4UiyQCwuGWlONxXamGzCMtFxLq4W1nZVGIQLYvMCJx3yAF9qyyuFpflABI9yVdtJAqbihOsCsJQ==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.0.3.tgz", + "integrity": "sha512-N7h4pG+Nnu5BEIzyeaaIYWs0LI5XC40OrRh5L60z0QjFsqGWcHcbkBvpe1WYpcIS9yQ8sOi/vIPt1ejQCrMVrg==", "dev": true }, "prelude-ls": { @@ -9371,9 +9382,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", @@ -9998,6 +10009,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", @@ -11863,6 +11879,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", diff --git a/package.json b/package.json index beb9511..c76dadc 100644 --- a/package.json +++ b/package.json @@ -72,15 +72,23 @@ "@nextcloud/auth": "^1.2.3", "@nextcloud/axios": "^1.3.2", "@nextcloud/dialogs": "^1.2.2", + "@nextcloud/event-bus": "^1.1.3", "@nextcloud/l10n": "^1.2.3", "@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", + "p-debounce": "^2.1.0", + "p-queue": "^6.3.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" diff --git a/src/Forms.vue b/src/Forms.vue index e77c025..d02ac3d 100644 --- a/src/Forms.vue +++ b/src/Forms.vue @@ -25,7 +25,7 @@ - @@ -52,7 +52,7 @@ @@ -71,8 +71,6 @@ import Content from '@nextcloud/vue/dist/Components/Content' import AppNavigationForm from './components/AppNavigationForm' import EmptyContent from './components/EmptyContent' -import { formatForm } from './utils/FormsUtils' - export default { name: 'Forms', @@ -97,17 +95,20 @@ export default { return this.forms && this.forms.length === 0 }, - formattedForms() { - return this.forms.map(formatForm) - }, - hash() { return this.$route.params.hash }, - selectedForm() { - // TODO: replace with form.hash - return this.forms.find(form => form.form.hash === this.hash) + selectedForm: { + get() { + return this.forms.find(form => form.hash === this.hash) + }, + set(form) { + const index = this.forms.findIndex(search => search.hash === this.hash) + if (index > -1) { + this.$set(this.forms, index, form) + } + }, }, }, @@ -141,7 +142,7 @@ export default { const response = await axios.post(generateUrl('/apps/forms/api/v1/form')) const newForm = response.data this.forms.push(newForm) - this.$router.push({ name: 'edit', params: { hash: newForm.form.hash } }) + this.$router.push({ name: 'edit', params: { hash: newForm.hash } }) } catch (error) { showError(t('forms', 'Unable to create a new form')) console.error(error) diff --git a/src/components/AppNavigationForm.vue b/src/components/AppNavigationForm.vue index 5b57bf7..988993f 100644 --- a/src/components/AppNavigationForm.vue +++ b/src/components/AppNavigationForm.vue @@ -66,6 +66,7 @@ import ActionSeparator from '@nextcloud/vue/dist/Components/ActionSeparator' import AppNavigationIconBullet from '@nextcloud/vue/dist/Components/AppNavigationIconBullet' import AppNavigationItem from '@nextcloud/vue/dist/Components/AppNavigationItem' import axios from '@nextcloud/axios' +import moment from '@nextcloud/moment' import Vue from 'vue' import VueClipboard from 'vue-clipboard2' @@ -106,7 +107,7 @@ export default { */ bulletColor() { const style = getComputedStyle(document.body) - if (this.form.expired) { + if (this.form.expires && moment().unix() > this.form.expires) { return style.getPropertyValue('--color-error').slice(-6) } return style.getPropertyValue('--color-success').slice(-6) diff --git a/src/components/EmptyContent.vue b/src/components/EmptyContent.vue index 7047d18..b85f1ca 100644 --- a/src/components/EmptyContent.vue +++ b/src/components/EmptyContent.vue @@ -21,9 +21,11 @@ -->