diff --git a/migrations/20170928_redesign_launch.js b/migrations/20170928_redesign_launch.js index 3ff9970d83..1139652050 100644 --- a/migrations/20170928_redesign_launch.js +++ b/migrations/20170928_redesign_launch.js @@ -1,4 +1,5 @@ -var updateStore = require('../website/common/script/libs/updateStore'); +import { selectGearToPin } from '../website/common/script/ops/pinnedGearUtils'; + var getItemInfo = require('../website/common/script/libs/getItemInfo'); var migrationName = '20170928_redesign_launch.js'; @@ -69,7 +70,7 @@ function updateUser (user) { var set = {'migration': migrationName}; - var oldRewardsList = updateStore(user); + var oldRewardsList = selectGearToPin(user); var newPinnedItems = [ { type: 'armoire', diff --git a/package-lock.json b/package-lock.json index e0f5d6b90f..7b84febe65 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "habitica", - "version": "4.1.2", + "version": "4.1.3", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -68,6 +68,15 @@ "@types/mime": "1.3.1" } }, + "JSONStream": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.1.tgz", + "integrity": "sha1-cH92HgHa6eFvG8+TcDt4xwlmV5o=", + "requires": { + "jsonparse": "1.3.1", + "through": "2.3.8" + } + }, "abbrev": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.0.tgz", @@ -782,6 +791,14 @@ "is-buffer": "1.1.5" } }, + "axios-progress-bar": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/axios-progress-bar/-/axios-progress-bar-0.1.7.tgz", + "integrity": "sha512-xStxJUtcQUH0ulLni5qc8YwvNMTUjO7rmUTsvnxS1bD2GJKEemozP6sJ5OeoC6jjd6MS5FZyx5BlkU/lD8JLUw==", + "requires": { + "nprogress": "0.2.0" + } + }, "babel-code-frame": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", @@ -2325,9 +2342,9 @@ "resolved": "https://registry.npmjs.org/browser-pack/-/browser-pack-6.0.2.tgz", "integrity": "sha1-+GzWzvT1MAyOY+B6TVEvZfv/RTE=", "requires": { + "JSONStream": "1.3.1", "combine-source-map": "0.7.2", "defined": "1.0.0", - "JSONStream": "1.3.1", "through2": "2.0.3", "umd": "3.0.1" } @@ -2357,6 +2374,7 @@ "resolved": "https://registry.npmjs.org/browserify/-/browserify-12.0.2.tgz", "integrity": "sha1-V/IeXm4wj/WYfE2v1EhAsrmPehk=", "requires": { + "JSONStream": "1.3.1", "assert": "1.3.0", "browser-pack": "6.0.2", "browser-resolve": "1.11.2", @@ -2378,7 +2396,6 @@ "inherits": "2.0.3", "insert-module-globals": "7.0.1", "isarray": "0.0.1", - "JSONStream": "1.3.1", "labeled-stream-splicer": "2.0.0", "module-deps": "4.1.1", "os-browserify": "0.1.2", @@ -7389,13 +7406,6 @@ } } }, - "string_decoder": { - "version": "1.0.1", - "bundled": true, - "requires": { - "safe-buffer": "5.0.1" - } - }, "string-width": { "version": "1.0.2", "bundled": true, @@ -7405,6 +7415,13 @@ "strip-ansi": "3.0.1" } }, + "string_decoder": { + "version": "1.0.1", + "bundled": true, + "requires": { + "safe-buffer": "5.0.1" + } + }, "stringstream": { "version": "0.0.5", "bundled": true, @@ -10353,10 +10370,10 @@ "resolved": "https://registry.npmjs.org/insert-module-globals/-/insert-module-globals-7.0.1.tgz", "integrity": "sha1-wDv04BywhtW15azorQr+eInWOMM=", "requires": { + "JSONStream": "1.3.1", "combine-source-map": "0.7.2", "concat-stream": "1.5.2", "is-buffer": "1.1.5", - "JSONStream": "1.3.1", "lexical-scope": "1.2.0", "process": "0.11.10", "through2": "2.0.3", @@ -11349,15 +11366,6 @@ "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=" }, - "JSONStream": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.1.tgz", - "integrity": "sha1-cH92HgHa6eFvG8+TcDt4xwlmV5o=", - "requires": { - "jsonparse": "1.3.1", - "through": "2.3.8" - } - }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -13257,6 +13265,7 @@ "resolved": "https://registry.npmjs.org/module-deps/-/module-deps-4.1.1.tgz", "integrity": "sha1-IyFYM/HaE/1gbMuAh7RIUty4If0=", "requires": { + "JSONStream": "1.3.1", "browser-resolve": "1.11.2", "cached-path-relative": "1.0.1", "concat-stream": "1.5.2", @@ -13264,7 +13273,6 @@ "detective": "4.5.0", "duplexer2": "0.1.4", "inherits": "2.0.3", - "JSONStream": "1.3.1", "parents": "1.0.1", "readable-stream": "2.0.6", "resolve": "1.4.0", @@ -14640,6 +14648,11 @@ "set-blocking": "2.0.0" } }, + "nprogress": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/nprogress/-/nprogress-0.2.0.tgz", + "integrity": "sha1-y480xTIT2JVyP8urkH6UIq28r7E=" + }, "nth-check": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.1.tgz", @@ -17234,22 +17247,6 @@ } } }, - "require_optional": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz", - "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==", - "requires": { - "resolve-from": "2.0.0", - "semver": "5.4.1" - }, - "dependencies": { - "semver": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", - "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==" - } - } - }, "require-again": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-again/-/require-again-2.0.0.tgz", @@ -17289,6 +17286,22 @@ } } }, + "require_optional": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz", + "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==", + "requires": { + "resolve-from": "2.0.0", + "semver": "5.4.1" + }, + "dependencies": { + "semver": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", + "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==" + } + } + }, "requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", @@ -18655,11 +18668,6 @@ "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=" }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" - }, "string-length": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-0.1.2.tgz", @@ -18693,6 +18701,11 @@ "strip-ansi": "3.0.1" } }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + }, "stringify-object": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-1.0.1.tgz", diff --git a/package.json b/package.json index 86dc2e7776..0deb21d7fb 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "habitica", "description": "A habit tracker app which treats your goals like a Role Playing Game.", - "version": "4.1.2", + "version": "4.1.3", "main": "./website/server/index.js", "dependencies": { "@slack/client": "^3.8.1", @@ -14,6 +14,7 @@ "autoprefixer": "^6.4.0", "aws-sdk": "^2.0.25", "axios": "^0.16.0", + "axios-progress-bar": "^0.1.7", "babel-core": "^6.0.0", "babel-eslint": "^7.2.3", "babel-loader": "^6.0.0", diff --git a/website/client/app.vue b/website/client/app.vue index 72318e7db1..618d65da46 100644 --- a/website/client/app.vue +++ b/website/client/app.vue @@ -68,6 +68,8 @@ diff --git a/website/client/components/group-plans/index.vue b/website/client/components/group-plans/index.vue index 037f2fdd1a..1c2cadcd8d 100644 --- a/website/client/components/group-plans/index.vue +++ b/website/client/components/group-plans/index.vue @@ -2,9 +2,14 @@ .row secondary-menu.col-12 router-link.nav-link(:to="{name: 'groupPlanDetailTaskInformation', params: {groupId}}", - exact, :class="{'active': $route.name === 'groupPlanDetailTaskInformation'}") Task Board + exact, :class="{'active': $route.name === 'groupPlanDetailTaskInformation'}") {{ $t('groupTaskBoard') }} router-link.nav-link(:to="{name: 'groupPlanDetailInformation', params: {groupId}}", - exact, :class="{'active': $route.name === 'groupPlanDetailInformation'}") Group Information + exact, :class="{'active': $route.name === 'groupPlanDetailInformation'}") {{ $t('groupInformation') }} + router-link.nav-link( + v-if='isLeader', + :to="{name: 'groupPlanBilling', params: {groupId}}", + exact, + :class="{'active': $route.name === 'groupPlanBilling'}") {{ $t('groupBilling') }} .col-12 router-view @@ -12,11 +17,26 @@ diff --git a/website/client/components/group-plans/taskInformation.vue b/website/client/components/group-plans/taskInformation.vue index 4e7f783f20..88fc39021b 100644 --- a/website/client/components/group-plans/taskInformation.vue +++ b/website/client/components/group-plans/taskInformation.vue @@ -349,15 +349,21 @@ export default { groupId: this.searchId, }); + let groupedApprovals = this.loadApprovals(); + + tasks.forEach((task) => { + if (groupedApprovals.length > 0) task.approvals = groupedApprovals[task._id]; + this.tasksByType[task.type].push(task); + }); + }, + async loadApprovals () { + if (this.group.leader._id !== this.user._id) return []; + let approvalRequests = await this.$store.dispatch('tasks:getGroupApprovals', { groupId: this.searchId, }); - let groupedApprovals = groupBy(approvalRequests, 'group.taskId'); - tasks.forEach((task) => { - task.approvals = groupedApprovals[task._id]; - this.tasksByType[task.type].push(task); - }); + return groupBy(approvalRequests, 'group.taskId'); }, editTask (task) { this.taskFormPurpose = 'edit'; diff --git a/website/client/components/notificationMenu.vue b/website/client/components/notificationMenu.vue index 1fe4a7e868..28be2e485a 100644 --- a/website/client/components/notificationMenu.vue +++ b/website/client/components/notificationMenu.vue @@ -1,8 +1,7 @@ - diff --git a/website/client/components/tasks/task.vue b/website/client/components/tasks/task.vue index 7e9581bc53..466a026509 100644 --- a/website/client/components/tasks/task.vue +++ b/website/client/components/tasks/task.vue @@ -17,8 +17,16 @@ h3.task-title(:class="{ 'has-notes': task.notes }", v-markdown="task.text") .task-notes.small-text(v-markdown="task.notes") .checklist(v-if="canViewchecklist") + .d-inline-flex + .collapse-checklist.d-flex.align-items-center.expand-toggle( + v-if="isUser", + @click="collapseChecklist(task)", + :class="{open: !task.collapseChecklist}", + ) + .svg-icon(v-html="icons.checklist") + span {{ checklistProgress }} label.custom-control.custom-checkbox.checklist-item( - v-if='!castingSpell', + v-if='!castingSpell && !task.collapseChecklist', v-for="item in task.checklist", :class="{'checklist-item-done': item.completed}", ) input.custom-control-input(type="checkbox", :checked="item.completed", @change="toggleChecklistItem(item)") @@ -119,6 +127,26 @@ margin-top: 8px; } + .collapse-checklist { + padding: 2px 6px; + margin-bottom: 9px; + border-radius: 1px; + background-color: $gray-600; + font-size: 10px; + line-height: 1.2; + text-align: center; + color: $gray-200; + + span { + margin: 0px 4px; + } + + .svg-icon { + width: 12px; + height: 8px; + } + } + .checklist-item { color: $gray-50; font-size: 14px; @@ -308,6 +336,7 @@ import calendarIcon from 'assets/svg/calendar.svg'; import challengeIcon from 'assets/svg/challenge.svg'; import tagsIcon from 'assets/svg/tags.svg'; import checkIcon from 'assets/svg/check.svg'; +import checklistIcon from 'assets/svg/checklist.svg'; import bPopover from 'bootstrap-vue/lib/components/popover'; import markdownDirective from 'client/directives/markdown'; import notifications from 'client/mixins/notifications'; @@ -336,6 +365,7 @@ export default { challenge: challengeIcon, tags: tagsIcon, check: checkIcon, + checklist: checklistIcon, }), }; }, @@ -353,6 +383,13 @@ export default { let userIsTaskUser = this.task.userId ? this.task.userId === this.user._id : true; return hasChecklist && userIsTaskUser; }, + checklistProgress () { + const totalItems = this.task.checklist.length; + const completedItems = this.task.checklist.reduce((total, item) => { + return item.completed ? total + 1 : total; + }, 0); + return `${completedItems}/${totalItems}`; + }, leftControl () { const task = this.task; if (task.type === 'reward') return false; @@ -392,10 +429,13 @@ export default { }, }, methods: { - ...mapActions({scoreChecklistItem: 'tasks:scoreChecklistItem'}), + ...mapActions({ + scoreChecklistItem: 'tasks:scoreChecklistItem', + collapseChecklist: 'tasks:collapseChecklist', + }), toggleChecklistItem (item) { if (this.castingSpell) return; - item.completed = !item.completed; + item.completed = !item.completed; // @TODO this should go into the action? this.scoreChecklistItem({taskId: this.task._id, itemId: item.id}); }, edit (e, task) { diff --git a/website/client/components/tasks/taskModal.vue b/website/client/components/tasks/taskModal.vue index 3e9ac99fde..c80b6bc203 100644 --- a/website/client/components/tasks/taskModal.vue +++ b/website/client/components/tasks/taskModal.vue @@ -110,29 +110,22 @@ span.custom-control-indicator span.custom-control-description {{ $t('dayOfWeek') }} - .option(v-if="isUserTask") - label(v-once) {{ $t('tags') }} - .category-wrap(@click="showTagsSelect = !showTagsSelect") - span.category-select(v-if='task.tags && task.tags.length === 0') {{$t('none')}} - span.category-select(v-else) - .category-label(v-for='tagName in getTagsFor(task)') {{tagName}} - .category-box(v-if="showTagsSelect") - .container - .row - .form-check.col-6( - v-for="tag in user.tags", - :key="tag.id", - ) - label.custom-control.custom-checkbox - input.custom-control-input(type="checkbox", :value="tag.id", v-model="task.tags") - span.custom-control-indicator - span.custom-control-description(v-once) {{ tag.name }} - .row - button.btn.btn-primary(@click="showTagsSelect = !showTagsSelect") {{$t('close')}} + .tags-select.option(v-if="isUserTask") + .tags-inline + label(v-once) {{ $t('tags') }} + .category-wrap(@click="showTagsSelect = !showTagsSelect", v-bind:class="{ active: showTagsSelect }") + span.category-select(v-if='task.tags && task.tags.length === 0') + .tags-none {{$t('none')}} + .dropdown-toggle + span.category-select(v-else) + .category-label(v-for='tagName in truncatedSelectedTags', :title="tagName") {{ tagName }} + .tags-more(v-if='remainingSelectedTags.length > 0') +{{ $t('more', { count: remainingSelectedTags.length }) }} + .dropdown-toggle + tags-popup(v-if="showTagsSelect", :tags="user.tags", v-model="task.tags") .option(v-if="task.type === 'habit'") label(v-once) {{ $t('resetStreak') }} - b-dropdown(:text="$t(task.frequency)") + b-dropdown.streak-dropdown(:text="$t(task.frequency)") b-dropdown-item(v-for="frequency in ['daily', 'weekly', 'monthly']", :key="frequency", @click="task.frequency = frequency", :class="{active: task.frequency === frequency}") | {{ $t(frequency) }} @@ -328,6 +321,88 @@ } } + .tags-select { + position: relative; + + .tags-inline { + align-items: center; + display: flex; + justify-content: flex-start; + + label { + margin: 0; + } + + .category-wrap { + cursor: inherit; + position: relative; + border: 1px solid transparent; + border-radius: 2px; + margin-left: 4em; + + &.active { + border-color: $purple-500; + + .category-select { + box-shadow: none; + } + } + + .category-select { + align-items: center; + display: flex; + padding: .6em; + padding-right: 2.8em; + width: 100%; + + .tags-none { + margin: .26em 0 .26em .6em; + + & + .dropdown-toggle { + right: 1.3em; + } + } + + .tags-more { + color: #a5a1ac; + flex: 0 1 auto; + font-size: 12px; + text-align: left; + position: relative; + left: .5em; + width: 100%; + } + + .dropdown-toggle { + position: absolute; + right: 1em; + top: .8em; + } + + .category-label { + min-width: 68px; + overflow: hidden; + padding: .5em 1em; + text-overflow: ellipsis; + white-space: nowrap; + width: 68px; + word-wrap: break-word; + } + } + } + } + + .tags-popup { + position: absolute; + top: 3.5em; + left: 6.2em; + } + } + + .streak-dropdown { + margin-left: .5em; + } + .checklist-group { border-top: 1px solid $gray-500; } @@ -418,6 +493,7 @@