Commit 065e3f6c authored by Emiliano Balbuena's avatar Emiliano Balbuena

(wip): Composer, service streams, overlay, button

1 merge request!778WIP: Composer (&25)
Pipeline #117734327 running with stages
......@@ -147,6 +147,7 @@ import { AttachmentPasteDirective } from './directives/paste/attachment-paste.di
import { FileUploadComponent } from './components/file-upload/file-upload.component';
import { IconComponent } from './components/icon/icon.component';
import { ButtonComponent } from './components/button-v2/button.component';
import { OverlayComponent } from './components/overlay/overlay.component';
const routes: Routes = [
{
......@@ -277,6 +278,7 @@ const routes: Routes = [
FileUploadComponent,
IconComponent,
ButtonComponent,
OverlayComponent,
],
exports: [
MINDS_PIPES,
......@@ -383,6 +385,7 @@ const routes: Routes = [
FileUploadComponent,
IconComponent,
ButtonComponent,
OverlayComponent,
],
providers: [
SiteService,
......
<a> </a>
<a class="m-button__defaultAction" (click)="emitAction()">
<ng-content></ng-content>
</a>
<a *ngIf="dropdown" (click)="openDropdown()" class="m-button__dropdownArrow">
<m-icon from="ion" iconId="arrow-down-b"></m-icon>
</a>
<span *ngIf="disabled" class="m-button__disabledOverlay"></span>
<ng-container *ngIf="dropdownOpen">
<div class="m-button__dropdownContainer">
<ng-container *ngTemplateOutlet="dropdown"></ng-container>
</div>
<m-overlay (onClick)="closeDropdown()"></m-overlay>
</ng-container>
m-button {
position: relative;
display: flex;
flex-direction: row;
align-items: center;
border-radius: 2px;
//m-button__
@include m-theme() {
// TODO: Avoid cascade-hell :(
color: themed($m-btn--primaryText) !important;
background: themed($m-btn--primary);
}
&.m-button--disabled {
pointer-events: none;
cursor: initial;
.m-button__disabledOverlay {
display: block;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 10;
@include m-theme() {
background: rgba(themed($m-white), 0.2);
}
}
}
a {
color: inherit !important;
}
.m-button__defaultAction {
padding: 7px 19px;
white-space: nowrap;
cursor: pointer;
overflow: hidden;
}
.m-button__dropdownArrow {
display: flex;
align-items: center;
justify-content: center;
padding: 0 12px;
border-left: 1px solid;
font-size: 9px; /* m-icon is 135% this size */
cursor: pointer;
@include m-theme() {
border-color: rgba(themed($m-btn--primaryText), 0.2);
}
}
.m-button__dropdownContainer {
position: absolute;
top: calc(100% + 4px);
left: 0;
min-width: 100%;
z-index: 10005;
}
m-overlay {
z-index: 10000;
}
}
......@@ -2,6 +2,7 @@ import {
ChangeDetectionStrategy,
Component,
EventEmitter,
HostBinding,
Input,
Output,
TemplateRef,
......@@ -17,8 +18,30 @@ export interface ButtonComponentAction {
templateUrl: 'button.component.html',
})
export class ButtonComponent {
@Input() type: string = 'default';
@Input() @HostBinding('class.m-button--disabled') disabled: boolean = false;
@Input() dropdown: TemplateRef<any>;
@Output() onAction: EventEmitter<ButtonComponentAction> = new EventEmitter<
ButtonComponentAction
>();
dropdownOpen: boolean = false;
emitAction() {
this.onAction.emit({
type: this.type,
});
}
openDropdown() {
if (!this.dropdown) {
return;
}
this.dropdownOpen = true;
}
closeDropdown() {
this.dropdownOpen = false;
}
}
<ng-content></ng-content>
m-overlay {
display: block;
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
import {
ChangeDetectionStrategy,
Component,
EventEmitter,
HostListener,
Output,
} from '@angular/core';
@Component({
selector: 'm-overlay',
changeDetection: ChangeDetectionStrategy.OnPush,
templateUrl: 'overlay.component.html',
})
export class OverlayComponent {
@Output() onClick: EventEmitter<void> = new EventEmitter<void>();
@HostListener('click', ['$event']) _click($event) {
// TODO: Check if $event.target is self, to avoid bubbles from other screen elements
this.onClick.emit();
}
}
......@@ -2,7 +2,7 @@
<div class="m-composer__container">
<div class="m-composer__titleBar">
<div class="m-composerTitleBar__title">
<label [for]="id">Compose</label>
<label [for]="id" i18n>Compose</label>
</div>
<div class="m-composerTitleBar__menuButton">
......@@ -17,43 +17,46 @@
[id]="id"
(focus)="popOut()"
[ngModel]="service.message$ | async"
(ngModelChange)="service.message$.next($event)"
(ngModelChange)="setMessage($event)"
></textarea>
</div>
<pre>{{ service.stream$ | async | json }}</pre>
<div class="m-composer__toolBar">
<m-file-upload
wrapperClass="m-composerToolbar__item"
accept="image/*,video/*"
(onSelect)="service.attachment$.next($event)"
(onSelect)="setAttachment($event)"
>
<m-icon from="ion" iconId="image"></m-icon>
<span>Upload</span>
<span i18n>Upload</span>
</m-file-upload>
<a class="m-composerToolbar__item">
<a class="m-composerToolbar__item" (click)="setNsfw()">
<m-icon iconId="explicit"></m-icon>
<span>NSFW</span>
<span i18n>NSFW</span>
</a>
<a class="m-composerToolbar__item">
<a class="m-composerToolbar__item" (click)="setMonetization()">
<m-icon from="ion" iconId="cash"></m-icon>
<span>Monetize</span>
<span i18n>Monetize</span>
</a>
<a class="m-composerToolbar__item">
<a class="m-composerToolbar__item" (click)="setTags()">
<m-icon from="ion" iconId="pound"></m-icon>
<span>Tag</span>
<span i18n>Tag</span>
</a>
<m-button [dropdown]="postButtonDropdown" (onAction)="onPost($event)">
<m-button
class="m-composerToolbar__item m-composerToolbarItem--container"
[dropdown]="postButtonDropdown"
(onAction)="onPost($event)"
i18n
>
Post
</m-button>
<ng-template #postButtonDropdown>
<ul>
<li></li>
<ul class="m-composerPost__dropdown">
<li (click)="setScheduler()">Set time and date</li>
</ul>
</ng-template>
</div>
......
......@@ -138,6 +138,11 @@ m-composer {
align-items: center;
padding: 8px 16px;
&.m-composerToolbarItem--container {
align-items: stretch;
padding: 0;
}
@include m-theme() {
color: themed($m-textColor--secondary);
}
......@@ -148,4 +153,31 @@ m-composer {
}
}
}
.m-composerPost__dropdown {
width: 100%;
list-style: none;
margin: 0;
padding: 0;
@include m-theme() {
color: themed($m-textColor--primary);
background: themed($m-bgColor--primary);
box-shadow: 3px 3px 4px rgba(themed($m-textColor--primary), 0.2);
}
> li {
padding: 8px;
border-bottom: 1px solid;
cursor: pointer;
&:last-child {
border-bottom: none;
}
@include m-theme() {
border-color: themed($m-borderColor--primary);
}
}
}
}
......@@ -22,11 +22,56 @@ export class ComposerComponent implements OnInit, OnDestroy {
constructor(public service: ComposerService) {}
ngOnInit(): void {}
ngOnInit(): void {
// TODO: Initialize based on bindings
ngOnDestroy(): void {}
this.service.message$.next('');
this.service.attachment$.next(null);
this.service.nsfw$.next(null);
this.service.monetization$.next(null);
this.service.scheduler$.next(null);
}
ngOnDestroy(): void {
// TODO: Destroy subscriptions
// TODO: GC
}
setMessage(message: string) {
this.service.message$.next(message);
}
setAttachment(file) {
this.service.attachment$.next(file);
}
setNsfw() {
// TODO: Set NSFW flags
this.service.nsfw$.next([+Date.now()]);
}
setMonetization() {
// TODO: Set Monetization attributes
this.service.monetization$.next({ monetization: +Date.now() });
}
setTags() {
this.service.alterMessageTags(/* Tags */);
}
setScheduler() {
// TODO: Set Scheduler attributes
this.service.scheduler$.next({ scheduler: +Date.now() });
}
onPost($event: ButtonComponentAction) {}
async onPost($event: ButtonComponentAction) {
try {
const response = await this.service.post();
} catch (e) {
console.log(e);
// TODO: Display errors nicely and with a clear language
}
}
popOut() {
// this.poppedOut = true;
......
import { Injectable } from '@angular/core';
import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
import { Injectable, OnDestroy } from '@angular/core';
import { BehaviorSubject, combineLatest, Observable, Subscription } from 'rxjs';
import { map } from 'rxjs/operators';
@Injectable()
export class ComposerService {
export class ComposerService implements OnDestroy {
// TODO: TYPES!!!
readonly message$: BehaviorSubject<any> = new BehaviorSubject<any>(null);
readonly attachment$: BehaviorSubject<any> = new BehaviorSubject<any>(null);
readonly nsfw$: BehaviorSubject<any> = new BehaviorSubject<any>(null);
readonly monetization$: BehaviorSubject<any> = new BehaviorSubject<any>(null);
readonly scheduler$: BehaviorSubject<any> = new BehaviorSubject<any>(null);
readonly stream$: Observable<any>;
protected data: any = null;
protected dataStreamSubscription: Subscription;
constructor() {
this.stream$ = combineLatest([
this.dataStreamSubscription = combineLatest([
this.message$,
this.attachment$,
this.nsfw$,
this.monetization$,
this.scheduler$,
]).pipe(
map(([message, attachment, nsfw, monetization, scheduler]) => ({
message,
attachment,
nsfw,
monetization,
scheduler,
}))
])
.pipe(
map(([message, attachment, nsfw, monetization, scheduler]) => ({
message,
attachment,
nsfw,
monetization,
scheduler,
}))
)
.subscribe(data => (this.data = data));
}
ngOnDestroy(): void {
this.dataStreamSubscription.unsubscribe();
}
alterMessageTags() {
// TODO: Parse message to add and delete passed tags
this.message$.next(
`${this.message$.getValue() || ''} #tag${+Date.now()}`.trim()
);
}
async post(): Promise<any> {
// TODO: Return type!
// TODO: Post
console.log(this.data);
}
}
......@@ -100,7 +100,8 @@ $themes: (
m-borderColor--tertiary: #ececec,
m-alert: #e03c20,
m-link: #1b85d6,
m-btn--primary: #1b85d6,
m-btn--primary: #5dbac0,
m-btn--primaryText: #ffffff,
// legacy colors
m-grey-950: $grey-950,
m-grey-900: $grey-900,
......@@ -168,7 +169,8 @@ $themes: (
m-borderColor--tertiary: #202527,
m-alert: #e03c20,
m-link: #1b85d6,
m-btn--primary: #1b85d6,
m-btn--primary: #5dbac0,
m-btn--primaryText: #ffffff,
// legacy colors
m-grey-950: lighten($grey-50, $percent),
m-grey-900: lighten($grey-100, $percent),
......@@ -299,6 +301,7 @@ $m-borderColor--tertiary: 'm-borderColor--tertiary';
$m-alert: 'm-alert';
$m-link: 'm-link';
$m-btn--primary: 'm-btn--primary';
$m-btn--primaryText: 'm-btn--primaryText';
$m-borderRadius: 2px;
$m-boxShadowBlur: 10px;
......
Please register or to comment