aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app/shared/misc
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2020-06-23 14:10:17 +0200
committerChocobozzz <chocobozzz@cpy.re>2020-06-23 16:00:49 +0200
commit67ed6552b831df66713bac9e672738796128d33f (patch)
tree59c97d41e0b49d75a90aa3de987968ab9b1ff447 /client/src/app/shared/misc
parent0c4bacbff53bc732f5a2677d62a6ead7752e2405 (diff)
downloadPeerTube-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.ts1
-rw-r--r--client/src/app/shared/misc/help.component.html40
-rw-r--r--client/src/app/shared/misc/help.component.scss42
-rw-r--r--client/src/app/shared/misc/help.component.ts94
-rw-r--r--client/src/app/shared/misc/list-overflow.component.html35
-rw-r--r--client/src/app/shared/misc/list-overflow.component.scss61
-rw-r--r--client/src/app/shared/misc/list-overflow.component.ts120
-rw-r--r--client/src/app/shared/misc/loader.component.html8
-rw-r--r--client/src/app/shared/misc/loader.component.scss45
-rw-r--r--client/src/app/shared/misc/loader.component.ts10
-rw-r--r--client/src/app/shared/misc/peertube-web-storage.ts81
-rw-r--r--client/src/app/shared/misc/screen.service.ts66
-rw-r--r--client/src/app/shared/misc/small-loader.component.html3
-rw-r--r--client/src/app/shared/misc/small-loader.component.ts11
-rw-r--r--client/src/app/shared/misc/storage.service.ts40
-rw-r--r--client/src/app/shared/misc/utils.ts210
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 @@
1export 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 @@
1import { AfterContentInit, Component, ContentChildren, Input, OnChanges, OnInit, QueryList, TemplateRef } from '@angular/core'
2import { I18n } from '@ngx-translate/i18n-polyfill'
3import { MarkdownService } from '@app/shared/renderer'
4import { 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
12export 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
16button {
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 @@
1import {
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'
14import { NgbDropdown, NgbModal } from '@ng-bootstrap/ng-bootstrap'
15import { lowerFirst, uniqueId } from 'lodash-es'
16import { ScreenService } from './screen.service'
17import { take } from 'rxjs/operators'
18
19export 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})
30export 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 @@
1import { Component, Input } from '@angular/core'
2
3@Component({
4 selector: 'my-loader',
5 styleUrls: [ './loader.component.scss' ],
6 templateUrl: './loader.component.html'
7})
8export 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
3const valuesMap = new Map()
4
5function 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
26class 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
65let peertubeLocalStorage: Storage
66let peertubeSessionStorage: Storage
67try {
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
78export {
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 @@
1import { Injectable } from '@angular/core'
2
3@Injectable()
4export 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 @@
1import { Component, Input } from '@angular/core'
2
3@Component({
4 selector: 'my-small-loader',
5 styleUrls: [ ],
6 templateUrl: './small-loader.component.html'
7})
8
9export 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 @@
1import { Injectable } from '@angular/core'
2import { Observable, Subject } from 'rxjs'
3import {
4 peertubeLocalStorage,
5 peertubeSessionStorage
6} from './peertube-web-storage'
7import { filter } from 'rxjs/operators'
8
9abstract 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()
33export class LocalStorageService extends StorageService {
34 protected instance: Storage = peertubeLocalStorage
35}
36
37@Injectable()
38export 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 @@
1import { DatePipe } from '@angular/common'
2import { environment } from '../../../environments/environment'
3import { AuthService } from '../../core/auth'
4
5// Thanks: https://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript
6function 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
19function 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
38function 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
48const datePipe = new DatePipe('en')
49function dateToHuman (date: string) {
50 return datePipe.transform(date, 'medium')
51}
52
53function 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
67function immutableAssign <A, B> (target: A, source: B) {
68 return Object.assign({}, target, source)
69}
70
71function 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
81function 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
106function objectLineFeedToHtml (obj: any, keyToNormalize: string) {
107 return immutableAssign(obj, {
108 [keyToNormalize]: lineFeedToHtml(obj[keyToNormalize])
109 })
110}
111
112function lineFeedToHtml (text: string) {
113 if (!text) return text
114
115 return text.replace(/\r?\n|\r/g, '<br />')
116}
117
118function removeElementFromArray <T> (arr: T[], elem: T) {
119 const index = arr.indexOf(elem)
120 if (index !== -1) arr.splice(index, 1)
121}
122
123function 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
134function scrollToTop () {
135 window.scroll(0, 0)
136}
137
138// Thanks: https://github.com/uupaa/dynamic-import-polyfill
139function 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
173function 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
183function 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
193export {
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}