Merge pull request #18 from Trivernis/develop

Develop
main v1.0.0
Julius Riegel 2 years ago committed by GitHub
commit 351f74bf14
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1395,7 +1395,7 @@ dependencies = [
[[package]]
name = "mediarepo-daemon"
version = "1.0.0-rc.4"
version = "1.0.0"
dependencies = [
"console-subscriber",
"glob",

@ -4,7 +4,7 @@ default-members = ["mediarepo-core", "mediarepo-database", "mediarepo-logic", "m
[package]
name = "mediarepo-daemon"
version = "1.0.0-rc.4"
version = "1.0.0"
edition = "2018"
license = "gpl-3"
repository = "https://github.com/Trivernis/mediarepo-daemon"

@ -1,9 +1,10 @@
use sea_orm::prelude::*;
use sea_orm::sea_query::Query;
use sea_orm::ActiveValue::Set;
use sea_orm::{DatabaseTransaction, TransactionTrait};
use mediarepo_core::error::RepoResult;
use mediarepo_database::entities::content_descriptor_tag;
use mediarepo_database::entities::{content_descriptor_tag, namespace, tag};
use crate::dao::tag::TagDao;
@ -41,11 +42,15 @@ impl TagDao {
#[tracing::instrument(level = "debug", skip(self))]
pub async fn remove_mappings(&self, cd_ids: Vec<i64>, tag_ids: Vec<i64>) -> RepoResult<()> {
let trx = self.ctx.db.begin().await?;
content_descriptor_tag::Entity::delete_many()
.filter(content_descriptor_tag::Column::CdId.is_in(cd_ids))
.filter(content_descriptor_tag::Column::TagId.is_in(tag_ids))
.exec(&self.ctx.db)
.exec(&trx)
.await?;
delete_orphans(&trx).await?;
trx.commit().await?;
Ok(())
}
@ -66,3 +71,34 @@ async fn get_existing_mappings(
.collect();
Ok(existing_mappings)
}
/// Deletes orphaned tag entries and namespaces from the database
async fn delete_orphans(trx: &DatabaseTransaction) -> RepoResult<()> {
tag::Entity::delete_many()
.filter(
tag::Column::Id.not_in_subquery(
Query::select()
.column(content_descriptor_tag::Column::TagId)
.from(content_descriptor_tag::Entity)
.group_by_col(content_descriptor_tag::Column::TagId)
.to_owned(),
),
)
.exec(trx)
.await?;
namespace::Entity::delete_many()
.filter(
namespace::Column::Id.not_in_subquery(
Query::select()
.column(tag::Column::NamespaceId)
.from(tag::Entity)
.group_by_col(tag::Column::NamespaceId)
.to_owned(),
),
)
.exec(trx)
.await?;
Ok(())
}

@ -1,6 +1,6 @@
{
"name": "mediarepo-ui",
"version": "1.0.0-rc.4",
"version": "1.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",

@ -40,7 +40,7 @@ checksum = "94a45b455c14666b85fc40a019e8ab9eb75e3a124e05494f5397122bc9eb06e0"
[[package]]
name = "app"
version = "1.0.0-rc.4"
version = "1.0.0"
dependencies = [
"mediarepo-api",
"serde",

@ -1,6 +1,6 @@
[package]
name = "app"
version = "1.0.0-rc.4"
version = "1.0.0"
description = "The UI for the mediarepo media management tool"
authors = ["you"]
license = ""

@ -28,9 +28,9 @@ export type PropertyQuery = PropertyQueryStatus
export type PropertyQueryStatus = { Status: FileStatus };
export type PropertyQueryFileSize = { FileSize: ValueComparator<number> };
export type PropertyQueryImportedTime = { ImportedTime: ValueComparator<Date> };
export type PropertyQueryChangedTime = { ChangedTime: ValueComparator<Date> };
export type PropertyQueryCreatedTime = { CreatedTime: ValueComparator<Date> };
export type PropertyQueryImportedTime = { ImportedTime: ValueComparator<string> };
export type PropertyQueryChangedTime = { ChangedTime: ValueComparator<string> };
export type PropertyQueryCreatedTime = { CreatedTime: ValueComparator<string> };
export type PropertyQueryTagCount = { TagCount: ValueComparator<number> };
export type PropertyQueryCd = { Cd: string };
export type PropertyQueryId = { Id: number };

@ -1,9 +1,14 @@
import {FileBasicData, FileStatus} from "../api-types/files";
import {BehaviorSubject, Observable} from "rxjs";
export class File {
private statusSubject: BehaviorSubject<FileStatus>;
constructor(
private basicData: FileBasicData,
) {
this.statusSubject = new BehaviorSubject(basicData.status);
}
public get rawData(): FileBasicData {
@ -18,15 +23,20 @@ export class File {
return this.basicData.cd;
}
public get status(): FileStatus {
return this.basicData.status;
public get status(): Observable<FileStatus> {
return this.statusSubject.asObservable();
}
public get mimeType(): string {
return this.basicData.mime_type;
}
public set status(value: FileStatus) {
public setStatus(value: FileStatus) {
this.basicData.status = value;
this.statusSubject.next(value);
}
public get mimeType(): string {
return this.basicData.mime_type;
public getStatus(): FileStatus {
return this.basicData.status;
}
}

@ -29,21 +29,21 @@ export class FilterQueryBuilder {
public static importedTime(date: Date, comparator: Comparator, max_date: Date): FilterQuery {
return filterQuery({
ImportedTime: valuesToCompareEnum(date, comparator,
max_date
ImportedTime: valuesToCompareEnum(formatDate(date)!!, comparator,
formatDate(max_date)
)
});
}
public static changedTime(date: Date, comparator: Comparator, max_date: Date): FilterQuery {
return filterQuery({
ChangedTime: valuesToCompareEnum(date, comparator, max_date)
ChangedTime: valuesToCompareEnum(formatDate(date)!!, comparator, formatDate(max_date))
});
}
public static createdTime(date: Date, comparator: Comparator, max_date: Date): FilterQuery {
return filterQuery({
CreatedTime: valuesToCompareEnum(date, comparator, max_date)
CreatedTime: valuesToCompareEnum(formatDate(date)!!, comparator, formatDate(max_date))
});
}
@ -150,6 +150,7 @@ export class FilterQueryBuilder {
}
break;
case "ImportedTime":
console.debug(propertyName, rawComparator, compareValue);
value = this.parsePropertyValue(compareValue, parseDate);
if (value != undefined) {
return this.importedTime(value[0], comparator, value[1]);
@ -263,7 +264,7 @@ function filterQuery(propertyQuery: PropertyQuery): FilterQuery {
return { Property: propertyQuery };
}
function valuesToCompareEnum<T>(min_value: T, comparator: Comparator, max_value?: T): ValueComparator<T> {
function valuesToCompareEnum<T>(min_value: T, comparator: Comparator, max_value: T | undefined): ValueComparator<T> {
switch (comparator) {
case "Less":
return { Less: min_value };
@ -299,9 +300,9 @@ function parseByteSize(value: string): number | undefined {
if (number) {
for (const key of Object.keys(valueMappings)) {
if (checkUnit(key)) {
console.log("key", key, "valueMapping", valueMappings[key]);
console.debug("key", key, "valueMapping", valueMappings[key]);
number *= valueMappings[key];
console.log("number", number);
console.debug("number", number);
break;
}
}
@ -311,7 +312,7 @@ function parseByteSize(value: string): number | undefined {
}
function parseDate(value: string): Date | undefined {
const date = Date.parse(value);
const date = Date.parse(value.toUpperCase());
if (isNaN(date)) {
return undefined;
@ -331,3 +332,13 @@ function parseStatus(value: string): FileStatus | undefined {
return undefined;
}
}
function formatDate(date?: Date): string | undefined {
if (date) {
const pad = (s: number) => s.toString().padStart(2, "0");
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(date.getHours())}:${pad(
date.getMinutes())}:${pad(
date.getSeconds())}`;
}
return;
}

@ -56,7 +56,7 @@ export class FileActionBaseComponent {
if (changeConfirmed) {
await this.errorBroker.try(async () => {
const newFile = await this.fileService.updateFileStatus(files[0].id, status);
files[0].status = newFile.status;
files[0].setStatus(newFile.getStatus());
});
}
} else {
@ -72,7 +72,7 @@ export class FileActionBaseComponent {
files,
(file) => this.errorBroker.try(async () => {
const newFile = await this.fileService.updateFileStatus(file.id, status);
file.status = newFile.status;
file.setStatus(newFile.getStatus());
})
);
}

@ -5,7 +5,6 @@
<app-file-thumbnail
(loadEnd)="this.loading = false"
[class.hidden]="this.loading"
[fileChanged]="this.fileChanged"
[file]="this.entry.data"
class="entry-image"></app-file-thumbnail>
</app-busy-indicator>

@ -13,7 +13,6 @@ import {
import {File} from "../../../../../api/models/File";
import {Selectable} from "../../../../models/Selectable";
import {SchedulingService} from "../../../../services/scheduling/scheduling.service";
import {BehaviorSubject} from "rxjs";
const LOADING_WORK_KEY = "FILE_THUMBNAIL_LOADING";
@ -26,7 +25,6 @@ const LOADING_WORK_KEY = "FILE_THUMBNAIL_LOADING";
export class FileCardComponent implements OnInit, OnChanges, OnDestroy {
@Input() public entry!: Selectable<File>;
@Input() public fileChanged: BehaviorSubject<void> = new BehaviorSubject<void>(undefined);
@Output() clickEvent = new EventEmitter<FileCardComponent>();
@Output() dblClickEvent = new EventEmitter<FileCardComponent>();
@ -47,9 +45,6 @@ export class FileCardComponent implements OnInit, OnChanges, OnDestroy {
this.cachedId = this.entry.data.id;
this.loading = true;
}
if (changes["fileChanged"]) {
this.fileChanged.subscribe(() => this.changeDetector.markForCheck());
}
}
public ngOnDestroy(): void {
@ -62,19 +57,4 @@ export class FileCardComponent implements OnInit, OnChanges, OnDestroy {
console.debug(this.entry.data.id);
this.clickEvent.emit(this);
}
private setImageDelayed() {
if (this.workId) {
this.schedulingService.cancelWork(LOADING_WORK_KEY, this.workId);
}
this.loading = true;
this.workId = this.schedulingService.addWork(
LOADING_WORK_KEY,
async () => {
await this.schedulingService.delay(0);
this.loading = false;
this.changeDetector.markForCheck();
}
);
}
}

@ -60,11 +60,11 @@ export class FileContextMenuComponent extends FileActionBaseComponent implements
this.actionDelete = this.actionArchive = this.actionImported = this.actionRestore = false;
for (const file of this.files) {
this.actionDeletePermantently &&= file.status === "Deleted";
this.actionDelete ||= file.status !== "Deleted";
this.actionArchive ||= file.status !== "Archived" && file.status !== "Deleted";
this.actionImported ||= file.status !== "Imported" && file.status !== "Deleted";
this.actionRestore ||= file.status === "Deleted";
this.actionDeletePermantently &&= file.getStatus() === "Deleted";
this.actionDelete ||= file.getStatus() !== "Deleted";
this.actionArchive ||= file.getStatus() !== "Archived" && file.getStatus() !== "Deleted";
this.actionImported ||= file.getStatus() !== "Imported" && file.getStatus() !== "Deleted";
this.actionRestore ||= file.getStatus() === "Deleted";
}
}
}

@ -24,11 +24,10 @@
<div #previewStripContainer class="file-preview-strip-container">
<div *ngFor="let entry of this.previewedEntries; trackBy: trackByFileId" class="file-item">
<app-file-card (clickEvent)="onEntrySelect($event.entry)" *ngIf="entry"
[entry]="entry" [fileChanged]="this.fileChanged"></app-file-card>
[entry]="entry"></app-file-card>
</div>
</div>
</div>
</div>
<app-file-context-menu #fileContextMenu
(fileDeleted)="this.fileDeleted.emit($event)"
(fileStatusChange)="this.onFileStatusChange()"></app-file-context-menu>
(fileDeleted)="this.fileDeleted.emit($event)"></app-file-context-menu>

@ -19,7 +19,6 @@ import {SafeResourceUrl} from "@angular/platform-browser";
import {Selectable} from "../../../../../models/Selectable";
import {TabService} from "../../../../../services/tab/tab.service";
import {Key} from "w3c-keys";
import {BehaviorSubject} from "rxjs";
@Component({
selector: "app-file-gallery",
@ -43,7 +42,6 @@ export class FileGalleryComponent implements OnChanges, OnInit, AfterViewInit, A
public entries: Selectable<File>[] = [];
public selectedFile: Selectable<File> | undefined;
public fileContentUrl: SafeResourceUrl | undefined;
public fileChanged = new BehaviorSubject<void>(undefined);
public selectedIndex = 0;
public imageViewHeightPercent = 80;
@ -182,10 +180,6 @@ export class FileGalleryComponent implements OnChanges, OnInit, AfterViewInit, A
return item?.data.id;
}
public onFileStatusChange(): void {
this.fileChanged.next();
}
public togglePreviewStrip(): void {
if (this.previewStripVisible) {
this.imageViewHeightPercent = 100;

@ -14,7 +14,7 @@
(contextmenu)="this.selectEntryWhenNotSelected(gridEntry); fileContextMenu.onContextMenu($event, this.getSelectedFiles())"
(dblClickEvent)="fileOpen.emit($event.entry.data)"
*ngIf="gridEntry"
[entry]="gridEntry" [fileChanged]="this.fileChanged"></app-file-card>
[entry]="gridEntry"></app-file-card>
<div *ngIf="!gridEntry" class="empty-grid-entry"></div>
</ng-container>
</div>
@ -23,8 +23,7 @@
</div>
<app-file-context-menu #fileContextMenu
(fileDeleted)="this.fileDeleted.emit($event)"
(fileStatusChange)="this.onFileStatusChange()">
(fileDeleted)="this.fileDeleted.emit($event)">
<button (click)="this.fileOpen.emit(fileContextMenu.files[0])"
*ngIf="fileContextMenu.files.length === 1"
content-before=""

@ -20,7 +20,6 @@ import {TabService} from "../../../../../services/tab/tab.service";
import {FileService} from "../../../../../services/file/file.service";
import {Selectable} from "../../../../../models/Selectable";
import {Key} from "w3c-keys";
import {BehaviorSubject} from "rxjs";
import {LoggingService} from "../../../../../services/logging/logging.service";
@Component({
@ -41,7 +40,6 @@ export class FileGridComponent implements OnChanges, OnInit, AfterViewInit, Afte
@ViewChild("virtualScrollGrid") virtualScroll!: CdkVirtualScrollViewport;
@ViewChild("inner") inner!: ElementRef<HTMLDivElement>;
public fileChanged = new BehaviorSubject<void>(undefined);
public selectedEntries: Selectable<File>[] = [];
public partitionedGridEntries: (Selectable<File> | undefined)[][] = [];
@ -195,10 +193,6 @@ export class FileGridComponent implements OnChanges, OnInit, AfterViewInit, Afte
return item?.data.id;
}
public onFileStatusChange(): void {
this.fileChanged.next();
}
public calculateColumnCount() {
if (this.inner && this.inner.nativeElement) {
const width = Math.abs(this.inner.nativeElement.clientWidth);

@ -71,7 +71,7 @@ export class FileMultiviewComponent extends FileActionBaseComponent implements A
let deletePermanently = true;
for (const file of files) {
deletePermanently &&= file.status === "Deleted";
deletePermanently &&= file.getStatus() === "Deleted";
}
if (deletePermanently) {

@ -13,7 +13,7 @@
<ng-icon *ngIf="fileType === 'audio'" name="mat-audiotrack"></ng-icon>
<ng-icon *ngIf="fileType === 'text'" name="mat-description"></ng-icon>
</div>
<div *ngIf="file.status !== 'Archived'" class="file-status-icon">
<ng-icon *ngIf="file.status === 'Deleted'" name="mat-auto-delete"></ng-icon>
<ng-icon *ngIf="file.status === 'Imported'" name="mat-fiber-new"></ng-icon>
<div *ngIf="(file.status | async) !== 'Archived'" class="file-status-icon">
<ng-icon *ngIf="(file.status | async) === 'Deleted'" name="mat-auto-delete"></ng-icon>
<ng-icon *ngIf="(file.status | async) === 'Imported'" name="mat-fiber-new"></ng-icon>
</div>

@ -1,5 +1,4 @@
import {
AfterViewChecked,
AfterViewInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
@ -14,7 +13,6 @@ import {File} from "../../../../../api/models/File";
import {FileService} from "../../../../services/file/file.service";
import {FileHelper} from "../../../../services/file/file.helper";
import {SafeResourceUrl} from "@angular/platform-browser";
import {BehaviorSubject} from "rxjs";
@Component({
selector: "app-file-thumbnail",
@ -22,10 +20,9 @@ import {BehaviorSubject} from "rxjs";
styleUrls: ["./file-thumbnail.component.scss"],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class FileThumbnailComponent implements OnChanges, AfterViewInit, AfterViewChecked {
export class FileThumbnailComponent implements OnChanges, AfterViewInit {
@Input() file!: File;
@Input() public fileChanged: BehaviorSubject<void> = new BehaviorSubject<void>(undefined);
@Output() loadEnd = new EventEmitter<void>();
public thumbUrl: SafeResourceUrl | undefined;
@ -34,7 +31,6 @@ export class FileThumbnailComponent implements OnChanges, AfterViewInit, AfterVi
public displayError = false;
private supportedThumbnailTypes = ["image", "video"];
private previousStatus = "imported";
constructor(private changeDetector: ChangeDetectorRef, private fileService: FileService) {
}
@ -45,13 +41,6 @@ export class FileThumbnailComponent implements OnChanges, AfterViewInit, AfterVi
}
}
public ngAfterViewChecked(): void {
if (this.file && this.file.status != this.previousStatus) {
this.previousStatus = this.file.status;
this.changeDetector.markForCheck();
}
}
public async ngOnChanges(changes: SimpleChanges) {
if (changes["file"]) {
this.thumbUrl = this.fileService.buildThumbnailUrl(this.file,
@ -61,9 +50,6 @@ export class FileThumbnailComponent implements OnChanges, AfterViewInit, AfterVi
this.thumbnailSupported = this.getThumbnailSupported();
this.displayError = false;
}
if (changes["fileChanged"]) {
this.fileChanged.subscribe(() => this.changeDetector.markForCheck());
}
}
public onImageLoadError(): void {

@ -6,6 +6,12 @@ import {DialogFilter} from "@tauri-apps/api/dialog";
import {FileOsMetadata} from "../../../../../../api/api-types/files";
import {ImportTabState} from "../../../../../models/state/ImportTabState";
const IMAGE_EXTENSIONS = ["png", "jpg", "jpeg", "webp", "bmp", "gif"];
const VIDEO_EXTENSIONS = ["mp4", "mkv", "wmv", "avi", "webm"];
const AUDIO_EXTENSIONS = ["mp3", "ogg", "wav", "flac", "aac"];
const DOCUMENT_EXTENSIONS = ["pdf", "doc", "docx", "odf"];
const TEXT_EXTENSIONS = ["txt", "md"];
@Component({
selector: "app-filesystem-import",
templateUrl: "./filesystem-import.component.html",
@ -20,14 +26,11 @@ export class FilesystemImportComponent implements OnInit {
public files: FileOsMetadata[] = [];
public importOptions = new AddFileOptions();
public filters: DialogFilter[] = [
{
name: "Images",
extensions: ["png", "jpg", "jpeg", "webp", "bmp", "gif"]
},
{ name: "Videos", extensions: ["mp4", "mkv", "wmv", "avi", "webm"] },
{ name: "Audio", extensions: ["mp3", "ogg", "wav", "flac", "aac"] },
{ name: "Documents", extensions: ["pdf", "doc", "docx", "odf"] },
{ name: "Text", extensions: ["txt", "md"] },
{ name: "Images", extensions: [...IMAGE_EXTENSIONS, ...IMAGE_EXTENSIONS.map(e => e.toUpperCase())] },
{ name: "Videos", extensions: [...VIDEO_EXTENSIONS, ...VIDEO_EXTENSIONS.map(e => e.toUpperCase())] },
{ name: "Audio", extensions: [...AUDIO_EXTENSIONS, ...AUDIO_EXTENSIONS.map(e => e.toUpperCase())] },
{ name: "Documents", extensions: [...DOCUMENT_EXTENSIONS, ...DOCUMENT_EXTENSIONS.map(e => e.toUpperCase())] },
{ name: "Text", extensions: [...TEXT_EXTENSIONS, ...TEXT_EXTENSIONS.map(e => e.toUpperCase())] },
{ name: "All", extensions: ["*"] }
];

@ -127,6 +127,7 @@ export class FileSearchComponent implements AfterViewChecked, OnInit {
public async removeAllSearchTags() {
this.filters = new SearchFilters([]);
this.state.setTagFilters(this.filters);
this.updateStatusFilters();
}
public async removeFilterExpression(expr: FilterExpression) {

@ -18,7 +18,7 @@
<div class="tag-input" fxFlex="200px">
<div class="tag-input-field">
<app-tag-input (tagAdded)="this.editTag($event)" [allowInvalid]="true"
[availableTags]="this.allTags"></app-tag-input>
[availableTags]="(this.allTags | async) ?? []"></app-tag-input>
<button class="add-tag-button" mat-icon-button>
<ng-icon *ngIf="editMode === 'Toggle'" name="mat-change-circle"></ng-icon>
<ng-icon *ngIf="editMode === 'Add'" name="mat-add-circle"></ng-icon>

@ -16,6 +16,7 @@ import {CdkVirtualScrollViewport} from "@angular/cdk/scrolling";
import {TagService} from "../../../../services/tag/tag.service";
import {LoggingService} from "../../../../services/logging/logging.service";
import {BusyIndicatorComponent} from "../../app-common/busy-indicator/busy-indicator.component";
import {Observable} from "rxjs";
@Component({
selector: "app-tag-edit",
@ -32,7 +33,7 @@ export class TagEditComponent implements AfterViewInit, OnChanges {
@ViewChild(BusyIndicatorComponent) busyIndicator!: BusyIndicatorComponent;
public tags: Tag[] = [];
public allTags: Tag[] = [];
public allTags: Observable<Tag[]>;
public editMode: string = "Toggle";
private fileTags: { [key: number]: Tag[] } = {};
@ -41,10 +42,10 @@ export class TagEditComponent implements AfterViewInit, OnChanges {
private logger: LoggingService,
private tagService: TagService,
) {
this.allTags = tagService.tags.asObservable();
}
async ngAfterViewInit() {
this.tagService.tags.subscribe(tags => this.allTags = tags);
await this.tagService.loadTags();
await this.tagService.loadNamespaces();
await this.loadFileTags();
@ -58,12 +59,12 @@ export class TagEditComponent implements AfterViewInit, OnChanges {
public async editTag(tag: string): Promise<void> {
if (tag.length > 0) {
let tagInstance = this.allTags.find(
let tagInstance = this.tagService.tags.value.find(
t => t.getNormalizedOutput() === tag);
if (!tagInstance) {
tagInstance = (await this.tagService.createTags([tag]))[0];
this.allTags.push(tagInstance);
this.tagService.tags.next([...this.tagService.tags.value, tagInstance]);
}
this.changeDetector.markForCheck();
switch (this.editMode) {
@ -95,11 +96,13 @@ export class TagEditComponent implements AfterViewInit, OnChanges {
file.id,
addedTags, removedTags
);
if (addedTags.length > 0) {
await this.tagService.loadTags();
await this.tagService.loadNamespaces();
}
}
await this.tagService.loadTags();
await this.tagService.loadNamespaces();
this.mapFileTagsToTagList();
const index = this.tags.indexOf(tag);
if (index >= 0) {
@ -142,6 +145,8 @@ export class TagEditComponent implements AfterViewInit, OnChanges {
);
}
}
await this.tagService.loadTags();
await this.tagService.loadNamespaces();
this.mapFileTagsToTagList();
});
this.tagEditEvent.emit(this);

@ -40,19 +40,19 @@ export function propertyQueryToStringParts(propertyQuery: PropertyQuery): [strin
return [
"ImportedTime",
getComparator(propertyQuery.ImportedTime),
getValue(propertyQuery.ImportedTime).toISOString()
getValue(propertyQuery.ImportedTime)
];
} else if ("ChangedTime" in propertyQuery) {
return [
"ChangedTime",
getComparator(propertyQuery.ChangedTime),
getValue(propertyQuery.ChangedTime).toISOString()
getValue(propertyQuery.ChangedTime)
];
} else if ("CreatedTime" in propertyQuery) {
return [
"CreatedTime",
getComparator(propertyQuery.CreatedTime),
getValue(propertyQuery.CreatedTime).toISOString()
getValue(propertyQuery.CreatedTime)
];
} else if ("TagCount" in propertyQuery) {
return [

Loading…
Cancel
Save