{ "version": 3, "sources": ["../../card/image_card.tsx"], "sourcesContent": ["import * as React from \"react\"\nimport {observer} from \"mobx-react\"\nimport {classNames} from \"@cling/lib.web.utils\"\nimport {\n CardColor,\n BlobUID,\n Card as CardModel,\n CardUID,\n MediaTranscodeStatus,\n is_BlobUID,\n ImageCardDisplay,\n ImageCardDisplayKind,\n} from \"@cling/lib.shared.model\"\nimport {i18n} from \"@cling/lib.web.i18n\"\nimport {media_info_resource} from \"@cling/lib.web.resources\"\nimport {ui_actions, ui_state} from \"../state/index\"\nimport {is_provisional_thumbnail_url, thumbnail_url} from \"@cling/lib.web.resources/thumbnails\"\nimport {LoadingIndicator} from \"@cling/lib.web.lazy_load/loading_indicator\"\nimport {simple_tooltip_event_handlers} from \"@cling/lib.web.mdc/simple_tooltip\"\n\n/**\n * We strive for cards to reach this aspect ratio.\n * This is 3:2.\n */\nconst MAX_CARD_ASPECT_RATIO = 0.66\n\n/**\n * Up until this aspect ratio images are displayed in `cover` mode even if this means that we\n * loose some image information at the top and the bottom.\n * This is ok, as it results in the most pleasant experience and others (namely Google Photos)\n * do the same.\n * This is 16:9.\n */\nconst MAX_COVER_ASPECT_RATIO = 0.77\n\nconst loading = (\n <div className=\"image-card__loading\" data-test-thumbnail-state=\"waiting\">\n <LoadingIndicator delay={2000} />\n </div>\n)\n\nconst broken = (\n <div className=\"image-card__broken\">\n <div className=\"image-card__broken-icon\" />\n </div>\n)\n\nexport const ImageCard = observer(\n ({\n blob_uid_or_style,\n card,\n title,\n title_display,\n onClick,\n className,\n display,\n playable_media,\n }: {\n blob_uid_or_style: BlobUID | React.CSSProperties\n card: CardModel\n title?: any\n title_display?: \"cinema\" | \"banner\"\n onClick?: React.MouseEventHandler<HTMLElement>\n className?: string\n display: ImageCardDisplay\n playable_media?: boolean\n }) => {\n const dimension_elm = React.useRef<HTMLDivElement>(null)\n const [image_dim, set_image_dim] = React.useState<{\n width: number\n height: number\n playable_media: boolean\n }>()\n const blob_uid = is_BlobUID(blob_uid_or_style) ? blob_uid_or_style : undefined\n const media_info = blob_uid ? media_info_resource.read(blob_uid) : undefined\n React.useEffect(() => {\n if (blob_uid) {\n if (media_info) {\n set_image_dim({\n width: media_info.width,\n height: media_info.height,\n playable_media: !!media_info.transcode_status,\n })\n }\n } else {\n set_image_dim({width: 128, height: 128, playable_media: false})\n }\n }, [blob_uid, media_info])\n let padding_top\n let background_size: \"cover\" | \"contain\"\n let thumbnail_width\n let thumbnail_height\n if (!dimension_elm.current || !image_dim) {\n padding_top = \"50%\"\n background_size = \"cover\"\n thumbnail_width = 0\n thumbnail_height = 0\n } else {\n const card_width = dimension_elm.current.getBoundingClientRect().width\n const {width: image_width, height: image_height} = image_dim\n const device_pixel_ratio = Math.min(window.devicePixelRatio || 1, 3)\n thumbnail_width = Math.min(image_width, Math.round(card_width * device_pixel_ratio))\n thumbnail_height =\n display.kind === ImageCardDisplayKind.banner\n ? 512\n : Math.min(\n image_height,\n Math.round(card_width * device_pixel_ratio * MAX_CARD_ASPECT_RATIO),\n )\n const img_aspect_ratio =\n display.kind === ImageCardDisplayKind.cinema && display.cinema_forced_aspect_ratio\n ? display.cinema_forced_aspect_ratio\n : image_height / image_width\n const card_aspect_ratio = Math.min(img_aspect_ratio, MAX_CARD_ASPECT_RATIO)\n padding_top = card_aspect_ratio * 100 + \"%\"\n if (display.is_pattern) {\n background_size = \"contain\"\n } else {\n if (\n !image_dim.playable_media &&\n display.kind !== ImageCardDisplayKind.banner &&\n img_aspect_ratio >= MAX_COVER_ASPECT_RATIO\n ) {\n // Image is in \"portrait\" mode - i.e. it is much taller than wide.\n background_size = \"contain\"\n } else {\n // Image almost or perfectly fits the card - i.e. has (almost) the same\n // aspect ratio or it is a video.\n background_size = \"cover\"\n }\n }\n }\n const show_more = React.useCallback(\n (e: React.SyntheticEvent) => {\n e.stopPropagation()\n e.preventDefault()\n ui_actions.toggle_content_collapsed(card)\n },\n [card],\n )\n const condensed = ui_state.is_content_collapsed(card)\n return (\n <div\n className={classNames(\"image-card\", className, {\n \"image-card--condensed\": condensed,\n \"image-card--banner\": display.kind === ImageCardDisplayKind.banner,\n \"image-card--cinema\": display.kind === ImageCardDisplayKind.cinema,\n })}\n ref={dimension_elm}\n data-test-id=\"ImageCard\"\n >\n <div style={{paddingTop: padding_top}}>\n {playable_media && <div className=\"image-card__play\" onClick={onClick}></div>}\n <AsyncThumbnail\n card={card}\n blob_uid_or_style={blob_uid_or_style}\n onClick={onClick}\n thumbnail_width={thumbnail_width}\n thumbnail_height={thumbnail_height}\n background_size={background_size}\n background_repeat={display.is_pattern ? \"repeat\" : \"no-repeat\"}\n background_color={display.background_color}\n />\n </div>\n {title && (\n <div\n onClick={onClick}\n className={classNames(\"image-card__title\", {\n \"image-card__title--cinema\":\n !title_display || title_display === \"cinema\",\n \"image-card__title--banner\": title_display === \"banner\",\n \"image-card__title--with-color\": card.color !== CardColor.no_color,\n })}\n >\n {title}\n </div>\n )}\n {condensed && (\n <div className=\"action image-card__more\" onClick={show_more}>\n {i18n.more_expand}\n </div>\n )}\n </div>\n )\n },\n)\n\nexport const AsyncThumbnail = observer(\n ({\n blob_uid_or_style,\n card,\n onClick,\n thumbnail_height,\n thumbnail_width,\n background_size,\n background_repeat,\n background_color,\n }: {\n blob_uid_or_style: BlobUID | React.CSSProperties\n card?: {uid: CardUID; color: CardColor}\n onClick?: React.MouseEventHandler<HTMLElement>\n thumbnail_width: number\n thumbnail_height: number\n background_size: \"cover\" | \"contain\"\n background_repeat?: \"no-repeat\" | \"repeat\"\n background_color?: string\n }) => {\n const [prev_url, set_prev_url] = React.useState(\"\")\n const [url, set_url] = React.useState(\"\")\n const [is_provisional_thumbnail, set_is_provisional_thumbnail] = React.useState(false)\n const blob_uid = is_BlobUID(blob_uid_or_style) ? blob_uid_or_style : undefined\n let style: React.CSSProperties\n if (blob_uid) {\n const media_info = media_info_resource.read(blob_uid)\n if (media_info?.invalid) {\n return broken\n }\n if (!media_info || media_info.transcode_status === MediaTranscodeStatus.in_progress) {\n return loading\n }\n if (thumbnail_width === 0 || thumbnail_height === 0) {\n return loading\n }\n const new_url = thumbnail_url({\n blob_uid,\n width: thumbnail_width,\n height: thumbnail_height,\n })\n if (prev_url && new_url !== prev_url && is_provisional_thumbnail) {\n const img = new Image()\n img.onload = () => {\n set_prev_url(new_url)\n set_url(new_url)\n set_is_provisional_thumbnail(is_provisional_thumbnail_url(new_url))\n }\n img.src = new_url\n } else if (new_url !== prev_url) {\n set_prev_url(new_url)\n set_url(new_url)\n set_is_provisional_thumbnail(is_provisional_thumbnail_url(new_url))\n }\n if (!url) {\n return loading\n }\n style = {\n backgroundImage: `url(${url})`,\n }\n } else {\n style = blob_uid_or_style as React.CSSProperties\n }\n return (\n <>\n {background_size === \"contain\" && (\n <div className=\"image-card__portrait-background\" style={style} />\n )}\n <div\n onClick={onClick}\n data-blob-uid={blob_uid}\n className=\"image-card__img\"\n data-test-id=\"ImageCard_image\"\n data-test-thumbnail-state={is_provisional_thumbnail ? \"waiting\" : \"\"}\n style={{\n ...style,\n backgroundSize: background_size,\n backgroundRepeat: background_repeat,\n backgroundColor: background_color,\n }}\n />\n {card && card.color !== CardColor.no_color && (\n <div\n aria-label={i18n.card_color(card.color)}\n className={`image-card__card-color-marker card--color-${card.color}`}\n {...simple_tooltip_event_handlers}\n />\n )}\n </>\n )\n },\n)\n\nexport const ImageCardPreview = observer(\n ({image_url, badge}: {image_url: string; badge?: string}) => {\n return (\n <div className=\"card-details image-card image-card-preview\">\n <div>\n <div\n className=\"image-card__img image-card-preview__img\"\n style={{backgroundImage: `url(${image_url})`}}\n />\n {!!badge && <div className=\"image-card-preview__badge\">{badge}</div>}\n </div>\n </div>\n )\n },\n)\n"], "mappings": "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", "names": ["init_compat_module", "MAX_CARD_ASPECT_RATIO", "MAX_COVER_ASPECT_RATIO", "loading", "_", "LoadingIndicator", "broken", "ImageCard", "observer", "blob_uid_or_style", "card", "title", "title_display", "onClick", "className", "display", "playable_media", "dimension_elm", "A", "image_dim", "set_image_dim", "d", "blob_uid", "is_BlobUID", "media_info", "media_info_resource", "y", "padding_top", "background_size", "thumbnail_width", "thumbnail_height", "card_width", "image_width", "image_height", "device_pixel_ratio", "img_aspect_ratio", "show_more", "q", "e", "ui_actions", "condensed", "ui_state", "classNames", "AsyncThumbnail", "i18n", "background_repeat", "background_color", "prev_url", "set_prev_url", "url", "set_url", "is_provisional_thumbnail", "set_is_provisional_thumbnail", "style", "new_url", "thumbnail_url", "img", "is_provisional_thumbnail_url", "k", "simple_tooltip_event_handlers", "ImageCardPreview", "image_url", "badge"] }