Configure imgproxy timeouts + add click to load on imgproxy errors (#404)
* Configure imgproxy timeouts * Use click to load on imgproxy errors * Add setting for click to load --------- Co-authored-by: ekzyis <ek@stacker.news>
This commit is contained in:
parent
45bad1219e
commit
670c567bff
|
@ -56,6 +56,9 @@ IMGPROXY_SALT=
|
||||||
IMGPROXY_ENABLE_WEBP_DETECTION=1
|
IMGPROXY_ENABLE_WEBP_DETECTION=1
|
||||||
IMGPROXY_MAX_ANIMATION_FRAMES=100
|
IMGPROXY_MAX_ANIMATION_FRAMES=100
|
||||||
IMGPROXY_MAX_SRC_RESOLUTION=200
|
IMGPROXY_MAX_SRC_RESOLUTION=200
|
||||||
|
IMGPROXY_READ_TIMEOUT=10
|
||||||
|
IMGPROXY_WRITE_TIMEOUT=10
|
||||||
|
IMGPROXY_DOWNLOAD_TIMEOUT=9
|
||||||
# IMGPROXY_DEVELOPMENT_ERRORS_MODE=1
|
# IMGPROXY_DEVELOPMENT_ERRORS_MODE=1
|
||||||
# IMGPROXY_ENABLE_DEBUG_HEADERS=true
|
# IMGPROXY_ENABLE_DEBUG_HEADERS=true
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ export default gql`
|
||||||
setSettings(tipDefault: Int!, turboTipping: Boolean!, fiatCurrency: String!, noteItemSats: Boolean!,
|
setSettings(tipDefault: Int!, turboTipping: Boolean!, fiatCurrency: String!, noteItemSats: Boolean!,
|
||||||
noteEarning: Boolean!, noteAllDescendants: Boolean!, noteMentions: Boolean!, noteDeposits: Boolean!,
|
noteEarning: Boolean!, noteAllDescendants: Boolean!, noteMentions: Boolean!, noteDeposits: Boolean!,
|
||||||
noteInvites: Boolean!, noteJobIndicator: Boolean!, noteCowboyHat: Boolean!, hideInvoiceDesc: Boolean!,
|
noteInvites: Boolean!, noteJobIndicator: Boolean!, noteCowboyHat: Boolean!, hideInvoiceDesc: Boolean!,
|
||||||
hideFromTopUsers: Boolean!, hideCowboyHat: Boolean!,
|
hideFromTopUsers: Boolean!, hideCowboyHat: Boolean!, clickToLoadImg: Boolean!,
|
||||||
wildWestMode: Boolean!, greeterMode: Boolean!, nostrPubkey: String, nostrRelays: [String!]): User
|
wildWestMode: Boolean!, greeterMode: Boolean!, nostrPubkey: String, nostrRelays: [String!]): User
|
||||||
setPhoto(photoId: ID!): Int!
|
setPhoto(photoId: ID!): Int!
|
||||||
upsertBio(bio: String!): User!
|
upsertBio(bio: String!): User!
|
||||||
|
@ -80,6 +80,7 @@ export default gql`
|
||||||
hideInvoiceDesc: Boolean!
|
hideInvoiceDesc: Boolean!
|
||||||
hideFromTopUsers: Boolean!
|
hideFromTopUsers: Boolean!
|
||||||
hideCowboyHat: Boolean!
|
hideCowboyHat: Boolean!
|
||||||
|
clickToLoadImg: Boolean!
|
||||||
wildWestMode: Boolean!
|
wildWestMode: Boolean!
|
||||||
greeterMode: Boolean!
|
greeterMode: Boolean!
|
||||||
lastCheckedJobs: String
|
lastCheckedJobs: String
|
||||||
|
|
|
@ -17,6 +17,7 @@ import copy from 'clipboard-copy'
|
||||||
import { IMGPROXY_URL_REGEXP, IMG_URL_REGEXP } from '../lib/url'
|
import { IMGPROXY_URL_REGEXP, IMG_URL_REGEXP } from '../lib/url'
|
||||||
import { extractUrls } from '../lib/md'
|
import { extractUrls } from '../lib/md'
|
||||||
import FileMissing from '../svgs/file-warning-line.svg'
|
import FileMissing from '../svgs/file-warning-line.svg'
|
||||||
|
import { useMe } from './me'
|
||||||
|
|
||||||
function searchHighlighter () {
|
function searchHighlighter () {
|
||||||
return (tree) => {
|
return (tree) => {
|
||||||
|
@ -35,6 +36,15 @@ function searchHighlighter () {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function decodeOriginalUrl (imgProxyUrl) {
|
||||||
|
const parts = imgProxyUrl.split('/')
|
||||||
|
// base64url is not a known encoding in browsers
|
||||||
|
// so we need to replace the invalid chars
|
||||||
|
const b64Url = parts[parts.length - 1].replace(/-/g, '+').replace(/_/, '/')
|
||||||
|
const originalUrl = Buffer.from(b64Url, 'base64').toString('utf-8')
|
||||||
|
return originalUrl
|
||||||
|
}
|
||||||
|
|
||||||
function Heading ({ h, slugger, noFragments, topLevel, children, node, ...props }) {
|
function Heading ({ h, slugger, noFragments, topLevel, children, node, ...props }) {
|
||||||
const [copied, setCopied] = useState(false)
|
const [copied, setCopied] = useState(false)
|
||||||
const [id] = useState(noFragments ? undefined : slugger.slug(toString(node).replace(/[^\w\-\s]+/gi, '')))
|
const [id] = useState(noFragments ? undefined : slugger.slug(toString(node).replace(/[^\w\-\s]+/gi, '')))
|
||||||
|
@ -183,8 +193,16 @@ export default memo(function Text ({ topLevel, noFragments, nofollow, onlyImgPro
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function ClickToLoad ({ children }) {
|
||||||
|
const [clicked, setClicked] = useState(false)
|
||||||
|
return clicked ? children : <div className='m-1 fst-italic pointer text-muted' onClick={() => setClicked(true)}>click to load image</div>
|
||||||
|
}
|
||||||
|
|
||||||
export function ZoomableImage ({ src, topLevel, ...props }) {
|
export function ZoomableImage ({ src, topLevel, ...props }) {
|
||||||
|
const me = useMe()
|
||||||
const [err, setErr] = useState()
|
const [err, setErr] = useState()
|
||||||
|
const [imgSrc, setImgSrc] = useState(src)
|
||||||
|
const [isImgProxy, setIsImgProxy] = useState(IMGPROXY_URL_REGEXP.test(src))
|
||||||
const defaultMediaStyle = {
|
const defaultMediaStyle = {
|
||||||
maxHeight: topLevel ? '75vh' : '25vh',
|
maxHeight: topLevel ? '75vh' : '25vh',
|
||||||
cursor: 'zoom-in'
|
cursor: 'zoom-in'
|
||||||
|
@ -199,19 +217,31 @@ export function ZoomableImage ({ src, topLevel, ...props }) {
|
||||||
|
|
||||||
if (!src) return null
|
if (!src) return null
|
||||||
if (err) {
|
if (err) {
|
||||||
return (
|
if (!isImgProxy) {
|
||||||
<span className='d-flex align-items-baseline text-warning-emphasis fw-bold pb-1'>
|
return (
|
||||||
<FileMissing width={18} height={18} className='fill-warning me-1 align-self-center' />
|
<span className='d-flex align-items-baseline text-warning-emphasis fw-bold pb-1'>
|
||||||
broken image <small className='ms-1'>stacker probably used an unreliable host</small>
|
<FileMissing width={18} height={18} className='fill-warning me-1 align-self-center' />
|
||||||
</span>
|
image error
|
||||||
)
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const originalUrl = decodeOriginalUrl(src)
|
||||||
|
setImgSrc(originalUrl)
|
||||||
|
setErr(null)
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
setErr(err)
|
||||||
|
}
|
||||||
|
// always set to false since imgproxy returned error
|
||||||
|
setIsImgProxy(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
const img = (
|
||||||
<img
|
<img
|
||||||
className={topLevel ? styles.topLevel : undefined}
|
className={topLevel ? styles.topLevel : undefined}
|
||||||
style={mediaStyle}
|
style={mediaStyle}
|
||||||
src={src}
|
src={imgSrc}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (mediaStyle.cursor === 'zoom-in') {
|
if (mediaStyle.cursor === 'zoom-in') {
|
||||||
setMediaStyle({
|
setMediaStyle({
|
||||||
|
@ -226,4 +256,11 @@ export function ZoomableImage ({ src, topLevel, ...props }) {
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
(!me || !me.clickToLoadImg || isImgProxy)
|
||||||
|
? img
|
||||||
|
: <ClickToLoad>{img}</ClickToLoad>
|
||||||
|
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ export const ME = gql`
|
||||||
hideInvoiceDesc
|
hideInvoiceDesc
|
||||||
hideFromTopUsers
|
hideFromTopUsers
|
||||||
hideCowboyHat
|
hideCowboyHat
|
||||||
|
clickToLoadImg
|
||||||
wildWestMode
|
wildWestMode
|
||||||
greeterMode
|
greeterMode
|
||||||
lastCheckedJobs
|
lastCheckedJobs
|
||||||
|
@ -51,6 +52,7 @@ export const SETTINGS_FIELDS = gql`
|
||||||
hideInvoiceDesc
|
hideInvoiceDesc
|
||||||
hideFromTopUsers
|
hideFromTopUsers
|
||||||
hideCowboyHat
|
hideCowboyHat
|
||||||
|
clickToLoadImg
|
||||||
nostrPubkey
|
nostrPubkey
|
||||||
nostrRelays
|
nostrRelays
|
||||||
wildWestMode
|
wildWestMode
|
||||||
|
@ -79,13 +81,13 @@ ${SETTINGS_FIELDS}
|
||||||
mutation setSettings($tipDefault: Int!, $turboTipping: Boolean!, $fiatCurrency: String!, $noteItemSats: Boolean!,
|
mutation setSettings($tipDefault: Int!, $turboTipping: Boolean!, $fiatCurrency: String!, $noteItemSats: Boolean!,
|
||||||
$noteEarning: Boolean!, $noteAllDescendants: Boolean!, $noteMentions: Boolean!, $noteDeposits: Boolean!,
|
$noteEarning: Boolean!, $noteAllDescendants: Boolean!, $noteMentions: Boolean!, $noteDeposits: Boolean!,
|
||||||
$noteInvites: Boolean!, $noteJobIndicator: Boolean!, $noteCowboyHat: Boolean!, $hideInvoiceDesc: Boolean!,
|
$noteInvites: Boolean!, $noteJobIndicator: Boolean!, $noteCowboyHat: Boolean!, $hideInvoiceDesc: Boolean!,
|
||||||
$hideFromTopUsers: Boolean!, $hideCowboyHat: Boolean!,
|
$hideFromTopUsers: Boolean!, $hideCowboyHat: Boolean!, $clickToLoadImg: Boolean!,
|
||||||
$wildWestMode: Boolean!, $greeterMode: Boolean!, $nostrPubkey: String, $nostrRelays: [String!]) {
|
$wildWestMode: Boolean!, $greeterMode: Boolean!, $nostrPubkey: String, $nostrRelays: [String!]) {
|
||||||
setSettings(tipDefault: $tipDefault, turboTipping: $turboTipping, fiatCurrency: $fiatCurrency,
|
setSettings(tipDefault: $tipDefault, turboTipping: $turboTipping, fiatCurrency: $fiatCurrency,
|
||||||
noteItemSats: $noteItemSats, noteEarning: $noteEarning, noteAllDescendants: $noteAllDescendants,
|
noteItemSats: $noteItemSats, noteEarning: $noteEarning, noteAllDescendants: $noteAllDescendants,
|
||||||
noteMentions: $noteMentions, noteDeposits: $noteDeposits, noteInvites: $noteInvites,
|
noteMentions: $noteMentions, noteDeposits: $noteDeposits, noteInvites: $noteInvites,
|
||||||
noteJobIndicator: $noteJobIndicator, noteCowboyHat: $noteCowboyHat, hideInvoiceDesc: $hideInvoiceDesc,
|
noteJobIndicator: $noteJobIndicator, noteCowboyHat: $noteCowboyHat, hideInvoiceDesc: $hideInvoiceDesc,
|
||||||
hideFromTopUsers: $hideFromTopUsers, hideCowboyHat: $hideCowboyHat,
|
hideFromTopUsers: $hideFromTopUsers, hideCowboyHat: $hideCowboyHat, clickToLoadImg: $clickToLoadImg,
|
||||||
wildWestMode: $wildWestMode, greeterMode: $greeterMode, nostrPubkey: $nostrPubkey, nostrRelays: $nostrRelays) {
|
wildWestMode: $wildWestMode, greeterMode: $greeterMode, nostrPubkey: $nostrPubkey, nostrRelays: $nostrRelays) {
|
||||||
...SettingsFields
|
...SettingsFields
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,6 +69,7 @@ export default function Settings ({ ssrData }) {
|
||||||
hideInvoiceDesc: settings?.hideInvoiceDesc,
|
hideInvoiceDesc: settings?.hideInvoiceDesc,
|
||||||
hideFromTopUsers: settings?.hideFromTopUsers,
|
hideFromTopUsers: settings?.hideFromTopUsers,
|
||||||
hideCowboyHat: settings?.hideCowboyHat,
|
hideCowboyHat: settings?.hideCowboyHat,
|
||||||
|
clickToLoadImg: settings?.clickToLoadImg,
|
||||||
wildWestMode: settings?.wildWestMode,
|
wildWestMode: settings?.wildWestMode,
|
||||||
greeterMode: settings?.greeterMode,
|
greeterMode: settings?.greeterMode,
|
||||||
nostrPubkey: settings?.nostrPubkey ? bech32encode(settings.nostrPubkey) : '',
|
nostrPubkey: settings?.nostrPubkey ? bech32encode(settings.nostrPubkey) : '',
|
||||||
|
@ -215,6 +216,11 @@ export default function Settings ({ ssrData }) {
|
||||||
<Checkbox
|
<Checkbox
|
||||||
label={<>hide my cowboy hat</>}
|
label={<>hide my cowboy hat</>}
|
||||||
name='hideCowboyHat'
|
name='hideCowboyHat'
|
||||||
|
groupClassName='mb-0'
|
||||||
|
/>
|
||||||
|
<Checkbox
|
||||||
|
label={<>click to load external images</>}
|
||||||
|
name='clickToLoadImg'
|
||||||
/>
|
/>
|
||||||
<div className='form-label'>content</div>
|
<div className='form-label'>content</div>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "users" ADD COLUMN "clickToLoadImg" BOOLEAN NOT NULL DEFAULT false;
|
|
@ -49,6 +49,7 @@ model User {
|
||||||
fiatCurrency String @default("USD")
|
fiatCurrency String @default("USD")
|
||||||
hideFromTopUsers Boolean @default(false)
|
hideFromTopUsers Boolean @default(false)
|
||||||
turboTipping Boolean @default(false)
|
turboTipping Boolean @default(false)
|
||||||
|
clickToLoadImg Boolean @default(false)
|
||||||
referrerId Int?
|
referrerId Int?
|
||||||
nostrPubkey String?
|
nostrPubkey String?
|
||||||
nostrAuthPubkey String? @unique(map: "users.nostrAuthPubkey_unique")
|
nostrAuthPubkey String? @unique(map: "users.nostrAuthPubkey_unique")
|
||||||
|
|
Loading…
Reference in New Issue