Client Tasks v1 (#8823)
* remove unused elements from tasks page * remove components * client: tasks: wip * tasks: order, start styling them * more tasks works * habits controls * more work * tasks icons * split columns in their own component * implement tags for tasks * wip * add columns description
39
website/client/assets/scss/categories.scss
Normal file
@@ -0,0 +1,39 @@
|
||||
.category-box {
|
||||
padding: 1em;
|
||||
max-width: 400px;
|
||||
position: absolute;
|
||||
top: -480px;
|
||||
padding: 2em;
|
||||
border-radius: 2px;
|
||||
background-color: $white;
|
||||
box-shadow: 0 2px 2px 0 rgba($black, 0.15), 0 1px 4px 0 rgba($black, 0.1);
|
||||
}
|
||||
|
||||
.category-label {
|
||||
min-width: 100px;
|
||||
border-radius: 100px;
|
||||
background-color: $gray-600;
|
||||
padding: .5em;
|
||||
display: inline-block;
|
||||
margin-right: .5em;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
line-height: 1.33;
|
||||
text-align: center;
|
||||
color: $gray-300;
|
||||
}
|
||||
|
||||
.category-select {
|
||||
border-radius: 2px;
|
||||
background-color: $white;
|
||||
box-shadow: 0 2px 2px 0 rgba($black, 0.16), 0 1px 4px 0 rgba($black, 0.12);
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
.category-select:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.category-wrap {
|
||||
margin-top: .5em;
|
||||
}
|
||||
@@ -1,13 +1,15 @@
|
||||
.svg-icon {
|
||||
display: inline-block;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
display: block;
|
||||
stroke-width: 0;
|
||||
stroke: currentColor;
|
||||
fill: currentColor;
|
||||
transition: none !important;
|
||||
}
|
||||
|
||||
.svg-icon * {
|
||||
transition: none !important;
|
||||
svg {
|
||||
display: block;
|
||||
}
|
||||
|
||||
* {
|
||||
transition: none !important;
|
||||
}
|
||||
}
|
||||
@@ -21,4 +21,6 @@
|
||||
@import './item';
|
||||
@import './stats';
|
||||
@import './icon';
|
||||
@import './task';
|
||||
@import './categories';
|
||||
@import './dragdrop';
|
||||
|
||||
@@ -3,7 +3,7 @@ html {
|
||||
}
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
height: calc(100% - 56px); // 56px is the menu
|
||||
background: $gray-700;
|
||||
}
|
||||
|
||||
|
||||
86
website/client/assets/scss/task.scss
Normal file
@@ -0,0 +1,86 @@
|
||||
.task {
|
||||
// for editing rewards or when a task is created
|
||||
&-purple {
|
||||
background: $purple-300;
|
||||
}
|
||||
|
||||
&-worst {
|
||||
background: $maroon-100;
|
||||
&-control {
|
||||
background: darken($maroon-100, 12%);
|
||||
}
|
||||
}
|
||||
|
||||
&-worse {
|
||||
background: $red-100;
|
||||
&-control {
|
||||
background: darken($red-100, 12%);
|
||||
}
|
||||
}
|
||||
|
||||
&-bad {
|
||||
background: $orange-100;
|
||||
&-control {
|
||||
background: darken($orange-100, 12%);
|
||||
}
|
||||
}
|
||||
|
||||
&-neutral {
|
||||
background: $yellow-50;
|
||||
&-control {
|
||||
background: darken($yellow-50, 12%);
|
||||
}
|
||||
}
|
||||
|
||||
&-good {
|
||||
background: $green-10;
|
||||
&-control {
|
||||
background: darken($green-10, 12%);
|
||||
}
|
||||
}
|
||||
|
||||
&-better {
|
||||
background: $blue-50;
|
||||
&-control {
|
||||
background: darken($blue-50, 12%);
|
||||
}
|
||||
}
|
||||
|
||||
&-best {
|
||||
background: $teal-50;
|
||||
&-control {
|
||||
background: darken($teal-50, 12%);
|
||||
}
|
||||
}
|
||||
|
||||
&-reward {
|
||||
background: rgba($yellow-500, 0.26);
|
||||
}
|
||||
|
||||
&-daily-todo-disabled {
|
||||
background: $gray-500;
|
||||
|
||||
&-control {
|
||||
background: $gray-400;
|
||||
color: $gray-200;
|
||||
}
|
||||
}
|
||||
|
||||
&-daily-todo-content-disabled {
|
||||
background: $gray-600;
|
||||
|
||||
* {
|
||||
color: $gray-300 !important;
|
||||
}
|
||||
}
|
||||
|
||||
&-habit-disabled {
|
||||
background: $gray-700;
|
||||
color: rgba(0, 0, 0, 0.12);
|
||||
|
||||
&-control {
|
||||
color: rgba(0, 0, 0, 0.12) !important;
|
||||
border: 1px solid rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
}
|
||||
}
|
||||
3
website/client/assets/svg/calendar.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14">
|
||||
<path fill-rule="evenodd" d="M2 12h10V6H2v6zM12 2V0h-2v2H4V0H2v2a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 227 B |
3
website/client/assets/svg/challenge.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="12" viewBox="0 0 14 12">
|
||||
<path fill-rule="evenodd" d="M10 6.306L2.582 7.542A.5.5 0 0 1 2 7.05V2.591a.5.5 0 0 1 .582-.493L10 3.334v2.972zm2.329-4.612l-.024-.004c-.007-.002-.012-.007-.02-.009-.017-.005-.035.001-.052-.003L2.329.028A2 2 0 0 0 0 2v5.64a2 2 0 0 0 2.329 1.972l7.056-1.176-.525 2.1a1.175 1.175 0 0 0 2.28.57l.772-3.09.417-.07A2 2 0 0 0 14 5.971V3.667a2 2 0 0 0-1.671-1.973z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 456 B |
3
website/client/assets/svg/check.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="10" viewBox="0 0 13 10">
|
||||
<path fill-rule="evenodd" d="M4.662 9.832c-.312 0-.61-.123-.831-.344L0 5.657l1.662-1.662 2.934 2.934L10.534 0l1.785 1.529-6.764 7.893a1.182 1.182 0 0 1-.848.409l-.045.001"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 268 B |
3
website/client/assets/svg/daily.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="20" viewBox="0 0 24 20">
|
||||
<path fill="#C3C0C7" fill-rule="evenodd" d="M13 16h2v-2h-2v2zm-4 0h2v-2H9v2zm-4 0h2v-2H5v2zm12-4h2v-2h-2v2zm-4 0h2v-2h-2v2zm-4 0h2v-2H9v2zm13-4H2v8a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V8zm2-2v10a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V6a4 4 0 0 1 4-4h1V0h2v2h10V0h2v2h1a4 4 0 0 1 4 4zM5 12h2v-2H5v2z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 382 B |
3
website/client/assets/svg/habit.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="20" viewBox="0 0 30 20">
|
||||
<path fill="#C3C0C7" fill-rule="evenodd" d="M11 11H9v2H7v-2H5V9h2V7h2v2h2v2zm8 0h6V9h-6v2zm9 5c0 1.103-.897 2-2 2H16V2h10c1.103 0 2 .897 2 2v12zM4 18c-1.103 0-2-.897-2-2V4c0-1.103.897-2 2-2h10v16H4zM26 0H4a4 4 0 0 0-4 4v12a4 4 0 0 0 4 4h22a4 4 0 0 0 4-4V4a4 4 0 0 0-4-4z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 369 B |
3
website/client/assets/svg/negative.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="2" viewBox="0 0 10 2">
|
||||
<path fill-rule="evenodd" d="M0 0h10v2H0z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 135 B |
3
website/client/assets/svg/positive.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 10 10">
|
||||
<path fill-rule="evenodd" d="M6 4V0H4v4H0v2h4v4h2V6h4V4H6z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 157 B |
3
website/client/assets/svg/reward.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="26" height="20" viewBox="0 0 26 20">
|
||||
<path fill="#C3C0C7" fill-rule="evenodd" d="M24 10h-8V8h4a2 2 0 0 0 2-2V2c1.103 0 2 .897 2 2v6zm0 6a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2v-4h8v1a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2v-1h8v4zM2 4c0-1.103.897-2 2-2v4a2 2 0 0 0 2 2h4v2H2V4zm10 9h2V8h-2v5zm8-11v4H6V2h14zm2-2H4a4 4 0 0 0-4 4v12a4 4 0 0 0 4 4h18a4 4 0 0 0 4-4V4a4 4 0 0 0-4-4z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 421 B |
3
website/client/assets/svg/streak.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="8" viewBox="0 0 12 8">
|
||||
<path fill-rule="evenodd" d="M11.376 3.15L6.777.086A.5.5 0 0 0 6 .5v6.132a.5.5 0 0 0 .777.416l4.599-3.066a.5.5 0 0 0 0-.832M.777.085L6 3.567.777 7.049A.5.5 0 0 1 0 6.633V.5A.5.5 0 0 1 .777.085"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 289 B |
3
website/client/assets/svg/tags.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14">
|
||||
<path fill-rule="evenodd" d="M10 3a1 1 0 1 1 0 2 1 1 0 0 1 0-2zM2.004 6.994L7 2h5l-.004 5.006L7 12l.004-.004-5-5.002zM0 7c0 .55.22 1.05.59 1.41l5 5a1.996 1.996 0 0 0 2.83 0l4.99-4.99c.37-.37.59-.87.59-1.42V2c0-1.11-.89-2-2-2H7c-.55 0-1.05.22-1.41.58l-5 5C.23 5.94 0 6.44 0 7z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 373 B |
3
website/client/assets/svg/todo.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20">
|
||||
<path fill="#C3C0C7" fill-rule="evenodd" d="M8.343 14.916c-.312 0-.61-.123-.831-.344l-3.831-3.831 1.662-1.662 2.934 2.934 5.938-6.929L16 6.613l-6.764 7.893a1.182 1.182 0 0 1-.848.409l-.045.001zM18 16c0 1.103-.897 2-2 2H4c-1.102 0-2-.897-2-2V4c0-1.103.898-2 2-2h12c1.103 0 2 .897 2 2v12zM16 0H4a4 4 0 0 0-4 4v12a4 4 0 0 0 4 4h12a4 4 0 0 0 4-4V4a4 4 0 0 0-4-4z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 457 B |
@@ -101,31 +101,6 @@
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.category-box {
|
||||
padding: 1em;
|
||||
max-width: 400px;
|
||||
position: absolute;
|
||||
top: -480px;
|
||||
padding: 2em;
|
||||
border-radius: 2px;
|
||||
background-color: $white;
|
||||
box-shadow: 0 2px 2px 0 rgba($black, 0.15), 0 1px 4px 0 rgba($black, 0.1);
|
||||
}
|
||||
|
||||
.category-label {
|
||||
min-width: 100px;
|
||||
border-radius: 100px;
|
||||
background-color: $gray-600;
|
||||
padding: .5em;
|
||||
display: inline-block;
|
||||
margin-right: .5em;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
line-height: 1.33;
|
||||
text-align: center;
|
||||
color: $gray-300;
|
||||
}
|
||||
|
||||
.item-with-icon {
|
||||
display: inline-block;
|
||||
|
||||
@@ -146,21 +121,6 @@
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.category-select {
|
||||
border-radius: 2px;
|
||||
background-color: $white;
|
||||
box-shadow: 0 2px 2px 0 rgba($black, 0.16), 0 1px 4px 0 rgba($black, 0.12);
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
.category-select:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.category-wrap {
|
||||
margin-top: .5em;
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-left: .5em;
|
||||
display: inline-block;
|
||||
|
||||
@@ -35,20 +35,6 @@
|
||||
box-shadow: 0 2px 2px 0 rgba($black, 0.15), 0 1px 4px 0 rgba($black, 0.1);
|
||||
margin-bottom: 1rem;
|
||||
|
||||
.category-label {
|
||||
min-width: 100px;
|
||||
border-radius: 100px;
|
||||
background-color: $gray-600;
|
||||
padding: .5em;
|
||||
display: inline-block;
|
||||
margin-right: .5em;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
line-height: 1.33;
|
||||
text-align: center;
|
||||
color: $gray-300;
|
||||
}
|
||||
|
||||
.recommend-text {
|
||||
font-size: 12px;
|
||||
font-style: italic;
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
<template lang="pug">
|
||||
li
|
||||
ul
|
||||
li
|
||||
strong {{task.text}}
|
||||
li(v-if="task.type === 'habit'") up: {{task.up}}, down: {{task.down}}
|
||||
li value: {{task.value}}
|
||||
template(v-if="task.type === 'daily' || task.type === 'todo'")
|
||||
li completed: {{task.completed}}
|
||||
li
|
||||
span checklist
|
||||
ul
|
||||
li(v-for="checklist in task.checklist") {{checklist.text}}
|
||||
template(v-if="task.type === 'daily'")
|
||||
li streak: {{task.streak}}
|
||||
li repeat: {{task.repeat}}
|
||||
li(v-if="task.type === 'todo'") due date: {{task.date}}
|
||||
li attribute {{task.attribute}}
|
||||
li difficulty {{task.priority}}
|
||||
li tags {{getTagsFor(task)}}
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState, mapGetters } from 'client/libs/store';
|
||||
|
||||
export default {
|
||||
props: ['task'],
|
||||
computed: {
|
||||
...mapState({user: 'user.data'}),
|
||||
...mapGetters({getTagsFor: 'tasks:getTagsFor'}),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
194
website/client/components/tasks/column.vue
Normal file
@@ -0,0 +1,194 @@
|
||||
<template lang="pug">
|
||||
.tasks-column
|
||||
.d-flex
|
||||
h2.tasks-column-title(v-once) {{ $t(types[type].label) }}
|
||||
.filters.d-flex.justify-content-end
|
||||
.filter.small-text(
|
||||
v-for="filter in types[type].filters",
|
||||
:class="{active: activeFilter.label === filter.label}",
|
||||
@click="activeFilter = filter",
|
||||
) {{ $t(filter.label) }}
|
||||
.tasks-list
|
||||
task(v-for="task in tasks[`${type}s`]", :key="task.id", :task="task", v-if="activeFilter.filter(task)")
|
||||
.bottom-gradient
|
||||
.column-background(v-if="isUser === true", :class="{'initial-description': tasks[`${type}s`].length === 0}")
|
||||
.svg-icon(v-html="icons[type]", :class="`icon-${type}`", v-once)
|
||||
h3(v-once) {{$t('theseAreYourTasks', {taskType: `${type}s`})}}
|
||||
.small-text {{$t(`${type}sDesc`)}}
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
|
||||
.tasks-column {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.tasks-list {
|
||||
border-radius: 4px;
|
||||
background: $gray-600;
|
||||
padding: 8px;
|
||||
// not sure why but this is necessary or the last task will overflow the container
|
||||
padding-bottom: 0.1px;
|
||||
position: relative;
|
||||
height: calc(100% - 64px);
|
||||
}
|
||||
|
||||
.bottom-gradient {
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
left: -0px;
|
||||
height: 42px;
|
||||
background-image: linear-gradient(to bottom, rgba(52, 49, 58, 0), #34313a);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tasks-column-title {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.filters {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.filter {
|
||||
font-weight: bold;
|
||||
color: $gray-100;
|
||||
font-style: normal;
|
||||
padding: 8px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: $purple-200;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: $purple-200;
|
||||
border-bottom: 2px solid $purple-200;
|
||||
padding-bottom: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.column-background {
|
||||
position: absolute;
|
||||
bottom: 32px;
|
||||
z-index: 7;
|
||||
|
||||
&.initial-description {
|
||||
top: 30%;
|
||||
}
|
||||
|
||||
.svg-icon {
|
||||
margin: 0 auto;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
h3, .small-text {
|
||||
color: $gray-300;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-weight: normal;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.small-text {
|
||||
font-style: normal;
|
||||
padding-left: 24px;
|
||||
padding-right: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.icon-habit {
|
||||
width: 30px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.icon-daily {
|
||||
width: 30px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.icon-todo {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.icon-reward {
|
||||
width: 26px;
|
||||
height: 20px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import Task from './task';
|
||||
import { mapState } from 'client/libs/store';
|
||||
import { shouldDo } from 'common/script/cron';
|
||||
import habitIcon from 'assets/svg/habit.svg';
|
||||
import dailyIcon from 'assets/svg/daily.svg';
|
||||
import todoIcon from 'assets/svg/todo.svg';
|
||||
import rewardIcon from 'assets/svg/reward.svg';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Task,
|
||||
},
|
||||
props: ['type', 'isUser'],
|
||||
data () {
|
||||
const types = Object.freeze({
|
||||
habit: {
|
||||
label: 'habits',
|
||||
filters: [
|
||||
{label: 'all', filter: () => true, default: true},
|
||||
{label: 'yellowred', filter: t => t.value < 1}, // weak
|
||||
{label: 'greenblue', filter: t => t.value >= 1}, // strong
|
||||
],
|
||||
},
|
||||
daily: {
|
||||
label: 'dailies',
|
||||
filters: [
|
||||
{label: 'all', filter: () => true, default: true},
|
||||
{label: 'due', filter: t => !t.completed && shouldDo(new Date(), t, this.userPreferences)},
|
||||
{label: 'notDue', filter: t => t.completed || !shouldDo(new Date(), t, this.userPreferences)},
|
||||
],
|
||||
},
|
||||
todo: {
|
||||
label: 'todos',
|
||||
filters: [
|
||||
{label: 'remaining', filter: t => !t.completed, default: true}, // active
|
||||
{label: 'scheduled', filter: t => !t.completed && t.date},
|
||||
{label: 'complete2', filter: t => t.completed},
|
||||
],
|
||||
},
|
||||
reward: {
|
||||
label: 'rewards',
|
||||
filters: [
|
||||
{label: 'all', filter: () => true, default: true},
|
||||
{label: 'custom', filter: () => true}, // all rewards made by the user
|
||||
{label: 'wishlist', filter: () => false}, // not user tasks
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const icons = Object.freeze({
|
||||
habit: habitIcon,
|
||||
daily: dailyIcon,
|
||||
todo: todoIcon,
|
||||
reward: rewardIcon,
|
||||
});
|
||||
|
||||
return {
|
||||
types,
|
||||
activeFilter: types[this.type].filters.find(f => f.default === true),
|
||||
icons,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
tasks: 'tasks.data',
|
||||
userPreferences: 'user.data.preferences',
|
||||
}),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
293
website/client/components/tasks/task.vue
Normal file
@@ -0,0 +1,293 @@
|
||||
<template lang="pug">
|
||||
.task.d-flex
|
||||
// Habits left side control
|
||||
.left-control.d-flex.align-items-center.justify-content-center(v-if="task.type === 'habit'", :class="controlClass.up")
|
||||
.task-control.habit-control(:class="controlClass.up + '-control'")
|
||||
.svg-icon.positive(v-html="icons.positive")
|
||||
// Dailies and todos left side control
|
||||
.left-control.d-flex.align-items-center.justify-content-center(v-if="task.type === 'daily' || task.type === 'todo'", :class="controlClass")
|
||||
.task-control.daily-todo-control(:class="controlClass + '-control'")
|
||||
.svg-icon.check(v-html="icons.check", v-if="task.completed")
|
||||
// Task title, description and icons
|
||||
.task-content(:class="contentClass")
|
||||
h3.task-title(:class="{ 'has-notes': task.notes }") {{task.text}}
|
||||
.task-notes.small-text {{task.notes}}
|
||||
.icons.small-text.d-flex.align-items-center
|
||||
.d-flex.align-items-center(v-if="task.type === 'todo' && task.date", :class="{'due-overdue': isDueOverdue}")
|
||||
.svg-icon.calendar(v-html="icons.calendar")
|
||||
span {{dueIn}}
|
||||
.icons-right.d-flex.justify-content-end
|
||||
.d-flex.align-items-center(v-if="showStreak")
|
||||
.svg-icon.streak(v-html="icons.streak")
|
||||
span(v-if="task.type === 'daily'") {{task.streak}}
|
||||
span(v-if="task.type === 'habit'")
|
||||
span.m-0(v-if="task.up") +{{task.counterUp}}
|
||||
span.m-0(v-if="task.up && task.down") |
|
||||
span.m-0(v-if="task.down") -{{task.counterDown}}
|
||||
.d-flex.align-items-center(v-if="task.challenge && task.challenge.id")
|
||||
.svg-icon.challenge(v-html="icons.challenge")
|
||||
b-popover.tags-popover.no-span-margin(
|
||||
:triggers="['hover']",
|
||||
:placement="'bottom'",
|
||||
:popover-style="{'max-width': '1000px'}",
|
||||
)
|
||||
.d-flex.align-items-center(slot="content")
|
||||
.tags-popover-title(v-once) {{ `${$t('tags')}:` }}
|
||||
.tag-label(v-for="tag in getTagsFor(task)") {{tag}}
|
||||
.d-flex.align-items-center(v-if="task.tags && task.tags.length > 0")
|
||||
.svg-icon.tags(v-html="icons.tags")
|
||||
|
||||
// Habits right side control
|
||||
.right-control.d-flex.align-items-center.justify-content-center(v-if="task.type === 'habit'", :class="controlClass.down")
|
||||
.task-control.habit-control(:class="controlClass.down + '-control'")
|
||||
.svg-icon.negative(v-html="icons.negative")
|
||||
// Rewards right side control
|
||||
.right-control.d-flex.align-items-center.justify-content-center.reward-control(v-if="task.type === 'reward'", :class="controlClass")
|
||||
.svg-icon(v-html="icons.gold")
|
||||
.small-text {{task.value}}
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
|
||||
.task {
|
||||
margin-bottom: 8px;
|
||||
box-shadow: 0 2px 2px 0 rgba($black, 0.16), 0 1px 4px 0 rgba($black, 0.12);
|
||||
background: $white;
|
||||
border-radius: 2px;
|
||||
z-index: 9;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.task-title {
|
||||
margin-bottom: 8px;
|
||||
color: $gray-10;
|
||||
font-weight: normal;
|
||||
|
||||
&.has-notes {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.task-notes {
|
||||
color: $gray-100;
|
||||
font-style: normal;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.task-content {
|
||||
padding: 8px;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.icons {
|
||||
color: $gray-300;
|
||||
font-style: normal;
|
||||
|
||||
&-right {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.icons-right .svg-icon {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.icons span {
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.no-span-margin span {
|
||||
margin-left: 0px !important;
|
||||
}
|
||||
|
||||
.svg-icon.streak {
|
||||
width: 11.6px;
|
||||
height: 7.1px;
|
||||
}
|
||||
|
||||
.tags.svg-icon, .calendar.svg-icon {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
.tags:hover {
|
||||
color: $purple-500;
|
||||
}
|
||||
|
||||
.due-overdue {
|
||||
color: $red-50;
|
||||
}
|
||||
|
||||
.calendar.svg-icon {
|
||||
margin-right: 2px;
|
||||
margin-top: -2px;
|
||||
}
|
||||
|
||||
.challenge.svg-icon {
|
||||
width: 14px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
.check.svg-icon {
|
||||
width: 12.3px;
|
||||
height: 9.8px;
|
||||
margin: 8px;
|
||||
}
|
||||
|
||||
.left-control, .right-control {
|
||||
width: 40px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.left-control {
|
||||
border-top-left-radius: 2px;
|
||||
border-bottom-left-radius: 2px;
|
||||
}
|
||||
|
||||
.right-control {
|
||||
border-top-right-radius: 2px;
|
||||
border-bottom-right-radius: 2px;
|
||||
}
|
||||
|
||||
.task-control {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
.habit-control {
|
||||
border-radius: 100px;
|
||||
color: $white;
|
||||
|
||||
.svg-icon {
|
||||
width: 10px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.positive {
|
||||
margin-top: 9px;
|
||||
}
|
||||
|
||||
.negative {
|
||||
margin-top: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
.daily-todo-control {
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.reward-control {
|
||||
flex-direction: column;
|
||||
padding-top: 16px;
|
||||
padding-bottom: 12px;
|
||||
|
||||
.svg-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.small-text {
|
||||
margin-top: 4px;
|
||||
color: $yellow-10;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss"> // not working as scoped css
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
|
||||
.tags-popover {
|
||||
// TODO fix padding, see https://github.com/bootstrap-vue/bootstrap-vue/issues/559#issuecomment-311150335
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tags-popover-title {
|
||||
margin-right: 4px;
|
||||
display: block;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.tag-label {
|
||||
display: block;
|
||||
float: left;
|
||||
margin-left: 4px;
|
||||
border-radius: 100px;
|
||||
background-color: $gray-50;
|
||||
padding: 4px 10px;
|
||||
color: $gray-300;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { mapState, mapGetters } from 'client/libs/store';
|
||||
import moment from 'moment';
|
||||
|
||||
import positiveIcon from 'assets/svg/positive.svg';
|
||||
import negativeIcon from 'assets/svg/negative.svg';
|
||||
import goldIcon from 'assets/svg/gold.svg';
|
||||
import streakIcon from 'assets/svg/streak.svg';
|
||||
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 bPopover from 'bootstrap-vue/lib/components/popover';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
bPopover,
|
||||
},
|
||||
props: ['task'],
|
||||
data () {
|
||||
return {
|
||||
icons: Object.freeze({
|
||||
positive: positiveIcon,
|
||||
negative: negativeIcon,
|
||||
gold: goldIcon,
|
||||
streak: streakIcon,
|
||||
calendar: calendarIcon,
|
||||
challenge: challengeIcon,
|
||||
tags: tagsIcon,
|
||||
check: checkIcon,
|
||||
}),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({user: 'user.data'}),
|
||||
...mapGetters({
|
||||
getTagsFor: 'tasks:getTagsFor',
|
||||
getTaskClasses: 'tasks:getTaskClasses',
|
||||
}),
|
||||
leftControl () {
|
||||
const task = this.task;
|
||||
if (task.type === 'reward') return false;
|
||||
return true;
|
||||
},
|
||||
rightControl () {
|
||||
const task = this.task;
|
||||
if (task.type === 'reward') return true;
|
||||
if (task.type === 'habit') return true;
|
||||
return false;
|
||||
},
|
||||
controlClass () {
|
||||
return this.getTaskClasses(this.task, 'control');
|
||||
},
|
||||
contentClass () {
|
||||
return this.getTaskClasses(this.task, 'content');
|
||||
},
|
||||
showStreak () {
|
||||
if (this.task.streak !== undefined) return true;
|
||||
if (this.task.type === 'habit' && (this.task.up || this.task.down)) return true;
|
||||
return false;
|
||||
},
|
||||
isDueOverdue () {
|
||||
return moment().diff(this.task.date, 'days') >= 0;
|
||||
},
|
||||
dueIn () {
|
||||
const dueIn = moment().to(this.task.date);
|
||||
return this.$t('dueIn', {dueIn});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
56
website/client/components/tasks/user.vue
Normal file
@@ -0,0 +1,56 @@
|
||||
<template lang="pug">
|
||||
.row.user-tasks-page
|
||||
.col-12
|
||||
.row.tasks-navigation
|
||||
.col-4.offset-4
|
||||
input.form-control.input-search(type="text", :placeholder="$t('search')")
|
||||
.col-1.offset-3
|
||||
button.btn.btn-success(v-once)
|
||||
.svg-icon.positive(v-html="icons.positive")
|
||||
| {{ $t('create') }}
|
||||
.row.tasks-columns
|
||||
task-column.col-3(v-for="column in columns", :type="column", :key="column", :isUser="true")
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
|
||||
.user-tasks-page {
|
||||
padding-top: 31px;
|
||||
}
|
||||
|
||||
.tasks-navigation {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.positive {
|
||||
display: inline-block;
|
||||
width: 10px;
|
||||
color: $green-500;
|
||||
margin-right: 8px;
|
||||
padding-top: 6px;
|
||||
}
|
||||
|
||||
.tasks-columns {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import Column from './column';
|
||||
import positiveIcon from 'assets/svg/positive.svg';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TaskColumn: Column,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
columns: ['habit', 'daily', 'todo', 'reward'],
|
||||
icons: Object.freeze({
|
||||
positive: positiveIcon,
|
||||
}),
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -1,163 +0,0 @@
|
||||
<template lang="pug">
|
||||
.row
|
||||
.col-12
|
||||
.row
|
||||
.col-3.p-4
|
||||
h3 Input
|
||||
input.form-control(type="text", placeholder="Placeholder")
|
||||
.col-3.p-4
|
||||
h3 Input Disabled
|
||||
input.form-control(type="text", placeholder="Placeholder", disabled)
|
||||
.col-3.p-4
|
||||
h3 Input With Icon
|
||||
input.form-control.input-search(type="text", placeholder="Placeholder")
|
||||
.col-3.p-4
|
||||
h3 Input With Icon Disabled
|
||||
input.form-control.input-search(type="text", placeholder="Placeholder", disabled)
|
||||
.col-3.p-4
|
||||
h3 Input Valid
|
||||
input.form-control.input-valid(type="text", placeholder="Placeholder")
|
||||
.col-3.p-4
|
||||
h3 Input Invalid
|
||||
input.form-control.input-invalid(type="text", placeholder="Placeholder")
|
||||
.row
|
||||
.col-6.p-4
|
||||
h3 Textarea
|
||||
textarea.form-control(rows="5", cols="50")
|
||||
.col-6.p-4
|
||||
h3 Textarea Disabled
|
||||
textarea.form-control(disabled, rows="10", cols="50")
|
||||
.row
|
||||
.col-2.p-4
|
||||
toggleSwitch(label="Toggle Switch")
|
||||
.row
|
||||
.col-3.p-4
|
||||
h3 Checkbox
|
||||
label.custom-control.custom-checkbox
|
||||
input.custom-control-input(type='checkbox')
|
||||
span.custom-control-indicator
|
||||
span.custom-control-description Check this custom checkbox
|
||||
.col-3.p-4
|
||||
h3 Checkbox Disabled Checked
|
||||
label.custom-control.custom-checkbox
|
||||
input.custom-control-input(type='checkbox', disabled, checked)
|
||||
span.custom-control-indicator
|
||||
span.custom-control-description Check this custom checkbox
|
||||
.col-3.p-4
|
||||
h3 Checkbox Disabled Not Checked
|
||||
label.custom-control.custom-checkbox
|
||||
input.custom-control-input(type='checkbox', disabled)
|
||||
span.custom-control-indicator
|
||||
span.custom-control-description Check this custom checkbox
|
||||
.col-6.p-4
|
||||
h3 Radio Button
|
||||
form
|
||||
label.custom-control.custom-radio
|
||||
input#radio1.custom-control-input(name='radio', type='radio')
|
||||
span.custom-control-indicator
|
||||
span.custom-control-description Toggle this custom radio
|
||||
label.custom-control.custom-radio
|
||||
input#radio2.custom-control-input(name='radio', type='radio')
|
||||
span.custom-control-indicator
|
||||
span.custom-control-description Toggle this custom radio
|
||||
.col-3.p-4
|
||||
h3 Radio Button Disabled Checked
|
||||
form
|
||||
label.custom-control.custom-radio
|
||||
input#radio3.custom-control-input(name='radio', type='radio', disabled, checked)
|
||||
span.custom-control-indicator
|
||||
span.custom-control-description Toggle this custom radio
|
||||
.col-3.p-4
|
||||
h3 Radio Button Disabled Not Checked
|
||||
form
|
||||
label.custom-control.custom-radio
|
||||
input#radio3.custom-control-input(name='radio', type='radio', disabled)
|
||||
span.custom-control-indicator
|
||||
span.custom-control-description Toggle this custom radio
|
||||
.row
|
||||
.col-3.p-4
|
||||
h3 Main Button
|
||||
button.btn.btn-primary Button
|
||||
.col-3.p-4
|
||||
h3 Secondary Button
|
||||
button.btn.btn-secondary Button
|
||||
.col-3.p-4
|
||||
h3 Green Button
|
||||
button.btn.btn-success Button
|
||||
.col-3.p-4
|
||||
h3 Blue Button
|
||||
button.btn.btn-info Button
|
||||
.col-3.p-4
|
||||
h3 Red Button
|
||||
button.btn.btn-danger Button
|
||||
.row
|
||||
.col-3.p-4
|
||||
h3 Main Button Disabled
|
||||
button.btn.btn-primary(disabled=true) Button
|
||||
.col-3.p-4
|
||||
h3 Secondary Button Disabled
|
||||
button.btn.btn-secondary(disabled=true) Button
|
||||
.col-3.p-4
|
||||
h3 Green Button Disabled
|
||||
button.btn.btn-success(disabled=true) Button
|
||||
.col-3.p-4
|
||||
h3 Blue Button Disabled
|
||||
button.btn.btn-info(disabled=true) Button
|
||||
.col-3.p-4
|
||||
h3 Red Button Disabled
|
||||
button.btn.btn-danger(disabled=true) Button
|
||||
.row
|
||||
.col-6.p-4
|
||||
h3 Dropdown Menu
|
||||
b-dropdown(text="Menu", right=false)
|
||||
b-dropdown-item(href="#") Menu item 1
|
||||
b-dropdown-item(href="#") Menu item 2
|
||||
b-dropdown-item(href="#") Menu item 3
|
||||
b-dropdown-item(href="#") Menu item 4
|
||||
.col-6.p-4
|
||||
h3 Dropdown Menu Disabled
|
||||
b-dropdown(text="Menu", disabled)
|
||||
b-dropdown-item(href="#") Menu item 1
|
||||
b-dropdown-item(href="#") Menu item 2
|
||||
b-dropdown-item(href="#") Menu item 3
|
||||
b-dropdown-item(href="#") Menu item 4
|
||||
.row
|
||||
.col-6.p-4
|
||||
h1 Heading 1
|
||||
h2 Heading 2
|
||||
h3 Heading 3
|
||||
h4 Heading 4
|
||||
.col-6.p-4
|
||||
p Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam vehicula, purus sit amet sodales pharetra, ipsum ipsum mollis orci, id pharetra velit diam et dui. Sed placerat ipsum eget pharetra rutrum. Ut vitae rutrum lacus, eu imperdiet velit. Pellentesque eu velit cursus, scelerisque dui quis, dapibus magna. Vestibulum molestie sed sapien et ultricies. Nam porta ipsum leo, non congue magna vestibulum a. Etiam dictum felis sit amet augue varius tincidunt. Sed eget urna auctor, convallis felis in, pretium justo. Curabitur aliquet, ligula id tincidunt ullamcorper, orci lorem pharetra neque, in ornare arcu magna accumsan arcu. Maecenas dignissim lorem sed eros accumsan scelerisque.
|
||||
p.small-text Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam vehicula, purus sit amet sodales pharetra, ipsum ipsum mollis orci, id pharetra velit diam et dui.
|
||||
.row
|
||||
.col(v-for="taskType in tasksTypes")
|
||||
h3 {{taskType}}s
|
||||
ul
|
||||
task(v-for="task in tasks", v-if="task.type === taskType", :key="task.id", :task="task")
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Task from './task';
|
||||
import { mapState } from 'client/libs/store';
|
||||
import bDropdown from 'bootstrap-vue/lib/components/dropdown';
|
||||
import bDropdownItem from 'bootstrap-vue/lib/components/dropdown-item';
|
||||
import toggleSwitch from 'client/components/ui/toggleSwitch';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Task,
|
||||
bDropdown,
|
||||
bDropdownItem,
|
||||
toggleSwitch,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
tasksTypes: ['habit', 'daily', 'todo', 'reward'],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({tasks: 'tasks.data'}),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -5,7 +5,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Habitica</title>
|
||||
<!-- TODO load google fonts separately as @import is slow, find alternative -->
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto+Condensed:700|Roboto:400,400i,700,700i" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto+Condensed:400,400i,700,700i|Roboto:400,400i,700,700i" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<!-- #loading-screen needs to be rendered before vue, will be deleted once app is loaded -->
|
||||
|
||||
@@ -36,8 +36,11 @@ export function loadAsyncResource ({store, path, url, deserialize, forceLoad = f
|
||||
} else if (loadingStatus === 'NOT_LOADED' || loadingStatus === 'LOADED' && forceLoad) {
|
||||
return axios.get(url).then(response => { // TODO support more params
|
||||
resource.loadingStatus = 'LOADED';
|
||||
resource.data = deserialize(response);
|
||||
return resource;
|
||||
// deserialize can be a promise
|
||||
return Promise.resolve(deserialize(response)).then(deserializedData => {
|
||||
resource.data = deserializedData;
|
||||
return resource;
|
||||
});
|
||||
});
|
||||
} else {
|
||||
return Promise.reject(new Error(`Invalid loading status "${loadingStatus} for resource at "${path}".`));
|
||||
|
||||
@@ -47,7 +47,7 @@ export default new Vue({
|
||||
|
||||
// Mount the app when user and tasks are loaded
|
||||
const userDataWatcher = this.$store.watch(state => [state.user.data, state.tasks.data], ([user, tasks]) => {
|
||||
if (user && user._id && Array.isArray(tasks)) {
|
||||
if (user && user._id && tasks && Array.isArray(tasks.habits)) {
|
||||
userDataWatcher(); // remove the watcher
|
||||
this.$mount('#app');
|
||||
}
|
||||
|
||||
@@ -7,14 +7,15 @@ import EmptyView from './components/emptyView';
|
||||
import ParentPage from './components/parentPage';
|
||||
import Page from './components/page';
|
||||
|
||||
// Tasks
|
||||
import UserTasks from './components/userTasks';
|
||||
|
||||
// Except for tasks that are always loaded all the other main level
|
||||
// All the main level
|
||||
// components are loaded in separate webpack chunks.
|
||||
// See https://webpack.js.org/guides/code-splitting-async/
|
||||
// for docs
|
||||
|
||||
// Tasks
|
||||
const UserTasks = () => import(/* webpackChunkName: "userTasks" */'./components/tasks/user');
|
||||
|
||||
// Inventory
|
||||
const InventoryContainer = () => import(/* webpackChunkName: "inventory" */'./components/inventory/index');
|
||||
const ItemsPage = () => import(/* webpackChunkName: "inventory" */'./components/inventory/items/index');
|
||||
|
||||
@@ -1,13 +1,54 @@
|
||||
import { loadAsyncResource } from 'client/libs/asyncResource';
|
||||
|
||||
import compact from 'lodash/compact';
|
||||
|
||||
export function fetchUserTasks (store, forceLoad = false) {
|
||||
return loadAsyncResource({
|
||||
store,
|
||||
path: 'tasks',
|
||||
url: '/api/v3/tasks/user',
|
||||
deserialize (response) {
|
||||
return response.data.data;
|
||||
// Wait for the user to be loaded before deserializing
|
||||
// because user.tasksOrder is necessary
|
||||
return store.dispatch('user:fetch').then((userResource) => {
|
||||
return store.dispatch('tasks:order', [response.data.data, userResource.data.tasksOrder]);
|
||||
});
|
||||
},
|
||||
forceLoad,
|
||||
});
|
||||
}
|
||||
|
||||
export function order (store, [rawTasks, tasksOrder]) {
|
||||
const tasks = {
|
||||
habits: [],
|
||||
dailys: [],
|
||||
todos: [],
|
||||
rewards: [],
|
||||
};
|
||||
|
||||
rawTasks.forEach(task => {
|
||||
tasks[`${task.type}s`].push(task);
|
||||
});
|
||||
|
||||
Object.keys(tasks).forEach((type) => {
|
||||
let tasksOfType = tasks[type];
|
||||
|
||||
const orderOfType = tasksOrder[type];
|
||||
const orderedTasks = new Array(tasksOfType.length);
|
||||
const unorderedTasks = []; // what we want to add later
|
||||
|
||||
tasksOfType.forEach((task, index) => {
|
||||
const taskId = task._id;
|
||||
const i = orderOfType[index] === taskId ? index : orderOfType.indexOf(taskId);
|
||||
if (i === -1) {
|
||||
unorderedTasks.push(task);
|
||||
} else {
|
||||
orderedTasks[i] = task;
|
||||
}
|
||||
});
|
||||
|
||||
tasks[type] = compact(orderedTasks).concat(unorderedTasks);
|
||||
});
|
||||
|
||||
return tasks;
|
||||
}
|
||||
@@ -1,6 +1,64 @@
|
||||
import { shouldDo } from 'common/script/cron';
|
||||
|
||||
// Return all the tags belonging to an user task
|
||||
export function getTagsFor (store) {
|
||||
return (task) => store.state.user.data.tags
|
||||
.filter(tag => task.tags.indexOf(tag.id) !== -1)
|
||||
.map(tag => tag.name);
|
||||
}
|
||||
|
||||
function getTaskColorByValue (value) {
|
||||
if (value < -20) {
|
||||
return 'task-worst';
|
||||
} else if (value < -10) {
|
||||
return 'task-worse';
|
||||
} else if (value < -1) {
|
||||
return 'task-bad';
|
||||
} else if (value < 1) {
|
||||
return 'task-neutral';
|
||||
} else if (value < 5) {
|
||||
return 'task-good';
|
||||
} else if (value < 10) {
|
||||
return 'task-better';
|
||||
} else {
|
||||
return 'task-best';
|
||||
}
|
||||
}
|
||||
|
||||
export function getTaskClasses (store) {
|
||||
const userPreferences = store.state.user.data.preferences;
|
||||
|
||||
// Purpose is one of 'controls', 'editModal', 'createModal', 'content'
|
||||
return (task, purpose) => {
|
||||
const type = task.type;
|
||||
|
||||
switch (purpose) {
|
||||
case 'createModal':
|
||||
return 'task-purple';
|
||||
case 'editModal':
|
||||
return type === 'reward' ? 'task-purple' : getTaskColorByValue(task.value);
|
||||
case 'control':
|
||||
switch (type) {
|
||||
case 'daily':
|
||||
if (task.completed || !shouldDo(new Date(), task, userPreferences)) return 'task-daily-todo-disabled';
|
||||
return getTaskColorByValue(task.value);
|
||||
case 'todo':
|
||||
if (task.completed) return 'task-daily-todo-disabled';
|
||||
return getTaskColorByValue(task.value);
|
||||
case 'habit':
|
||||
return {
|
||||
up: task.up ? getTaskColorByValue(task.value) : 'task-habit-disabled',
|
||||
down: task.down ? getTaskColorByValue(task.value) : 'task-habit-disabled',
|
||||
};
|
||||
case 'reward':
|
||||
return 'task-reward';
|
||||
}
|
||||
break;
|
||||
case 'content':
|
||||
if (type === 'daily' && (task.completed || !task.isDue) || type === 'todo' && task.completed) {
|
||||
return 'task-daily-todo-content-disabled';
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"viewParty": "View Party",
|
||||
"shops": "Shops",
|
||||
"faq": "FAQ",
|
||||
"costumePopoverText": "Select \"Use Costume\" to equip items to your avatar without affecting the stats from your Battle Gear! This means that you can dress up your avatar in whatever outfit you like while still having your best Battle Gear equipped.",
|
||||
@@ -9,6 +10,16 @@
|
||||
"guildBank": "Guild Bank",
|
||||
"chatPlaceHolder": "Type your message to Guild members here",
|
||||
"today": "Today",
|
||||
"theseAreYourTasks": "These are your <%= taskType %>",
|
||||
"habitsDesc": "Habits don't have a rigid schedule. You can check them off multiple times per day.",
|
||||
"dailysDesc": "Dailies repeat on a regular basis. Choose the schedule that works best for you!",
|
||||
"todosDesc": "To-Dos need to be completed once. Add checklists to your To-Dos to increase their value.",
|
||||
"rewardsDesc": "Rewards are a great way to use Habitica and complete your tasks. Try adding a few today!",
|
||||
"dueIn": "Due <%= dueIn %>",
|
||||
"complete2": "Complete",
|
||||
"custom": "Custom",
|
||||
"wishlist": "Wishlist",
|
||||
"scheduled": "Scheduled",
|
||||
"like": "Like",
|
||||
"copyAsTodo": "Copy as To-Do",
|
||||
"report": "Report",
|
||||
|
||||