This commit is contained in:
schmelczerandras 2019-08-25 15:49:51 +02:00
parent e96ac49c8e
commit e992152a75
28 changed files with 289 additions and 79 deletions

View file

@ -7,8 +7,8 @@
<!-- wrapper for easier styling -->
<app-select-add
class="select"
[options]="modalService.active.input"
[default]="modalService.active.input[0]"
[options]="modalService.active.input.options"
[default]="modalService.active.input.options[0]"
[alwaysDropShadow]="true"
[onlyShadowBorder]="true"
[placeholder]="'Tag this item…'"
@ -24,10 +24,10 @@
<app-toggle
[beforeText]="'This task hasn\'t been finished yet'"
[afterText]="'Goal already accomplished'"
[default]="false"
[default]="!modalService.active.input.isTask"
(value)="isDone = $event"
></app-toggle>
</div>
<!-- wrapper for easier styling -->
<button (click)="submit()">Create</button>
<button (click)="submit()" [disabled]="!selected">Create</button>

View file

@ -14,6 +14,10 @@ export class CreateBlockComponent {
constructor(public modalService: ModalService) {}
submit() {
if (!this.selected) {
return;
}
this.modalService.submit({
selected: this.selected,
description: this.description,

View file

@ -16,7 +16,7 @@
</section>
<img
*ngIf="isDragging"
[ngClass]="isDragging ? 'active' : ''"
src="assets/trash.svg"
alt="trashcan"
(pointerenter)="trashEnter()"
@ -24,9 +24,9 @@
(pointerup)="removeTower()"
/>
<div class="double-slider-container">
<div class="double-slider-container" [ngStyle]="{ opacity: isDragging ? '0' : '1' }">
<app-double-slider
*ngIf="!isDragging && dates.length >= MIN_BLOCK_COUNT_BEFORE_SHOWING_SLIDER"
*ngIf="dates.length >= MIN_BLOCK_COUNT_BEFORE_SHOWING_SLIDER"
[values]="dates"
[labels]="dateLabels"
(lowerBound)="startDate = $event"

View file

@ -17,6 +17,8 @@
justify-content: center;
width: 100%;
margin-left: auto;
margin-right: auto;
flex: 1 0 auto;
@ -28,7 +30,33 @@
}
}
@include inner-spacing(var(--medium-padding), $horizontal: true);
max-width: 800px;
& > * {
max-width: 200px;
box-sizing: content-box;
flex: 0 0 auto;
&:not(:nth-last-child(1)) {
margin-right: var(--medium-padding);
@media (max-width: $mobile-width) {
margin-right: var(--small-padding);
}
}
}
position: relative;
@for $i from 1 to 6 {
& > *:first-child:nth-last-child(#{$i}),
& > *:first-child:nth-last-child(#{$i}) ~ * {
width: calc((100% - (#{$i} - 1) * var(--medium-padding)) / #{$i});
@media (max-width: $mobile-width) {
width: calc((100% - (#{$i} - 1) * var(--small-padding)) / #{$i});
}
}
}
}
.double-slider-container {
@ -38,16 +66,25 @@
}
img {
@include square(47.33333px);
padding: 47.3333px 0;
margin: auto;
@include square(48px);
padding: 16px;
position: relative;
position: absolute;
z-index: 1500;
bottom: 8px;
left: 50%;
margin: 0 !important;
transform: translateX(-50%) scale(0);
transition: transform $long-animation-time;
&.active {
transform: translateX(-50%) scale(1);
}
&:hover {
transform: scale(1.2);
transform: translateX(-50%) scale(1.1);
}
}
}

View file

@ -1,4 +1,4 @@
import { Component, Input } from '@angular/core';
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { Page } from '../../../model/page';
import { ModalService } from '../../../services/modal.service';
import { DataService } from '../../../services/data.service';
@ -20,6 +20,8 @@ export class PageComponent {
this.updateDates();
}
@Output() isDragHappening: EventEmitter<boolean> = new EventEmitter();
get page(): Page {
return this._page;
}
@ -59,11 +61,13 @@ export class PageComponent {
dropDrag(event: any) {
this.page.moveTower(event);
this.isDragging = false;
this.isDragHappening.emit(false);
}
startDrag(id: number) {
this.draggedTowerIndex = id;
this.isDragging = true;
this.isDragHappening.emit(true);
}
trashEnter() {

View file

@ -1,16 +1,14 @@
<div class="container">
<div class="container {{ tasks.length > 0 ? 'show-hover' : '' }}">
<p class="header" (click)="isOpen = !isOpen">
{{ tasks.length == 0 ? 'no' : '' }}
<strong>
{{ tasks.length == 0 ? '' : tasks.length }}
</strong>
{{ tasks.length == 1 ? 'task' : 'tasks' }}
{{ tasks.length == 0 ? '&#8203;' : tasks.length == 1 ? 'task' : 'tasks' }}
</p>
<div class="all-task" #allTask [ngStyle]="{ height: (isOpen ? allTask?.scrollHeight : 0) + 'px' }">
<p
*ngFor="let task of tasks"
(click)="handleClick(task)"
[innerText]="task.description ? task.description : 'unknown'"
></p>
<div class="task-container" *ngFor="let task of tasks" [ngStyle]="{ color: task.color.toString() }">
<div [ngStyle]="{ 'background-color': task.color.toString() }"></div>
<p (click)="handleClick(task)" [innerText]="task.description ? task.description : 'unknown'"></p>
</div>
</div>
</div>

View file

@ -3,12 +3,20 @@
:host {
width: 100%;
box-sizing: border-box;
position: relative;
z-index: 100000;
.container {
@include card();
box-shadow: $shadow-border;
padding: var(--small-padding);
margin: var(--small-padding);
cursor: pointer;
transition: box-shadow $long-animation-time;
&.show-hover:hover {
box-shadow: $shadow-border;
}
padding: calc(var(--small-padding) / 2);
margin: calc(var(--small-padding) / 2);
max-height: 30vh;
overflow-y: auto;
@ -18,7 +26,6 @@
}
p {
width: 100%;
font-size: var(--medium-font-size);
}
@ -29,23 +36,43 @@
margin-top: var(--small-padding);
}
width: 100%;
height: 0;
box-sizing: border-box;
transition: height $long-animation-time;
overflow-y: hidden;
height: 0;
transition: height $long-animation-time;
p {
max-width: 60px;
white-space: nowrap;
text-overflow: ellipsis;
overflow-x: hidden;
text-align: left;
.task-container {
display: flex;
align-items: center;
&:hover {
font-weight: bold;
p {
@media (min-width: $mobile-width) {
color: inherit !important;
}
}
}
div {
flex: 0 0 auto;
margin: 0 calc(var(--small-padding) / 2) 0 0;
@include square(var(--small-padding));
@media (max-width: $mobile-width) {
@include square(calc(var(--small-padding) / 2));
}
}
p {
white-space: nowrap;
text-overflow: ellipsis;
overflow-x: hidden;
text-align: left;
@media (max-width: $mobile-width) {
font-size: var(--small-font-size);
}
position: relative;
}
}
}

View file

@ -2,6 +2,7 @@ import { Component, ElementRef, Input, OnInit, ViewChild } from '@angular/core';
import { Block } from '../../../../../model/block';
import { Tower } from '../../../../../model/tower';
import { ModalService } from '../../../../../services/modal.service';
import { CancelService } from '../../../../../services/cancel.service';
@Component({
selector: 'app-tasks',
@ -12,11 +13,25 @@ export class TasksComponent implements OnInit {
@Input() tasks: Block[];
@Input() tower: Tower;
@Input() isOpen = false;
private _isOpen = false;
@Input() set isOpen(value: boolean) {
if (value) {
this.cancelService.cancelAllExcept(this);
}
this._isOpen = value;
}
get isOpen(): boolean {
return this._isOpen;
}
@ViewChild('allTask') allTask: ElementRef;
constructor(private modalService: ModalService) {}
constructor(private modalService: ModalService, private cancelService: CancelService) {
this.cancelService.subscribe(this, () => {
this.isOpen = false;
});
}
ngOnInit() {}
@ -39,4 +54,16 @@ export class TasksComponent implements OnInit {
// pass
}
}
public async addTask() {
try {
const { selected: tag, description, isDone } = await this.modalService.showCreateBlock({
options: this.tower.tags,
isTask: true
});
this.tower.addBlock({ tag, description, isDone });
} catch (e) {
// pass
}
}
}

View file

@ -15,7 +15,7 @@
<input
id="tower-name"
type="text"
placeholder="…"
placeholder="name…"
[(ngModel)]="tower.name"
[ngStyle]="{ color: tower.baseColor.toString() }"
/>

View file

@ -11,8 +11,17 @@
opacity: 0;
}
&.cdk-drag-preview,
&:hover {
@media (min-width: $mobile-width) {
div {
.container {
box-shadow: $shadow;
}
}
}
}
&.cdk-drag-preview {
div {
.container {
box-shadow: $shadow;
@ -39,7 +48,7 @@
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
max-width: 100%;
height: 100%;
@include inner-spacing(var(--small-padding));
@ -52,7 +61,7 @@
position: relative;
@include card();
overflow: hidden;
transition: transform $short-animation-time, box-shadow $long-animation-time;
@include inner-spacing(var(--medium-padding));
@ -65,7 +74,7 @@
pointer-events: none;
position: absolute;
z-index: 3;
z-index: 2;
left: 0;
top: 0;
@ -85,9 +94,9 @@
position: relative;
z-index: 2;
height: 56px;
height: 48px;
@media (max-width: $mobile-width) {
height: 42px;
height: 32px;
}
opacity: 0.33;
@ -130,7 +139,7 @@
}
input[type='text'] {
font-size: var(--medium-font-size);
font-size: var(--small-font-size);
text-align: center;
@media (min-width: $mobile-width) {
width: 50%;

View file

@ -1,4 +1,4 @@
import { Component, ElementRef, Input, ViewChild } from '@angular/core';
import { Component, Input } from '@angular/core';
import { Tower } from '../../../../model/tower';
import { ModalService } from '../../../../services/modal.service';
import { Block } from '../../../../model/block';
@ -40,7 +40,10 @@ export class TowerComponent {
public async addBlock() {
try {
const { selected: tag, description, isDone } = await this.modalService.showCreateBlock(this.tower.tags);
const { selected: tag, description, isDone } = await this.modalService.showCreateBlock({
options: this.tower.tags,
isTask: false
});
this.tower.addBlock({ tag, description, isDone });
} catch (e) {
// pass

View file

@ -11,8 +11,12 @@
<div class="page-container">
<!-- wrapper for easier styling -->
<app-page *ngIf="dataService.active !== null" [page]="dataService.active"></app-page>
<app-page
*ngIf="dataService.active !== null"
[page]="dataService.active"
(isDragHappening)="isDragHappening = $event"
></app-page>
</div>
<!-- wrapper for easier styling -->
<button (click)="openSettings()">Settings</button>
<button [ngClass]="isDragHappening ? 'transparent' : ''" (click)="openSettings()">Settings</button>

View file

@ -18,4 +18,12 @@
.page-container {
flex: 1 0 auto;
}
button {
transition: opacity $long-animation-time;
&.transparent {
opacity: 0;
}
}
}

View file

@ -13,6 +13,8 @@ export class PagesComponent {
@ViewChild('page') page: ElementRef;
@ViewChild('bottom') bottom: ElementRef;
isDragHappening = false;
constructor(public dataService: DataService, private modalService: ModalService) {}
async selectPage(selected: string) {

View file

@ -37,11 +37,12 @@ $slider-size: 40px;
transition: box-shadow $long-animation-time, transform $long-animation-time;
&:hover {
box-shadow: $shadow;
transform: translateY(-$slider-size / 2 + $line-height / 2) scale(1.1);
@media (min-width: $mobile-width) {
&:hover {
box-shadow: $shadow;
transform: translateY(-$slider-size / 2 + $line-height / 2) scale(1.1);
}
}
cursor: pointer;
position: relative;

View file

@ -16,6 +16,12 @@ export class DoubleSliderComponent {
this._values = values;
this.calculateLabels();
if (this._oneValue > this._otherValue) {
this._oneValue = this.MAX - 1;
} else {
this._otherValue = this.MAX - 1;
}
this.emitValue();
}

View file

@ -98,8 +98,10 @@ $inner-padding: var(--medium-padding);
}
&:hover {
.background {
box-shadow: $shadow;
@media (min-width: $mobile-width) {
.background {
box-shadow: $shadow;
}
}
}

View file

@ -1,4 +1,5 @@
import { Component, Input, Output, EventEmitter, ViewChild, ElementRef } from '@angular/core';
import { CancelService } from '../../../services/cancel.service';
@Component({
selector: 'app-select-add',
@ -28,6 +29,12 @@ export class SelectAddComponent {
newOption: string;
isOpen = false;
constructor(private cancelService: CancelService) {
this.cancelService.subscribe(this, () => {
this.isOpen = false;
});
}
get otherOptions(): string[] {
return this.options.filter(a => a !== this.selected);
}

View file

@ -4,6 +4,11 @@ import { Color } from './color';
export class Block extends Base implements IBlock {
constructor(props: IBlock) {
// TODO: remove
if (props.isDone === undefined) {
props.isDone = true;
}
super(props);
if (this.created.constructor.name !== 'Date') {

View file

@ -5,6 +5,11 @@ import { ITower } from '../interfaces/persistance/tower';
export class Page extends Base implements IPage {
constructor(props) {
// TODO: remove
if (!props.userData) {
props.userData = {};
}
super(props);
// @ts-ignore to prevent update message
this.__towers = this.towers.map(t => this.createTower(t));
@ -37,7 +42,6 @@ export class Page extends Base implements IPage {
do {
hue = Math.random() * 360;
} while (30 <= hue && hue <= 200);
console.log(hue);
this.towers.push(
this.createTower({

View file

@ -3,7 +3,7 @@ import { Color } from './color';
import { Block } from './block';
import { Base } from './base';
import { IBlock } from '../interfaces/persistance/block';
import { hashCode } from '../utils/hash';
import { hash } from '../utils/hash';
export class Tower extends Base implements ITower {
constructor(props: ITower) {
@ -46,7 +46,7 @@ export class Tower extends Base implements ITower {
if (!this.tags.includes(block.tag)) {
this.tags.push(block.tag);
}
block.color = this.baseColor.lighten(hashCode(block.tag) * 50);
block.color = this.baseColor.lighten(hash(block.tag) * 50);
}
super.update();

View file

@ -0,0 +1,30 @@
import { Injectable } from '@angular/core';
interface Subscriber {
callback: () => void;
object: object;
}
@Injectable({
providedIn: 'root'
})
export class CancelService {
private subscribers: Subscriber[] = [];
constructor() {}
subscribe(object: object, callback: () => void) {
this.subscribers.push({
object,
callback
});
}
cancelAllExcept(except: object) {
this.subscribers.filter(s => s.object !== except).map(s => s.callback());
}
cancelAll() {
this.subscribers.map(s => s.callback());
}
}

View file

@ -1,6 +1,7 @@
import { Injectable } from '@angular/core';
import { Tower } from '../model/tower';
import { top } from '../utils/top';
import { CancelService } from './cancel.service';
export enum ModalType {
createBlock,
@ -25,8 +26,13 @@ interface Modal {
export class ModalService {
private modalStack: Modal[] = [];
showCreateBlock(options: string[]): Promise<{ selected: string; description: string; isDone: boolean }> {
return this.createPromiseAndPushToStack(options, ModalType.createBlock);
constructor(private cancelService: CancelService) {}
showCreateBlock(input: {
options: string[];
isTask: boolean;
}): Promise<{ selected: string; description: string; isDone: boolean }> {
return this.createPromiseAndPushToStack(input, ModalType.createBlock);
}
showEditBlock(data: {
@ -65,6 +71,8 @@ export class ModalService {
}
private createPromiseAndPushToStack(input: any, type: ModalType): Promise<any> {
this.cancelService.cancelAll();
const modal = {
input,
type,

View file

@ -1,7 +1,7 @@
import { Injectable } from '@angular/core';
import { Page } from '../model/page';
const LOCAL_STORAGE_KEY = 'life-towers.data.v.2.1';
const LOCAL_STORAGE_KEY = 'life-towers.data.v.2';
@Injectable({
providedIn: 'root'

View file

@ -1,14 +1,8 @@
export const hashCode = (text: string) => {
let hash = 0;
if (text.length == 0) {
return hash;
export const hash = (text: string): number => {
// Return number between 0 and 1.
if (!text) {
return 0;
}
for (let i = 0; i < text.length; i++) {
const char = text.charCodeAt(i);
hash = (hash << 5) - hash + char;
hash = hash & hash;
}
hash /= Math.pow(2, 32) - 1;
return hash;
const hash = Array.prototype.reduce.call(text, (hash, char) => (hash << 5) - hash + char.charCodeAt(0), 7);
return hash / (Math.pow(2, 32) - 1);
};

View file

@ -1,5 +1,5 @@
$accent-color: #a2666f;
$text-color: #5d576b;
$text-color: #5d576bff;
$light-color: #ffffff;
$background-gradient: linear-gradient(90deg, #fff9e077 0, #ffd6d677 100%);

View file

@ -43,10 +43,35 @@ button {
background: transparent;
border: 0;
text-decoration: underline;
@include medium-text();
@include jump();
font-size: var(--large-font-size);
$height: 2px;
cursor: pointer;
border-bottom: solid $height #5d576b55;
position: relative;
&:disabled {
color: #5d576b55;
border-bottom: solid $height #5d576b33;
cursor: not-allowed;
}
&:not(:disabled):hover {
&:after {
width: 100%;
}
}
&:after {
content: '';
width: 0;
height: $height;
position: absolute;
left: 0;
bottom: calc(-1 * #{$height});
background-color: $text-color;
transition: width 300ms;
}
}
label {

View file

@ -60,3 +60,8 @@ img {
border-radius: var(--border-radius);
cursor: pointer;
}
* {
-webkit-touch-callout: none;
-webkit-tap-highlight-color: transparent;
}