old client structure

This commit is contained in:
Matteo Pagliazzi
2019-10-01 11:44:38 +02:00
parent 5b61f65711
commit b44fbdcb14
906 changed files with 44 additions and 44 deletions
@@ -0,0 +1,26 @@
<template lang="pug">
.form-check
.custom-control.custom-checkbox
input.custom-control-input(type="checkbox", v-model="isChecked", :id="id")
label.custom-control-label(v-once, :for="id") {{ text }}
</template>
<script>
export default {
props: {
checked: Boolean,
id: String,
text: String,
},
data () {
return {
isChecked: this.checked,
};
},
watch: {
isChecked (after) {
this.$emit('update:checked', after);
},
},
};
</script>
@@ -0,0 +1,34 @@
<template lang="pug">
span.badge.badge-pill.badge-item.badge-count(
v-if="show && count > 0",
) {{ count }}
</template>
<style lang="scss">
@import '~client/assets/scss/colors.scss';
.badge-count {
right: -9px;
color: $white;
background: $gray-200;
padding: 4.5px 8.5px;
min-width: 24px;
height: 24px;
box-shadow: 0 1px 1px 0 rgba($black, 0.12);
z-index: 1;
}
</style>
<script>
export default {
props: {
show: {
type: Boolean,
},
count: {
type: Number,
},
},
};
</script>
@@ -0,0 +1,97 @@
<!--
A simplified dropdown component that doesn't rely on buttons as toggles like bootstrap-vue
-->
<template lang="pug">
.habitica-menu-dropdown.dropdown(@click="toggleDropdown()", @keypress.enter.space.stop.prevent="toggleDropdown()", role="button", tabindex="0", :class="{open: isOpen}", :aria-pressed="isPressed")
.habitica-menu-dropdown-toggle
slot(name="dropdown-toggle")
.dropdown-menu(:class="{'dropdown-menu-right': right}")
slot(name="dropdown-content")
</template>
<style lang="scss">
@import '~client/assets/scss/colors.scss';
.habitica-menu-dropdown {
&:hover,
&:focus { // NB focus styles match the hover styles for .svg-icon
outline: none;
}
&.open {
.habitica-menu-dropdown-toggle .svg-icon {
color: $white !important;
}
}
}
</style>
<style lang='scss' scoped>
@import '~client/assets/scss/colors.scss';
.dropdown {
&:hover {
cursor: pointer;
}
.dropdown-menu {
cursor: auto;
box-shadow: 0 2px 2px 0 rgba($black, 0.16), 0 1px 4px 0 rgba($black, 0.12);
max-height: calc(100vh - 100px);
overflow: auto;
left: inherit;
right: 0px !important;
/deep/ .dropdown-separated {
border-bottom: 1px solid $gray-500;
}
}
&.open {
.dropdown-menu {
display: block;
margin-top: 8px;
}
}
}
</style>
<script>
export default {
props: {
right: Boolean,
openStatus: Number,
},
data () {
return {
isDropdownOpen: false,
};
},
computed: {
isOpen () {
// Open status is a number so we can tell if the value was passed
if (this.openStatus !== undefined) return this.openStatus === 1 ? true : false;
return this.isDropdownOpen;
},
isPressed () {
return this.isOpen ? 'true' : 'false';
},
},
mounted () {
document.documentElement.addEventListener('click', this._clickOutListener);
},
destroyed () {
document.removeEventListener('click', this._clickOutListener);
},
methods: {
_clickOutListener (e) {
if (!this.$el.contains(e.target) && this.isOpen) {
this.toggleDropdown();
}
},
toggleDropdown () {
this.isDropdownOpen = !this.isOpen;
this.$emit('toggled', this.isDropdownOpen);
},
},
};
</script>
+191
View File
@@ -0,0 +1,191 @@
<template lang="pug">
.drawer-container
.drawer-title(@click="toggle()")
| {{title}}
.drawer-toggle-icon.svg-icon.icon-10(v-html="isOpen ? icons.minimize : icons.expand", :class="{ closed: !isOpen }")
transition(name="slide-up", @afterLeave="adjustPagePadding", @afterEnter="adjustPagePadding")
.drawer-content(v-show="isOpen")
slot(name="drawer-header")
.drawer-slider
slot(name="drawer-slider")
div.message(v-if="errorMessage != null")
.content {{ errorMessage }}
</template>
<style lang="scss">
@import '~client/assets/scss/colors.scss';
.drawer-container {
z-index: 19;
position: fixed;
font-size: 12px;
font-weight: bold;
bottom: 0;
left: 19%;
right: 3%;
max-width: 80%;
@media screen and (min-width: 1241px) {
max-width: 978px;
// 236px is the width of the .standard-sidebar
left: calc((100% + 236px - 978px) / 2);
right: 0%;
}
}
.drawer-toggle-icon {
position: absolute;
right: 16px;
top: 16px;
&.closed {
top: 10px;
}
}
.drawer-title {
position: relative;
background-color: $gray-10;
box-shadow: 0 1px 2px 0 rgba($black, 0.2);
cursor: pointer;
border-top-right-radius: 8px;
border-top-left-radius: 8px;
text-align: center;
line-height: 1.67;
color: $white;
padding: 6px 0;
}
.drawer-content {
line-height: 1.33;
max-height: 300px;
background-color: $gray-50;
color: $gray-500;
box-shadow: 0 2px 16px 0 rgba($black, 0.3);
padding-top: 6px;
padding-left: 24px;
padding-right: 24px;
}
.drawer-tab {
&-container {
display: flex;
margin-left: 24px;
& > div {
flex: 1;
}
}
&-text {
font-size: 12px;
font-weight: bold;
line-height: 1.67;
text-align: center;
color: $white;
border-bottom: 2px solid transparent;
padding: 0px 8px 8px 8px;
&-active {
border-color: $purple-400;
}
}
}
.drawer-help-text {
cursor: pointer;
float: right;
.svg-icon {
position: relative;
top: 4px;
margin-left: 8px;
}
}
.drawer-slider {
padding: 12px 0 0 8px;
white-space: nowrap;
position: relative;
& .message {
display: flex;
align-items: center;
justify-content: center;
top: calc(50% - 30px);
left: 24px;
right: 0;
position: absolute;
& .content {
background-color: rgba($gray-200, 0.5);
border-radius: 8px;
padding: 12px;
}
}
}
.slide-up-enter-active, .slide-up-leave-active {
transition-property: all;
transition-duration: 450ms;
transition-timing-function: cubic-bezier(0.445, 0.05, 0.55, 0.95);
}
.slide-up-enter, .slide-up-leave-to {
max-height: 0;
}
</style>
<script>
import expandIcon from 'assets/svg/expand.svg';
import minimizeIcon from 'assets/svg/minimize.svg';
export default {
props: {
title: {
type: String,
required: true,
},
errorMessage: {
type: String,
},
openStatus: {
type: Number,
},
},
data () {
return {
open: true,
icons: Object.freeze({
expand: expandIcon,
minimize: minimizeIcon,
}),
};
},
computed: {
isOpen () {
// Open status is a number so we can tell if the value was passed
if (this.openStatus !== undefined) return this.openStatus === 1 ? true : false;
return this.open;
},
},
methods: {
adjustPagePadding () {
let minPaddingBottom = 20;
let drawerHeight = this.$el.offsetHeight;
let standardPage = document.getElementsByClassName('standard-page')[0];
if (standardPage) {
standardPage.style.paddingBottom = `${drawerHeight + minPaddingBottom}px`;
}
},
toggle () {
this.open = !this.isOpen;
this.$emit('toggled', this.open);
},
},
mounted () {
// Make sure the page has enough space so the drawer does not overlap content
this.adjustPagePadding();
},
};
</script>
@@ -0,0 +1,78 @@
<template lang="pug">
.header-tabs
ul.drawer-tab-container
li.drawer-tab(v-for="(tab, index) in tabs")
a.drawer-tab-text(
@click="changeTab(index)",
:class="{'drawer-tab-text-active': selectedTabPosition === index}",
:title="tab.label"
) {{ tab.label }}
aside.help-item
slot(name="right-item")
</template>
<style lang="scss" scoped>
.drawer-tab-text {
display: block;
overflow-x: hidden;
text-overflow: ellipsis;
}
.drawer-tab {
flex: inherit;
overflow-x: hidden;
white-space: nowrap;
}
.drawer-tab-container {
grid-column-start: 2;
grid-column-end: 3;
justify-self: center;
margin: 0;
padding: 0;
}
.help-item {
grid-column-start: 3;
position: relative;
right: -11px;
text-align: right;
top: -2px;
}
.header-tabs {
display: grid;
grid-template-columns: 1fr auto 1fr;
}
// MS Edge
@supports (-ms-ime-align: auto) {
.help-item {
align-self: center;
top: 1px;
}
}
</style>
<script>
export default {
props: {
tabs: {
type: Array,
required: true,
},
},
data () {
return {
selectedTabPosition: 0,
};
},
methods: {
changeTab (newIndex) {
this.selectedTabPosition = newIndex;
this.$emit('changedPosition', newIndex);
},
},
};
</script>
@@ -0,0 +1,189 @@
<template lang="pug">
div.slider-root
div.slider-button-area.left-button(
v-if="scrollButtonsVisible()",
@mousedown.left="shiftRight"
)
a.slider-button
.svg-icon(v-html="icons.previous")
div.slider-button-area.right-button(
v-if="scrollButtonsVisible()",
@mousedown.left="shiftLeft"
)
a.slider-button
.svg-icon(v-html="icons.next")
// 120 = width of the left/right buttons
div.sliding-content(v-resize="500", @resized="currentWidth = $event.width - 120")
.items.items-one-line
template(v-for="item in showItems")
div.vertical-divider(
v-if="shouldAddVerticalLine(item)"
v-bind:style="dividerMargins"
)
slot(
name="item",
:item="item",
)
</template>
<style lang="scss">
@import '~client/assets/scss/colors.scss';
$buttonAreaWidth: 60;
.slider-root {
position: relative;
}
.slider-button {
width: 40px;
height: 40px;
border-radius: 50%;
background-color: #ffffff;
box-shadow: 0 2px 2px 0 rgba($black, 0.16), 0 1px 4px 0 rgba($black, 0.12);
position: absolute;
top: calc((100% - 40px) / 2);
.svg-icon {
color: #a5a1ac;
margin: auto 0;
position: absolute;
top: calc((100% - 12px) / 2);
width: 8px;
height: 16px;
right: 16px;
}
}
.scrollButtonsVisible {
.sliding-content {
overflow: hidden;
}
}
.slider-button-area {
width: $buttonAreaWidth+px;
height: 100%;
position: absolute;
z-index: 2;
&.left-button {
left: 0;
}
&.right-button {
right: 0;
}
}
.sliding-content .items {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
padding-top: 10px;
margin-left: $buttonAreaWidth+ px;
margin-right: $buttonAreaWidth+ px;
}
.vertical-divider {
height: 92px;
width: 1px;
background: #34313a;
margin-bottom: 8px;
}
</style>
<script>
import previous from 'assets/svg/previous.svg';
import next from 'assets/svg/next.svg';
import ResizeDirective from 'client/directives/resize.directive';
export default {
directives: {
resize: ResizeDirective,
},
data () {
return {
icons: Object.freeze({
previous,
next,
}),
currentWidth: 0,
pointer: 0,
};
},
computed: {
showItems () {
let items = this.items;
let pointer = this.pointer;
let itemsPerPage = this.itemsPerPage();
let firstSlice = items.slice(pointer, pointer + itemsPerPage);
if (firstSlice.length === itemsPerPage || items.length < itemsPerPage) {
return firstSlice;
} else {
let getRemainderItems = itemsPerPage - firstSlice.length;
let secondSlice = items.slice(0, getRemainderItems);
return firstSlice.concat(secondSlice);
}
},
dividerMargins () {
return {
marginLeft: `${-this.itemMargin / 2}px`,
marginRight: `${this.itemMargin / 2}px`,
};
},
},
watch: {
itemType () {
this.pointer = 0;
},
},
methods: {
shiftLeft () {
if (this.pointer < this.items.length - 1) {
this.pointer++;
} else {
this.pointer = 0;
}
},
shiftRight () {
if (this.pointer > 0) {
this.pointer--;
} else {
this.pointer = this.items.length - 1;
}
},
itemsPerPage () {
return Math.floor(this.currentWidth / (this.itemWidth + this.itemMargin));
},
shouldAddVerticalLine (item) {
return this.items[this.itemsPerPage() - 1] === item && this.pointer !== 5;
},
scrollButtonsVisible () {
return this.items.length > this.itemsPerPage();
},
},
props: {
items: {
type: Array,
},
itemType: {
type: Number,
},
itemWidth: {
type: Number,
},
itemMargin: {
type: Number,
},
},
};
</script>
@@ -0,0 +1,45 @@
<template lang="pug">
span
span.dropdown-label {{ label }}
b-dropdown(right=true)
span(slot="text", :class="{'dropdown-icon-item': withIcon}")
slot(name="item", :item="selectedItem")
b-dropdown-item(
v-for="item in items",
@click="selectItem(item)",
:active="selectedItem.id === item.id",
:key="item.id"
)
span(:class="{'dropdown-icon-item': withIcon}")
slot(name="item", :item="item")
</template>
<script>
export default {
props: {
label: String,
items: Array,
initialItem: Object,
withIcon: {
type: Boolean,
default: false,
},
},
data () {
return {
selectedItem: this.initialItem,
};
},
methods: {
selectItem (item) {
this.selectedItem = item;
this.$emit('selected', item);
},
},
};
</script>
<style scoped lang="scss">
</style>
@@ -0,0 +1,102 @@
<template lang="pug">
.item-rows
div.items(v-resize="500", @resized="setCurrentWidth($event)")
template(v-for="item in itemsToShow(showAll)")
slot(
name="item",
:item="item"
)
div(v-if="items.length === 0")
p(v-once) {{ noItemsLabel }}
.btn.btn-flat.btn-show-more(
@click="toggleItemsToShow()",
v-if="items.length > itemsPerRow"
) {{ showAll ? $t('showLess') : $t('showMore') }}
div.fill-height(v-else)
</template>
<style lang="scss" scoped>
.fill-height {
height: 38px; // button + margin + padding
}
.item-rows {
margin-right: -24px;
}
</style>
<script>
import ResizeDirective from 'client/directives/resize.directive';
import openedItemRowsMixin from 'client/mixins/openedItemRows';
import _take from 'lodash/take';
export default {
mixins: [openedItemRowsMixin],
directives: {
resize: ResizeDirective,
},
computed: {
itemsPerRow () {
return Math.floor(this.currentWidth / (this.itemWidth + this.itemMargin));
},
},
data () {
return {
currentWidth: 0,
currentPage: 0,
showAll: false,
};
},
methods: {
toggleItemsToShow () {
this.showAll = !this.showAll;
this.$_openedItemRows_toggleByType(this.type, this.showAll);
},
itemsToShow (showAll) {
let itemsLength = this.items.length;
if (itemsLength === 0)
return [];
let itemsPerRow = this.itemsPerRow;
if (showAll || itemsLength <= itemsPerRow) {
return this.items;
}
return _take(this.items, itemsPerRow);
},
setCurrentWidth ($event) {
if (this.currentWidth !== $event.width) {
this.currentWidth = $event.width;
}
},
},
props: {
items: {
type: Array,
},
type: {
type: String,
},
itemWidth: {
type: Number,
},
itemMargin: {
type: Number,
},
noItemsLabel: {
type: String,
},
},
created () {
this.showAll = this.$_openedItemRows_isToggled(this.type);
},
};
</script>
@@ -0,0 +1,33 @@
<template lang="pug">
div
.clearfix
h2.float-left.mb-3.filters-title {{ title }}
.filters.float-right
slot(name="filters")
br
slot(name="content")
</template>
<script>
export default {
props: {
title: String,
},
};
</script>
<style scoped lang="scss">
@media only screen and (max-width: 768px) {
.filters, .filters-title {
float: none;
button {
margin-right: 4em;
margin-bottom: 1em;
}
}
}
</style>
@@ -0,0 +1,25 @@
<template lang="pug">
.row
.standard-sidebar.d-none.d-sm-block(v-if="showSidebar")
slot(name="sidebar")
.standard-page
slot(name="page")
</template>
<script>
export default {
props: {
showSidebar: {
type: Boolean,
default: true,
},
},
};
</script>
<style scoped lang="scss">
.standard-page {
position: relative;
}
</style>
@@ -0,0 +1,48 @@
<template lang="pug">
span.badge.badge-pill.badge-item.badge-star(
:class="{'item-selected-badge': selected === true}",
@click.stop="click",
v-if="show",
) &#9733;
</template>
<style lang="scss">
@import '~client/assets/scss/colors.scss';
.badge-star {
cursor: pointer;
display: none;
left: -9px;
color: $gray-400;
background: $white;
padding: 4.5px 6px;
&.item-selected-badge {
display: block;
background: $teal-50;
color: $white;
}
}
.item:hover > .badge-star {
display: block;
}
</style>
<script>
export default {
props: {
show: {
type: Boolean,
},
selected: {
type: Boolean,
},
},
methods: {
click () {
this.$emit('click');
},
},
};
</script>
@@ -0,0 +1,126 @@
<template lang="pug">
.progress-container(ref="container", :id="elementId", :class="{condensed}")
.svg-icon(v-html="icon")
.progress
.progress-bar(:class="progressClass", :style="{width: `${percent(value, maxValue)}%`}")
span.small-text {{value | statFloor}} / {{maxValue}}
b-tooltip.myClass(:target="() => $refs.container", :container="elementId", :title="tooltip", triggers="hover", placement="bottom")
</template>
<style lang="scss" scoped>
@import '~client/assets/scss/colors.scss';
.progress-container {
margin-left: 4px;
margin-bottom: .5em;
height: 24px;
/deep/ .bs-tooltip-bottom {
top: -16px !important
}
}
.progress-container > span {
color: $header-color;
margin-left: 8px;
font-style: normal;
line-height: 1;
}
.progress-container > .svg-icon {
width: 24px;
height: 24px;
margin-right: 8px;
}
.progress-container > .progress {
min-width: 200px;
margin: 0px;
border-radius: 1px;
height: 12px;
background-color: $header-dark-background;
}
.progress-container > .progress > .progress-bar {
border-radius: 1px;
height: 12px;
min-width: 0px;
}
.progress-container .svg-icon, .progress-container .progress, .progress-container .small-text {
display: inline-block;
vertical-align: middle;
}
.progress-container.condensed {
> .svg-icon {
width: 19px;
height: 19px;
margin-top: -2px;
}
> .progress {
width: 152px;
border-radius: 1px;
height: 10px;
margin-top: 2px;
background: $purple-100;
}
> .progress > .progress-bar {
border-radius: 1px;
height: 10px;
}
}
</style>
<script>
import percent from '../../../common/script/libs/percent';
export default {
props: {
icon: {
type: String,
},
value: {
type: Number,
},
maxValue: {
type: Number,
},
tooltip: {
type: String,
},
progressClass: {
type: String,
},
condensed: {
type: Boolean,
default: false,
},
},
data () {
return {
elementId: null,
};
},
filters: {
statFloor (value) {
if (value < 1 && value > 0) {
return Math.ceil(value * 10) / 10;
} else {
return Math.floor(value);
}
},
},
methods: {
percent,
click () {
this.$emit('click');
},
},
mounted () {
this.elementId = `container_${this._uid}`;
},
};
</script>
@@ -0,0 +1,153 @@
<template lang="pug">
.popover-box
.clearfix(:id="containerId")
.float-left.toggle-switch-description(v-if="label", :class="hoverText ? 'hasPopOver' : ''") {{ label }}
.toggle-switch.float-left
input.toggle-switch-checkbox(
type='checkbox', :id="toggleId",
@change="handleChange",
:checked="isChecked",
:value="value",
)
label.toggle-switch-label(:for="toggleId")
span.toggle-switch-inner
span.toggle-switch-switch
b-popover(
v-if="hoverText"
:target="containerId"
triggers="hover",
placement="top"
)
.popover-content-text {{ hoverText }}
</template>
<style lang="scss" scoped>
@import '~client/assets/scss/colors.scss';
.toggle-switch {
position: relative;
width: 40px;
user-select: none;
margin-left: 9px;
}
.toggle-switch-description {
height: 20px;
&.hasPopOver {
border-bottom: 1px dashed $gray-200;
}
}
.toggle-switch-checkbox {
display: none;
}
.toggle-switch-label {
display: block;
overflow: hidden;
cursor: pointer;
border-radius: 100px;
margin-bottom: 0px;
margin-top: 3px;
}
.toggle-switch-inner {
display: block;
width: 200%;
margin-left: -100%;
transition: margin 0.3s ease-in 0s;
}
.toggle-switch-inner:before, .toggle-switch-inner:after {
display: block;
float: left;
width: 50%;
height: 16px;
padding: 0;
}
.toggle-switch-inner:before {
content: "";
padding-left: 10px;
background-color: $purple-400;
}
.toggle-switch-inner:after {
content: "";
padding-right: 10px;
background-color: $gray-200;
text-align: right;
}
.toggle-switch-switch {
box-shadow: 0 1px 2px 0 rgba($black, 0.32);
display: block;
width: 20px;
margin: -2px;
margin-top: 1px;
height: 20px;
background: $white;
position: absolute;
top: 0;
bottom: 0;
right: 22px;
border-radius: 100px;
transition: all 0.3s ease-in 0s;
}
.toggle-switch-checkbox:checked + .toggle-switch-label .toggle-switch-inner {
margin-left: 0;
}
.toggle-switch-checkbox:checked + .toggle-switch-label .toggle-switch-switch {
right: 0px;
}
</style>
<script>
export default {
data () {
return {
// The toggle requires a unique id to link it to the label
toggleId: this.generateId(),
// The container requires a unique id to link it to the pop-over
containerId: this.generateId(),
};
},
model: {
prop: 'checked',
event: 'change',
},
props: {
value: {
default: true,
},
label: {
type: String,
},
checked: {
type: Boolean,
default: false,
},
hoverText: {
type: String,
},
},
computed: {
isChecked () {
return this.checked === this.value;
},
},
methods: {
handleChange ({ target: { checked } }) {
this.$emit('change', checked);
},
generateId () {
return `id-${Math.random().toString(36).substr(2, 16)}`;
},
},
};
</script>