mirror of https://github.com/kern/filepizza
Add retrieve code mode support. (e.g. Like send anywhere)
If enable retrieve mode (NEXT_PUBLIC_RETRIEVE_CODE_MODE=true), it allows user to get retrieve code to be used to download the file directly from home page. BTW, hide long url & short url download links for clean layout.pull/189/head
parent
b79363b750
commit
adabe57cd8
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,19 @@
|
|||||||
|
import React, { JSX } from 'react'
|
||||||
|
import { ClipboardIcon } from '@heroicons/react/24/outline'
|
||||||
|
import useClipboard from '../hooks/useClipboard'
|
||||||
|
|
||||||
|
export function CopyableIcon({ value }: { value: string }): JSX.Element {
|
||||||
|
const { hasCopied, onCopy } = useClipboard(value)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative flex items-center">
|
||||||
|
<ClipboardIcon
|
||||||
|
onClick={onCopy}
|
||||||
|
className="w-6 h-6 text-gray-500 cursor-pointer hover:text-gray-600"
|
||||||
|
/>
|
||||||
|
{hasCopied && (
|
||||||
|
<span className="absolute ml-8 text-sm text-green-500">Copied!</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -0,0 +1,113 @@
|
|||||||
|
import React, { JSX, useRef, useState } from 'react'
|
||||||
|
import config from '../config'
|
||||||
|
|
||||||
|
type DownloadZoneProps = {
|
||||||
|
onComplete: (code: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DownloadZone({
|
||||||
|
onComplete,
|
||||||
|
}: DownloadZoneProps): JSX.Element {
|
||||||
|
const inputsRef = useRef<(HTMLInputElement | null)[]>([])
|
||||||
|
const [code, setCode] = useState<string[]>(
|
||||||
|
Array(config.retrieveCodeSlug.numChars).fill(''),
|
||||||
|
)
|
||||||
|
|
||||||
|
const handleInputChange = (
|
||||||
|
e: React.ChangeEvent<HTMLInputElement>,
|
||||||
|
index: number,
|
||||||
|
) => {
|
||||||
|
const value = e.target.value
|
||||||
|
// Only allow chars defined in config, in fact, it only has at most 1 char
|
||||||
|
if (
|
||||||
|
!value
|
||||||
|
.split('')
|
||||||
|
.every((char) => config.retrieveCodeSlug.chars.includes(char))
|
||||||
|
) {
|
||||||
|
e.target.value = ''
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const newCode = [...code]
|
||||||
|
newCode[index] = value
|
||||||
|
setCode(newCode)
|
||||||
|
|
||||||
|
if (value && index < inputsRef.current.length - 1) {
|
||||||
|
const nextInput = inputsRef.current[index + 1]
|
||||||
|
nextInput?.focus()
|
||||||
|
nextInput?.select()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index === inputsRef.current.length - 1 && value) {
|
||||||
|
onComplete(newCode.join(''))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleKeyDown = (
|
||||||
|
e: React.KeyboardEvent<HTMLInputElement>,
|
||||||
|
index: number,
|
||||||
|
) => {
|
||||||
|
if (e.key === 'Backspace' && !e.currentTarget.value && index > 0) {
|
||||||
|
const prevInput = inputsRef.current[index - 1]
|
||||||
|
prevInput?.focus()
|
||||||
|
prevInput?.select()
|
||||||
|
} else if (e.key === 'ArrowLeft' && index > 0) {
|
||||||
|
const prevInput = inputsRef.current[index - 1]
|
||||||
|
prevInput?.focus()
|
||||||
|
prevInput?.select()
|
||||||
|
} else if (e.key === 'ArrowRight' && index < inputsRef.current.length - 1) {
|
||||||
|
const nextInput = inputsRef.current[index + 1]
|
||||||
|
nextInput?.focus()
|
||||||
|
nextInput?.select()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlePaste = (e: React.ClipboardEvent<HTMLInputElement>) => {
|
||||||
|
const pasteData = e.clipboardData.getData('Text')
|
||||||
|
const validChars = pasteData
|
||||||
|
.split('')
|
||||||
|
.filter((char) => config.retrieveCodeSlug.chars.includes(char))
|
||||||
|
const newCode = validChars.slice(0, config.retrieveCodeSlug.numChars)
|
||||||
|
|
||||||
|
newCode.forEach((char, index) => {
|
||||||
|
if (inputsRef.current[index]) {
|
||||||
|
inputsRef.current[index]!.value = char
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
setCode(newCode)
|
||||||
|
|
||||||
|
if (newCode.length === config.retrieveCodeSlug.numChars) {
|
||||||
|
onComplete(newCode.join(''))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mt-4 text-center">
|
||||||
|
<h2 className="text-lg font-semibold">Retrieve your file</h2>
|
||||||
|
<div className="flex justify-center space-x-2 mt-2">
|
||||||
|
{Array(config.retrieveCodeSlug.numChars)
|
||||||
|
.fill('')
|
||||||
|
.map((_, index) => (
|
||||||
|
<input
|
||||||
|
key={index}
|
||||||
|
type="text"
|
||||||
|
maxLength={1}
|
||||||
|
className="w-10 h-10 border border-gray-500 rounded text-center"
|
||||||
|
onChange={(e) => handleInputChange(e, index)}
|
||||||
|
onKeyDown={(e) => handleKeyDown(e, index)}
|
||||||
|
onPaste={handlePaste}
|
||||||
|
ref={(el) => {
|
||||||
|
if (el) {
|
||||||
|
inputsRef.current[index] = el
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-gray-500 mt-2">
|
||||||
|
Enter the code to download file.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
import React, { JSX } from 'react'
|
||||||
|
import { CopyableIcon } from './CopyableIcon'
|
||||||
|
import config from '../config'
|
||||||
|
|
||||||
|
export function RetrieveCodeBox({
|
||||||
|
retrieveCode,
|
||||||
|
}: {
|
||||||
|
retrieveCode: string
|
||||||
|
}): JSX.Element {
|
||||||
|
return (
|
||||||
|
<div className="flex-auto text-center pr-12">
|
||||||
|
<div className="flex items-center justify-center space-x-2">
|
||||||
|
<h2 className="text-lg font-semibold">File Retrieve Code</h2>
|
||||||
|
<CopyableIcon value={retrieveCode} />
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-center space-x-2 mt-2">
|
||||||
|
{Array(config.retrieveCodeSlug.numChars)
|
||||||
|
.fill('')
|
||||||
|
.map((_, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className="w-10 h-10 border border-gray-500 rounded text-center flex items-center justify-center"
|
||||||
|
>
|
||||||
|
{retrieveCode[index]}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-gray-500 mt-2">
|
||||||
|
Use the above code to retrieve the file.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue