feat: pasting image/video urls

This commit is contained in:
Peng Xiao
2022-08-31 13:25:38 +08:00
parent 318c188eff
commit 9f58518318
4 changed files with 51 additions and 19 deletions

View File

@@ -3,6 +3,7 @@ import {
getSizeFromSrc,
TLAsset,
TLBinding,
TLCursor,
TLShapeModel,
uniqueId,
validUUID,
@@ -38,6 +39,10 @@ const safeParseJson = (json: string) => {
}
}
const IMAGE_EXTENSIONS = ['.png', '.svg', '.jpg', '.jpeg', '.gif']
const VIDEO_EXTENSIONS = ['.mp4', '.webm', '.ogg']
// FIXME: for assets, we should prompt the user a loading spinner
export function usePaste(context: LogseqContextValue) {
const { handlers } = context
@@ -56,11 +61,32 @@ export function usePaste(context: LogseqContextValue) {
return await handlers.saveAsset(file)
}
async function handleAssetUrl(url: string, isVideo: boolean) {
// Do we already have an asset for this image?
const existingAsset = Object.values(app.assets).find(asset => asset.src === url)
if (existingAsset) {
imageAssetsToCreate.push(existingAsset as VideoImageAsset)
return true
} else {
try {
// Create a new asset for this image
const asset: VideoImageAsset = {
id: uniqueId(),
type: isVideo ? 'video' : 'image',
src: url,
size: await getSizeFromSrc(handlers.makeAssetUrl(url), isVideo),
}
imageAssetsToCreate.push(asset)
return true
} finally {
return false
}
}
}
// TODO: handle PDF?
async function handleFiles(files: File[]) {
const IMAGE_EXTENSIONS = ['.png', '.svg', '.jpg', '.jpeg', '.gif']
const VIDEO_EXTENSIONS = ['.mp4', '.webm', '.ogg']
let added = false
for (const file of files) {
// Get extension, verify that it's an image
const extensionMatch = file.name.match(/\.[0-9a-z]+$/i)
@@ -78,24 +104,14 @@ export function usePaste(context: LogseqContextValue) {
if (!dataurl) {
continue
}
// Do we already have an asset for this image?
const existingAsset = Object.values(app.assets).find(asset => asset.src === dataurl)
if (existingAsset) {
imageAssetsToCreate.push(existingAsset as VideoImageAsset)
continue
if (await handleAssetUrl(dataurl, isVideo)) {
added = true
}
// Create a new asset for this image
const asset: VideoImageAsset = {
id: uniqueId(),
type: isVideo ? 'video' : 'image',
src: dataurl,
size: await getSizeFromSrc(handlers.makeAssetUrl(dataurl), isVideo),
}
imageAssetsToCreate.push(asset)
} catch (error) {
console.error(error)
}
}
return added
}
async function handleHTML(item: ClipboardItem) {
@@ -118,7 +134,7 @@ export function usePaste(context: LogseqContextValue) {
const blob = await item.getType('text/plain')
const rawText = (await blob.text()).trim()
if (handleURL(rawText)) {
if (await handleURL(rawText)) {
return true
}
@@ -204,7 +220,7 @@ export function usePaste(context: LogseqContextValue) {
return false
}
function handleURL(rawText: string) {
async function handleURL(rawText: string) {
if (isValidURL(rawText)) {
const isYoutubeUrl = (url: string) => {
const youtubeRegex =
@@ -219,6 +235,14 @@ export function usePaste(context: LogseqContextValue) {
})
return true
}
const extension = rawText.match(/\.[0-9a-z]+$/i)?.[0].toLowerCase()
if (
extension &&
[...IMAGE_EXTENSIONS, ...VIDEO_EXTENSIONS].includes(extension) &&
(await handleAssetUrl(rawText, VIDEO_EXTENSIONS.includes(extension)))
) {
return true
}
// ??? deal with normal URLs?
}
return false
@@ -279,6 +303,8 @@ export function usePaste(context: LogseqContextValue) {
return false
}
app.cursors.setCursor(TLCursor.Progress)
try {
if (files && files.length > 0) {
await handleFiles(files)
@@ -324,6 +350,7 @@ export function usePaste(context: LogseqContextValue) {
app.currentPage.updateBindings(Object.fromEntries(bindingsToCreate.map(b => [b.id, b])))
app.setSelectedShapes(allShapesToAdd.map(s => s.id))
})
app.cursors.setCursor(TLCursor.Default)
},
[]
)

View File

@@ -5,6 +5,8 @@ export enum TLCursor {
Cross = 'crosshair',
Grab = 'grab',
Rotate = 'rotate',
Wait = 'wait',
Progress = 'progress',
Grabbing = 'grabbing',
ResizeEdge = 'resize-edge',
ResizeCorner = 'resize-corner',

View File

@@ -77,7 +77,7 @@ export function fileToBase64(file: Blob): Promise<string | ArrayBuffer | null> {
}
export function getSizeFromSrc(dataURL: string, isVideo: boolean): Promise<number[]> {
return new Promise(resolve => {
return new Promise((resolve, reject) => {
if (isVideo) {
const video = document.createElement('video')
@@ -100,6 +100,7 @@ export function getSizeFromSrc(dataURL: string, isVideo: boolean): Promise<numbe
const img = new Image()
img.onload = () => resolve([img.width, img.height])
img.src = dataURL
img.onerror = err => reject(err)
}
})
}

View File

@@ -24,6 +24,8 @@ const CURSORS: Record<TLCursor, (r: number, f?: boolean) => string> = {
[TLCursor.Pointer]: (r, f) => 'pointer',
[TLCursor.Cross]: (r, f) => 'crosshair',
[TLCursor.Move]: (r, f) => 'move',
[TLCursor.Wait]: (r, f) => 'wait',
[TLCursor.Progress]: (r, f) => 'progress',
[TLCursor.Grab]: (r, f) => getCursorCss(GRAB_SVG, r, f),
[TLCursor.Grabbing]: (r, f) => getCursorCss(GRABBING_SVG, r, f),
[TLCursor.Text]: (r, f) => getCursorCss(TEXT_SVG, r, f),