BetterImageEditor: Better look of the shelf and couple of other fixes. (#1369)
* Move the shelf next to the editor Fixing a couple of bugs and issues and moving the shelf with pictures beside the image editor for better usage and much better look from the previous scroller implementation * misc --------- Co-authored-by: thororen <78185467+thororen1234@users.noreply.github.com>
This commit is contained in:
parent
19a57bebfd
commit
f5fe2bd9a4
5 changed files with 284 additions and 41 deletions
|
|
@ -4,7 +4,7 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { DeleteIcon } from "@components/Icons";
|
import { DeleteIcon, SearchIcon } from "@components/Icons";
|
||||||
import { classNameFactory } from "@utils/css";
|
import { classNameFactory } from "@utils/css";
|
||||||
import { React, useCallback, useEffect, useRef, useState } from "@webpack/common";
|
import { React, useCallback, useEffect, useRef, useState } from "@webpack/common";
|
||||||
|
|
||||||
|
|
@ -53,23 +53,31 @@ export function Shelf({ kind, group, entries, thumbs, activeId, wornId, onGroup,
|
||||||
const shape = SQUARE.has(kind) ? "square" : "wide";
|
const shape = SQUARE.has(kind) ? "square" : "wide";
|
||||||
|
|
||||||
const [hovered, setHovered] = useState<string | null>(null);
|
const [hovered, setHovered] = useState<string | null>(null);
|
||||||
|
const [filter, setFilter] = useState("");
|
||||||
const [playing, setPlaying] = useState<Record<string, string>>({});
|
const [playing, setPlaying] = useState<Record<string, string>>({});
|
||||||
const played = useRef<string[]>([]);
|
const played = useRef<string[]>([]);
|
||||||
|
const live = useRef(true);
|
||||||
|
|
||||||
useEffect(() => () => played.current.forEach(URL.revokeObjectURL), []);
|
useEffect(() => () => {
|
||||||
|
live.current = false;
|
||||||
|
played.current.forEach(URL.revokeObjectURL);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const play = useCallback(async (entry: Entry) => {
|
const play = useCallback(async (entry: Entry) => {
|
||||||
setHovered(entry.id);
|
setHovered(entry.id);
|
||||||
if (!isAnimated(entry) || playing[entry.id]) return;
|
if (!isAnimated(entry) || playing[entry.id]) return;
|
||||||
|
|
||||||
const blob = await getFile(entry.id);
|
const blob = await getFile(entry.id);
|
||||||
if (!blob) return;
|
if (!blob || !live.current) return;
|
||||||
|
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
played.current.push(url);
|
played.current.push(url);
|
||||||
setPlaying(current => ({ ...current, [entry.id]: url }));
|
setPlaying(current => ({ ...current, [entry.id]: url }));
|
||||||
}, [playing]);
|
}, [playing]);
|
||||||
|
|
||||||
|
const wanted = filter.trim().toLowerCase();
|
||||||
|
const shown = wanted ? entries.filter(entry => entry.name.toLowerCase().includes(wanted)) : entries;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cl("panel", shape)}>
|
<div className={cl("panel", shape)}>
|
||||||
<div className={cl("tabs")} role="tablist">
|
<div className={cl("tabs")} role="tablist">
|
||||||
|
|
@ -96,9 +104,36 @@ export function Shelf({ kind, group, entries, thumbs, activeId, wornId, onGroup,
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className={cl("bar")}>
|
||||||
|
<span className={cl("count")}>
|
||||||
|
{wanted ? `${shown.length} of ${entries.length}` : `${entries.length} kept`}
|
||||||
|
</span>
|
||||||
|
{entries.length > 12 && (
|
||||||
|
<div className={cl("find")}>
|
||||||
|
<SearchIcon width={12} height={12} aria-hidden />
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={filter}
|
||||||
|
placeholder="Search"
|
||||||
|
aria-label="Search saved pictures"
|
||||||
|
onChange={event => setFilter(event.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
{filter && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
aria-label="Clear the search"
|
||||||
|
onClick={() => setFilter("")}
|
||||||
|
>×</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className={cl("strip")} role="group" aria-label="Saved pictures">
|
<div className={cl("strip")} role="group" aria-label="Saved pictures">
|
||||||
{entries.map(entry => (
|
{shown.map(entry => {
|
||||||
<div
|
const picture = (hovered === entry.id && playing[entry.id]) || thumbs[entry.id];
|
||||||
|
|
||||||
|
return <div
|
||||||
key={entry.id}
|
key={entry.id}
|
||||||
className={cl("item", { pinned: entry.pinned, worn: entry.id === wornId })}
|
className={cl("item", { pinned: entry.pinned, worn: entry.id === wornId })}
|
||||||
onMouseEnter={() => play(entry)}
|
onMouseEnter={() => play(entry)}
|
||||||
|
|
@ -110,7 +145,7 @@ export function Shelf({ kind, group, entries, thumbs, activeId, wornId, onGroup,
|
||||||
title={entry.id === wornId ? `${entry.name}
|
title={entry.id === wornId ? `${entry.name}
|
||||||
You are wearing this` : entry.name}
|
You are wearing this` : entry.name}
|
||||||
className={cl("thumb", { active: activeId === entry.id })}
|
className={cl("thumb", { active: activeId === entry.id })}
|
||||||
style={{ backgroundImage: `url(${(hovered === entry.id && playing[entry.id]) || thumbs[entry.id]})` }}
|
style={picture ? { backgroundImage: `url(${picture})` } : undefined}
|
||||||
onClick={() => onPick(entry)}
|
onClick={() => onPick(entry)}
|
||||||
onContextMenu={event => {
|
onContextMenu={event => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
@ -136,14 +171,16 @@ You are wearing this` : entry.name}
|
||||||
>
|
>
|
||||||
<PinIcon width={10} height={10} />
|
<PinIcon width={10} height={10} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>;
|
||||||
))}
|
})}
|
||||||
|
|
||||||
{!entries.length && (
|
{!shown.length && (
|
||||||
<span className={cl("empty")}>
|
<span className={cl("empty")}>
|
||||||
{group === "original"
|
{entries.length
|
||||||
? "Pictures you pick, paste or drop land here"
|
? "Nothing here matches that"
|
||||||
: "Pictures you crop land here"}
|
: group === "original"
|
||||||
|
? "Pictures you pick, paste or drop land here"
|
||||||
|
: "Pictures you crop land here"}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import { definePluginSettings } from "@api/Settings";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { EquicordDevs } from "@utils/constants";
|
import { EquicordDevs } from "@utils/constants";
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
import { pluralise } from "@utils/misc";
|
import { classes, pluralise } from "@utils/misc";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { chooseFile, saveFile } from "@utils/web";
|
import { chooseFile, saveFile } from "@utils/web";
|
||||||
import { Alerts, Button, React, showToast, Toasts, useCallback, useEffect, useRef, useState } from "@webpack/common";
|
import { Alerts, Button, React, showToast, Toasts, useCallback, useEffect, useRef, useState } from "@webpack/common";
|
||||||
|
|
@ -62,7 +62,7 @@ const settings = definePluginSettings({
|
||||||
librarySize: {
|
librarySize: {
|
||||||
type: OptionType.SLIDER,
|
type: OptionType.SLIDER,
|
||||||
description: "How many pictures to keep on each shelf.",
|
description: "How many pictures to keep on each shelf.",
|
||||||
markers: [6, 12, 24, 48],
|
markers: [6, 12, 24, 48, 100],
|
||||||
default: 24,
|
default: 24,
|
||||||
stickToMarkers: true
|
stickToMarkers: true
|
||||||
},
|
},
|
||||||
|
|
@ -139,7 +139,6 @@ const settings = definePluginSettings({
|
||||||
let handoff: { id: string; transform: CropState | null; file: File; } | null = null;
|
let handoff: { id: string; transform: CropState | null; file: File; } | null = null;
|
||||||
let offered: { kind: Kind; id: string; uris: (string | undefined)[]; } | null = null;
|
let offered: { kind: Kind; id: string; uris: (string | undefined)[]; } | null = null;
|
||||||
const handedOver = new Map<string, string | null>();
|
const handedOver = new Map<string, string | null>();
|
||||||
let editorAllowed = true;
|
|
||||||
|
|
||||||
const PENDING = [["AVATAR", "pendingAvatar"], ["BANNER", "pendingBanner"]] as const;
|
const PENDING = [["AVATAR", "pendingAvatar"], ["BANNER", "pendingBanner"]] as const;
|
||||||
|
|
||||||
|
|
@ -235,7 +234,7 @@ function useLibrary(kind: Kind) {
|
||||||
.catch(err => logger.error("could not read what you are wearing", err));
|
.catch(err => logger.error("could not read what you are wearing", err));
|
||||||
}, [kind, version]);
|
}, [kind, version]);
|
||||||
|
|
||||||
return { group, setGroup, entries, thumbs, worn, bump };
|
return { group, setGroup, entries, thumbs, worn, version, bump };
|
||||||
}
|
}
|
||||||
|
|
||||||
function useForget(bump: () => void) {
|
function useForget(bump: () => void) {
|
||||||
|
|
@ -258,6 +257,7 @@ function useForget(bump: () => void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
let editorsOpen = 0;
|
let editorsOpen = 0;
|
||||||
|
let gatedPickers = 0;
|
||||||
|
|
||||||
function usePasteAndDrop(accept: (file: File) => void, active: () => boolean) {
|
function usePasteAndDrop(accept: (file: File) => void, active: () => boolean) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -274,7 +274,7 @@ function usePasteAndDrop(accept: (file: File) => void, active: () => boolean) {
|
||||||
const onPaste = (event: ClipboardEvent) => take(imageIn(event.clipboardData?.files), event);
|
const onPaste = (event: ClipboardEvent) => take(imageIn(event.clipboardData?.files), event);
|
||||||
const onDrop = (event: DragEvent) => take(imageIn(event.dataTransfer?.files), event);
|
const onDrop = (event: DragEvent) => take(imageIn(event.dataTransfer?.files), event);
|
||||||
const onDragOver = (event: DragEvent) => {
|
const onDragOver = (event: DragEvent) => {
|
||||||
if (!event.dataTransfer?.types.includes("Files")) return;
|
if (!active() || !event.dataTransfer?.types.includes("Files")) return;
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
};
|
};
|
||||||
|
|
@ -293,8 +293,8 @@ function usePasteAndDrop(accept: (file: File) => void, active: () => boolean) {
|
||||||
|
|
||||||
function EditorGate() {
|
function EditorGate() {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
editorAllowed = false;
|
gatedPickers++;
|
||||||
return () => { editorAllowed = true; };
|
return () => { gatedPickers--; };
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -306,7 +306,7 @@ function PickerShelf({ kind, open, complete, maxSize }: {
|
||||||
complete(result: PickResult): void;
|
complete(result: PickResult): void;
|
||||||
maxSize?: number;
|
maxSize?: number;
|
||||||
}) {
|
}) {
|
||||||
const { group, setGroup, entries, thumbs, worn, bump } = useLibrary(kind);
|
const { group, setGroup, entries, thumbs, worn, version, bump } = useLibrary(kind);
|
||||||
const onForget = useForget(bump);
|
const onForget = useForget(bump);
|
||||||
|
|
||||||
const hand = useCallback(async (id: string, file: File, crop: CropState | null) => {
|
const hand = useCallback(async (id: string, file: File, crop: CropState | null) => {
|
||||||
|
|
@ -320,11 +320,11 @@ function PickerShelf({ kind, open, complete, maxSize }: {
|
||||||
previousApplied(kind)
|
previousApplied(kind)
|
||||||
.then(setPutBack)
|
.then(setPutBack)
|
||||||
.catch(err => logger.error("could not read what you wore before", err));
|
.catch(err => logger.error("could not read what you wore before", err));
|
||||||
}, [kind]);
|
}, [kind, version]);
|
||||||
|
|
||||||
const onPick = useCallback(async (entry: Entry) => {
|
const onPick = useCallback(async (entry: Entry) => {
|
||||||
const blob = await getFile(entry.id);
|
const blob = await getFile(entry.id);
|
||||||
if (!blob) return;
|
if (!blob) return showToast("That picture is missing", Toasts.Type.FAILURE);
|
||||||
|
|
||||||
const file = new File([blob], entry.name, { type: blob.type });
|
const file = new File([blob], entry.name, { type: blob.type });
|
||||||
await touch(entry.id);
|
await touch(entry.id);
|
||||||
|
|
@ -352,6 +352,7 @@ function PickerShelf({ kind, open, complete, maxSize }: {
|
||||||
await hand(entry.id, file, settings.store.rememberCrop ? entry.crop ?? null : null);
|
await hand(entry.id, file, settings.store.rememberCrop ? entry.crop ?? null : null);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error("could not take that picture", err);
|
logger.error("could not take that picture", err);
|
||||||
|
showToast("Could not use that picture", Toasts.Type.FAILURE);
|
||||||
}
|
}
|
||||||
}, [kind, hand, bump, maxSize]);
|
}, [kind, hand, bump, maxSize]);
|
||||||
|
|
||||||
|
|
@ -431,6 +432,7 @@ function EditorShelf({ Original, ownProps }: { Original: React.ComponentType<Edi
|
||||||
await show(entry.id, file, settings.store.rememberCrop ? entry.crop ?? null : null);
|
await show(entry.id, file, settings.store.rememberCrop ? entry.crop ?? null : null);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error("could not take that picture", err);
|
logger.error("could not take that picture", err);
|
||||||
|
showToast("Could not use that picture", Toasts.Type.FAILURE);
|
||||||
}
|
}
|
||||||
}, [kind, show, bump, setGroup]);
|
}, [kind, show, bump, setGroup]);
|
||||||
|
|
||||||
|
|
@ -438,7 +440,7 @@ function EditorShelf({ Original, ownProps }: { Original: React.ComponentType<Edi
|
||||||
|
|
||||||
const onPick = useCallback(async (entry: Entry) => {
|
const onPick = useCallback(async (entry: Entry) => {
|
||||||
const blob = await getFile(entry.id);
|
const blob = await getFile(entry.id);
|
||||||
if (!blob) return;
|
if (!blob) return showToast("That picture is missing", Toasts.Type.FAILURE);
|
||||||
|
|
||||||
await touch(entry.id);
|
await touch(entry.id);
|
||||||
bump();
|
bump();
|
||||||
|
|
@ -460,7 +462,10 @@ function EditorShelf({ Original, ownProps }: { Original: React.ComponentType<Edi
|
||||||
await add(new File([blob], name, { type: blob.type }), kind, "cropped", settings.store.librarySize, source);
|
await add(new File([blob], name, { type: blob.type }), kind, "cropped", settings.store.librarySize, source);
|
||||||
bump();
|
bump();
|
||||||
};
|
};
|
||||||
const run = () => save().catch(err => logger.error("could not keep the cropped copy", err));
|
const run = () => save().catch(err => {
|
||||||
|
logger.error("could not keep the cropped copy", err);
|
||||||
|
showToast("Could not keep the cropped copy", Toasts.Type.FAILURE);
|
||||||
|
});
|
||||||
|
|
||||||
if (!settings.store.askBeforeSavingCropped) return void run();
|
if (!settings.store.askBeforeSavingCropped) return void run();
|
||||||
|
|
||||||
|
|
@ -496,7 +501,9 @@ function EditorShelf({ Original, ownProps }: { Original: React.ComponentType<Edi
|
||||||
{...props}
|
{...props}
|
||||||
bieShelf={
|
bieShelf={
|
||||||
<ErrorBoundary noop>
|
<ErrorBoundary noop>
|
||||||
<div>
|
<div className={cl("drawer")} onMouseDown={event => {
|
||||||
|
if (!(event.target instanceof HTMLInputElement)) event.preventDefault();
|
||||||
|
}}>
|
||||||
<Shelf
|
<Shelf
|
||||||
kind={kind}
|
kind={kind}
|
||||||
group={group}
|
group={group}
|
||||||
|
|
@ -545,9 +552,16 @@ function onProfileDiscarded() {
|
||||||
|
|
||||||
let wrapped: React.ComponentType<EditorProps> | null = null;
|
let wrapped: React.ComponentType<EditorProps> | null = null;
|
||||||
|
|
||||||
|
interface Size {
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
let area: Size | null = null;
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "BetterImageEditor",
|
name: "BetterImageEditor",
|
||||||
description: "Image editor with shelving for older images and allowing cropping",
|
description: "Keeps a shelf of your pictures in the avatar and banner editor, and remembers how you cropped each one.",
|
||||||
authors: [EquicordDevs.heart_menace],
|
authors: [EquicordDevs.heart_menace],
|
||||||
settings,
|
settings,
|
||||||
|
|
||||||
|
|
@ -578,6 +592,35 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
match: /(\(0,\i\.jsx\)\(\i\.A,\{id:\i,children:)/,
|
match: /(\(0,\i\.jsx\)\(\i\.A,\{id:\i,children:)/,
|
||||||
replace: "bieShelf,$1"
|
replace: "bieShelf,$1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
match: /\("div",\{role:"group","aria-label":/,
|
||||||
|
replace: '("div",{className:"vc-bie-split",role:"group","aria-label":'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: '"SET_IMAGE_ZOOM_RATIO"',
|
||||||
|
replacement: {
|
||||||
|
match: /size:"md",actionBarInput:/,
|
||||||
|
replace: 'size:"lg",actionBarInput:'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: '"SET_IMAGE_ZOOM_RATIO"',
|
||||||
|
group: true,
|
||||||
|
replacement: [
|
||||||
|
{
|
||||||
|
match: /(?<==(\i)\.current\.getBoundingClientRect\(\),)\{width:(\i),height:(\i)\}=(\(0,\i\.\i\)\(\i,\i,\i,\i\))(?=,\i=(\(0,\i\.\i\))\((\i),\2,\3,(\i),(\i)\))/,
|
||||||
|
replace: "{width:$2,height:$3}=$self.fill($4,(bieW,bieH)=>$5($6,bieW,bieH,$7,$8),$1.current)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
match: /(\i)=(\(0,\i\.\i\)\(\i,\i,\i,\i\))(?=,\i=(\(0,\i\.\i\))\((\i),\1\.width,\1\.height,(\i),(\i)\))/,
|
||||||
|
replace: "$1=$self.fill($2,(bieW,bieH)=>$3($4,bieW,bieH,$5,$6))"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
match: /\{width:(\i),height:(\i)\}=(\(0,\i\.\i\)\(\i,\i,\i,\i\))(?=,\i=(\(0,\i\.\i\))\((\i),\1,\2,(\i),(\i)\))/,
|
||||||
|
replace: "{width:$1,height:$2}=$self.fill($3,(bieW,bieH)=>$4($5,bieW,bieH,$6,$7))"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
@ -587,15 +630,36 @@ export default definePlugin({
|
||||||
match: /(uploadType:(\i),guild:\i,handleOpenImageEditingModal:(\i),[\s\S]{0,500}?)\i&&\(0,\i\.jsx\)\(\i,\{onComplete:(\i),returnRef:\i\}\)/,
|
match: /(uploadType:(\i),guild:\i,handleOpenImageEditingModal:(\i),[\s\S]{0,500}?)\i&&\(0,\i\.jsx\)\(\i,\{onComplete:(\i),returnRef:\i\}\)/,
|
||||||
replace: "$1$self.pickerRow($2,$3,$4,arguments[0])"
|
replace: "$1$self.pickerRow($2,$3,$4,arguments[0])"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: 'displayName="RecentAvatarsStore"',
|
||||||
|
replacement: {
|
||||||
|
match: /size:"md"(,title:\i,actions:\[\],returnRef:\i,children:\(0,\i\.jsxs\)\("div",\{className:)(\i\.\i)/,
|
||||||
|
replace: 'size:"lg"$1$self.pickerClass($2)'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
|
pickerClass(base: string) {
|
||||||
|
return classes(base, cl("picker"));
|
||||||
|
},
|
||||||
|
|
||||||
|
fill(size: Size, cropOf: (width: number, height: number) => Size, image?: HTMLImageElement) {
|
||||||
|
const box = image?.parentElement;
|
||||||
|
if (box) area = { width: box.clientWidth, height: box.clientHeight };
|
||||||
|
if (!area) return size;
|
||||||
|
|
||||||
|
const crop = cropOf(size.width, size.height);
|
||||||
|
const scale = Math.min(area.width / crop.width, area.height / crop.height);
|
||||||
|
return { width: size.width * scale, height: size.height * scale };
|
||||||
|
},
|
||||||
|
|
||||||
wrapEditor(Original: React.ComponentType<EditorProps>) {
|
wrapEditor(Original: React.ComponentType<EditorProps>) {
|
||||||
if (!wrapped) {
|
if (!wrapped) {
|
||||||
const Safe = ErrorBoundary.wrap(EditorShelf, {
|
const Safe = ErrorBoundary.wrap(EditorShelf, {
|
||||||
fallback: ({ wrappedProps }) => <Original {...wrappedProps.ownProps} />
|
fallback: ({ wrappedProps }) => <Original {...wrappedProps.ownProps} />
|
||||||
});
|
});
|
||||||
wrapped = (props: EditorProps) => editorAllowed
|
wrapped = (props: EditorProps) => gatedPickers === 0
|
||||||
? <Safe Original={Original} ownProps={props} />
|
? <Safe Original={Original} ownProps={props} />
|
||||||
: <Original {...props} />;
|
: <Original {...props} />;
|
||||||
}
|
}
|
||||||
|
|
@ -608,7 +672,9 @@ export default definePlugin({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ErrorBoundary noop key="bie-picker">
|
<ErrorBoundary noop key="bie-picker">
|
||||||
<PickerShelf kind={kindOf(uploadType)} open={open} complete={complete} maxSize={picker.maxFileSizeBytes} />
|
<div className={cl("drawer")}>
|
||||||
|
<PickerShelf kind={kindOf(uploadType)} open={open} complete={complete} maxSize={picker.maxFileSizeBytes} />
|
||||||
|
</div>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -127,7 +127,7 @@ export function add(file: File, kind: Kind, group: Group, limit: number, from?:
|
||||||
if (known) return known;
|
if (known) return known;
|
||||||
|
|
||||||
const source = from ? stored.find(entry => entry.id === from)?.sig : undefined;
|
const source = from ? stored.find(entry => entry.id === from)?.sig : undefined;
|
||||||
const cropOf = (entry: Entry) => entry.from === source || entry.from === from;
|
const cropOf = (entry: Entry) => entry.from === source;
|
||||||
const replaced = source ? stored.filter(entry => sameShelf(entry) && cropOf(entry) && !entry.pinned) : [];
|
const replaced = source ? stored.filter(entry => sameShelf(entry) && cropOf(entry) && !entry.pinned) : [];
|
||||||
const id = nanoid();
|
const id = nanoid();
|
||||||
const thumb = await thumbnail(file);
|
const thumb = await thumbnail(file);
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
.vc-bie-tabs {
|
.vc-bie-tabs {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
}
|
}
|
||||||
|
|
@ -62,22 +63,99 @@
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-bie-strip {
|
.vc-bie-bar {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-wrap: nowrap;
|
gap: 8px;
|
||||||
|
padding: 8px 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-bie-count {
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 12px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-bie-find {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
margin-left: auto;
|
||||||
|
padding: 0 6px;
|
||||||
|
height: 22px;
|
||||||
|
width: 116px;
|
||||||
|
border-radius: 11px;
|
||||||
|
background: var(--background-modifier-hover);
|
||||||
|
color: var(--text-muted);
|
||||||
|
transition: width .12s ease-out, background-color .12s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-bie-find:focus-within {
|
||||||
|
width: 168px;
|
||||||
|
background: var(--background-modifier-selected);
|
||||||
|
color: var(--interactive-active);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-bie-find input {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-width: 0;
|
||||||
|
height: 100%;
|
||||||
|
padding: 0;
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
color: var(--text-default);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-bie-find input:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-bie-find input::placeholder {
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-bie-find button {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
padding: 0 2px;
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 1;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-bie-find button:hover {
|
||||||
|
color: var(--interactive-active);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.vc-bie-find {
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-bie-strip {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(44px, 1fr));
|
||||||
|
align-content: start;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
padding: 8px 4px 6px;
|
padding: 8px 4px 6px;
|
||||||
min-height: 38px;
|
min-height: 38px;
|
||||||
overflow: auto hidden;
|
max-height: 168px;
|
||||||
|
overflow: hidden auto;
|
||||||
|
overscroll-behavior: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-bie-wide .vc-bie-strip {
|
.vc-bie-wide .vc-bie-strip {
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
max-height: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-bie-strip::-webkit-scrollbar {
|
.vc-bie-strip::-webkit-scrollbar {
|
||||||
height: 6px;
|
width: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-bie-strip::-webkit-scrollbar-track {
|
.vc-bie-strip::-webkit-scrollbar-track {
|
||||||
|
|
@ -85,23 +163,24 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-bie-strip::-webkit-scrollbar-thumb {
|
.vc-bie-strip::-webkit-scrollbar-thumb {
|
||||||
background: transparent;
|
background: var(--background-modifier-accent);
|
||||||
border-radius: 3px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-bie-strip:hover::-webkit-scrollbar-thumb {
|
.vc-bie-strip:hover::-webkit-scrollbar-thumb {
|
||||||
background: var(--background-modifier-accent);
|
background: var(--background-modifier-selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-bie-item {
|
.vc-bie-item {
|
||||||
position: relative;
|
position: relative;
|
||||||
flex: 0 0 auto;
|
justify-self: center;
|
||||||
line-height: 0;
|
line-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-bie-thumb {
|
.vc-bie-thumb {
|
||||||
width: 44px;
|
width: 44px;
|
||||||
height: 44px;
|
height: 44px;
|
||||||
|
display: block;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border: 2px solid transparent;
|
border: 2px solid transparent;
|
||||||
|
|
@ -215,3 +294,64 @@
|
||||||
transform: none;
|
transform: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vc-bie-split {
|
||||||
|
position: relative;
|
||||||
|
padding-right: 260px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-bie-picker {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr) 248px;
|
||||||
|
grid-template-rows: 360px;
|
||||||
|
column-gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-bie-picker > :first-child {
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-bie-picker > :first-child > ul {
|
||||||
|
flex: 1 1 0;
|
||||||
|
min-height: 0;
|
||||||
|
grid-template-columns: minmax(0, 1fr);
|
||||||
|
grid-template-rows: repeat(2, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-bie-picker > :first-child > ul > li > :last-child {
|
||||||
|
box-sizing: border-box;
|
||||||
|
height: 100%;
|
||||||
|
aspect-ratio: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-bie-split > .vc-bie-drawer,
|
||||||
|
.vc-bie-picker > .vc-bie-drawer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 248px;
|
||||||
|
padding: 8px;
|
||||||
|
border-radius: var(--radius-md, 8px);
|
||||||
|
background: var(--background-surface-higher, var(--background-secondary));
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-bie-split > .vc-bie-drawer {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-bie-drawer > .vc-bie-panel {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-height: 0;
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-bie-split > .vc-bie-drawer .vc-bie-strip,
|
||||||
|
.vc-bie-picker > .vc-bie-drawer .vc-bie-strip {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-height: 0;
|
||||||
|
max-height: none;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -702,10 +702,6 @@ export const EquicordDevs = Object.freeze({
|
||||||
name: "nobody",
|
name: "nobody",
|
||||||
id: 0n
|
id: 0n
|
||||||
},
|
},
|
||||||
heart_menace: {
|
|
||||||
name: "heart_menace",
|
|
||||||
id: 281162701303185408n
|
|
||||||
},
|
|
||||||
thororen: {
|
thororen: {
|
||||||
name: "thororen",
|
name: "thororen",
|
||||||
id: 848339671629299742n
|
id: 848339671629299742n
|
||||||
|
|
@ -1447,6 +1443,10 @@ export const EquicordDevs = Object.freeze({
|
||||||
name: "Kurt",
|
name: "Kurt",
|
||||||
id: 112222963276750848n
|
id: 112222963276750848n
|
||||||
},
|
},
|
||||||
|
heart_menace: {
|
||||||
|
name: "heart_menace",
|
||||||
|
id: 281162701303185408n
|
||||||
|
},
|
||||||
} satisfies Record<string, Dev>);
|
} satisfies Record<string, Dev>);
|
||||||
|
|
||||||
// iife so #__PURE__ works correctly
|
// iife so #__PURE__ works correctly
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue