diff options
author | Chocobozzz <me@florianbigard.com> | 2020-06-23 14:10:17 +0200 |
---|---|---|
committer | Chocobozzz <chocobozzz@cpy.re> | 2020-06-23 16:00:49 +0200 |
commit | 67ed6552b831df66713bac9e672738796128d33f (patch) | |
tree | 59c97d41e0b49d75a90aa3de987968ab9b1ff447 /client/src/app/shared/shared-main/misc | |
parent | 0c4bacbff53bc732f5a2677d62a6ead7752e2405 (diff) | |
download | PeerTube-67ed6552b831df66713bac9e672738796128d33f.tar.gz PeerTube-67ed6552b831df66713bac9e672738796128d33f.tar.zst PeerTube-67ed6552b831df66713bac9e672738796128d33f.zip |
Reorganize client shared modules
Diffstat (limited to 'client/src/app/shared/shared-main/misc')
7 files changed, 394 insertions, 0 deletions
diff --git a/client/src/app/shared/shared-main/misc/help.component.html b/client/src/app/shared/shared-main/misc/help.component.html new file mode 100644 index 000000000..9a6d3e48e --- /dev/null +++ b/client/src/app/shared/shared-main/misc/help.component.html | |||
@@ -0,0 +1,40 @@ | |||
1 | <ng-template #tooltipTemplate> | ||
2 | <p *ngIf="preHtmlTemplate"> | ||
3 | <ng-template *ngTemplateOutlet="preHtmlTemplate"></ng-template> | ||
4 | </p> | ||
5 | |||
6 | <ng-container *ngIf="preHtmlTemplate && (customHtmlTemplate || mainHtml || postHtmlTemplate)"> | ||
7 | <br /><br /> | ||
8 | </ng-container> | ||
9 | |||
10 | <p *ngIf="customHtmlTemplate"> | ||
11 | <ng-template *ngTemplateOutlet="customHtmlTemplate"></ng-template> | ||
12 | </p> | ||
13 | |||
14 | <p *ngIf="mainHtml" [innerHTML]="mainHtml"></p> | ||
15 | |||
16 | <ng-container *ngIf="(customHtmlTemplate || mainHtml) && postHtmlTemplate"> | ||
17 | <br /><br /> | ||
18 | </ng-container> | ||
19 | |||
20 | <p *ngIf="postHtmlTemplate"> | ||
21 | <ng-template *ngTemplateOutlet="postHtmlTemplate"></ng-template> | ||
22 | </p> | ||
23 | </ng-template> | ||
24 | |||
25 | <span | ||
26 | role="button" | ||
27 | class="help-tooltip-button" | ||
28 | container="body" | ||
29 | title="Get help" | ||
30 | i18n-title | ||
31 | popoverClass="help-popover" | ||
32 | [attr.aria-pressed]="isPopoverOpened" | ||
33 | [ngbPopover]="tooltipTemplate" | ||
34 | [placement]="tooltipPlacement" | ||
35 | autoClose="outside" | ||
36 | (onHidden)="onPopoverHidden()" | ||
37 | (onShown)="onPopoverShown()" | ||
38 | > | ||
39 | <my-global-icon iconName="help"></my-global-icon> | ||
40 | </span> | ||
diff --git a/client/src/app/shared/shared-main/misc/help.component.scss b/client/src/app/shared/shared-main/misc/help.component.scss new file mode 100644 index 000000000..43f33a53a --- /dev/null +++ b/client/src/app/shared/shared-main/misc/help.component.scss | |||
@@ -0,0 +1,42 @@ | |||
1 | @import '_variables'; | ||
2 | @import '_mixins'; | ||
3 | |||
4 | .help-tooltip-button { | ||
5 | cursor: pointer; | ||
6 | border: none; | ||
7 | |||
8 | my-global-icon { | ||
9 | width: 17px; | ||
10 | position: relative; | ||
11 | top: -2px; | ||
12 | margin: 5px; | ||
13 | |||
14 | @include apply-svg-color(pvar(--mainForegroundColor)) | ||
15 | } | ||
16 | } | ||
17 | |||
18 | ::ng-deep { | ||
19 | .help-popover { | ||
20 | z-index: z(help-popover) !important; | ||
21 | max-width: 300px; | ||
22 | |||
23 | .popover-body { | ||
24 | font-family: $main-fonts; | ||
25 | text-align: left; | ||
26 | padding: 10px; | ||
27 | font-size: 13px; | ||
28 | background-color: pvar(--mainBackgroundColor); | ||
29 | color: pvar(--mainForegroundColor); | ||
30 | border-radius: 3px; | ||
31 | |||
32 | p { | ||
33 | margin-bottom: 0; | ||
34 | } | ||
35 | |||
36 | ul { | ||
37 | padding-left: 20px; | ||
38 | margin-bottom: 0; | ||
39 | } | ||
40 | } | ||
41 | } | ||
42 | } | ||
diff --git a/client/src/app/shared/shared-main/misc/help.component.ts b/client/src/app/shared/shared-main/misc/help.component.ts new file mode 100644 index 000000000..0825b96de --- /dev/null +++ b/client/src/app/shared/shared-main/misc/help.component.ts | |||
@@ -0,0 +1,94 @@ | |||
1 | import { AfterContentInit, Component, ContentChildren, Input, OnChanges, OnInit, QueryList, TemplateRef } from '@angular/core' | ||
2 | import { MarkdownService } from '@app/core' | ||
3 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
4 | import { PeerTubeTemplateDirective } from '../angular' | ||
5 | |||
6 | @Component({ | ||
7 | selector: 'my-help', | ||
8 | styleUrls: [ './help.component.scss' ], | ||
9 | templateUrl: './help.component.html' | ||
10 | }) | ||
11 | |||
12 | export class HelpComponent implements OnInit, OnChanges, AfterContentInit { | ||
13 | @Input() helpType: 'custom' | 'markdownText' | 'markdownEnhanced' = 'custom' | ||
14 | @Input() tooltipPlacement = 'right auto' | ||
15 | |||
16 | @ContentChildren(PeerTubeTemplateDirective) templates: QueryList<PeerTubeTemplateDirective<'preHtml' | 'customHtml' | 'postHtml'>> | ||
17 | |||
18 | isPopoverOpened = false | ||
19 | mainHtml = '' | ||
20 | |||
21 | preHtmlTemplate: TemplateRef<any> | ||
22 | customHtmlTemplate: TemplateRef<any> | ||
23 | postHtmlTemplate: TemplateRef<any> | ||
24 | |||
25 | constructor (private i18n: I18n) { } | ||
26 | |||
27 | ngOnInit () { | ||
28 | this.init() | ||
29 | } | ||
30 | |||
31 | ngAfterContentInit () { | ||
32 | { | ||
33 | const t = this.templates.find(t => t.name === 'preHtml') | ||
34 | if (t) this.preHtmlTemplate = t.template | ||
35 | } | ||
36 | |||
37 | { | ||
38 | const t = this.templates.find(t => t.name === 'customHtml') | ||
39 | if (t) this.customHtmlTemplate = t.template | ||
40 | } | ||
41 | |||
42 | { | ||
43 | const t = this.templates.find(t => t.name === 'postHtml') | ||
44 | if (t) this.postHtmlTemplate = t.template | ||
45 | } | ||
46 | } | ||
47 | |||
48 | ngOnChanges () { | ||
49 | this.init() | ||
50 | } | ||
51 | |||
52 | onPopoverHidden () { | ||
53 | this.isPopoverOpened = false | ||
54 | } | ||
55 | |||
56 | onPopoverShown () { | ||
57 | this.isPopoverOpened = true | ||
58 | } | ||
59 | |||
60 | private init () { | ||
61 | if (this.helpType === 'markdownText') { | ||
62 | this.mainHtml = this.formatMarkdownSupport(MarkdownService.TEXT_RULES) | ||
63 | return | ||
64 | } | ||
65 | |||
66 | if (this.helpType === 'markdownEnhanced') { | ||
67 | this.mainHtml = this.formatMarkdownSupport(MarkdownService.ENHANCED_RULES) | ||
68 | return | ||
69 | } | ||
70 | } | ||
71 | |||
72 | private formatMarkdownSupport (rules: string[]) { | ||
73 | // tslint:disable:max-line-length | ||
74 | return this.i18n('<a href="https://en.wikipedia.org/wiki/Markdown#Example" target="_blank" rel="noopener noreferrer">Markdown</a> compatible that supports:') + | ||
75 | this.createMarkdownList(rules) | ||
76 | } | ||
77 | |||
78 | private createMarkdownList (rules: string[]) { | ||
79 | const rulesToText = { | ||
80 | 'emphasis': this.i18n('Emphasis'), | ||
81 | 'link': this.i18n('Links'), | ||
82 | 'newline': this.i18n('New lines'), | ||
83 | 'list': this.i18n('Lists'), | ||
84 | 'image': this.i18n('Images') | ||
85 | } | ||
86 | |||
87 | const bullets = rules.map(r => rulesToText[r]) | ||
88 | .filter(text => text) | ||
89 | .map(text => '<li>' + text + '</li>') | ||
90 | .join('') | ||
91 | |||
92 | return '<ul>' + bullets + '</ul>' | ||
93 | } | ||
94 | } | ||
diff --git a/client/src/app/shared/shared-main/misc/index.ts b/client/src/app/shared/shared-main/misc/index.ts new file mode 100644 index 000000000..d3e7e4be7 --- /dev/null +++ b/client/src/app/shared/shared-main/misc/index.ts | |||
@@ -0,0 +1,2 @@ | |||
1 | export * from './help.component' | ||
2 | export * from './list-overflow.component' | ||
diff --git a/client/src/app/shared/shared-main/misc/list-overflow.component.html b/client/src/app/shared/shared-main/misc/list-overflow.component.html new file mode 100644 index 000000000..986572801 --- /dev/null +++ b/client/src/app/shared/shared-main/misc/list-overflow.component.html | |||
@@ -0,0 +1,35 @@ | |||
1 | <div #itemsParent class="d-flex align-items-center text-nowrap w-100 list-overflow-parent"> | ||
2 | <span [id]="getId(id)" #itemsRendered *ngFor="let item of items; index as id"> | ||
3 | <ng-container *ngTemplateOutlet="itemTemplate; context: {item: item}"></ng-container> | ||
4 | </span> | ||
5 | |||
6 | <ng-container *ngIf="isMenuDisplayed()"> | ||
7 | <button *ngIf="isInMobileView" class="btn btn-outline-secondary btn-sm list-overflow-menu" (click)="toggleModal()"> | ||
8 | <span class="glyphicon glyphicon-chevron-down"></span> | ||
9 | </button> | ||
10 | |||
11 | <div *ngIf="!isInMobileView" class="list-overflow-menu" ngbDropdown container="body" #dropdown="ngbDropdown" (mouseleave)="closeDropdownIfHovered(dropdown)" (mouseenter)="openDropdownOnHover(dropdown)"> | ||
12 | <button class="btn btn-outline-secondary btn-sm" [ngClass]="{ routeActive: active }" | ||
13 | ngbDropdownAnchor (click)="dropdownAnchorClicked(dropdown)" role="button" | ||
14 | > | ||
15 | <span class="glyphicon glyphicon-chevron-down"></span> | ||
16 | </button> | ||
17 | |||
18 | <div ngbDropdownMenu> | ||
19 | <a *ngFor="let item of items | slice:showItemsUntilIndexExcluded:items.length" | ||
20 | [routerLink]="item.routerLink" routerLinkActive="active" class="dropdown-item"> | ||
21 | {{ item.label }} | ||
22 | </a> | ||
23 | </div> | ||
24 | </div> | ||
25 | </ng-container> | ||
26 | </div > | ||
27 | |||
28 | <ng-template #modal let-close="close" let-dismiss="dismiss"> | ||
29 | <div class="modal-body"> | ||
30 | <a *ngFor="let item of items | slice:showItemsUntilIndexExcluded:items.length" | ||
31 | [routerLink]="item.routerLink" routerLinkActive="active" (click)="dismissOtherModals()"> | ||
32 | {{ item.label }} | ||
33 | </a> | ||
34 | </div> | ||
35 | </ng-template> | ||
diff --git a/client/src/app/shared/shared-main/misc/list-overflow.component.scss b/client/src/app/shared/shared-main/misc/list-overflow.component.scss new file mode 100644 index 000000000..1ec044489 --- /dev/null +++ b/client/src/app/shared/shared-main/misc/list-overflow.component.scss | |||
@@ -0,0 +1,61 @@ | |||
1 | @import '_mixins'; | ||
2 | |||
3 | :host { | ||
4 | width: 100%; | ||
5 | } | ||
6 | |||
7 | .list-overflow-parent { | ||
8 | overflow: hidden; | ||
9 | } | ||
10 | |||
11 | .list-overflow-menu { | ||
12 | position: absolute; | ||
13 | right: 25px; | ||
14 | } | ||
15 | |||
16 | button { | ||
17 | width: 30px; | ||
18 | border: none; | ||
19 | |||
20 | &::after { | ||
21 | display: none; | ||
22 | } | ||
23 | |||
24 | &.routeActive { | ||
25 | &::after { | ||
26 | display: inherit; | ||
27 | border: 2px solid pvar(--mainColor); | ||
28 | position: relative; | ||
29 | right: 95%; | ||
30 | top: 50%; | ||
31 | } | ||
32 | } | ||
33 | } | ||
34 | |||
35 | ::ng-deep .dropdown-menu { | ||
36 | margin-top: 0 !important; | ||
37 | position: static; | ||
38 | right: auto; | ||
39 | bottom: auto | ||
40 | } | ||
41 | |||
42 | .modal-body { | ||
43 | a { | ||
44 | @include disable-default-a-behaviour; | ||
45 | |||
46 | color: currentColor; | ||
47 | box-sizing: border-box; | ||
48 | display: block; | ||
49 | font-size: 1.2rem; | ||
50 | padding: 9px 12px; | ||
51 | text-align: initial; | ||
52 | text-transform: unset; | ||
53 | width: 100%; | ||
54 | |||
55 | &.active { | ||
56 | color: pvar(--mainBackgroundColor) !important; | ||
57 | background-color: pvar(--mainHoverColor); | ||
58 | opacity: .9; | ||
59 | } | ||
60 | } | ||
61 | } | ||
diff --git a/client/src/app/shared/shared-main/misc/list-overflow.component.ts b/client/src/app/shared/shared-main/misc/list-overflow.component.ts new file mode 100644 index 000000000..144e0f156 --- /dev/null +++ b/client/src/app/shared/shared-main/misc/list-overflow.component.ts | |||
@@ -0,0 +1,120 @@ | |||
1 | import { lowerFirst, uniqueId } from 'lodash-es' | ||
2 | import { take } from 'rxjs/operators' | ||
3 | import { | ||
4 | AfterViewInit, | ||
5 | ChangeDetectionStrategy, | ||
6 | ChangeDetectorRef, | ||
7 | Component, | ||
8 | ElementRef, | ||
9 | HostListener, | ||
10 | Input, | ||
11 | QueryList, | ||
12 | TemplateRef, | ||
13 | ViewChild, | ||
14 | ViewChildren | ||
15 | } from '@angular/core' | ||
16 | import { ScreenService } from '@app/core' | ||
17 | import { NgbDropdown, NgbModal } from '@ng-bootstrap/ng-bootstrap' | ||
18 | |||
19 | export interface ListOverflowItem { | ||
20 | label: string | ||
21 | routerLink: string | any[] | ||
22 | } | ||
23 | |||
24 | @Component({ | ||
25 | selector: 'list-overflow', | ||
26 | templateUrl: './list-overflow.component.html', | ||
27 | styleUrls: [ './list-overflow.component.scss' ], | ||
28 | changeDetection: ChangeDetectionStrategy.OnPush | ||
29 | }) | ||
30 | export class ListOverflowComponent<T extends ListOverflowItem> implements AfterViewInit { | ||
31 | @Input() items: T[] | ||
32 | @Input() itemTemplate: TemplateRef<{item: T}> | ||
33 | |||
34 | @ViewChild('modal', { static: true }) modal: ElementRef | ||
35 | @ViewChild('itemsParent', { static: true }) parent: ElementRef<HTMLDivElement> | ||
36 | @ViewChildren('itemsRendered') itemsRendered: QueryList<ElementRef> | ||
37 | |||
38 | showItemsUntilIndexExcluded: number | ||
39 | active = false | ||
40 | isInTouchScreen = false | ||
41 | isInMobileView = false | ||
42 | |||
43 | private openedOnHover = false | ||
44 | |||
45 | constructor ( | ||
46 | private cdr: ChangeDetectorRef, | ||
47 | private modalService: NgbModal, | ||
48 | private screenService: ScreenService | ||
49 | ) {} | ||
50 | |||
51 | ngAfterViewInit () { | ||
52 | setTimeout(() => this.onWindowResize(), 0) | ||
53 | } | ||
54 | |||
55 | isMenuDisplayed () { | ||
56 | return !!this.showItemsUntilIndexExcluded | ||
57 | } | ||
58 | |||
59 | @HostListener('window:resize') | ||
60 | onWindowResize () { | ||
61 | this.isInTouchScreen = !!this.screenService.isInTouchScreen() | ||
62 | this.isInMobileView = !!this.screenService.isInMobileView() | ||
63 | |||
64 | const parentWidth = this.parent.nativeElement.getBoundingClientRect().width | ||
65 | let showItemsUntilIndexExcluded: number | ||
66 | let accWidth = 0 | ||
67 | |||
68 | for (const [index, el] of this.itemsRendered.toArray().entries()) { | ||
69 | accWidth += el.nativeElement.getBoundingClientRect().width | ||
70 | if (showItemsUntilIndexExcluded === undefined) { | ||
71 | showItemsUntilIndexExcluded = (parentWidth < accWidth) ? index : undefined | ||
72 | } | ||
73 | |||
74 | const e = document.getElementById(this.getId(index)) | ||
75 | const shouldBeVisible = showItemsUntilIndexExcluded ? index < showItemsUntilIndexExcluded : true | ||
76 | e.style.visibility = shouldBeVisible ? 'inherit' : 'hidden' | ||
77 | } | ||
78 | |||
79 | this.showItemsUntilIndexExcluded = showItemsUntilIndexExcluded | ||
80 | this.cdr.markForCheck() | ||
81 | } | ||
82 | |||
83 | openDropdownOnHover (dropdown: NgbDropdown) { | ||
84 | this.openedOnHover = true | ||
85 | dropdown.open() | ||
86 | |||
87 | // Menu was closed | ||
88 | dropdown.openChange | ||
89 | .pipe(take(1)) | ||
90 | .subscribe(() => this.openedOnHover = false) | ||
91 | } | ||
92 | |||
93 | dropdownAnchorClicked (dropdown: NgbDropdown) { | ||
94 | if (this.openedOnHover) { | ||
95 | this.openedOnHover = false | ||
96 | return | ||
97 | } | ||
98 | |||
99 | return dropdown.toggle() | ||
100 | } | ||
101 | |||
102 | closeDropdownIfHovered (dropdown: NgbDropdown) { | ||
103 | if (this.openedOnHover === false) return | ||
104 | |||
105 | dropdown.close() | ||
106 | this.openedOnHover = false | ||
107 | } | ||
108 | |||
109 | toggleModal () { | ||
110 | this.modalService.open(this.modal, { centered: true }) | ||
111 | } | ||
112 | |||
113 | dismissOtherModals () { | ||
114 | this.modalService.dismissAll() | ||
115 | } | ||
116 | |||
117 | getId (id: number | string = uniqueId()): string { | ||
118 | return lowerFirst(this.constructor.name) + '_' + id | ||
119 | } | ||
120 | } | ||