From 12c7faab4e3549c35747d0c42c15dbf104386004 Mon Sep 17 00:00:00 2001 From: WithoutPants <53250216+WithoutPants@users.noreply.github.com> Date: Fri, 6 Dec 2019 04:24:22 +1100 Subject: [PATCH] Scene ui improvements (#232) * Move duration and resolution to overlay * Improve display of portrait videos * Condense filter controls * Add performer images to scene tags * Add studio overlay to scene cards * Fade out scene overlays on hover * CSS grid tweaks * Align overlay to bottom of video preview * Fix opacity value * Fix performer thumbnails * Show studio overlay on mouseover * Correct display colour for display mode buttons * Add scene zoom slider * Add show studio as text option * Move select all/none to more button --- graphql/documents/data/config.graphql | 1 + graphql/schema/types/config.graphql | 4 + pkg/api/resolver_mutation_configure.go | 4 + pkg/api/resolver_query_configuration.go | 2 + pkg/manager/config/config.go | 6 + .../Settings/SettingsInterfacePanel.tsx | 15 ++ ui/v2/src/components/list/AddFilter.tsx | 15 +- ui/v2/src/components/list/ListFilter.tsx | 102 +++++++++--- ui/v2/src/components/scenes/SceneCard.tsx | 112 +++++++++++--- ui/v2/src/components/scenes/SceneList.tsx | 8 +- ui/v2/src/hooks/ListHook.tsx | 12 +- ui/v2/src/index.scss | 145 +++++++++++++++++- ui/v2/src/utils/text.ts | 12 +- ui/v2/src/utils/zoom.ts | 6 + 14 files changed, 391 insertions(+), 53 deletions(-) create mode 100644 ui/v2/src/utils/zoom.ts diff --git a/graphql/documents/data/config.graphql b/graphql/documents/data/config.graphql index 3ea0d6c53..a62ca9c24 100644 --- a/graphql/documents/data/config.graphql +++ b/graphql/documents/data/config.graphql @@ -17,6 +17,7 @@ fragment ConfigInterfaceData on ConfigInterfaceResult { wallShowTitle maximumLoopDuration autostartVideo + showStudioAsText css cssEnabled } diff --git a/graphql/schema/types/config.graphql b/graphql/schema/types/config.graphql index f0be74e66..937b60855 100644 --- a/graphql/schema/types/config.graphql +++ b/graphql/schema/types/config.graphql @@ -66,6 +66,8 @@ input ConfigInterfaceInput { maximumLoopDuration: Int """If true, video will autostart on load in the scene player""" autostartVideo: Boolean + """If true, studio overlays will be shown as text instead of logo images""" + showStudioAsText: Boolean """Custom CSS""" css: String cssEnabled: Boolean @@ -80,6 +82,8 @@ type ConfigInterfaceResult { maximumLoopDuration: Int """If true, video will autostart on load in the scene player""" autostartVideo: Boolean + """If true, studio overlays will be shown as text instead of logo images""" + showStudioAsText: Boolean """Custom CSS""" css: String cssEnabled: Boolean diff --git a/pkg/api/resolver_mutation_configure.go b/pkg/api/resolver_mutation_configure.go index 32d31d2b2..e7da15f3b 100644 --- a/pkg/api/resolver_mutation_configure.go +++ b/pkg/api/resolver_mutation_configure.go @@ -98,6 +98,10 @@ func (r *mutationResolver) ConfigureInterface(ctx context.Context, input models. config.Set(config.AutostartVideo, *input.AutostartVideo) } + if input.ShowStudioAsText != nil { + config.Set(config.ShowStudioAsText, *input.ShowStudioAsText) + } + css := "" if input.CSS != nil { diff --git a/pkg/api/resolver_query_configuration.go b/pkg/api/resolver_query_configuration.go index b1961d5a3..18b227748 100644 --- a/pkg/api/resolver_query_configuration.go +++ b/pkg/api/resolver_query_configuration.go @@ -53,6 +53,7 @@ func makeConfigInterfaceResult() *models.ConfigInterfaceResult { wallShowTitle := config.GetWallShowTitle() maximumLoopDuration := config.GetMaximumLoopDuration() autostartVideo := config.GetAutostartVideo() + showStudioAsText := config.GetShowStudioAsText() css := config.GetCSS() cssEnabled := config.GetCSSEnabled() @@ -61,6 +62,7 @@ func makeConfigInterfaceResult() *models.ConfigInterfaceResult { WallShowTitle: &wallShowTitle, MaximumLoopDuration: &maximumLoopDuration, AutostartVideo: &autostartVideo, + ShowStudioAsText: &showStudioAsText, CSS: &css, CSSEnabled: &cssEnabled, } diff --git a/pkg/manager/config/config.go b/pkg/manager/config/config.go index 7f566c962..9005e55ac 100644 --- a/pkg/manager/config/config.go +++ b/pkg/manager/config/config.go @@ -35,6 +35,7 @@ const SoundOnPreview = "sound_on_preview" const WallShowTitle = "wall_show_title" const MaximumLoopDuration = "maximum_loop_duration" const AutostartVideo = "autostart_video" +const ShowStudioAsText = "show_studio_as_text" const CSSEnabled = "cssEnabled" // Logging options @@ -191,6 +192,11 @@ func GetAutostartVideo() bool { return viper.GetBool(AutostartVideo) } +func GetShowStudioAsText() bool { + viper.SetDefault(ShowStudioAsText, false) + return viper.GetBool(ShowStudioAsText) +} + func GetCSSPath() string { // use custom.css in the same directory as the config file configFileUsed := viper.ConfigFileUsed() diff --git a/ui/v2/src/components/Settings/SettingsInterfacePanel.tsx b/ui/v2/src/components/Settings/SettingsInterfacePanel.tsx index b74beb240..3fef846ca 100644 --- a/ui/v2/src/components/Settings/SettingsInterfacePanel.tsx +++ b/ui/v2/src/components/Settings/SettingsInterfacePanel.tsx @@ -22,6 +22,7 @@ export const SettingsInterfacePanel: FunctionComponent = () => { const [wallShowTitle, setWallShowTitle] = useState(); const [maximumLoopDuration, setMaximumLoopDuration] = useState(0); const [autostartVideo, setAutostartVideo] = useState(); + const [showStudioAsText, setShowStudioAsText] = useState(); const [css, setCSS] = useState(); const [cssEnabled, setCSSEnabled] = useState(); @@ -30,6 +31,7 @@ export const SettingsInterfacePanel: FunctionComponent = () => { wallShowTitle, maximumLoopDuration, autostartVideo, + showStudioAsText, css, cssEnabled }); @@ -42,6 +44,7 @@ export const SettingsInterfacePanel: FunctionComponent = () => { setWallShowTitle(iCfg.wallShowTitle !== undefined ? iCfg.wallShowTitle : true); setMaximumLoopDuration(iCfg.maximumLoopDuration || 0); setAutostartVideo(iCfg.autostartVideo !== undefined ? iCfg.autostartVideo : false); + setShowStudioAsText(iCfg.showStudioAsText !== undefined ? iCfg.showStudioAsText : false); setCSS(config.data.configuration.interface.css || ""); setCSSEnabled(config.data.configuration.interface.cssEnabled || false); } @@ -78,6 +81,18 @@ export const SettingsInterfacePanel: FunctionComponent = () => { /> + + { + setShowStudioAsText(!showStudioAsText) + }} + /> + + diff --git a/ui/v2/src/components/list/AddFilter.tsx b/ui/v2/src/components/list/AddFilter.tsx index f7f2bf3a4..fd3c9fa78 100644 --- a/ui/v2/src/components/list/AddFilter.tsx +++ b/ui/v2/src/components/list/AddFilter.tsx @@ -5,6 +5,7 @@ import { FormGroup, HTMLSelect, InputGroup, + Tooltip, } from "@blueprintjs/core"; import _ from "lodash"; import React, { FunctionComponent, useEffect, useRef, useState } from "react"; @@ -188,7 +189,19 @@ export const AddFilter: FunctionComponent = (props: IAddFilterP const title = !props.editingCriterion ? "Add Filter" : "Update Filter"; return ( <> - + + + + onToggle()} title={title}>
{maybeRenderFilterSelect()} diff --git a/ui/v2/src/components/list/ListFilter.tsx b/ui/v2/src/components/list/ListFilter.tsx index 1aa97cf5a..62b8ff029 100644 --- a/ui/v2/src/components/list/ListFilter.tsx +++ b/ui/v2/src/components/list/ListFilter.tsx @@ -9,6 +9,8 @@ import { MenuItem, Popover, Tag, + Tooltip, + Slider, } from "@blueprintjs/core"; import { debounce } from "lodash"; import React, { FunctionComponent, SyntheticEvent, useEffect, useRef, useState } from "react"; @@ -25,6 +27,8 @@ interface IListFilterProps { onChangeDisplayMode: (displayMode: DisplayMode) => void; onAddCriterion: (criterion: Criterion, oldId?: string) => void; onRemoveCriterion: (criterion: Criterion) => void; + zoomIndex?: number; + onChangeZoom?: (zoomIndex: number) => void; onSelectAll?: () => void; onSelectNone?: () => void; filter: ListFilterModel; @@ -111,13 +115,14 @@ export const ListFilter: FunctionComponent = (props: IListFilt } } return props.filter.displayModeOptions.map((option) => ( - {renderSortByOptions()} - + + +
diff --git a/ui/v2/src/components/scenes/SceneCard.tsx b/ui/v2/src/components/scenes/SceneCard.tsx index b804c3e49..9ff938324 100644 --- a/ui/v2/src/components/scenes/SceneCard.tsx +++ b/ui/v2/src/components/scenes/SceneCard.tsx @@ -17,10 +17,13 @@ import { ColorUtils } from "../../utils/color"; import { TextUtils } from "../../utils/text"; import { TagLink } from "../Shared/TagLink"; import { SceneHelpers } from "./helpers"; +import { ZoomUtils } from "../../utils/zoom"; +import { StashService } from "../../core/StashService"; interface ISceneCardProps { scene: GQL.SlimSceneDataFragment; selected: boolean | undefined; + zoomIndex: number; onSelectedChanged: (selected : boolean, shiftKey : boolean) => void; } @@ -28,6 +31,8 @@ export const SceneCard: FunctionComponent = (props: ISceneCardP const [previewPath, setPreviewPath] = useState(undefined); const videoHoverHook = VideoHoverHook.useVideoHover({resetOnMouseLeave: false}); + const config = StashService.useConfiguration(); + const showStudioAsText = !!config.data && !!config.data.configuration ? config.data.configuration.interface.showStudioAsText : false; function maybeRenderRatingBanner() { if (!props.scene.rating) { return; } @@ -38,6 +43,43 @@ export const SceneCard: FunctionComponent = (props: ISceneCardP ); } + function maybeRenderSceneSpecsOverlay() { + return ( +
+ {!!props.scene.file.height ? {TextUtils.resolution(props.scene.file.height)} : undefined} + {props.scene.file.duration !== undefined && props.scene.file.duration >= 1 ? TextUtils.secondsToTimestamp(props.scene.file.duration) : ""} +
+ ); + } + + function maybeRenderSceneStudioOverlay() { + if (!props.scene.studio) { + return; + } + + let style: React.CSSProperties = { + backgroundImage: `url('${props.scene.studio.image_path}')`, + }; + + let text = ""; + + if (showStudioAsText) { + style = {}; + text = props.scene.studio.name; + } + + return ( +
+ + {text} + +
+ ); + } + function maybeRenderTagPopoverButton() { if (props.scene.tags.length <= 0) { return; } @@ -58,9 +100,20 @@ export const SceneCard: FunctionComponent = (props: ISceneCardP function maybeRenderPerformerPopoverButton() { if (props.scene.performers.length <= 0) { return; } - const performers = props.scene.performers.map((performer) => ( - - )); + const performers = props.scene.performers.map((performer) => { + return ( + <> +
+ + +
+ + ); + }); return (