Teams UI Redesign and A11y Updates (#12142)

* WIP(a11y): task modal updates

* fix(tasks): borders in modal

* fix(tasks): circley locks

* fix(task-modal): placeholders

* WIP(task-modal): disabled states, hide empty options, +/- restyle

* fix(task-modal): box shadows instead of borders, habit control pointer

* fix(task-modal): button states?

* fix(modal): tighten up layout, new spacing utils

* fix(tasks): more stylin

* fix(tasks): habit hovers

* fix(css): checklist labels, a11y colors

* fix(css): one more missed hover issue

* fix(css): lock Challenges, label fixes

* fix(css): scope input/textarea changes

* fix(style): task tweakies

* fix(style): more button fixage

* WIP(component): start select list story

* working example of a templated selectList

* fix(style): more button corrections

* fix(lint): EOL

* fix(buttons): factor btn-secondary to better override Bootstrap

* fix(styles): standardize more buttons

* wip: difficulty select - style fixes

* selectDifficulty works! 🎉 - fix styles

* change the dropdown-item sizes only for the selectList ones

* selectTranslatedArray

* changed many label margins

* more correct dropdown style

* fix(modals): button corrections

* input-group styling + datetime picker without today button

* Style/margins for "repeat every" - extract selectTag.vue

* working tag-selection / update - cleanup

* fix stories

* fix svg color on create modal (purple)

* fix task modal bottom padding

* correct dropdown shadow

* update dropdown-toggle caret size / color

* fixed checklist style

* sync checked state

* selectTag padding

* fix spacing between positive/negative streak inputs

* toggle-checkbox + fix some spacings

* disable repeat-on when its a groupTask

* fix new checklist-item

* fix toggle-checkbox style - fix difficulty style

* fix checklist ui

* add tags label , when there arent any tags selected

* WORKING select-tag component 🎉

* fix taglist story

* show max 5 items in tag dropdown + "X more" label

* fix datetime clear button

* replace m-b-xs to mb-1 (bootstrap) - fix input-group-text style

* fix styles of advanced settings

* fix delete task styles

* always show grippy on hover of the item

* extract modal-text-input mixin + fix the borders/dropshadow

* fix(spacing): revert most to Bootstrap

* feat(checklists): make local copy of master checklist non-editable
also aggressively update checklists because they weren't syncing??

* fix(checklists): handle add/remove options better

* feat(teams): manager notes field

* fix select/dropdown styles

* input border + icon colors

* delete task underline color

* fix checklist "delete icon" vertical position

* selectTag fixes - normal open/close toggle working again - remove icon color

* fixing icons:

Trash can - Delete
Little X - Remove
Big X - Close
Block - Block

* fix taglist margins / icon sizes

* wip margin overview (in storybook)

* fix routerlink

* remove unused method

* new selectTag style + add markdown inside tagList + scrollable tag selection

* fix selectTag / selectList active border

* fix difficulty select (svg default color)

* fix input padding-left + fix reset habit streak fullwidth / padding + "repeat every" gray text (no border)

* feat(teams): improved approval request > approve > reward flow

* fix(tests): address failures

* fix(lint): oops only

* fix(tasks): short-circuit group related logic

* fix(tasks): more short circuiting

* fix(tasks): more lines, less lint

* fix(tasks): how do i keep missing these

* feat(teams): provide assigning user summary

* fix(teams): don't attempt to record assiging user if not supplied

* fix advanced-settings styling / margin

* fix merge + hide advanced streak settings when none enabled

* fix styles

* set Roboto font for advanced settings

* Add Challenge flag to the tag list

* add tag with enter, when no other tag is found

* fix styles + tag cancel button

* refactor footer / margin

* split repeat fields into option mt-3 groups

* button all the things

* fix(tasks): style updates
* no hover state for non-editable tasks on team board
* keep assign/claim footer on task after requesting approval
* disable more fields on user copy of team task, and remove hover states 
for them

* fix(tasks): functional revisions
* "Claim Rewards" instead of "x" in task approved notif
* Remove default transition supplied by Bootstrap, apply individually to 
some elements
* Delete individual tasks and related notifications when master task 
deleted from team board
* Manager notes now save when supplied at task initial creation
* Can no longer dismiss rewards from approved task by hitting Dismiss 
All

* fix(tasks): clean tasksOrder
also adjust related test expectation

* fix(tests): adjust integration expectations

* fix(test): ratzen fratzen only

* fix(teams): checklist, notes

* fix(teams): improve disabled states

* fix(teams): more style fixage

* BREAKING(teams): return 202 instead of 401 for approval request

* fix(teams): better taskboard sync
also re-re-fix checklist borders

* fix(tests): update expectations for breaking change

* refactor(task-modal): lockable label component

* refactor(teams): move task scoring to mixin

* fix(teams): style corrections

* fix(tasks): spacing and wording corrections

* fix(teams): don't bork manager notes

* fix(teams): assignment fix and more approval flow revisions

* WIP(teams): use tag dropdown control for assignment

* refactor(tasks): better spacing, generic multi select

* fix(tasks): various visual and behavior updates

* fix(tasks): incidental style tweaks

* fix(teams): standardize approval request response

* refactor(teams): correct test, use res.respond message param

* fix(storybook): renamed component

* fix(teams): age approval-required To Do's
Fixes #8730

* fix(teams): sync personal data as well as team on mixin sync

* fix(teams): hide unclaim button, not whole footer; fix switch focus

* fix(achievements): unrevert width fix

Co-authored-by: Sabe Jones <sabrecat@gmail.com>
This commit is contained in:
negue
2020-07-25 14:37:10 +02:00
committed by GitHub
parent 7ee6ff18ce
commit aaf32cc09b
73 changed files with 2769 additions and 1409 deletions

View File

@@ -1,8 +1,12 @@
<template>
<div class="task-wrapper">
<div
class="task"
:class="[{'groupTask': task.group.id}, `type_${task.type}`]"
class="task transition"
:class="[{
'groupTask': task.group.id,
'task-not-editable': !teamManagerAccess},
`type_${task.type}`
]"
@click="castEnd($event, task)"
>
<approval-header
@@ -12,12 +16,13 @@
/>
<div
class="d-flex"
:class="{'task-not-scoreable': isUser !== true}"
:class="{'task-not-scoreable': isUser !== true || task.group.approval.requested
&& !(task.group.approval.approved && task.type === 'habit')}"
>
<!-- Habits left side control-->
<div
v-if="task.type === 'habit'"
class="left-control d-flex align-items-center justify-content-center"
class="left-control d-flex justify-content-center pt-3"
:class="[{
'control-bottom-box': task.group.id,
'control-top-box': approvalsClass
@@ -28,8 +33,11 @@
:class="[{
'habit-control-positive-enabled': task.up && isUser,
'habit-control-positive-disabled': !task.up && isUser,
'task-not-scoreable': isUser !== true
|| (task.group.approval.requested && !task.group.approval.approved),
}, controlClass.up.inner]"
@click="(isUser && task.up) ? score('up') : null"
@click="(isUser && task.up && (!task.group.approval.requested
|| task.group.approval.approved)) ? score('up') : null"
>
<div
v-if="!isUser"
@@ -55,7 +63,8 @@
<div
class="task-control daily-todo-control"
:class="controlClass.inner"
@click="isUser ? score(task.completed ? 'down' : 'up') : null"
@click="isUser && !task.group.approval.requested
? score(task.completed ? 'down' : 'up' ) : null"
>
<div
v-if="!isUser"
@@ -66,7 +75,10 @@
<div
v-else
class="svg-icon check"
:class="{'display-check-icon': task.completed, [controlClass.checkbox]: true}"
:class="{
'display-check-icon': task.completed || task.group.approval.requested,
[controlClass.checkbox]: true,
}"
v-html="icons.check"
></div>
</div>
@@ -74,7 +86,7 @@
<!-- Task title, description and icons-->
<div
class="task-content"
:class="contentClass"
:class="[{'cursor-auto': !teamManagerAccess}, contentClass]"
>
<div
class="task-clickable-area"
@@ -85,7 +97,7 @@
<h3
v-markdown="task.text"
class="task-title"
:class="{ 'has-notes': task.notes }"
:class="{ 'has-notes': task.notes || (!isUser && task.group.managerNotes)}"
></h3>
<menu-dropdown
v-if="!isRunningYesterdailies && showOptions"
@@ -157,7 +169,7 @@
</menu-dropdown>
</div>
<div
v-markdown="task.notes"
v-markdown="displayNotes"
class="task-notes small-text"
:class="{'has-checklist': task.notes && hasChecklist}"
></div>
@@ -171,13 +183,13 @@
<div
v-b-tooltip.hover.right="$t(`${task.collapseChecklist
? 'expand': 'collapse'}Checklist`)"
class="collapse-checklist d-flex align-items-center expand-toggle"
class="collapse-checklist mb-2 d-flex align-items-center expand-toggle"
:class="{open: !task.collapseChecklist}"
@click="collapseChecklist(task)"
>
<div
class="svg-icon"
v-html="icons.checklist"
<div v-once
class="svg-icon"
v-html="icons.checklist"
></div>
<span>{{ checklistProgress }}</span>
</div>
@@ -302,7 +314,7 @@
<!-- Habits right side control-->
<div
v-if="task.type === 'habit'"
class="right-control d-flex align-items-center justify-content-center"
class="right-control d-flex justify-content-center pt-3"
:class="[{
'control-bottom-box': task.group.id,
'control-top-box': approvalsClass}, controlClass.down.bg]"
@@ -312,8 +324,11 @@
:class="[{
'habit-control-negative-enabled': task.down && isUser,
'habit-control-negative-disabled': !task.down && isUser,
'task-not-scoreable': isUser !== true
|| (task.group.approval.requested && !task.group.approval.approved),
}, controlClass.down.inner]"
@click="(isUser && task.down) ? score('down') : null"
@click="(isUser && task.down && (!task.group.approval.requested
|| task.group.approval.approved)) ? score('down') : null"
>
<div
v-if="!isUser"
@@ -348,6 +363,7 @@
v-if="task.group.id"
:task="task"
:group="group"
@claimRewards="score('up')"
/>
</div>
</div>
@@ -368,7 +384,7 @@
}
.cursor-auto {
cursor: auto;
cursor: auto !important;
}
.task {
@@ -378,7 +394,7 @@
border-radius: 2px;
position: relative;
&:hover {
&:hover:not(.task-not-editable) {
box-shadow: 0 1px 8px 0 rgba($black, 0.12), 0 4px 4px 0 rgba($black, 0.16);
z-index: 11;
}
@@ -393,8 +409,7 @@
}
.task.groupTask {
&:hover {
&:hover:not(.task-not-editable) {
border: $purple-400 solid 1px;
border-radius: 3px;
margin: -1px; // to counter the border width
@@ -551,7 +566,6 @@
line-height: 1.2;
text-align: center;
color: $gray-200;
margin-bottom: 9px;
&.open {
}
@@ -795,13 +809,9 @@
<script>
import moment from 'moment';
import axios from 'axios';
import Vue from 'vue';
import { v4 as uuid } from 'uuid';
import isEmpty from 'lodash/isEmpty';
import { mapState, mapGetters, mapActions } from '@/libs/store';
import scoreTask from '@/../../common/script/ops/scoreTask';
import * as Analytics from '@/libs/analytics';
import positiveIcon from '@/assets/svg/positive.svg';
import negativeIcon from '@/assets/svg/negative.svg';
@@ -820,7 +830,7 @@ import checklistIcon from '@/assets/svg/checklist.svg';
import lockIcon from '@/assets/svg/lock.svg';
import menuIcon from '@/assets/svg/menu.svg';
import markdownDirective from '@/directives/markdown';
import notifications from '@/mixins/notifications';
import scoreTask from '@/mixins/scoreTask';
import approvalHeader from './approvalHeader';
import approvalFooter from './approvalFooter';
import MenuDropdown from '../ui/customMenuDropdown';
@@ -834,7 +844,7 @@ export default {
directives: {
markdown: markdownDirective,
},
mixins: [notifications],
mixins: [scoreTask],
props: ['task', 'isUser', 'group', 'challenge', 'dueDate'], // @TODO: maybe we should store the group on state?
data () {
return {
@@ -918,6 +928,7 @@ export default {
return classes;
},
showStreak () {
if (!this.task.userId) return false;
if (this.task.streak !== undefined) return true;
if (this.task.type === 'habit' && (this.task.up || this.task.down)) return true;
return false;
@@ -948,7 +959,7 @@ export default {
return this.task.challenge.shortName ? this.task.challenge.shortName.toString() : '';
},
isChallangeTask () {
isChallengeTask () {
return !isEmpty(this.task.challenge);
},
isGroupTask () {
@@ -957,7 +968,7 @@ export default {
taskCategory () {
let taskCategory = 'default';
if (this.isGroupTask) taskCategory = 'group';
else if (this.isChallangeTask) taskCategory = 'challenge';
else if (this.isChallengeTask) taskCategory = 'challenge';
return taskCategory;
},
showDelete () {
@@ -969,6 +980,14 @@ export default {
showOptions () {
return this.showEdit || this.showDelete || this.isUser;
},
teamManagerAccess () {
if (!this.isGroupTask || !this.group) return true;
return (this.group.leader._id === this.user._id || this.group.managers[this.user._id]);
},
displayNotes () {
if (this.isGroupTask && !this.isUser) return this.task.group.managerNotes;
return this.task.notes;
},
},
methods: {
...mapActions({
@@ -1021,125 +1040,8 @@ export default {
castEnd (e, task) {
setTimeout(() => this.$root.$emit('castEnd', task, 'task', e), 0);
},
async score (direction) {
if (this.castingSpell) return;
// TODO move to an action
const Content = this.$store.state.content;
const { user } = this;
const { task } = this;
if (task.group.approval.required) {
task.group.approval.requested = true;
const groupResponse = await axios.get(`/api/v4/groups/${task.group.id}`);
const managers = Object.keys(groupResponse.data.data.managers);
managers.push(groupResponse.data.data.leader._id);
if (managers.indexOf(user._id) !== -1) {
task.group.approval.approved = true;
}
}
try {
scoreTask({ task, user, direction });
} catch (err) {
this.text(err.message);
return;
}
switch (this.task.type) { // eslint-disable-line default-case
case 'habit':
this.$root.$emit('playSound', direction === 'up' ? 'Plus_Habit' : 'Minus_Habit');
break;
case 'todo':
this.$root.$emit('playSound', 'Todo');
break;
case 'daily':
this.$root.$emit('playSound', 'Daily');
break;
case 'reward':
this.$root.$emit('playSound', 'Reward');
break;
}
Analytics.updateUser();
const response = await axios.post(`/api/v4/tasks/${task._id}/score/${direction}`);
// used to notify drops, critical hits and other bonuses
const tmp = response.data.data._tmp || {};
const { crit } = tmp;
const { drop } = tmp;
const { firstDrops } = tmp;
const { quest } = tmp;
if (crit) {
const critBonus = crit * 100 - 100;
this.crit(critBonus);
}
if (quest && user.party.quest && user.party.quest.key) {
const userQuest = Content.quests[user.party.quest.key];
if (quest.progressDelta && userQuest.boss) {
this.damage(quest.progressDelta.toFixed(1));
} else if (quest.collection && userQuest.collect) {
user.party.quest.progress.collectedItems += 1;
this.quest('questCollection', quest.collection);
}
}
if (firstDrops) {
if (!user.items.eggs[firstDrops.egg]) Vue.set(user.items.eggs, firstDrops.egg, 0);
if (!user.items.hatchingPotions[firstDrops.hatchingPotion]) {
Vue.set(user.items.hatchingPotions, firstDrops.hatchingPotion, 0);
}
user.items.eggs[firstDrops.egg] += 1;
user.items.hatchingPotions[firstDrops.hatchingPotion] += 1;
}
if (drop) {
let dropText;
let dropNotes;
let type;
this.$root.$emit('playSound', 'Item_Drop');
// Note: For Mystery Item gear, drop.type will be 'head', 'armor', etc
// so we use drop.notificationType below.
if (drop.type !== 'gear' && drop.type !== 'Quest' && drop.notificationType !== 'Mystery') {
if (drop.type === 'Food') {
type = 'food';
} else if (drop.type === 'HatchingPotion') {
type = 'hatchingPotions';
} else {
type = `${drop.type.toLowerCase()}s`;
}
if (!user.items[type][drop.key]) {
Vue.set(user.items[type], drop.key, 0);
}
user.items[type][drop.key] += 1;
}
if (drop.type === 'HatchingPotion') {
dropText = Content.hatchingPotions[drop.key].text();
dropNotes = Content.hatchingPotions[drop.key].notes();
this.drop(this.$t('messageDropPotion', { dropText, dropNotes }), drop);
} else if (drop.type === 'Egg') {
dropText = Content.eggs[drop.key].text();
dropNotes = Content.eggs[drop.key].notes();
this.drop(this.$t('messageDropEgg', { dropText, dropNotes }), drop);
} else if (drop.type === 'Food') {
dropText = Content.food[drop.key].textA();
dropNotes = Content.food[drop.key].notes();
this.drop(this.$t('messageDropFood', { dropText, dropNotes }), drop);
} else if (drop.type === 'Quest') {
// TODO $rootScope.selectedQuest = Content.quests[drop.key];
// $rootScope.openModal('questDrop', {controller:'PartyCtrl', size:'sm'});
} else {
// Keep support for another type of drops that might be added
this.drop(drop.dialog);
}
}
score (direction) {
this.taskScore(this.task, direction);
},
handleBrokenTask (task) {
if (this.$store.state.isRunningYesterdailies) return;