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/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/misc')
-rw-r--r-- | client/src/app/shared/misc/constants.ts | 1 | ||||
-rw-r--r-- | client/src/app/shared/misc/help.component.html | 40 | ||||
-rw-r--r-- | client/src/app/shared/misc/help.component.scss | 42 | ||||
-rw-r--r-- | client/src/app/shared/misc/help.component.ts | 94 | ||||
-rw-r--r-- | client/src/app/shared/misc/list-overflow.component.html | 35 | ||||
-rw-r--r-- | client/src/app/shared/misc/list-overflow.component.scss | 61 | ||||
-rw-r--r-- | client/src/app/shared/misc/list-overflow.component.ts | 120 | ||||
-rw-r--r-- | client/src/app/shared/misc/loader.component.html | 8 | ||||
-rw-r--r-- | client/src/app/shared/misc/loader.component.scss | 45 | ||||
-rw-r--r-- | client/src/app/shared/misc/loader.component.ts | 10 | ||||
-rw-r--r-- | client/src/app/shared/misc/peertube-web-storage.ts | 81 | ||||
-rw-r--r-- | client/src/app/shared/misc/screen.service.ts | 66 | ||||
-rw-r--r-- | client/src/app/shared/misc/small-loader.component.html | 3 | ||||
-rw-r--r-- | client/src/app/shared/misc/small-loader.component.ts | 11 | ||||
-rw-r--r-- | client/src/app/shared/misc/storage.service.ts | 40 | ||||
-rw-r--r-- | client/src/app/shared/misc/utils.ts | 210 |
16 files changed, 0 insertions, 867 deletions
diff --git a/client/src/app/shared/misc/constants.ts b/client/src/app/shared/misc/constants.ts deleted file mode 100644 index bb4a0884e..000000000 --- a/client/src/app/shared/misc/constants.ts +++ /dev/null | |||
@@ -1 +0,0 @@ | |||
1 | export const POP_STATE_MODAL_DISMISS = 'pop state dismiss' | ||
diff --git a/client/src/app/shared/misc/help.component.html b/client/src/app/shared/misc/help.component.html deleted file mode 100644 index 9a6d3e48e..000000000 --- a/client/src/app/shared/misc/help.component.html +++ /dev/null | |||
@@ -1,40 +0,0 @@ | |||
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/misc/help.component.scss b/client/src/app/shared/misc/help.component.scss deleted file mode 100644 index 43f33a53a..000000000 --- a/client/src/app/shared/misc/help.component.scss +++ /dev/null | |||
@@ -1,42 +0,0 @@ | |||
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/misc/help.component.ts b/client/src/app/shared/misc/help.component.ts deleted file mode 100644 index e8c199e7d..000000000 --- a/client/src/app/shared/misc/help.component.ts +++ /dev/null | |||
@@ -1,94 +0,0 @@ | |||
1 | import { AfterContentInit, Component, ContentChildren, Input, OnChanges, OnInit, QueryList, TemplateRef } from '@angular/core' | ||
2 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
3 | import { MarkdownService } from '@app/shared/renderer' | ||
4 | import { PeerTubeTemplateDirective } from '@app/shared/angular/peertube-template.directive' | ||
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/misc/list-overflow.component.html b/client/src/app/shared/misc/list-overflow.component.html deleted file mode 100644 index 986572801..000000000 --- a/client/src/app/shared/misc/list-overflow.component.html +++ /dev/null | |||
@@ -1,35 +0,0 @@ | |||
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/misc/list-overflow.component.scss b/client/src/app/shared/misc/list-overflow.component.scss deleted file mode 100644 index 1ec044489..000000000 --- a/client/src/app/shared/misc/list-overflow.component.scss +++ /dev/null | |||
@@ -1,61 +0,0 @@ | |||
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/misc/list-overflow.component.ts b/client/src/app/shared/misc/list-overflow.component.ts deleted file mode 100644 index 30f43ba43..000000000 --- a/client/src/app/shared/misc/list-overflow.component.ts +++ /dev/null | |||
@@ -1,120 +0,0 @@ | |||
1 | import { | ||
2 | AfterViewInit, | ||
3 | ChangeDetectionStrategy, | ||
4 | ChangeDetectorRef, | ||
5 | Component, | ||
6 | ElementRef, | ||
7 | HostListener, | ||
8 | Input, | ||
9 | QueryList, | ||
10 | TemplateRef, | ||
11 | ViewChild, | ||
12 | ViewChildren | ||
13 | } from '@angular/core' | ||
14 | import { NgbDropdown, NgbModal } from '@ng-bootstrap/ng-bootstrap' | ||
15 | import { lowerFirst, uniqueId } from 'lodash-es' | ||
16 | import { ScreenService } from './screen.service' | ||
17 | import { take } from 'rxjs/operators' | ||
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 | } | ||
diff --git a/client/src/app/shared/misc/loader.component.html b/client/src/app/shared/misc/loader.component.html deleted file mode 100644 index ca8ed063e..000000000 --- a/client/src/app/shared/misc/loader.component.html +++ /dev/null | |||
@@ -1,8 +0,0 @@ | |||
1 | <div *ngIf="loading"> | ||
2 | <div class="loader"> | ||
3 | <div></div> | ||
4 | <div></div> | ||
5 | <div></div> | ||
6 | <div></div> | ||
7 | </div> | ||
8 | </div> | ||
diff --git a/client/src/app/shared/misc/loader.component.scss b/client/src/app/shared/misc/loader.component.scss deleted file mode 100644 index ffac9c707..000000000 --- a/client/src/app/shared/misc/loader.component.scss +++ /dev/null | |||
@@ -1,45 +0,0 @@ | |||
1 | @import '_variables'; | ||
2 | @import '_mixins'; | ||
3 | |||
4 | // Thanks to https://loading.io/css/ (CC0 License) | ||
5 | |||
6 | .loader { | ||
7 | display: inline-block; | ||
8 | position: relative; | ||
9 | width: 50px; | ||
10 | height: 50px; | ||
11 | } | ||
12 | |||
13 | .loader div { | ||
14 | box-sizing: border-box; | ||
15 | display: block; | ||
16 | position: absolute; | ||
17 | width: 44px; | ||
18 | height: 44px; | ||
19 | margin: 6px; | ||
20 | border: 4px solid; | ||
21 | border-radius: 50%; | ||
22 | animation: loader 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; | ||
23 | border-color: #999999 transparent transparent transparent; | ||
24 | } | ||
25 | |||
26 | .loader div:nth-child(1) { | ||
27 | animation-delay: -0.45s; | ||
28 | } | ||
29 | |||
30 | .loader div:nth-child(2) { | ||
31 | animation-delay: -0.3s; | ||
32 | } | ||
33 | |||
34 | .loader div:nth-child(3) { | ||
35 | animation-delay: -0.15s; | ||
36 | } | ||
37 | |||
38 | @keyframes loader { | ||
39 | 0% { | ||
40 | transform: rotate(0deg); | ||
41 | } | ||
42 | 100% { | ||
43 | transform: rotate(360deg); | ||
44 | } | ||
45 | } | ||
diff --git a/client/src/app/shared/misc/loader.component.ts b/client/src/app/shared/misc/loader.component.ts deleted file mode 100644 index e3b1eea3a..000000000 --- a/client/src/app/shared/misc/loader.component.ts +++ /dev/null | |||
@@ -1,10 +0,0 @@ | |||
1 | import { Component, Input } from '@angular/core' | ||
2 | |||
3 | @Component({ | ||
4 | selector: 'my-loader', | ||
5 | styleUrls: [ './loader.component.scss' ], | ||
6 | templateUrl: './loader.component.html' | ||
7 | }) | ||
8 | export class LoaderComponent { | ||
9 | @Input() loading: boolean | ||
10 | } | ||
diff --git a/client/src/app/shared/misc/peertube-web-storage.ts b/client/src/app/shared/misc/peertube-web-storage.ts deleted file mode 100644 index 0db1301bd..000000000 --- a/client/src/app/shared/misc/peertube-web-storage.ts +++ /dev/null | |||
@@ -1,81 +0,0 @@ | |||
1 | // Thanks: https://github.com/capaj/localstorage-polyfill | ||
2 | |||
3 | const valuesMap = new Map() | ||
4 | |||
5 | function proxify (instance: MemoryStorage) { | ||
6 | return new Proxy(instance, { | ||
7 | set: function (obj, prop: string | number, value) { | ||
8 | if (MemoryStorage.prototype.hasOwnProperty(prop)) { | ||
9 | instance[prop] = value | ||
10 | } else { | ||
11 | instance.setItem(prop, value) | ||
12 | } | ||
13 | return true | ||
14 | }, | ||
15 | get: function (target, name: string | number) { | ||
16 | if (MemoryStorage.prototype.hasOwnProperty(name)) { | ||
17 | return instance[name] | ||
18 | } | ||
19 | if (valuesMap.has(name)) { | ||
20 | return instance.getItem(name) | ||
21 | } | ||
22 | } | ||
23 | }) | ||
24 | } | ||
25 | |||
26 | class MemoryStorage { | ||
27 | [key: string]: any | ||
28 | [index: number]: string | ||
29 | |||
30 | getItem (key: any) { | ||
31 | const stringKey = String(key) | ||
32 | if (valuesMap.has(key)) { | ||
33 | return String(valuesMap.get(stringKey)) | ||
34 | } | ||
35 | |||
36 | return null | ||
37 | } | ||
38 | |||
39 | setItem (key: any, val: any) { | ||
40 | valuesMap.set(String(key), String(val)) | ||
41 | } | ||
42 | |||
43 | removeItem (key: any) { | ||
44 | valuesMap.delete(key) | ||
45 | } | ||
46 | |||
47 | clear () { | ||
48 | valuesMap.clear() | ||
49 | } | ||
50 | |||
51 | key (i: any) { | ||
52 | if (arguments.length === 0) { | ||
53 | throw new TypeError('Failed to execute "key" on "Storage": 1 argument required, but only 0 present.') | ||
54 | } | ||
55 | |||
56 | const arr = Array.from(valuesMap.keys()) | ||
57 | return arr[i] | ||
58 | } | ||
59 | |||
60 | get length () { | ||
61 | return valuesMap.size | ||
62 | } | ||
63 | } | ||
64 | |||
65 | let peertubeLocalStorage: Storage | ||
66 | let peertubeSessionStorage: Storage | ||
67 | try { | ||
68 | peertubeLocalStorage = localStorage | ||
69 | peertubeSessionStorage = sessionStorage | ||
70 | } catch (err) { | ||
71 | const instanceLocalStorage = new MemoryStorage() | ||
72 | const instanceSessionStorage = new MemoryStorage() | ||
73 | |||
74 | peertubeLocalStorage = proxify(instanceLocalStorage) | ||
75 | peertubeSessionStorage = proxify(instanceSessionStorage) | ||
76 | } | ||
77 | |||
78 | export { | ||
79 | peertubeLocalStorage, | ||
80 | peertubeSessionStorage | ||
81 | } | ||
diff --git a/client/src/app/shared/misc/screen.service.ts b/client/src/app/shared/misc/screen.service.ts deleted file mode 100644 index a69fad31d..000000000 --- a/client/src/app/shared/misc/screen.service.ts +++ /dev/null | |||
@@ -1,66 +0,0 @@ | |||
1 | import { Injectable } from '@angular/core' | ||
2 | |||
3 | @Injectable() | ||
4 | export class ScreenService { | ||
5 | private windowInnerWidth: number | ||
6 | private lastFunctionCallTime: number | ||
7 | private cacheForMs = 500 | ||
8 | |||
9 | constructor () { | ||
10 | this.refreshWindowInnerWidth() | ||
11 | } | ||
12 | |||
13 | isInSmallView (marginLeft = 0) { | ||
14 | if (marginLeft > 0) { | ||
15 | const contentWidth = this.getWindowInnerWidth() - marginLeft | ||
16 | return contentWidth < 800 | ||
17 | } | ||
18 | |||
19 | return this.getWindowInnerWidth() < 800 | ||
20 | } | ||
21 | |||
22 | isInMediumView () { | ||
23 | return this.getWindowInnerWidth() < 1100 | ||
24 | } | ||
25 | |||
26 | isInMobileView () { | ||
27 | return this.getWindowInnerWidth() < 500 | ||
28 | } | ||
29 | |||
30 | isInTouchScreen () { | ||
31 | return 'ontouchstart' in window || navigator.msMaxTouchPoints | ||
32 | } | ||
33 | |||
34 | getNumberOfAvailableMiniatures () { | ||
35 | const screenWidth = this.getWindowInnerWidth() | ||
36 | |||
37 | let numberOfVideos = 1 | ||
38 | |||
39 | if (screenWidth > 1850) numberOfVideos = 7 | ||
40 | else if (screenWidth > 1600) numberOfVideos = 6 | ||
41 | else if (screenWidth > 1370) numberOfVideos = 5 | ||
42 | else if (screenWidth > 1100) numberOfVideos = 4 | ||
43 | else if (screenWidth > 850) numberOfVideos = 3 | ||
44 | |||
45 | return numberOfVideos | ||
46 | } | ||
47 | |||
48 | // Cache window inner width, because it's an expensive call | ||
49 | getWindowInnerWidth () { | ||
50 | if (this.cacheWindowInnerWidthExpired()) this.refreshWindowInnerWidth() | ||
51 | |||
52 | return this.windowInnerWidth | ||
53 | } | ||
54 | |||
55 | private refreshWindowInnerWidth () { | ||
56 | this.lastFunctionCallTime = new Date().getTime() | ||
57 | |||
58 | this.windowInnerWidth = window.innerWidth | ||
59 | } | ||
60 | |||
61 | private cacheWindowInnerWidthExpired () { | ||
62 | if (!this.lastFunctionCallTime) return true | ||
63 | |||
64 | return new Date().getTime() > (this.lastFunctionCallTime + this.cacheForMs) | ||
65 | } | ||
66 | } | ||
diff --git a/client/src/app/shared/misc/small-loader.component.html b/client/src/app/shared/misc/small-loader.component.html deleted file mode 100644 index 7886f8918..000000000 --- a/client/src/app/shared/misc/small-loader.component.html +++ /dev/null | |||
@@ -1,3 +0,0 @@ | |||
1 | <div class="root" *ngIf="loading"> | ||
2 | <div class="glyphicon glyphicon-refresh glyphicon-refresh-animate"></div> | ||
3 | </div> | ||
diff --git a/client/src/app/shared/misc/small-loader.component.ts b/client/src/app/shared/misc/small-loader.component.ts deleted file mode 100644 index 191877f14..000000000 --- a/client/src/app/shared/misc/small-loader.component.ts +++ /dev/null | |||
@@ -1,11 +0,0 @@ | |||
1 | import { Component, Input } from '@angular/core' | ||
2 | |||
3 | @Component({ | ||
4 | selector: 'my-small-loader', | ||
5 | styleUrls: [ ], | ||
6 | templateUrl: './small-loader.component.html' | ||
7 | }) | ||
8 | |||
9 | export class SmallLoaderComponent { | ||
10 | @Input() loading: boolean | ||
11 | } | ||
diff --git a/client/src/app/shared/misc/storage.service.ts b/client/src/app/shared/misc/storage.service.ts deleted file mode 100644 index 0d4a8ab53..000000000 --- a/client/src/app/shared/misc/storage.service.ts +++ /dev/null | |||
@@ -1,40 +0,0 @@ | |||
1 | import { Injectable } from '@angular/core' | ||
2 | import { Observable, Subject } from 'rxjs' | ||
3 | import { | ||
4 | peertubeLocalStorage, | ||
5 | peertubeSessionStorage | ||
6 | } from './peertube-web-storage' | ||
7 | import { filter } from 'rxjs/operators' | ||
8 | |||
9 | abstract class StorageService { | ||
10 | protected instance: Storage | ||
11 | static storageSub = new Subject<string>() | ||
12 | |||
13 | watch (keys?: string[]): Observable<string> { | ||
14 | return StorageService.storageSub.asObservable().pipe(filter(val => keys ? keys.includes(val) : true)) | ||
15 | } | ||
16 | |||
17 | getItem (key: string) { | ||
18 | return this.instance.getItem(key) | ||
19 | } | ||
20 | |||
21 | setItem (key: string, data: any, notifyOfUpdate = true) { | ||
22 | this.instance.setItem(key, data) | ||
23 | if (notifyOfUpdate) StorageService.storageSub.next(key) | ||
24 | } | ||
25 | |||
26 | removeItem (key: string, notifyOfUpdate = true) { | ||
27 | this.instance.removeItem(key) | ||
28 | if (notifyOfUpdate) StorageService.storageSub.next(key) | ||
29 | } | ||
30 | } | ||
31 | |||
32 | @Injectable() | ||
33 | export class LocalStorageService extends StorageService { | ||
34 | protected instance: Storage = peertubeLocalStorage | ||
35 | } | ||
36 | |||
37 | @Injectable() | ||
38 | export class SessionStorageService extends StorageService { | ||
39 | protected instance: Storage = peertubeSessionStorage | ||
40 | } | ||
diff --git a/client/src/app/shared/misc/utils.ts b/client/src/app/shared/misc/utils.ts deleted file mode 100644 index bc3ab85b3..000000000 --- a/client/src/app/shared/misc/utils.ts +++ /dev/null | |||
@@ -1,210 +0,0 @@ | |||
1 | import { DatePipe } from '@angular/common' | ||
2 | import { environment } from '../../../environments/environment' | ||
3 | import { AuthService } from '../../core/auth' | ||
4 | |||
5 | // Thanks: https://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript | ||
6 | function getParameterByName (name: string, url: string) { | ||
7 | if (!url) url = window.location.href | ||
8 | name = name.replace(/[\[\]]/g, '\\$&') | ||
9 | |||
10 | const regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)') | ||
11 | const results = regex.exec(url) | ||
12 | |||
13 | if (!results) return null | ||
14 | if (!results[2]) return '' | ||
15 | |||
16 | return decodeURIComponent(results[2].replace(/\+/g, ' ')) | ||
17 | } | ||
18 | |||
19 | function populateAsyncUserVideoChannels (authService: AuthService, channel: { id: number, label: string, support?: string }[]) { | ||
20 | return new Promise(res => { | ||
21 | authService.userInformationLoaded | ||
22 | .subscribe( | ||
23 | () => { | ||
24 | const user = authService.getUser() | ||
25 | if (!user) return | ||
26 | |||
27 | const videoChannels = user.videoChannels | ||
28 | if (Array.isArray(videoChannels) === false) return | ||
29 | |||
30 | videoChannels.forEach(c => channel.push({ id: c.id, label: c.displayName, support: c.support })) | ||
31 | |||
32 | return res() | ||
33 | } | ||
34 | ) | ||
35 | }) | ||
36 | } | ||
37 | |||
38 | function getAbsoluteAPIUrl () { | ||
39 | let absoluteAPIUrl = environment.apiUrl | ||
40 | if (!absoluteAPIUrl) { | ||
41 | // The API is on the same domain | ||
42 | absoluteAPIUrl = window.location.origin | ||
43 | } | ||
44 | |||
45 | return absoluteAPIUrl | ||
46 | } | ||
47 | |||
48 | const datePipe = new DatePipe('en') | ||
49 | function dateToHuman (date: string) { | ||
50 | return datePipe.transform(date, 'medium') | ||
51 | } | ||
52 | |||
53 | function durationToString (duration: number) { | ||
54 | const hours = Math.floor(duration / 3600) | ||
55 | const minutes = Math.floor((duration % 3600) / 60) | ||
56 | const seconds = duration % 60 | ||
57 | |||
58 | const minutesPadding = minutes >= 10 ? '' : '0' | ||
59 | const secondsPadding = seconds >= 10 ? '' : '0' | ||
60 | const displayedHours = hours > 0 ? hours.toString() + ':' : '' | ||
61 | |||
62 | return ( | ||
63 | displayedHours + minutesPadding + minutes.toString() + ':' + secondsPadding + seconds.toString() | ||
64 | ).replace(/^0/, '') | ||
65 | } | ||
66 | |||
67 | function immutableAssign <A, B> (target: A, source: B) { | ||
68 | return Object.assign({}, target, source) | ||
69 | } | ||
70 | |||
71 | function objectToUrlEncoded (obj: any) { | ||
72 | const str: string[] = [] | ||
73 | for (const key of Object.keys(obj)) { | ||
74 | str.push(encodeURIComponent(key) + '=' + encodeURIComponent(obj[key])) | ||
75 | } | ||
76 | |||
77 | return str.join('&') | ||
78 | } | ||
79 | |||
80 | // Thanks: https://gist.github.com/ghinda/8442a57f22099bdb2e34 | ||
81 | function objectToFormData (obj: any, form?: FormData, namespace?: string) { | ||
82 | const fd = form || new FormData() | ||
83 | let formKey | ||
84 | |||
85 | for (const key of Object.keys(obj)) { | ||
86 | if (namespace) formKey = `${namespace}[${key}]` | ||
87 | else formKey = key | ||
88 | |||
89 | if (obj[key] === undefined) continue | ||
90 | |||
91 | if (Array.isArray(obj[key]) && obj[key].length === 0) { | ||
92 | fd.append(key, null) | ||
93 | continue | ||
94 | } | ||
95 | |||
96 | if (obj[key] !== null && typeof obj[ key ] === 'object' && !(obj[ key ] instanceof File)) { | ||
97 | objectToFormData(obj[ key ], fd, formKey) | ||
98 | } else { | ||
99 | fd.append(formKey, obj[ key ]) | ||
100 | } | ||
101 | } | ||
102 | |||
103 | return fd | ||
104 | } | ||
105 | |||
106 | function objectLineFeedToHtml (obj: any, keyToNormalize: string) { | ||
107 | return immutableAssign(obj, { | ||
108 | [keyToNormalize]: lineFeedToHtml(obj[keyToNormalize]) | ||
109 | }) | ||
110 | } | ||
111 | |||
112 | function lineFeedToHtml (text: string) { | ||
113 | if (!text) return text | ||
114 | |||
115 | return text.replace(/\r?\n|\r/g, '<br />') | ||
116 | } | ||
117 | |||
118 | function removeElementFromArray <T> (arr: T[], elem: T) { | ||
119 | const index = arr.indexOf(elem) | ||
120 | if (index !== -1) arr.splice(index, 1) | ||
121 | } | ||
122 | |||
123 | function sortBy (obj: any[], key1: string, key2?: string) { | ||
124 | return obj.sort((a, b) => { | ||
125 | const elem1 = key2 ? a[key1][key2] : a[key1] | ||
126 | const elem2 = key2 ? b[key1][key2] : b[key1] | ||
127 | |||
128 | if (elem1 < elem2) return -1 | ||
129 | if (elem1 === elem2) return 0 | ||
130 | return 1 | ||
131 | }) | ||
132 | } | ||
133 | |||
134 | function scrollToTop () { | ||
135 | window.scroll(0, 0) | ||
136 | } | ||
137 | |||
138 | // Thanks: https://github.com/uupaa/dynamic-import-polyfill | ||
139 | function importModule (path: string) { | ||
140 | return new Promise((resolve, reject) => { | ||
141 | const vector = '$importModule$' + Math.random().toString(32).slice(2) | ||
142 | const script = document.createElement('script') | ||
143 | |||
144 | const destructor = () => { | ||
145 | delete window[ vector ] | ||
146 | script.onerror = null | ||
147 | script.onload = null | ||
148 | script.remove() | ||
149 | URL.revokeObjectURL(script.src) | ||
150 | script.src = '' | ||
151 | } | ||
152 | |||
153 | script.defer = true | ||
154 | script.type = 'module' | ||
155 | |||
156 | script.onerror = () => { | ||
157 | reject(new Error(`Failed to import: ${path}`)) | ||
158 | destructor() | ||
159 | } | ||
160 | script.onload = () => { | ||
161 | resolve(window[ vector ]) | ||
162 | destructor() | ||
163 | } | ||
164 | const absURL = (environment.apiUrl || window.location.origin) + path | ||
165 | const loader = `import * as m from "${absURL}"; window.${vector} = m;` // export Module | ||
166 | const blob = new Blob([ loader ], { type: 'text/javascript' }) | ||
167 | script.src = URL.createObjectURL(blob) | ||
168 | |||
169 | document.head.appendChild(script) | ||
170 | }) | ||
171 | } | ||
172 | |||
173 | function isInViewport (el: HTMLElement) { | ||
174 | const bounding = el.getBoundingClientRect() | ||
175 | return ( | ||
176 | bounding.top >= 0 && | ||
177 | bounding.left >= 0 && | ||
178 | bounding.bottom <= (window.innerHeight || document.documentElement.clientHeight) && | ||
179 | bounding.right <= (window.innerWidth || document.documentElement.clientWidth) | ||
180 | ) | ||
181 | } | ||
182 | |||
183 | function isXPercentInViewport (el: HTMLElement, percentVisible: number) { | ||
184 | const rect = el.getBoundingClientRect() | ||
185 | const windowHeight = (window.innerHeight || document.documentElement.clientHeight) | ||
186 | |||
187 | return !( | ||
188 | Math.floor(100 - (((rect.top >= 0 ? 0 : rect.top) / +-(rect.height / 1)) * 100)) < percentVisible || | ||
189 | Math.floor(100 - ((rect.bottom - windowHeight) / rect.height) * 100) < percentVisible | ||
190 | ) | ||
191 | } | ||
192 | |||
193 | export { | ||
194 | sortBy, | ||
195 | durationToString, | ||
196 | lineFeedToHtml, | ||
197 | objectToUrlEncoded, | ||
198 | getParameterByName, | ||
199 | populateAsyncUserVideoChannels, | ||
200 | getAbsoluteAPIUrl, | ||
201 | dateToHuman, | ||
202 | immutableAssign, | ||
203 | objectToFormData, | ||
204 | objectLineFeedToHtml, | ||
205 | removeElementFromArray, | ||
206 | importModule, | ||
207 | scrollToTop, | ||
208 | isInViewport, | ||
209 | isXPercentInViewport | ||
210 | } | ||