mirror of https://github.com/Chocobozzz/PeerTube
search filtering improvements per #1654
parent
fee47735bd
commit
2526690866
|
@ -65,7 +65,7 @@ export class AdvancedSearch {
|
|||
for (const k of Object.keys(obj)) {
|
||||
if (k === 'sort') continue // Exception
|
||||
|
||||
if (obj[k] !== undefined) return true
|
||||
if (obj[k] !== undefined && obj[k] !== '') return true
|
||||
}
|
||||
|
||||
return false
|
||||
|
@ -131,7 +131,7 @@ export class AdvancedSearch {
|
|||
for (const k of Object.keys(obj)) {
|
||||
if (k === 'sort') continue // Exception
|
||||
|
||||
if (obj[k] !== undefined) acc++
|
||||
if (obj[k] !== undefined && obj[k] !== '') acc++
|
||||
}
|
||||
|
||||
return acc
|
||||
|
|
|
@ -3,7 +3,12 @@
|
|||
<div class="row">
|
||||
<div class="col-lg-4 col-md-6 col-xs-12">
|
||||
<div class="form-group">
|
||||
<div i18n class="radio-label">Sort</div>
|
||||
<div class="radio-label label-container">
|
||||
<label i18n>Sort</label>
|
||||
<button i18n class="reset-button reset-button-small" (click)="resetField('sort', '-match')" *ngIf="advancedSearch.sort !== '-match'">
|
||||
Reset
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="peertube-radio-container" *ngFor="let sort of sorts">
|
||||
<input type="radio" name="sort" [id]="sort.id" [value]="sort.id" [(ngModel)]="advancedSearch.sort">
|
||||
|
@ -12,20 +17,32 @@
|
|||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div i18n class="radio-label">Published date</div>
|
||||
<div class="radio-label label-container">
|
||||
<label i18n>Published date</label>
|
||||
<button i18n class="reset-button reset-button-small" (click)="resetLocalField('publishedDateRange')" *ngIf="publishedDateRange !== undefined">
|
||||
Reset
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="peertube-radio-container" *ngFor="let date of publishedDateRanges">
|
||||
<input type="radio" name="publishedDateRange" [id]="date.id" [value]="date.id" [(ngModel)]="publishedDateRange">
|
||||
<input type="radio" (change)="inputUpdated()" name="publishedDateRange" [id]="date.id" [value]="date.id" [(ngModel)]="publishedDateRange">
|
||||
<label [for]="date.id" class="radio">{{ date.label }}</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label i18n for="original-publication-after">Original publication year</label>
|
||||
<div class="label-container">
|
||||
<label i18n for="original-publication-after">Original publication year</label>
|
||||
<button i18n class="reset-button reset-button-small" (click)="resetOriginalPublicationYears()" *ngIf="originallyPublishedStartYear || originallyPublishedEndYear">
|
||||
Reset
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<input
|
||||
(change)="inputUpdated()"
|
||||
(keydown.enter)="$event.preventDefault()"
|
||||
type="text" id="original-publication-after" name="original-publication-after"
|
||||
i18n-placeholder placeholder="After..."
|
||||
[(ngModel)]="originallyPublishedStartYear"
|
||||
|
@ -33,6 +50,8 @@
|
|||
</div>
|
||||
<div class="col-sm-6">
|
||||
<input
|
||||
(change)="inputUpdated()"
|
||||
(keydown.enter)="$event.preventDefault()"
|
||||
type="text" id="original-publication-before" name="original-publication-before"
|
||||
i18n-placeholder placeholder="Before..."
|
||||
[(ngModel)]="originallyPublishedEndYear"
|
||||
|
@ -42,16 +61,26 @@
|
|||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div i18n class="radio-label">Duration</div>
|
||||
<div class="radio-label label-container">
|
||||
<label i18n>Duration</label>
|
||||
<button i18n class="reset-button reset-button-small" (click)="resetLocalField('durationRange')" *ngIf="durationRange !== undefined">
|
||||
Reset
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="peertube-radio-container" *ngFor="let duration of durationRanges">
|
||||
<input type="radio" name="durationRange" [id]="duration.id" [value]="duration.id" [(ngModel)]="durationRange">
|
||||
<input type="radio" (change)="inputUpdated()" name="durationRange" [id]="duration.id" [value]="duration.id" [(ngModel)]="durationRange">
|
||||
<label [for]="duration.id" class="radio">{{ duration.label }}</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div i18n class="radio-label">Display sensitive content</div>
|
||||
<div class="radio-label label-container">
|
||||
<label i18n>Display sensitive content</label>
|
||||
<button i18n class="reset-button reset-button-small" (click)="resetField('nsfw')" *ngIf="advancedSearch.nsfw !== undefined">
|
||||
Reset
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="peertube-radio-container">
|
||||
<input type="radio" name="sensitiveContent" id="sensitiveContentYes" value="both" [(ngModel)]="advancedSearch.nsfw">
|
||||
|
@ -69,9 +98,12 @@
|
|||
<div class="col-lg-4 col-md-6 col-xs-12">
|
||||
<div class="form-group">
|
||||
<label i18n for="category">Category</label>
|
||||
<button i18n class="reset-button reset-button-small" (click)="resetField('categoryOneOf')" *ngIf="advancedSearch.categoryOneOf !== undefined">
|
||||
Reset
|
||||
</button>
|
||||
<div class="peertube-select-container">
|
||||
<select id="category" name="category" [(ngModel)]="advancedSearch.categoryOneOf">
|
||||
<option></option>
|
||||
<option [value]="undefined" i18n>Any or no category set</option>
|
||||
<option *ngFor="let category of videoCategories" [value]="category.id">{{ category.label }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
@ -79,9 +111,12 @@
|
|||
|
||||
<div class="form-group">
|
||||
<label i18n for="licence">Licence</label>
|
||||
<button i18n class="reset-button reset-button-small" (click)="resetField('licenceOneOf')" *ngIf="advancedSearch.licenceOneOf !== undefined">
|
||||
Reset
|
||||
</button>
|
||||
<div class="peertube-select-container">
|
||||
<select id="licence" name="licence" [(ngModel)]="advancedSearch.licenceOneOf">
|
||||
<option></option>
|
||||
<option [value]="undefined" i18n>Any or no license set</option>
|
||||
<option *ngFor="let licence of videoLicences" [value]="licence.id">{{ licence.label }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
@ -89,9 +124,12 @@
|
|||
|
||||
<div class="form-group">
|
||||
<label i18n for="language">Language</label>
|
||||
<button i18n class="reset-button reset-button-small" (click)="resetField('languageOneOf')" *ngIf="advancedSearch.languageOneOf !== undefined">
|
||||
Reset
|
||||
</button>
|
||||
<div class="peertube-select-container">
|
||||
<select id="language" name="language" [(ngModel)]="advancedSearch.languageOneOf">
|
||||
<option></option>
|
||||
<option [value]="undefined" i18n>Any or no language set</option>
|
||||
<option *ngFor="let language of videoLanguages" [value]="language.id">{{ language.label }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
@ -101,17 +139,37 @@
|
|||
<div class="col-lg-4 col-md-6 col-xs-12">
|
||||
<div class="form-group">
|
||||
<label i18n for="tagsAllOf">All of these tags</label>
|
||||
<input type="text" name="tagsAllOf" id="tagsAllOf" [(ngModel)]="advancedSearch.tagsAllOf" />
|
||||
<button i18n class="reset-button reset-button-small" (click)="resetField('tagsAllOf')" *ngIf="advancedSearch.tagsAllOf">
|
||||
Reset
|
||||
</button>
|
||||
<tag-input
|
||||
[(ngModel)]="advancedSearch.tagsAllOf" name="tagsAllOf" id="tagsAllOf"
|
||||
[validators]="tagValidators" [errorMessages]="tagValidatorsMessages"
|
||||
i18n-placeholder placeholder="+ Tag" i18n-secondaryPlaceholder secondaryPlaceholder="Enter a tag"
|
||||
maxItems="5" modelAsStrings="true"
|
||||
></tag-input>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label i18n for="tagsOneOf">One of these tags</label>
|
||||
<input type="text" name="tagsOneOf" id="tagsOneOf" [(ngModel)]="advancedSearch.tagsOneOf" />
|
||||
<button i18n class="reset-button reset-button-small" (click)="resetField('tagsOneOf')" *ngIf="advancedSearch.tagsOneOf">
|
||||
Reset
|
||||
</button>
|
||||
<tag-input
|
||||
[(ngModel)]="advancedSearch.tagsOneOf" name="tagsOneOf" id="tagsOneOf"
|
||||
[validators]="tagValidators" [errorMessages]="tagValidatorsMessages"
|
||||
i18n-placeholder placeholder="+ Tag" i18n-secondaryPlaceholder secondaryPlaceholder="Enter a tag"
|
||||
maxItems="5" modelAsStrings="true"
|
||||
></tag-input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="submit-button">
|
||||
<button i18n class="reset-button" (click)="reset()" *ngIf="advancedSearch.size()">
|
||||
Reset
|
||||
</button>
|
||||
|
||||
<input type="submit" i18n-value value="Filter">
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -19,6 +19,8 @@ form {
|
|||
|
||||
.peertube-select-container {
|
||||
@include peertube-select-container(auto);
|
||||
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
|
@ -37,4 +39,92 @@ input[type=submit] {
|
|||
|
||||
.submit-button {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
.reset-button {
|
||||
@include peertube-button;
|
||||
|
||||
font-weight: $font-semibold;
|
||||
display: inline-block;
|
||||
padding: 0 10px 0 10px;
|
||||
white-space: nowrap;
|
||||
background: transparent;
|
||||
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.reset-button-small {
|
||||
font-size: 80%;
|
||||
height: unset;
|
||||
line-height: unset;
|
||||
margin: unset;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.label-container {
|
||||
display: flex;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
::ng-deep {
|
||||
.ng2-tag-input {
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.ng2-tags-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: 1px solid #C6C6C6;
|
||||
border-radius: 3px;
|
||||
padding: 5px !important;
|
||||
height: max-content;
|
||||
}
|
||||
|
||||
tag-input-form {
|
||||
input {
|
||||
height: 30px !important;
|
||||
font-size: 12px !important;
|
||||
|
||||
background-color: var(--mainBackgroundColor) !important;
|
||||
color: var(--mainForegroundColor) !important;
|
||||
}
|
||||
}
|
||||
|
||||
tag {
|
||||
background-color: $grey-background-color !important;
|
||||
color: #000 !important;
|
||||
border-radius: 3px !important;
|
||||
font-size: 12px !important;
|
||||
height: 30px !important;
|
||||
line-height: 30px !important;
|
||||
margin: 0 5px 0 0 !important;
|
||||
cursor: default !important;
|
||||
padding: 0 8px 0 10px !important;
|
||||
|
||||
div {
|
||||
height: 100% !important;
|
||||
}
|
||||
}
|
||||
|
||||
delete-icon {
|
||||
cursor: pointer !important;
|
||||
height: auto !important;
|
||||
vertical-align: middle !important;
|
||||
padding-left: 6px !important;
|
||||
|
||||
svg {
|
||||
position: relative;
|
||||
top: -1px;
|
||||
height: auto !important;
|
||||
vertical-align: middle !important;
|
||||
|
||||
path {
|
||||
fill: $grey-foreground-color !important;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transform: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'
|
||||
import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges } from '@angular/core'
|
||||
import { ValidatorFn } from '@angular/forms'
|
||||
import { VideoValidatorsService } from '@app/shared'
|
||||
import { ServerService } from '@app/core'
|
||||
import { I18n } from '@ngx-translate/i18n-polyfill'
|
||||
import { AdvancedSearch } from '@app/search/advanced-search.model'
|
||||
|
@ -18,6 +20,9 @@ export class SearchFiltersComponent implements OnInit {
|
|||
videoLicences: VideoConstant<number>[] = []
|
||||
videoLanguages: VideoConstant<string>[] = []
|
||||
|
||||
tagValidators: ValidatorFn[]
|
||||
tagValidatorsMessages: { [ name: string ]: string }
|
||||
|
||||
publishedDateRanges: { id: string, label: string }[] = []
|
||||
sorts: { id: string, label: string }[] = []
|
||||
durationRanges: { id: string, label: string }[] = []
|
||||
|
@ -30,9 +35,16 @@ export class SearchFiltersComponent implements OnInit {
|
|||
|
||||
constructor (
|
||||
private i18n: I18n,
|
||||
private videoValidatorsService: VideoValidatorsService,
|
||||
private serverService: ServerService
|
||||
) {
|
||||
this.tagValidators = this.videoValidatorsService.VIDEO_TAGS.VALIDATORS
|
||||
this.tagValidatorsMessages = this.videoValidatorsService.VIDEO_TAGS.MESSAGES
|
||||
this.publishedDateRanges = [
|
||||
{
|
||||
id: undefined,
|
||||
label: this.i18n('Any')
|
||||
},
|
||||
{
|
||||
id: 'today',
|
||||
label: this.i18n('Today')
|
||||
|
@ -52,6 +64,10 @@ export class SearchFiltersComponent implements OnInit {
|
|||
]
|
||||
|
||||
this.durationRanges = [
|
||||
{
|
||||
id: undefined,
|
||||
label: this.i18n('Any')
|
||||
},
|
||||
{
|
||||
id: 'short',
|
||||
label: this.i18n('Short (< 4 min)')
|
||||
|
@ -92,11 +108,14 @@ export class SearchFiltersComponent implements OnInit {
|
|||
this.loadOriginallyPublishedAtYears()
|
||||
}
|
||||
|
||||
formUpdated () {
|
||||
inputUpdated () {
|
||||
this.updateModelFromDurationRange()
|
||||
this.updateModelFromPublishedRange()
|
||||
this.updateModelFromOriginallyPublishedAtYears()
|
||||
}
|
||||
|
||||
formUpdated () {
|
||||
this.inputUpdated()
|
||||
this.filtered.emit(this.advancedSearch)
|
||||
}
|
||||
|
||||
|
@ -216,4 +235,26 @@ export class SearchFiltersComponent implements OnInit {
|
|||
this.advancedSearch.startDate = date.toISOString()
|
||||
}
|
||||
|
||||
private reset () {
|
||||
this.advancedSearch.reset()
|
||||
this.durationRange = undefined
|
||||
this.publishedDateRange = undefined
|
||||
this.originallyPublishedStartYear = undefined
|
||||
this.originallyPublishedEndYear = undefined
|
||||
this.inputUpdated()
|
||||
}
|
||||
|
||||
private resetField (fieldName: string, value?: any) {
|
||||
this.advancedSearch[fieldName] = value
|
||||
}
|
||||
|
||||
private resetLocalField (fieldName: string, value?: any) {
|
||||
this[fieldName] = value
|
||||
this.inputUpdated()
|
||||
}
|
||||
|
||||
private resetOriginalPublicationYears () {
|
||||
this.originallyPublishedStartYear = this.originallyPublishedEndYear = undefined
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { NgModule } from '@angular/core'
|
||||
import { TagInputModule } from 'ngx-chips'
|
||||
import { SharedModule } from '../shared'
|
||||
import { SearchComponent } from '@app/search/search.component'
|
||||
import { SearchService } from '@app/search/search.service'
|
||||
|
@ -7,6 +8,8 @@ import { SearchFiltersComponent } from '@app/search/search-filters.component'
|
|||
|
||||
@NgModule({
|
||||
imports: [
|
||||
TagInputModule,
|
||||
|
||||
SearchRoutingModule,
|
||||
SharedModule
|
||||
],
|
||||
|
@ -17,6 +20,7 @@ import { SearchFiltersComponent } from '@app/search/search-filters.component'
|
|||
],
|
||||
|
||||
exports: [
|
||||
TagInputModule,
|
||||
SearchComponent
|
||||
],
|
||||
|
||||
|
|
|
@ -150,12 +150,13 @@ p-calendar {
|
|||
border: 1px solid #C6C6C6;
|
||||
border-radius: 3px;
|
||||
padding: 5px !important;
|
||||
height: 40px;
|
||||
height: max-content;
|
||||
}
|
||||
|
||||
tag-input-form {
|
||||
input {
|
||||
height: 30px !important;
|
||||
font-size: 12px !important;
|
||||
|
||||
background-color: var(--mainBackgroundColor) !important;
|
||||
color: var(--mainForegroundColor) !important;
|
||||
|
@ -166,7 +167,7 @@ p-calendar {
|
|||
background-color: $grey-background-color !important;
|
||||
color: #000 !important;
|
||||
border-radius: 3px !important;
|
||||
font-size: 15px !important;
|
||||
font-size: 12px !important;
|
||||
height: 30px !important;
|
||||
line-height: 30px !important;
|
||||
margin: 0 5px 0 0 !important;
|
||||
|
|
|
@ -205,6 +205,7 @@ $play-overlay-width: 18px;
|
|||
color: $grey-foreground-color;
|
||||
margin-bottom: 10px;
|
||||
font-weight: $font-semibold;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@media screen and (max-width: $mobile-view) {
|
||||
|
|
Loading…
Reference in New Issue