Merge pull request #648 from storytold/entity-input-mobile-improvements

EntityInput mobile improvements
This commit is contained in:
Victor O authored and GitHub committed 2024-08-01 12:49:24 -07:00
commit d06e17d5ce
5 files changed
+199 -139

No files matched your search

@@ -0,0 +1,99 @@
@import "scss/colours.scss";
@import "scss/site-refresh.scss";
.fy-zoom-slider {
display: flex;
&.fy-zoom-slider-vertical {
height: 100%;
flex-direction: column;
.fy-zoom-slider-container {
height: 100%;
margin: 1.5rem 0;
}
.fy-zoom-slider-track {
height: 100%;
width: 2.25rem;
justify-content: center;
}
.fy-zoom-slider-track-bar {
width: 0.5rem;
height: 100%;
}
}
&.fy-zoom-slider-horizontal {
width: 100%;
flex-direction: row-reverse;
.fy-zoom-slider-container {
width: 100%;
margin: 0 1.5rem;
}
.fy-zoom-slider-track {
width: 100%;
height: 2.25rem;
align-items: center;
}
.fy-zoom-slider-track-bar {
height: 0.5rem;
width: 100%;
}
svg {
align-self: center;
}
}
svg {
color: rgba(#fff, 0.25);
cursor: pointer;
transition: color 0.25s ease-in-out;
&:hover {
color: rgba(#fff, 0.5);
}
}
.fy-zoom-slider-container {
height: 100%;
}
.fy-zoom-slider-track {
display: inline-flex;
.fy-zoom-slider-track-bar {
background-color: $secondary-darker;
border-radius: 0.25rem;
box-shadow: inset 0 5px 10px rgb(#000, 0.25);
border-bottom: 1px solid rgba(#fff, 0.07);
}
}
.fy-zoom-slider-thumb {
color: rgba(#fff, 0.6);
border: 1px solid rgba(#fff, 0.04);
border-left: none;
border-right: none;
box-shadow: 0 5px 10px rgb(#000, 0.25);
display: flex;
justify-content: center;
align-items: center;
font-size: 0.75rem;
background-color: $secondary;
border-radius: 0.75rem;
height: 2.25rem;
width: 2.25rem;
&:focus-visible {
background-color: $secondary-light;
outline: none;
}
}
}
@@ -5,13 +5,15 @@ import {
faMagnifyingGlassPlus,
faMagnifyingGlassMinus,
} from "@fortawesome/pro-solid-svg-icons";
import "./ZoomSlider.scss";
export interface ZoomSliderOnChangeEvent {
target: { name?: string; type: "zoom"; value: number };
}
interface Props {
interface ZoomSliderProps {
name?: string;
horizontal?: boolean;
onChange: (event: ZoomSliderOnChangeEvent) => void;
value: number;
}
@@ -35,37 +37,64 @@ const thumb =
export default function ZoomSlider({
name = "",
horizontal,
onChange: inChange,
value,
}: Props) {
}: ZoomSliderProps) {
const onChange = (newVal: number[]) =>
inChange({
target: { ...(name && { name }), type: "zoom", value: newVal[0] },
});
// react-easy-crop returns a zoom value from 1 to 3, so a range of 2 when starting with 0
const valueRange = 2;
const incrementDecrement = valueRange / 100;
return (
<div {...{ className: "fy-zoom-slider" }}>
<Icon {...{ icon: faMagnifyingGlassPlus }} />
<div
{...{
className: `fy-zoom-slider${
horizontal ? " fy-zoom-slider-horizontal" : " fy-zoom-slider-vertical"
}`,
}}
>
<Icon
{...{
icon: faMagnifyingGlassPlus,
onClick: () => {
if (value < 3) {
onChange([value + incrementDecrement]);
}
},
}}
/>
<div {...{ className: "fy-zoom-slider-container" }}>
<Range
{...{
direction: Direction.Up,
...(!horizontal ? { direction: Direction.Up } : {}),
min: 1,
max: 3,
onChange,
step: 0.01,
renderThumb: thumb(
Math.round(((value - 1) / 2) * 100).toString() + "%"
Math.round(((value - 1) / valueRange) * 100).toString() + "%"
),
renderTrack,
// renderTrack,
// step: step,
// thumbTip,
values: [value],
}}
/>
</div>
<Icon {...{ icon: faMagnifyingGlassMinus }} />
<Icon
{...{
icon: faMagnifyingGlassMinus,
onClick: () => {
if (value > 1) {
onChange([value - incrementDecrement]);
}
},
}}
/>
</div>
);
}
@@ -17,7 +17,7 @@
border-radius: 0.5rem;
overflow: hidden;
position: relative;
// background-color: $panel;
width: 100%;
.fy-entity-input-crop-container-hidden {
cursor: default;
@@ -38,6 +38,33 @@
0 0 15px rgb(#000, 0.5);
}
.fy-entity-input-full {
display: grid;
}
&.fy-entity-input-narrow {
min-height: 400px;
// background-color: red;
.fy-entity-input-full {
grid-template-columns: 1fr;
grid-template-rows: 1fr auto;
& > div {
width: 100%;
}
}
}
&.fy-entity-input-wide {
min-height: 300px;
// background-color: red;
.fy-entity-input-full {
grid-template-columns: 1fr fit-content(50%);
}
}
.fy-entity-input-play-button {
@include video-tint;
border: none;
@@ -61,14 +88,6 @@
}
}
.fy-entity-input-square {
aspect-ratio: 1/1;
height: 300px;
.fy-entity-input-empty-controls {
}
}
.fy-entity-input-landscape {
width: 100%;
aspect-ratio: 4/3;
@@ -77,16 +96,13 @@
.fy-entity-input-empty,
.fy-entity-input-full {
// background-color: red;
position: relative;
// overflow: hidden;
justify-content: center;
.fy-entity-input-icon {
position: absolute;
height: 80%;
opacity: 75%;
// background-color: blue;
margin: auto;
inset: 0;
@@ -97,8 +113,6 @@
.fy-entity-input-file-details {
padding: 0 0.5rem;
// background-color: red;
// display: inline-flex;
div {
font-size: 0.75rem;
}
@@ -121,70 +135,43 @@
margin: 0;
}
.fy-entity-input-full {
display: grid;
grid-template-columns: 1fr fit-content(50%);
// grid-template-rows: 1fr 1fr;
// display: flex;
// video {
// width: 100%;
// }
}
.fy-entity-input-media-preview {
// display: inline-block;
position: relative;
// width: 66%;
// height: 100%;
// width: 100%;
// aspect-ratio: 1;
padding: 0.5rem;
}
.fy-entity-input-preview-controls {
// background-color: green;
display: grid;
// background-color: red;
grid-template-rows: 1fr auto;
background-color: $secondary-dark;
gap: 0.5rem;
padding: 1rem;
}
.fy-entity-input-preview-tools {
.fy-entity-input-preview-tools-narrow,
.fy-entity-input-preview-tools-wide {
display: grid;
grid-template-columns: auto 1fr;
// grid-template-rows: 1fr auto;
// grid-template-columns: auto 1fr;
gap: 0.5rem;
// display: inline-block;
// width: 33%;
// height: 100%;
background-color: $secondary-dark;
// display: flex;
// align-items: center;
// justify-content: space-between;
// position: absolute;
// background-color: red;
// background-color: rgba(#000, 0.5);
// background: linear-gradient(
// 0deg,
// rgba(0, 0, 0, 0) 0%,
// rgba(0, 0, 0, 0.1) 6.25%,
// rgba(0, 0, 0, 0.25) 12.5%,
// rgba(0, 0, 0, 0.4) 18.75%,
// rgba(0, 0, 0, 0.5) 25%
// );
// padding: 1rem;
// backdrop-filter: blur(2px);
overflow: hidden;
border-radius: 0.5rem 0.5rem 0 0;
// width: 100%;
.fy-zoom-slider {
padding: 0.5rem 0;
}
}
.fy-entity-input-preview-tools-wide {
grid-template-columns: auto 1fr;
}
.fy-entity-input-preview-tools-narrow {
grid-template-rows: auto 1fr;
}
.fy-entity-input-empty-controls {
// background-color: red;
position: absolute;
@@ -230,69 +217,3 @@
}
}
}
.fy-zoom-slider {
display: flex;
flex-direction: column;
height: 100%;
// padding: 1rem 0;
svg {
color: rgba(#fff, 0.25);
}
}
.fy-zoom-slider-container {
height: 100%;
// background-color: red;
margin: 1.5rem 0;
// width: 3rem;
}
.fy-zoom-slider-track {
display: inline-flex;
justify-content: center;
height: 100%;
width: 2.25rem;
// background-color: green;
.fy-zoom-slider-track-bar {
background-color: $secondary-darker;
border-radius: 0.25rem;
// margin: 1rem 0;
// flex-shrink: 1;
box-shadow:
// inset 0 0 1px 1px rgba(#000, 0.16),
inset 0 5px 10px rgb(#000, 0.25);
border-bottom: 1px solid rgba(#fff, 0.07);
// border-left: none;
// border-right: none;
width: 0.5rem;
height: 100%;
}
}
.fy-zoom-slider-thumb {
color: rgba(#fff, 0.6);
border: 1px solid rgba(#fff, 0.04);
border-left: none;
border-right: none;
box-shadow:
// 0 0 0 1px rgba(#000, 0.16),
0 5px 10px rgb(#000, 0.25);
// @include panel;
display: flex;
justify-content: center;
align-items: center;
font-size: 0.75rem;
background-color: $secondary;
border-radius: 0.75rem;
height: 2.25rem;
width: 2.25rem;
&:focus-visible {
// color: $secondary;
background-color: $secondary-light;
outline: none;
}
}
@@ -1,6 +1,7 @@
import React, { useEffect, useState } from "react";
import { useLocation } from "react-router-dom";
import { a, useTransition } from "@react-spring/web";
import useMeasure from "react-use-measure";
import { MediaFile } from "@storyteller/components/src/api/media_files/GetMedia";
import Iframe from "react-iframe";
import { Area, Point } from "react-easy-crop";
@@ -32,7 +33,7 @@ export interface CropProps {
interface EntityInputProps {
accept?: AcceptTypes | AcceptTypes[];
aspectRatio?: "square" | "landscape" | "portrait";
// aspectRatio?: "square" | "landscape" | "portrait";
className?: string;
cropProps?: CropProps;
debug?: string;
@@ -47,6 +48,7 @@ interface EntityInputProps {
export interface SlideProps {
clear: () => void;
cropProps?: CropProps;
isNarrow: boolean;
media?: MediaFile;
}
@@ -63,7 +65,7 @@ const MediaBusy = () => {
return <Spinner />;
};
const EntityInputFull = ({ clear, cropProps, media }: SlideProps) => {
const EntityInputFull = ({ clear, cropProps, isNarrow, media }: SlideProps) => {
const bucketConfig = new BucketConfig();
const mediaUrl = media?.public_bucket_path
? bucketConfig.getGcsUrl(media.public_bucket_path)
@@ -94,6 +96,7 @@ const EntityInputFull = ({ clear, cropProps, media }: SlideProps) => {
{...{
clear,
entityType: "image",
isNarrow,
media,
showCrop: !!cropProps,
zoomSliderChange,
@@ -119,6 +122,7 @@ const EntityInputFull = ({ clear, cropProps, media }: SlideProps) => {
{...{
clear,
entityType: "video",
isNarrow,
media,
showCrop: !!cropProps,
zoomSliderChange,
@@ -166,7 +170,6 @@ const AnimatedSlide = ({
export default function EntityInput({
accept,
aspectRatio = "square",
className,
cropProps,
debug,
@@ -194,6 +197,10 @@ export default function EntityInput({
mediaToken: mediaToken || value,
});
const [updated, updatedSet] = useState(false);
const [outerRef, { width }] = useMeasure();
const isNarrow = (width || 0) <= 480;
const clear = () => {
mediaSet(undefined);
mediaTokenSet("");
@@ -260,9 +267,10 @@ export default function EntityInput({
<Label {...{ label }} />
<div
{...{
className: `fy-entity-input panel-inner${
aspectRatio ? " fy-entity-input-" + aspectRatio : ""
}${className ? " " + className : ""}`,
className: `fy-entity-input ${className ? " " + className : ""}${
isNarrow ? " fy-entity-input-narrow" : " fy-entity-input-wide"
}`,
ref: outerRef,
}}
>
{transitions((style: any, i: number, state: any) => {
@@ -283,6 +291,7 @@ export default function EntityInput({
className: "fy-entity-input-full",
clear,
cropProps,
isNarrow,
media,
render: EntityInputFull,
...sharedProps,
@@ -5,6 +5,7 @@ import { Button, ZoomSlider, ZoomSliderOnChangeEvent } from "components/common";
interface Props {
clear: () => void;
entityType: "image" | "video";
isNarrow: boolean;
media?: MediaFile;
showCrop: boolean;
zoom: number;
@@ -14,6 +15,7 @@ interface Props {
export default function EntityInputSidePanel({
clear,
entityType,
isNarrow,
media,
showCrop,
zoomSliderChange,
@@ -24,9 +26,9 @@ export default function EntityInputSidePanel({
}`;
return (
<div {...{ className: "fy-entity-input-preview-controls" }}>
<div {...{ className: "fy-entity-input-preview-tools" }}>
<div {...{ className: `fy-entity-input-preview-tools${ isNarrow ? "-narrow" : "-wide" }` }}>
{showCrop && (
<ZoomSlider {...{ onChange: zoomSliderChange, value: zoom }} />
<ZoomSlider {...{ ...isNarrow ? {horizontal: true } : {}, onChange: zoomSliderChange, value: zoom }} />
)}
<div {...{ className: "fy-entity-input-file-details" }}>