{"version":3,"file":"FileViewer.cjs","sources":["../../../src/components/FileViewer/FileViewer.tsx"],"sourcesContent":["'use client'\n\nimport Decimal from 'decimal.js'\nimport {\n  type ComponentProps,\n  type FC,\n  memo,\n  useCallback,\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n} from 'react'\n\nimport {\n  Button,\n  Cluster,\n  DropdownMenuButton,\n  FaArrowRotateLeftIcon,\n  FaMagnifyingGlassMinusIcon,\n  FaMagnifyingGlassPlusIcon,\n  Loader,\n  Scroller,\n  Text,\n  VisuallyHiddenText,\n} from '../..'\nimport { Localizer } from '../../intl'\n\nimport { ImageViewer } from './ImageViewer'\nimport { PDFViewer } from './PDFViewer'\n\nimport type { FileForViewer } from './types'\n\nconst defaultScaleStep = new Decimal(0.2)\nconst defaultScaleSteps = [0.2, 0.6, 1, 1.6, 2, 3]\n\ntype Props = {\n  file: FileForViewer\n  width?: number\n\n  /*\n   * 拡大縮小率のステップを、100%を1とした配列で指定します。\n   * */\n  scaleSteps?: number[]\n\n  scaleStep?: number\n  onPassword?: ComponentProps<typeof PDFViewer>['onPassword']\n  onLoadError?: () => void\n}\n\nexport const FileViewer: FC<Props> = ({\n  file,\n  scaleStep,\n  scaleSteps,\n  width: fixedWidth,\n  onPassword,\n  onLoadError,\n}) => {\n  const ref = useRef<HTMLDivElement>(null)\n  const [scale, setScale] = useState(1)\n  const [loaded, setLoaded] = useState(false)\n  const [rotation, setRotation] = useState<number | undefined>(undefined)\n  const [width, setWidth] = useState(fixedWidth ?? 0)\n\n  const internalScaleStep = useMemo(\n    () => (scaleStep ? new Decimal(scaleStep) : defaultScaleStep),\n    [scaleStep],\n  )\n\n  const scaleUp = useCallback(() => {\n    setScale((currentScale) => new Decimal(currentScale).add(internalScaleStep).toNumber())\n  }, [internalScaleStep])\n\n  const scaleDown = useCallback(() => {\n    setScale((currentScale) => new Decimal(currentScale).sub(internalScaleStep).toNumber())\n  }, [internalScaleStep])\n\n  const rotate = useCallback(() => {\n    // HINT: react-pdf側のAnnotationLayer.cssではマイナスの回転に対応しておらず、また0, 90, 180, 270度のみ対応しているため、-90度の場合は+270度として扱う\n    const currentRotation = rotation ?? 0\n    const newRotation = currentRotation === 0 ? 270 : currentRotation - 90\n    setRotation(newRotation)\n  }, [rotation])\n\n  const handleLoaded = useCallback(() => {\n    setLoaded(true)\n  }, [])\n\n  const handlePDFLoaded = useCallback((defaultRotation: number) => {\n    setRotation(defaultRotation)\n  }, [])\n\n  useEffect(() => {\n    if (!ref.current || fixedWidth !== undefined) {\n      return\n    }\n\n    const resizeObserver = new ResizeObserver(() => {\n      setWidth((ref.current?.clientWidth ?? 0) - 64)\n    })\n\n    resizeObserver.observe(ref.current)\n\n    return () => {\n      resizeObserver.disconnect()\n    }\n  }, [fixedWidth])\n\n  return (\n    <Scroller\n      direction=\"both\"\n      className=\"shr-flex shr-h-full shr-w-full shr-flex-col shr-gap-2 shr-bg-scrim shr-bg-[radial-gradient(theme(textColor.black)_1px,_transparent_0)] shr-bg-[length:16px_16px]\"\n      ref={ref}\n    >\n      <div className=\"shr-sticky shr-start-0 shr-top-0 shr-z-[1] shr-flex shr-w-full shr-flex-shrink-0 shr-gap-0.5\">\n        <Controller\n          scale={scale}\n          setScale={setScale}\n          scaleSteps={scaleSteps || defaultScaleSteps}\n          onClickScaleUpButton={scaleUp}\n          onClickScaleDownButton={scaleDown}\n          onClickRotateButton={rotate}\n        />\n      </div>\n      <div className=\"shr-z-[0] shr-mx-auto shr-my-0 shr-box-border shr-flex shr-w-fit shr-flex-shrink-0 shr-grow shr-items-center shr-justify-center shr-px-2 shr-pb-2\">\n        {!loaded && (\n          <div className=\"shr-pointer-events-none shr-fixed shr-inset-0 shr-flex shr-h-full shr-w-full shr-items-center shr-justify-center\">\n            <Loader type=\"light\" size=\"M\" />\n          </div>\n        )}\n        <div className={!loaded ? 'shr-invisible' : ''}>\n          {file.contentType === 'application/pdf' ? (\n            <PDFViewer\n              scale={scale}\n              rotation={rotation}\n              file={file}\n              width={width}\n              onLoad={handleLoaded}\n              onPDFLoaded={handlePDFLoaded}\n              onPassword={onPassword}\n              onLoadError={onLoadError}\n            />\n          ) : file.contentType.startsWith('image/') ? (\n            <ImageViewer\n              scale={scale}\n              rotation={rotation}\n              file={file}\n              width={width}\n              onLoad={handleLoaded}\n              onLoadError={onLoadError}\n            />\n          ) : (\n            <Text>\n              <Localizer\n                id=\"smarthr-ui/FileViewer/unsupportedFileText\"\n                defaultText=\"サポートされていない形式のファイルです。\"\n              />\n            </Text>\n          )}\n        </div>\n      </div>\n    </Scroller>\n  )\n}\n\ntype ControllerProps = {\n  scale: number\n  setScale: (scale: number) => void\n  scaleSteps: number[]\n  onClickScaleUpButton: () => void\n  onClickScaleDownButton: () => void\n  onClickRotateButton: () => void\n}\n\nconst Controller: FC<ControllerProps> = memo(\n  ({\n    scale,\n    setScale,\n    scaleSteps,\n    onClickScaleUpButton,\n    onClickScaleDownButton,\n    onClickRotateButton,\n  }) => (\n    <div className=\"shr-sticky shr-flex shr-w-full shr-items-center shr-justify-center shr-justify-items-center shr-gap-0.5 shr-bg-scrim shr-p-0.5 shr-shadow-layer-1\">\n      <Cluster gap={0.5}>\n        <div className=\"shr-border-shorthand shr-flex shr-divide-x shr-divide-solid shr-overflow-hidden shr-rounded-m\">\n          <Button\n            onClick={onClickScaleDownButton}\n            disabled={scale <= scaleSteps[0]}\n            className=\"shr-rounded-r-none shr-border-none\"\n          >\n            <FaMagnifyingGlassMinusIcon\n              alt={<Localizer id=\"smarthr-ui/FileViewer/scaleDownAlt\" defaultText=\"縮小\" />}\n            />\n          </Button>\n          <DropdownMenuButton\n            trigger={\n              <Text>\n                <VisuallyHiddenText>\n                  <Localizer id=\"smarthr-ui/FileViewer/scaleRateLabel\" defaultText=\"拡大率\" />\n                </VisuallyHiddenText>\n                {`${(scale * 100).toFixed(0)}%`}\n              </Text>\n            }\n            className=\"shr-border-y-0 shr-border-[theme(borderColor.default)] [&_.smarthr-ui-Button]:shr-rounded-none [&_.smarthr-ui-Button]:shr-border-[transparent]\"\n          >\n            {scaleSteps.map((step) => (\n              <Button\n                key={step.toString()}\n                onClick={() => setScale(step)}\n                className=\"shr-rounded-none shr-border-0\"\n              >\n                {`${(step * 100).toFixed(0)}%`}\n              </Button>\n            ))}\n          </DropdownMenuButton>\n          <Button onClick={onClickScaleUpButton} className=\"shr-rounded-l-none shr-border-0\">\n            <FaMagnifyingGlassPlusIcon\n              alt={<Localizer id=\"smarthr-ui/FileViewer/scaleUpAlt\" defaultText=\"拡大\" />}\n            />\n          </Button>\n        </div>\n        <Button onClick={onClickRotateButton} className=\"shr-p-0.75\">\n          <FaArrowRotateLeftIcon\n            alt={<Localizer id=\"smarthr-ui/FileViewer/rotateAlt\" defaultText=\"左回転\" />}\n          />\n        </Button>\n      </Cluster>\n    </div>\n  ),\n)\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiCA;AACA;;AAwBE;;;;AAIA;;AAOA;;AAEA;AAEA;;AAEA;AAEA;;AAEE;AACA;;AAEF;AAEA;;;AAIA;;;;;;;AASE;AACE;AACF;AAEA;AAEA;;AAEA;AACF;;AAyDF;AAWA;;"}