This commit is contained in:
Infinite
2020-01-03 22:29:21 +01:00
parent 716c33fc8e
commit 4d44deff64
128 changed files with 28709 additions and 0 deletions

View File

@@ -0,0 +1,12 @@
export class ColorUtils {
public static classForRating(rating: number): string {
switch (rating) {
case 5: return "rating-5";
case 4: return "rating-4";
case 3: return "rating-3";
case 2: return "rating-2";
case 1: return "rating-1";
default: return "";
}
}
}

View File

@@ -0,0 +1,71 @@
import { HTMLSelect, InputGroup, IOptionProps, TextArea, Label } from "@blueprintjs/core";
import React from "react";
export class EditableTextUtils {
public static renderTextArea(options: {
value: string | undefined,
isEditing: boolean,
onChange: ((value: string) => void)
}) {
let element: JSX.Element;
if (options.isEditing) {
element = (
<TextArea
fill={true}
onChange={(newValue) => options.onChange(newValue.target.value)}
value={options.value}
/>
);
} else {
element = <p className="pre">{options.value}</p>;
}
return element;
}
public static renderInputGroup(options: {
value: string | undefined,
isEditing: boolean,
placeholder?: string,
onChange: ((value: string) => void),
}) {
let element: JSX.Element;
if (options.isEditing) {
element = (
<InputGroup
onChange={(newValue: any) => options.onChange(newValue.target.value)}
value={options.value}
placeholder={options.placeholder}
/>
);
} else {
element = <Label>{options.value}</Label>;
}
return element;
}
public static renderHtmlSelect(options: {
value: string | number | undefined,
isEditing: boolean,
onChange: ((value: string) => void),
selectOptions: Array<string | number | IOptionProps>,
}) {
let stringValue = options.value;
if (typeof stringValue === "number") {
stringValue = stringValue.toString();
}
let element: JSX.Element;
if (options.isEditing) {
element = (
<HTMLSelect
options={options.selectOptions}
onChange={(event) => options.onChange(event.target.value)}
value={stringValue}
/>
);
} else {
element = <span>{options.value}</span>;
}
return element;
}
}

View File

@@ -0,0 +1,24 @@
import { Intent, Position, Toaster } from "@blueprintjs/core";
import { ApolloError } from "apollo-boost";
const toaster = Toaster.create({
position: Position.TOP,
});
export class ErrorUtils {
public static handle(error: any) {
console.error(error);
toaster.show({
message: error.toString(),
intent: Intent.DANGER,
});
}
public static handleApolloError(error: ApolloError) {
console.error(error);
toaster.show({
message: error.message,
intent: Intent.DANGER,
});
}
}

View File

@@ -0,0 +1,34 @@
import React, { useEffect } from "react";
export class ImageUtils {
private static readImage(file: File, onLoadEnd: (this: FileReader) => any) {
const reader: FileReader = new FileReader();
reader.onloadend = onLoadEnd;
reader.readAsDataURL(file);
}
public static onImageChange(event: React.FormEvent<HTMLInputElement>, onLoadEnd: (this: FileReader) => any) {
const file: File = (event.target as any).files[0];
ImageUtils.readImage(file, onLoadEnd);
}
public static pasteImage(e : any, onLoadEnd: (this: FileReader) => any) {
if (e.clipboardData.files.length == 0) {
return;
}
const file: File = e.clipboardData.files[0];
ImageUtils.readImage(file, onLoadEnd);
}
public static addPasteImageHook(onLoadEnd: (this: FileReader) => any) {
useEffect(() => {
const pasteImage = (e: any) => { ImageUtils.pasteImage(e, onLoadEnd) }
window.addEventListener("paste", pasteImage);
return () => window.removeEventListener("paste", pasteImage);
});
}
}

View File

@@ -0,0 +1,49 @@
import * as GQL from "../core/generated-graphql";
import { PerformersCriterion } from "../models/list-filter/criteria/performers";
import { StudiosCriterion } from "../models/list-filter/criteria/studios";
import { TagsCriterion } from "../models/list-filter/criteria/tags";
import { ListFilterModel } from "../models/list-filter/filter";
import { FilterMode } from "../models/list-filter/types";
export class NavigationUtils {
public static makePerformerScenesUrl(performer: Partial<GQL.PerformerDataFragment>): string {
if (performer.id === undefined) { return "#"; }
const filter = new ListFilterModel(FilterMode.Scenes);
const criterion = new PerformersCriterion();
criterion.value = [{ id: performer.id, label: performer.name || `Performer ${performer.id}` }];
filter.criteria.push(criterion);
return `/scenes?${filter.makeQueryParameters()}`;
}
public static makeStudioScenesUrl(studio: Partial<GQL.StudioDataFragment>): string {
if (studio.id === undefined) { return "#"; }
const filter = new ListFilterModel(FilterMode.Scenes);
const criterion = new StudiosCriterion();
criterion.value = [{ id: studio.id, label: studio.name || `Studio ${studio.id}` }];
filter.criteria.push(criterion);
return `/scenes?${filter.makeQueryParameters()}`;
}
public static makeTagScenesUrl(tag: Partial<GQL.TagDataFragment>): string {
if (tag.id === undefined) { return "#"; }
const filter = new ListFilterModel(FilterMode.Scenes);
const criterion = new TagsCriterion("tags");
criterion.value = [{ id: tag.id, label: tag.name || `Tag ${tag.id}` }];
filter.criteria.push(criterion);
return `/scenes?${filter.makeQueryParameters()}`;
}
public static makeTagSceneMarkersUrl(tag: Partial<GQL.TagDataFragment>): string {
if (tag.id === undefined) { return "#"; }
const filter = new ListFilterModel(FilterMode.SceneMarkers);
const criterion = new TagsCriterion("tags");
criterion.value = [{ id: tag.id, label: tag.name || `Tag ${tag.id}` }];
filter.criteria.push(criterion);
return `/scenes/markers?${filter.makeQueryParameters()}`;
}
public static makeSceneMarkerUrl(sceneMarker: Partial<GQL.SceneMarkerDataFragment>): string {
if (sceneMarker.id === undefined || sceneMarker.scene === undefined) { return "#"; }
return `/scenes/${sceneMarker.scene.id}?t=${sceneMarker.seconds}`;
}
}

138
ui/v2.5/src/utils/table.tsx Normal file
View File

@@ -0,0 +1,138 @@
import { EditableText, IOptionProps } from "@blueprintjs/core";
import { Form } from 'react-bootstrap';
import React from "react";
import { EditableTextUtils } from "./editabletext";
import { FilterMultiSelect } from "../components/select/FilterMultiSelect";
import { FilterSelect } from "../components/select/FilterSelect";
import _ from "lodash";
export class TableUtils {
public static renderEditableTextTableRow(options: {
title: string;
value: string | number | undefined;
isEditing: boolean;
onChange: ((value: string) => void);
}) {
let stringValue = options.value;
if (typeof stringValue === "number") {
stringValue = stringValue.toString();
}
return (
<tr>
<td>{options.title}</td>
<td>
<EditableText
disabled={!options.isEditing}
value={stringValue}
placeholder={options.title}
multiline={true}
onChange={(newValue) => options.onChange(newValue)}
/>
</td>
</tr>
);
}
public static renderTextArea(options: {
title: string,
value: string | undefined,
isEditing: boolean,
onChange: ((value: string) => void),
}) {
return (
<tr>
<td>{options.title}</td>
<td>
{EditableTextUtils.renderTextArea(options)}
</td>
</tr>
);
}
public static renderInputGroup(options: {
title: string,
placeholder?: string,
value: string | undefined,
isEditing: boolean,
onChange: ((value: string) => void),
}) {
let optionsCopy = _.clone(options);
optionsCopy.placeholder = options.placeholder || options.title;
return (
<tr>
<td>{options.title}</td>
<td>
{ !options.isEditing
? <h4>{optionsCopy.value}</h4>
: <Form.Control
defaultValue={options.value}
placeholder={optionsCopy.placeholder}
onChange={ (event:any) => options.onChange(event.target.value) }
/>
}
</td>
</tr>
);
}
public static renderHtmlSelect(options: {
title: string,
value: string | number | undefined,
isEditing: boolean,
onChange: ((value: string) => void),
selectOptions: Array<string | number | IOptionProps>,
}) {
return (
<tr>
<td>{options.title}</td>
<td>
{EditableTextUtils.renderHtmlSelect(options)}
</td>
</tr>
);
}
// TODO: isediting
public static renderFilterSelect(options: {
title: string,
type: "performers" | "studios" | "tags",
initialId: string | undefined,
onChange: ((id: string | undefined) => void),
}) {
return (
<tr>
<td>{options.title}</td>
<td>
<FilterSelect
type={options.type}
onSelectItem={(item) => options.onChange(item ? item.id : undefined)}
initialId={options.initialId}
/>
</td>
</tr>
);
}
// TODO: isediting
public static renderMultiSelect(options: {
title: string,
type: "performers" | "studios" | "tags",
initialIds: string[] | undefined,
onChange: ((ids: string[]) => void),
}) {
return (
<tr>
<td>{options.title}</td>
<td>
<FilterMultiSelect
type={options.type}
onUpdate={(items) => options.onChange(items.map((i) => i.id))}
openOnKeyDown={true}
initialIds={options.initialIds}
/>
</td>
</tr>
);
}
}

83
ui/v2.5/src/utils/text.ts Normal file
View File

@@ -0,0 +1,83 @@
export class TextUtils {
public static truncate(value?: string, limit: number = 100, tail: string = "..."): string {
if (!value) { return ""; }
return value.length > limit ? value.substring(0, limit) + tail : value;
}
public static fileSize(bytes: number = 0, precision: number = 2): string {
if (isNaN(parseFloat(String(bytes))) || !isFinite(bytes)) { return "?"; }
let unit = 0;
while ( bytes >= 1024 ) {
bytes /= 1024;
unit++;
}
return bytes.toFixed(+precision) + " " + this.units[unit];
}
public static secondsToTimestamp(seconds: number): string {
let ret = new Date(seconds * 1000).toISOString().substr(11, 8);
if (ret.startsWith("00")) {
// strip hours if under one hour
ret = ret.substr(3);
}
if (ret.startsWith("0")) {
// for duration under a minute, leave one leading zero
ret = ret.substr(1);
}
return ret;
}
public static fileNameFromPath(path: string): string {
if (!!path === false) { return "No File Name"; }
return path.replace(/^.*[\\\/]/, "");
}
public static age(dateString?: string, fromDateString?: string): number {
if (!dateString) { return 0; }
const birthdate = new Date(dateString);
const fromDate = !!fromDateString ? new Date(fromDateString) : new Date();
let age = fromDate.getFullYear() - birthdate.getFullYear();
if (birthdate.getMonth() > fromDate.getMonth() ||
(birthdate.getMonth() >= fromDate.getMonth() && birthdate.getDay() > fromDate.getDay())) {
age -= 1;
}
return age;
}
public static bitRate(bitrate: number) {
const megabits = bitrate / 1000000;
return `${megabits.toFixed(2)} megabits per second`;
}
public static resolution(height: number) {
if (height >= 240 && height < 480) {
return "240p";
} else if (height >= 480 && height < 720) {
return "480p";
} else if (height >= 720 && height < 1080) {
return "720p";
} else if (height >= 1080 && height < 2160) {
return "1080p";
} else if (height >= 2160) {
return "4K";
} else {
return undefined;
}
}
private static units = [
"bytes",
"kB",
"MB",
"GB",
"TB",
"PB",
];
}

View File

@@ -0,0 +1,14 @@
import { Intent, Position, Toaster } from "@blueprintjs/core";
const toaster = Toaster.create({
position: Position.TOP,
});
export class ToastUtils {
public static success(message: string) {
toaster.show({
message,
intent: Intent.SUCCESS,
});
}
}

View File

@@ -0,0 +1,6 @@
export class ZoomUtils {
public static classForZoom(zoomIndex: number): string {
return "zoom-" + zoomIndex;
}
}