Added mobile friendly UI changes

This commit is contained in:
Vijay 2024-01-28 11:54:12 +05:30
parent 0da058987c
commit 03466de9dc
3 changed files with 310 additions and 169 deletions

View file

@ -16,16 +16,13 @@
<div class="navbar-nav ms-auto">
<div class="nav-item dropdown">
<button class="btn btn-link nav-link py-2 px-0 px-sm-2 dropdown-toggle d-flex align-items-center"
id="theme-select"
type="button"
aria-expanded="false"
data-bs-toggle="dropdown"
data-bs-display="static">
id="theme-select" type="button" aria-expanded="false" data-bs-toggle="dropdown" data-bs-display="static">
<fa-icon [icon]="activeTheme.icon"></fa-icon>
</button>
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="theme-select">
<li *ngFor="let theme of themes">
<button type="button" class="dropdown-item d-flex align-items-center" [ngClass]="{'active' : activeTheme == theme}" (click)="themeChanged(theme)">
<button type="button" class="dropdown-item d-flex align-items-center"
[ngClass]="{'active' : activeTheme == theme}" (click)="themeChanged(theme)">
<span class="me-2 opacity-50">
<fa-icon [icon]="theme.icon"></fa-icon>
</span>
@ -42,159 +39,277 @@
</nav>
<main role="main" class="container container-xl">
<form #f="ngForm">
<div class="container add-url-box">
<div class="row">
<div class="col add-url-component input-group">
<input type="text" autocomplete="off" spellcheck="false" class="form-control" placeholder="Video or playlist URL" name="addUrl" [(ngModel)]="addUrl" [disabled]="addInProgress || downloads.loading">
</div>
</div>
<div class="row">
<div class="col-md-3 add-url-component">
<div class="input-group">
<span class="input-group-text">Quality</span>
<select class="form-select" name="quality" [(ngModel)]="quality" (change)="qualityChanged()" [disabled]="addInProgress || downloads.loading">
<option *ngFor="let q of qualities" [ngValue]="q.id">{{ q.text }}</option>
</select>
<div class="section-wrapper">
<form #f="ngForm">
<div class="add-url-box">
<div class="row">
<div class="col component input-group">
<input type="text" autocomplete="off" spellcheck="false" class="form-control"
placeholder="Video or playlist URL" name="addUrl" [(ngModel)]="addUrl"
[disabled]="addInProgress || downloads.loading">
</div>
</div>
<div class="col-md-3 add-url-component">
<div class="input-group">
<span class="input-group-text">Format</span>
<select class="form-select" name="format" [(ngModel)]="format" (change)="formatChanged()" [disabled]="addInProgress || downloads.loading">
<option *ngFor="let f of formats" [ngValue]="f.id">{{ f.text }}</option>
</select>
<div class="row">
<div class="col-md-4 component">
<div class="input-group">
<span class="input-group-text">Quality</span>
<select class="form-select" name="quality" [(ngModel)]="quality" (change)="qualityChanged()"
[disabled]="addInProgress || downloads.loading">
<option *ngFor="let q of qualities" [ngValue]="q.id">{{ q.text }}</option>
</select>
</div>
</div>
</div>
<div class="col-md-3 add-url-component">
<div class="input-group">
<span class="input-group-text">Auto Start</span>
<select class="form-select" name="autoStart" [(ngModel)]="autoStart" (change)="autoStartChanged()" [disabled]="addInProgress || downloads.loading">
<option [ngValue]="true">Yes</option>
<option [ngValue]="false">No</option>
</select>
<div class="col-md-4 component">
<div class="input-group">
<span class="input-group-text">Format</span>
<select class="form-select" name="format" [(ngModel)]="format" (change)="formatChanged()"
[disabled]="addInProgress || downloads.loading">
<option *ngFor="let f of formats" [ngValue]="f.id">{{ f.text }}</option>
</select>
</div>
</div>
</div>
<div class="col-md-3 add-url-component">
<div [attr.class]="showAdvanced() ? 'btn-group add-url-group' : 'add-url-group'" ngbDropdown #advancedDropdown="ngbDropdown" display="dynamic" placement="bottom-end">
<button class="btn btn-primary add-url" type="submit" (click)="addDownload()" [disabled]="addInProgress || downloads.loading">
<span class="spinner-border spinner-border-sm" role="status" id="add-spinner" *ngIf="addInProgress"></span>
{{ addInProgress ? "Adding..." : "Add" }}
</button>
<button class="btn btn-primary dropdown-toggle dropdown-toggle-split" id="advancedButton" type="button" title="Advanced options" [disabled]="addInProgress || downloads.loading" ngbDropdownAnchor (click)="advancedDropdown.open()" *ngIf="showAdvanced()">
<span class="sr-only">Advanced options</span>
</button>
<div ngbDropdownMenu aria-labelledby="advancedButton" class="dropdown-menu dropdown-menu-end folder-dropdown-menu">
<div class="container">
<div class="input-group add-url-component">
<span class="input-group-text">Download Folder</span>
<ng-select [items]="customDirs$ | async" placeholder="Default" [addTag]="allowCustomDir.bind(this)" addTagText="Create directory" [ngStyle]="{'flex-grow':'1'}" bindLabel="folder" [(ngModel)]="folder" [disabled]="addInProgress || downloads.loading"></ng-select>
</div>
<div class="input-group add-url-component">
<span class="input-group-text">Custom Name Prefix</span>
<input type="text" autocomplete="off" spellcheck="false" class="form-control" placeholder="Default" name="customNamePrefix" [(ngModel)]="customNamePrefix" [disabled]="addInProgress || downloads.loading">
<div class="col-md-4 component">
<div class="input-group">
<span class="input-group-text">Auto Start</span>
<select class="form-select" name="autoStart" [(ngModel)]="autoStart" (change)="autoStartChanged()"
[disabled]="addInProgress || downloads.loading">
<option [ngValue]="true">Yes</option>
<option [ngValue]="false">No</option>
</select>
</div>
</div>
<div class="component">
<div class="accordion" id="advanedOptionsAccordion">
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
data-bs-target="#collapseAdvancedOptions" aria-expanded="false"
aria-controls="collapseAdvancedOptions">
Advanced Options
</button>
</h2>
<div id="collapseAdvancedOptions" class="accordion-collapse collapse"
data-bs-parent="#advanedOptionsAccordion">
<div class="accordion-body">
<div class="row">
<div class="col-lg-12">
<div class="component folder-dropdown">
<label for="downloadFolder" class="form-label">Download Folder</label>
<ng-select [items]="customDirs$ | async" placeholder="Default" name="downloadFolder"
[addTag]="allowCustomDir.bind(this)" addTagText="Create directory"
[ngStyle]="{'flex-grow':'1'}" bindLabel="folder" [(ngModel)]="folder"
[disabled]="addInProgress || downloads.loading"></ng-select>
</div>
</div>
<div class="col-lg-12">
<div class="component custom-name-prefix">
<label for="customNamePrefix" class="form-label">Custom Name Prefix</label>
<input type="text" autocomplete="off" spellcheck="false" class="form-control"
placeholder="Default" name="customNamePrefix" [(ngModel)]="customNamePrefix"
[disabled]="addInProgress || downloads.loading">
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="button-component">
<button class="btn btn-primary add-url" type="submit" (click)="addDownload()"
[disabled]="addInProgress || downloads.loading">
<span class="spinner-border spinner-border-sm" role="status" id="add-spinner"
*ngIf="addInProgress"></span>
{{ addInProgress ? "Adding..." : "Add" }}
</button>
</div>
<!-- <div class="col-md-3 component">
<div [attr.class]="showAdvanced() ? 'btn-group add-url-group' : 'add-url-group'" ngbDropdown
#advancedDropdown="ngbDropdown" display="dynamic" placement="bottom-end">
<button class="btn btn-primary add-url" type="submit" (click)="addDownload()"
[disabled]="addInProgress || downloads.loading">
<span class="spinner-border spinner-border-sm" role="status" id="add-spinner"
*ngIf="addInProgress"></span>
{{ addInProgress ? "Adding..." : "Add" }}
</button>
<div ngbDropdownMenu aria-labelledby="advancedButton"
class="dropdown-menu dropdown-menu-end folder-dropdown-menu">
<div class="container">
</div>
</div>
</div>
</div> -->
</div>
</div>
</form>
<div *ngIf="downloads.loading" class="alert alert-info" role="alert">
Connecting to server...
</div>
</form>
<div *ngIf="downloads.loading" class="alert alert-info" role="alert">
Connecting to server...
<div class="downloads-section">
<h2>Downloading</h2>
<hr />
<div class="flex-space-between">
<button type="button" class="btn btn-link text-decoration-none px-0 me-4" disabled #queueDelSelected
(click)="delSelectedDownloads('queue')"><fa-icon [icon]="faTrashAlt"></fa-icon>&nbsp; Cancel
selected</button>
</div>
<div class="flex" *ngIf="downloads.queue.size > 0">
<span>
<app-master-checkbox #queueMasterCheckbox [id]="'queue'" [list]="downloads.queue"
(changed)="queueSelectionChanged($event)"></app-master-checkbox>
</span>
<span>
Select All
</span>
</div>
<div class="table-resposive">
<table class="table">
<tbody>
<tr *ngFor="let download of downloads.queue | keyvalue: asIsOrder; trackBy: identifyDownloadRow"
[class.disabled]='download.value.deleting'>
<td>
<app-slave-checkbox [id]="download.key" [master]="queueMasterCheckbox"
[checkable]="download.value"></app-slave-checkbox>
</td>
<td>
<div class="table-detail-row">
<div title="{{ download.value.filename }}" class="table-detail-row">
{{ download.value.title }}
</div>
</div>
<div class="table-detail-row">
<div class="progress-bar-wrapper">
<ngb-progressbar height="1.5rem" [showValue]="download.value.status != 'preparing'"
[striped]="download.value.status == 'preparing'" [animated]="download.value.status == 'preparing'"
type="success"
[value]="download.value.status == 'preparing' ? 100 : download.value.percent | number:'1.0-0'"></ngb-progressbar>
</div>
</div>
<div class="table-detail-row">
{{ download.value.speed | speed }}
</div>
<div class="table-detail-row">
{{ download.value.eta | eta }}
</div>
<div class="table-detail-row">
<span>
<button *ngIf="download.value.status === 'pending'" type="button" class="btn btn-link"
(click)="downloadItemByKey(download.key)"><fa-icon [icon]="faDownload"></fa-icon></button>
</span>
<span>
<button type="button" class="btn btn-link" (click)="delDownload('queue', download.key)"><fa-icon
[icon]="faTrashAlt"></fa-icon></button>
</span>
<span>
<button class="btn bt-link">
<a href="{{download.value.url}}" target="_blank"><fa-icon
[icon]="faExternalLinkAlt"></fa-icon></a>
</button>
</span>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="downloads-section">
<h2>Completed</h2>
<hr />
<div class="flex-space-between">
<button type="button" class="btn btn-link text-decoration-none px-0 me-4" disabled #doneDelSelected
(click)="delSelectedDownloads('done')"><fa-icon [icon]="faTrashAlt"></fa-icon>&nbsp; Clear
selected</button>
<button type="button" class="btn btn-link text-decoration-none px-0 me-4" disabled #doneClearCompleted
(click)="clearCompletedDownloads()"><fa-icon [icon]="faCheckCircle"></fa-icon>&nbsp; Clear
completed</button>
<button type="button" class="btn btn-link text-decoration-none px-0 me-4" disabled #doneClearFailed
(click)="clearFailedDownloads()"><fa-icon [icon]="faTimesCircle"></fa-icon>&nbsp; Clear failed</button>
<button type="button" class="btn btn-link text-decoration-none px-0 me-4" disabled #doneRetryFailed
(click)="retryFailedDownloads()"><fa-icon [icon]="faRedoAlt"></fa-icon>&nbsp; Retry failed</button>
</div>
<div class="flex" *ngIf="downloads.done.size > 0">
<span>
<app-master-checkbox #doneMasterCheckbox [id]="'done'" [list]="downloads.done"
(changed)="doneSelectionChanged($event)"></app-master-checkbox>
</span>
<span>
Select All
</span>
</div>
<div class="table-resposive">
<table class="table">
<tbody>
<tr *ngFor="let download of downloads.done | keyvalue: asIsOrder; trackBy: identifyDownloadRow"
[class.disabled]='download.value.deleting'>
<td>
<app-slave-checkbox [id]="download.key" [master]="doneMasterCheckbox"
[checkable]="download.value"></app-slave-checkbox>
</td>
<td>
<div class="table-detail-row">
<span class="status-icon">
<fa-icon *ngIf="download.value.status == 'finished'" [icon]="faCheckCircle"
class="text-success"></fa-icon>
<fa-icon *ngIf="download.value.status == 'error'" [icon]="faTimesCircle"
class="text-danger"></fa-icon>
</span>
<span ngbTooltip="{{download.value.msg}} | {{download.value.error}}" class="table-detail-row">
<a *ngIf="!!download.value.filename; else noDownloadLink"
href="{{buildDownloadLink(download.value)}}" target="_blank">{{ download.value.title }}</a>
</span>
<ng-template #noDownloadLink>
<span class="table-detail-row" *ngIf="download.value.title">
{{download.value.title}}
</span>
<div class="table-detail-row italic-message" *ngIf="download.value.msg">
{{download.value.msg}}
</div>
<div class="table-detail-row italic-message" *ngIf="download.value.error">
Error: {{download.value.error}}
</div>
</ng-template>
</div>
<div *ngIf="download.value.size" class="table-detail-row">
{{ download.value.size | fileSize }}
</div>
<div class="table-detail-row">
<span>
<button *ngIf="download.value.status == 'error'" type="button" class="btn btn-link"
(click)="retryDownload(download.key, download.value)"><fa-icon
[icon]="faRedoAlt"></fa-icon></button>
</span>
<span>
<button class="btn btn-link" *ngIf="download.value.filename">
<a href="{{buildDownloadLink(download.value)}}" download><fa-icon
[icon]="faDownload"></fa-icon></a>
</button>
</span>
<span>
<button class="btn btn-link">
<a href="{{download.value.url}}" target="_blank"><fa-icon
[icon]="faExternalLinkAlt"></fa-icon></a>
</button>
</span>
<span>
<button type="button" class="btn btn-link" (click)="delDownload('done', download.key)"><fa-icon
[icon]="faTrashAlt"></fa-icon></button>
</span>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="metube-section-header">Downloading</div>
<table class="table">
<thead>
<tr>
<th scope="col" style="width: 1rem;">
<app-master-checkbox #queueMasterCheckbox [id]="'queue'" [list]="downloads.queue" (changed)="queueSelectionChanged($event)"></app-master-checkbox>
</th>
<th scope="col">
<button type="button" class="btn btn-link text-decoration-none px-0 me-4" disabled #queueDelSelected (click)="delSelectedDownloads('queue')"><fa-icon [icon]="faTrashAlt"></fa-icon>&nbsp; Cancel selected</button>
</th>
<th scope="col" style="width: 14rem;"></th>
<th scope="col" style="width: 8rem;">Speed</th>
<th scope="col" style="width: 7rem;">ETA</th>
<th scope="col" style="width: 2rem;"></th>
<th scope="col" style="width: 2rem;"></th>
<th scope="col" style="width: 2rem;"></th>
</tr>
</thead>
<tbody>
<tr *ngFor="let download of downloads.queue | keyvalue: asIsOrder; trackBy: identifyDownloadRow" [class.disabled]='download.value.deleting'>
<td>
<app-slave-checkbox [id]="download.key" [master]="queueMasterCheckbox" [checkable]="download.value"></app-slave-checkbox>
</td>
<td title="{{ download.value.filename }}">{{ download.value.title }}</td>
<td><ngb-progressbar height="1.5rem" [showValue]="download.value.status != 'preparing'" [striped]="download.value.status == 'preparing'" [animated]="download.value.status == 'preparing'" type="success" [value]="download.value.status == 'preparing' ? 100 : download.value.percent | number:'1.0-0'"></ngb-progressbar></td>
<td>{{ download.value.speed | speed }}</td>
<td>{{ download.value.eta | eta }}</td>
<td>
<button *ngIf="download.value.status === 'pending'" type="button" class="btn btn-link" (click)="downloadItemByKey(download.key)"><fa-icon [icon]="faDownload"></fa-icon></button>
</td>
<td><button type="button" class="btn btn-link" (click)="delDownload('queue', download.key)"><fa-icon [icon]="faTrashAlt"></fa-icon></button></td>
<td><a href="{{download.value.url}}" target="_blank"><fa-icon [icon]="faExternalLinkAlt"></fa-icon></a></td>
</tr>
</tbody>
</table>
<div class="metube-section-header">Completed</div>
<table class="table">
<thead>
<tr>
<th scope="col" style="width: 1rem;">
<app-master-checkbox #doneMasterCheckbox [id]="'done'" [list]="downloads.done" (changed)="doneSelectionChanged($event)"></app-master-checkbox>
</th>
<th scope="col">
<button type="button" class="btn btn-link text-decoration-none px-0 me-4" disabled #doneDelSelected (click)="delSelectedDownloads('done')"><fa-icon [icon]="faTrashAlt"></fa-icon>&nbsp; Clear selected</button>
<button type="button" class="btn btn-link text-decoration-none px-0 me-4" disabled #doneClearCompleted (click)="clearCompletedDownloads()"><fa-icon [icon]="faCheckCircle"></fa-icon>&nbsp; Clear completed</button>
<button type="button" class="btn btn-link text-decoration-none px-0 me-4" disabled #doneClearFailed (click)="clearFailedDownloads()"><fa-icon [icon]="faTimesCircle"></fa-icon>&nbsp; Clear failed</button>
<button type="button" class="btn btn-link text-decoration-none px-0 me-4" disabled #doneRetryFailed (click)="retryFailedDownloads()"><fa-icon [icon]="faRedoAlt"></fa-icon>&nbsp; Retry failed</button>
</th>
<th scope="col" >File Size</th>
<th scope="col" style="width: 2rem;"></th>
<th scope="col" style="width: 2rem;"></th>
<th scope="col" style="width: 2rem;"></th>
<th scope="col" style="width: 2rem;"></th>
</tr>
</thead>
<tbody>
<tr *ngFor="let download of downloads.done | keyvalue: asIsOrder; trackBy: identifyDownloadRow" [class.disabled]='download.value.deleting'>
<td>
<app-slave-checkbox [id]="download.key" [master]="doneMasterCheckbox" [checkable]="download.value"></app-slave-checkbox>
</td>
<td>
<div style="display: inline-block; width: 1.5rem;">
<fa-icon *ngIf="download.value.status == 'finished'" [icon]="faCheckCircle" class="text-success"></fa-icon>
<fa-icon *ngIf="download.value.status == 'error'" [icon]="faTimesCircle" class="text-danger"></fa-icon>
</div>
<span ngbTooltip="{{download.value.msg}} | {{download.value.error}}"><a *ngIf="!!download.value.filename; else noDownloadLink" href="{{buildDownloadLink(download.value)}}" target="_blank">{{ download.value.title }}</a></span>
<ng-template #noDownloadLink>
{{download.value.title}}
<span *ngIf="download.value.msg"><br>{{download.value.msg}}</span>
<span *ngIf="download.value.error"><br>Error: {{download.value.error}}</span>
</ng-template>
</td>
<td>
<span *ngIf="download.value.size">{{ download.value.size | fileSize }}</span>
</td>
<td>
<button *ngIf="download.value.status == 'error'" type="button" class="btn btn-link" (click)="retryDownload(download.key, download.value)"><fa-icon [icon]="faRedoAlt"></fa-icon></button>
</td>
<td>
<a *ngIf="download.value.filename" href="{{buildDownloadLink(download.value)}}" download><fa-icon [icon]="faDownload"></fa-icon></a>
</td>
<td>
<a href="{{download.value.url}}" target="_blank"><fa-icon [icon]="faExternalLinkAlt"></fa-icon></a>
</td>
<td>
<button type="button" class="btn btn-link" (click)="delDownload('done', download.key)"><fa-icon [icon]="faTrashAlt"></fa-icon></button>
</td>
</tr>
</tbody>
</table>
</main><!-- /.container -->
</main>

View file

@ -3,25 +3,32 @@
outline: 0px
.add-url-box
max-width: 960px
margin: 4rem auto
margin: 2rem auto
.add-url-component
margin: 0.5rem auto
.section-wrapper
width: min(90%, 1080px)
margin: auto
.component
margin: 1rem 0rem
.button-component
margin: 2rem 0rem
display: flex
justify-content: center
.add-url-group
width: 100%
button.add-url
width: 100%
.add-url
width: min(80%, 280px)
line-height: 2rem
.folder-dropdown-menu
width: 500px
.folder-dropdown
width: min(100%, 1080px)
.folder-dropdown-menu .input-group
display: flex
padding-left: 5px
padding-right: 5px
.custom-name-prefix
width: min(90%, 480px)
.metube-section-header
font-size: 1.8rem
@ -44,14 +51,33 @@ button.add-url
button:hover
text-decoration: none
th
border-top: 0
border-bottom-width: 3px !important
vertical-align: middle !important
td
vertical-align: middle
.disabled
opacity: 0.5
pointer-events: none
.table-detail-row
padding: 0.25rem 0rem
.flex-space-between
display: flex
justify-content: space-between
align-items: center
.flex
display: flex
.downloads-section
margin: 2rem auto
.progress-bar-wrapper
width: min(90%, 680px)
.status-icon
width: 1.5rem
.italic-message
font-style: italic
color: gray
.accordion-button:not(.collapsed)
background-color: #ffffff

View file

@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<title>MeTube</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<link rel="apple-touch-icon" sizes="180x180" href="assets/icons/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="assets/icons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="assets/icons/favicon-16x16.png">