diff --git a/client/src/components/tapestry-elements/item-toolbar/change-thumbnail-button/index.tsx b/client/src/components/tapestry-elements/item-toolbar/change-thumbnail-button/index.tsx index c8d77ac..c0036bd 100644 --- a/client/src/components/tapestry-elements/item-toolbar/change-thumbnail-button/index.tsx +++ b/client/src/components/tapestry-elements/item-toolbar/change-thumbnail-button/index.tsx @@ -4,7 +4,6 @@ import { SimpleModal } from 'tapestry-core-client/src/components/lib/modal/index import styles from './styles.module.css' import { Icon } from 'tapestry-core-client/src/components/lib/icon/index' import { Text } from 'tapestry-core-client/src/components/lib/text/index' -import { hasThumbnail } from 'tapestry-core/src/utils' import { FilePicker } from 'tapestry-core-client/src/components/lib/file-picker/index' import { CSSProperties, useState } from 'react' import { DropArea } from 'tapestry-core-client/src/components/lib/drop-area' @@ -17,6 +16,7 @@ import { itemSizes, uploadAsset } from '../../../../model/data/utils' import { LoadingSpinner } from 'tapestry-core-client/src/components/lib/loading-spinner/index' import { compressImage, getImageSize } from '../../../../lib/media' import { Size } from 'tapestry-core/src/data-format/schemas/common' +import { pick } from 'lodash-es' import { aspectRatio } from 'tapestry-core/src/lib/geometry' interface NoCustomThumbnailOptionProps { @@ -55,25 +55,14 @@ export function ChangeThumbnailButton({ onClick, className }: ChangeThumbnailBut } interface ItemThumbnailProps { - item: ItemDto + thumbnail?: string | null className?: string - useAutoGenerated: boolean customBlobUrl: string | null style?: CSSProperties } -function ItemThumbnail({ - item, - className, - useAutoGenerated, - customBlobUrl, - style, -}: ItemThumbnailProps) { - const autoThumbnail = hasThumbnail(item) ? item.thumbnail.source : null - - const src = useAutoGenerated - ? autoThumbnail - : (customBlobUrl ?? item.customThumbnail ?? autoThumbnail) +function ItemThumbnail({ thumbnail, className, customBlobUrl, style }: ItemThumbnailProps) { + const src = customBlobUrl ?? thumbnail return src ? Thumbnail preview : null } @@ -89,6 +78,15 @@ async function getNewItemSize(item: ItemDto, thumbnail?: File) { } } +async function getImageDimensions(file: File): Promise { + const objectUrl = URL.createObjectURL(file) + const img = new Image() + img.src = objectUrl + await img.decode() + URL.revokeObjectURL(objectUrl) + return pick(img, 'width', 'height') +} + interface ChangeThumbnailDialogProps { onClose: () => unknown item: ItemDto @@ -96,27 +94,30 @@ interface ChangeThumbnailDialogProps { export function ChangeThumbnailDialog({ onClose, item }: ChangeThumbnailDialogProps) { const [useAutoGenerated, setUseAutoGenerated] = useState(false) - const [selectedFile, setSelectedFile] = useState() + const [selectedFile, setSelectedFile] = useState<{ file: File; size: Size }>() const [customThumbnailItemSize, setCustomThumbnailItemSize] = useState() - const blobUrl = useMediaSource(selectedFile ?? item.customThumbnail ?? null) + const customThumbnail = item.thumbnail?.renditions.find((r) => !r.isAutoGenerated) + const thumbnail = customThumbnail ?? item.thumbnail?.renditions.find((r) => r.isPrimary) + const blobUrl = useMediaSource(selectedFile?.file ?? customThumbnail?.source ?? null) const onNewFile = async (file: File) => { const newItemSize = await getNewItemSize(item, file) setCustomThumbnailItemSize(newItemSize) const compressed = await compressImage(file, newItemSize) + const size = await getImageDimensions(compressed) setUseAutoGenerated(false) - setSelectedFile(compressed) + setSelectedFile({ file: compressed, size }) } const { trigger: save, loading } = useAsyncAction(async ({ signal }) => { const newSize = useAutoGenerated ? await getNewItemSize(item) : customThumbnailItemSize if (useAutoGenerated) { - if (item.customThumbnail) { + if (customThumbnail) { await resource('items').update( { id: item.id }, { type: item.type, - customThumbnail: null, + thumbnail: null, ...(newSize ? { size: newSize } : {}), }, undefined, @@ -128,7 +129,7 @@ export function ChangeThumbnailDialog({ onClose, item }: ChangeThumbnailDialogPr } if (selectedFile) { const key = await uploadAsset( - selectedFile, + selectedFile.file, { tapestryId: item.tapestryId, type: 'tapestry-asset', @@ -139,7 +140,10 @@ export function ChangeThumbnailDialog({ onClose, item }: ChangeThumbnailDialogPr { id: item.id }, { type: item.type, - customThumbnail: key, + thumbnail: { + source: key, + size: selectedFile.size, + }, ...(newSize ? { size: newSize } : {}), }, undefined, @@ -158,8 +162,7 @@ export function ChangeThumbnailDialog({ onClose, item }: ChangeThumbnailDialogPr
setUseAutoGenerated(true)} > - {hasThumbnail(item) ? ( - - ) : null} + {thumbnail ? : null}
diff --git a/client/src/components/tapestry-elements/items/tapestry-item/index.tsx b/client/src/components/tapestry-elements/items/tapestry-item/index.tsx index 7f7c269..97f128d 100644 --- a/client/src/components/tapestry-elements/items/tapestry-item/index.tsx +++ b/client/src/components/tapestry-elements/items/tapestry-item/index.tsx @@ -3,11 +3,8 @@ import { ReactNode } from 'react' import { useObservable } from 'tapestry-core-client/src/components/lib/hooks/use-observable' import { TapestryItem as BaseTapestryItem } from 'tapestry-core-client/src/components/tapestry/items/tapestry-item' import { THEMES } from 'tapestry-core-client/src/theme/themes' -import { - computeRestrictedScale, - positionAtViewport, -} from 'tapestry-core-client/src/view-model/utils' -import { ORIGIN, Rectangle, scaleSize, Size } from 'tapestry-core/src/lib/geometry' +import { computeRestrictedScale } from 'tapestry-core-client/src/view-model/utils' +import { Size } from 'tapestry-core/src/lib/geometry' import { idMapToArray } from 'tapestry-core/src/utils' import { useDispatch, useTapestryData } from '../../../../pages/tapestry/tapestry-providers' import { @@ -34,20 +31,12 @@ export function TapestryItem({ id, children, halo }: TapestryItemProps) { interactiveElement, interactionMode, theme: themeName, - viewport, } = useTapestryData(['interactiveElement', 'interactionMode', 'theme', 'viewport']) const dispatch = useDispatch() const isEditMode = interactionMode === 'edit' const isContentInteractive = id === interactiveElement?.modelId - const viewportRect = new Rectangle( - positionAtViewport(viewport, ORIGIN), - scaleSize(viewport.size, 1 / viewport.transform.scale), - ) - - const isVisible = viewportRect.intersects(new Rectangle(dto)) - // @ts-expect-error TS wants us to check for a media item const item = useObservable(itemUpload).find((i) => i.objectUrl === dto.source) @@ -59,7 +48,6 @@ export function TapestryItem({ id, children, halo }: TapestryItemProps) { root: styles.root, hitArea: styles.hitArea, }} - style={!isVisible ? { display: 'none' } : undefined} title={ <> {dto.title} diff --git a/client/src/components/tapestry-elements/items/webpage/index.tsx b/client/src/components/tapestry-elements/items/webpage/index.tsx index e7779c6..7638925 100644 --- a/client/src/components/tapestry-elements/items/webpage/index.tsx +++ b/client/src/components/tapestry-elements/items/webpage/index.tsx @@ -10,7 +10,6 @@ import { SimpleMenuItem } from 'tapestry-core-client/src/components/lib/toolbar' import { ALLOWED_ORIGINS, getPlaybackInterval, - WebFrameProps, WebpageItemViewer, WebpageItemViewerApi, } from 'tapestry-core-client/src/components/tapestry/items/webpage/viewer' @@ -28,12 +27,16 @@ import { PlayableShareMenu, shareMenu } from '../../item-toolbar/share-menu' import { useItemToolbar } from '../../item-toolbar/use-item-toolbar' import { TapestryItem } from '../tapestry-item' import styles from './styles.module.css' +import { + WebFrame, + WebFrameSwitchProps, +} from 'tapestry-core-client/src/components/tapestry/items/webpage/web-frame' const checkedSources = new Map() const PLAYABLE_WEBPAGE_TYPES: WebpageType[] = ['iaAudio', 'iaVideo', 'vimeo', 'youtube'] -function Webpage({ src, onLoad, ...props }: WebFrameProps) { +function Webpage({ src, onLoad, ...props }: WebFrameSwitchProps) { const onLoadRef = usePropRef(onLoad) const interactionMode = useTapestryData('interactionMode') const checkCanFrame = interactionMode === 'edit' @@ -70,7 +73,7 @@ function Webpage({ src, onLoad, ...props }: WebFrameProps) { ) return canFrame ? ( -