WIP: MediaQuickview: rewritten full-screen media viewer #50
					 2 changed files with 61 additions and 7 deletions
				
			
		| 
						 | 
					@ -47,8 +47,6 @@
 | 
				
			||||||
      max-width: 100vw;
 | 
					      max-width: 100vw;
 | 
				
			||||||
      max-height: 100vh;
 | 
					      max-height: 100vh;
 | 
				
			||||||
      contain: strict;
 | 
					      contain: strict;
 | 
				
			||||||
      display: grid;
 | 
					 | 
				
			||||||
      place-items: center;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      cursor: grab;
 | 
					      cursor: grab;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -56,19 +56,39 @@ export function createMediaQuickview() {
 | 
				
			||||||
  return createCallback(renderIsolateMediaQuickview);
 | 
					  return createCallback(renderIsolateMediaQuickview);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type VisualMediaMeta = {
 | 
				
			||||||
 | 
					  width: number;
 | 
				
			||||||
 | 
					  height: number;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function ImagePage(props: {
 | 
					function ImagePage(props: {
 | 
				
			||||||
  src: string;
 | 
					  src: string;
 | 
				
			||||||
  alt?: string;
 | 
					  alt?: string;
 | 
				
			||||||
  scale: number;
 | 
					  scale: number;
 | 
				
			||||||
  offsetX: number;
 | 
					  offsetX: number;
 | 
				
			||||||
  offsetY: number;
 | 
					  offsetY: number;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  onMetadata?(metadata: VisualMediaMeta): void;
 | 
				
			||||||
}) {
 | 
					}) {
 | 
				
			||||||
 | 
					  const [loaded, setLoaded] = createSignal(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <img
 | 
					    <img
 | 
				
			||||||
 | 
					      onLoad={({ currentTarget }) => {
 | 
				
			||||||
 | 
					        const { naturalHeight, naturalWidth } = currentTarget;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        props.onMetadata?.({
 | 
				
			||||||
 | 
					          width: naturalWidth,
 | 
				
			||||||
 | 
					          height: naturalHeight,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        setLoaded(true);
 | 
				
			||||||
 | 
					      }}
 | 
				
			||||||
      src={props.src}
 | 
					      src={props.src}
 | 
				
			||||||
      alt={props.alt}
 | 
					      alt={props.alt}
 | 
				
			||||||
      style={{
 | 
					      style={{
 | 
				
			||||||
        transform: `scale(${props.scale}) translateX(${-props.offsetX}px) translateY(${-props.offsetY}px)`,
 | 
					        transform: `scale(${props.scale}) translateX(${-props.offsetX}px) translateY(${-props.offsetY}px)`,
 | 
				
			||||||
 | 
					        opacity: loaded() ? 1 : 0,
 | 
				
			||||||
      }}
 | 
					      }}
 | 
				
			||||||
    ></img>
 | 
					    ></img>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
| 
						 | 
					@ -88,10 +108,6 @@ export type MediaQuickviewProps = {
 | 
				
			||||||
  onClose?(): void;
 | 
					  onClose?(): void;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function clamp<T extends number>(value: T, min: T, max: T) {
 | 
					 | 
				
			||||||
  return Math.max(Math.min(value, max), min);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const MediaQuickview: Component<MediaQuickviewProps> = (props) => {
 | 
					const MediaQuickview: Component<MediaQuickviewProps> = (props) => {
 | 
				
			||||||
  let root: HTMLDialogElement;
 | 
					  let root: HTMLDialogElement;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -126,6 +142,11 @@ const MediaQuickview: Component<MediaQuickviewProps> = (props) => {
 | 
				
			||||||
       * positive = the top edge move towards top
 | 
					       * positive = the top edge move towards top
 | 
				
			||||||
       */
 | 
					       */
 | 
				
			||||||
      offsetY: number;
 | 
					      offsetY: number;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      size?: {
 | 
				
			||||||
 | 
					        width: number;
 | 
				
			||||||
 | 
					        height: number;
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
    }[],
 | 
					    }[],
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -185,7 +206,7 @@ const MediaQuickview: Component<MediaQuickviewProps> = (props) => {
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    const dx = movOriginX - event.clientX;
 | 
					    const dx = movOriginX - event.clientX;
 | 
				
			||||||
    const dy = movOriginY - event.clientY ;
 | 
					    const dy = movOriginY - event.clientY;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    setTransformations(index, ({ offsetX, offsetY }) => {
 | 
					    setTransformations(index, ({ offsetX, offsetY }) => {
 | 
				
			||||||
      return {
 | 
					      return {
 | 
				
			||||||
| 
						 | 
					@ -202,6 +223,40 @@ const MediaQuickview: Component<MediaQuickviewProps> = (props) => {
 | 
				
			||||||
    setIsMoving(false);
 | 
					    setIsMoving(false);
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const recenter = (index: number) => {
 | 
				
			||||||
 | 
					    const sz = transformations[index].size;
 | 
				
			||||||
 | 
					    if (!sz) return;
 | 
				
			||||||
 | 
					    const { width, height } = sz;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const xscale = Math.min(window.innerWidth / width, 1);
 | 
				
			||||||
 | 
					    const yscale = Math.min(window.innerHeight / height, 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const finalScale = Math.min(xscale, yscale);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const nheight = height * finalScale;
 | 
				
			||||||
 | 
					    const top = (window.innerHeight - nheight) / 2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const nwidth = width * finalScale;
 | 
				
			||||||
 | 
					    const left = (window.innerWidth - nwidth) / 2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    setTransformations(index, {
 | 
				
			||||||
 | 
					      scale: finalScale,
 | 
				
			||||||
 | 
					      offsetX: -left,
 | 
				
			||||||
 | 
					      offsetY: -top,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const onMetadata = (index: number, { width, height }: VisualMediaMeta) => {
 | 
				
			||||||
 | 
					    setTransformations(index, {
 | 
				
			||||||
 | 
					      size: {
 | 
				
			||||||
 | 
					        width,
 | 
				
			||||||
 | 
					        height,
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    recenter(index);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <dialog
 | 
					    <dialog
 | 
				
			||||||
      ref={(e) => {
 | 
					      ref={(e) => {
 | 
				
			||||||
| 
						 | 
					@ -254,6 +309,7 @@ const MediaQuickview: Component<MediaQuickviewProps> = (props) => {
 | 
				
			||||||
                  <Switch>
 | 
					                  <Switch>
 | 
				
			||||||
                    <Match when={item().cat === "image"}>
 | 
					                    <Match when={item().cat === "image"}>
 | 
				
			||||||
                      <ImagePage
 | 
					                      <ImagePage
 | 
				
			||||||
 | 
					                        onMetadata={(m) => onMetadata(index, m)}
 | 
				
			||||||
                        src={item().src}
 | 
					                        src={item().src}
 | 
				
			||||||
                        alt={item().alt}
 | 
					                        alt={item().alt}
 | 
				
			||||||
                        scale={transformationGetOrSetDefault(index).scale}
 | 
					                        scale={transformationGetOrSetDefault(index).scale}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue