Implement bulk delete in files
Closes #10 Also updates Deno and fixes a typo in variables
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
FROM denoland/deno:ubuntu-1.45.5
|
FROM denoland/deno:ubuntu-1.46.2
|
||||||
|
|
||||||
EXPOSE 8000
|
EXPOSE 8000
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ import { humanFileSize, TRASH_PATH } from '/lib/utils/files.ts';
|
|||||||
interface ListFilesProps {
|
interface ListFilesProps {
|
||||||
directories: Directory[];
|
directories: Directory[];
|
||||||
files: DirectoryFile[];
|
files: DirectoryFile[];
|
||||||
|
chosenDirectories?: Pick<Directory, 'parent_path' | 'directory_name'>[];
|
||||||
|
chosenFiles?: Pick<DirectoryFile, 'parent_path' | 'file_name'>[];
|
||||||
|
onClickChooseFile?: (parentPath: string, name: string) => void;
|
||||||
|
onClickChooseDirectory?: (parentPath: string, name: string) => void;
|
||||||
onClickOpenRenameDirectory?: (parentPath: string, name: string) => void;
|
onClickOpenRenameDirectory?: (parentPath: string, name: string) => void;
|
||||||
onClickOpenRenameFile?: (parentPath: string, name: string) => void;
|
onClickOpenRenameFile?: (parentPath: string, name: string) => void;
|
||||||
onClickOpenMoveDirectory?: (parentPath: string, name: string) => void;
|
onClickOpenMoveDirectory?: (parentPath: string, name: string) => void;
|
||||||
@@ -18,6 +22,10 @@ export default function ListFiles(
|
|||||||
{
|
{
|
||||||
directories,
|
directories,
|
||||||
files,
|
files,
|
||||||
|
chosenDirectories = [],
|
||||||
|
chosenFiles = [],
|
||||||
|
onClickChooseFile,
|
||||||
|
onClickChooseDirectory,
|
||||||
onClickOpenRenameDirectory,
|
onClickOpenRenameDirectory,
|
||||||
onClickOpenRenameFile,
|
onClickOpenRenameFile,
|
||||||
onClickOpenMoveDirectory,
|
onClickOpenMoveDirectory,
|
||||||
@@ -55,13 +63,38 @@ export default function ListFiles(
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isAnyItemChosen = chosenDirectories.length > 0 || chosenFiles.length > 0;
|
||||||
|
|
||||||
|
function chooseAllItems() {
|
||||||
|
if (typeof onClickChooseFile !== 'undefined') {
|
||||||
|
files.forEach((files) => onClickChooseFile(files.parent_path, files.file_name));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof onClickChooseDirectory !== 'undefined') {
|
||||||
|
directories.forEach((directory) => onClickChooseDirectory(directory.parent_path, directory.directory_name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section class='mx-auto max-w-7xl my-8'>
|
<section class='mx-auto max-w-7xl my-8'>
|
||||||
<table class='w-full border-collapse bg-gray-900 text-left text-sm text-slate-500 shadow-sm rounded-md'>
|
<table class='w-full border-collapse bg-gray-900 text-left text-sm text-slate-500 shadow-sm rounded-md'>
|
||||||
<thead>
|
<thead>
|
||||||
<tr class='border-b border-slate-600'>
|
<tr class='border-b border-slate-600'>
|
||||||
|
{(directories.length === 0 && files.length === 0) ||
|
||||||
|
(typeof onClickChooseFile === 'undefined' && typeof onClickChooseDirectory === 'undefined')
|
||||||
|
? null
|
||||||
|
: (
|
||||||
|
<th scope='col' class='pl-6 pr-2 font-medium text-white w-3'>
|
||||||
|
<input
|
||||||
|
class='w-3 h-3 cursor-pointer text-[#51A4FB] bg-slate-100 border-slate-300 rounded dark:bg-slate-700 dark:border-slate-600'
|
||||||
|
type='checkbox'
|
||||||
|
onClick={() => chooseAllItems()}
|
||||||
|
checked={isAnyItemChosen}
|
||||||
|
/>
|
||||||
|
</th>
|
||||||
|
)}
|
||||||
<th scope='col' class='px-6 py-4 font-medium text-white'>Name</th>
|
<th scope='col' class='px-6 py-4 font-medium text-white'>Name</th>
|
||||||
<th scope='col' class='px-6 py-4 font-medium text-white w-56'>Last update</th>
|
<th scope='col' class='px-6 py-4 font-medium text-white w-64'>Last update</th>
|
||||||
{isShowingNotes || isShowingPhotos
|
{isShowingNotes || isShowingPhotos
|
||||||
? null
|
? null
|
||||||
: <th scope='col' class='px-6 py-4 font-medium text-white w-32'>Size</th>}
|
: <th scope='col' class='px-6 py-4 font-medium text-white w-32'>Size</th>}
|
||||||
@@ -74,6 +107,21 @@ export default function ListFiles(
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<tr class='bg-slate-700 hover:bg-slate-600 group'>
|
<tr class='bg-slate-700 hover:bg-slate-600 group'>
|
||||||
|
{typeof onClickChooseDirectory === 'undefined' ? null : (
|
||||||
|
<td class='gap-3 pl-6 pr-2 py-4'>
|
||||||
|
{fullPath === TRASH_PATH ? null : (
|
||||||
|
<input
|
||||||
|
class='w-3 h-3 cursor-pointer text-[#51A4FB] bg-slate-100 border-slate-300 rounded dark:bg-slate-700 dark:border-slate-600'
|
||||||
|
type='checkbox'
|
||||||
|
onClick={() => onClickChooseDirectory(directory.parent_path, directory.directory_name)}
|
||||||
|
checked={Boolean(chosenDirectories.find((_directory) =>
|
||||||
|
_directory.parent_path === directory.parent_path &&
|
||||||
|
_directory.directory_name === directory.directory_name
|
||||||
|
))}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
)}
|
||||||
<td class='flex gap-3 px-6 py-4'>
|
<td class='flex gap-3 px-6 py-4'>
|
||||||
<a
|
<a
|
||||||
href={`/${routePath}?path=${fullPath}`}
|
href={`/${routePath}?path=${fullPath}`}
|
||||||
@@ -155,6 +203,20 @@ export default function ListFiles(
|
|||||||
})}
|
})}
|
||||||
{files.map((file) => (
|
{files.map((file) => (
|
||||||
<tr class='bg-slate-700 hover:bg-slate-600 group'>
|
<tr class='bg-slate-700 hover:bg-slate-600 group'>
|
||||||
|
{typeof onClickChooseFile === 'undefined' ? null : (
|
||||||
|
<td class='gap-3 pl-6 pr-2 py-4'>
|
||||||
|
<input
|
||||||
|
class='w-3 h-3 cursor-pointer text-[#51A4FB] bg-slate-100 border-slate-300 rounded dark:bg-slate-700 dark:border-slate-600'
|
||||||
|
type='checkbox'
|
||||||
|
onClick={() => onClickChooseFile(file.parent_path, file.file_name)}
|
||||||
|
checked={Boolean(
|
||||||
|
chosenFiles.find((_file) =>
|
||||||
|
_file.parent_path === file.parent_path && _file.file_name === file.file_name
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
)}
|
||||||
<td class='flex gap-3 px-6 py-4'>
|
<td class='flex gap-3 px-6 py-4'>
|
||||||
<a
|
<a
|
||||||
href={`/${routePath}/open/${file.file_name}?path=${file.parent_path}`}
|
href={`/${routePath}/open/${file.file_name}?path=${file.parent_path}`}
|
||||||
@@ -237,7 +299,7 @@ export default function ListFiles(
|
|||||||
{directories.length === 0 && files.length === 0
|
{directories.length === 0 && files.length === 0
|
||||||
? (
|
? (
|
||||||
<tr>
|
<tr>
|
||||||
<td class='flex gap-3 px-6 py-4 font-normal' colspan={4}>
|
<td class='flex gap-3 px-6 py-4 font-normal' colspan={5}>
|
||||||
<div class='text-md'>
|
<div class='text-md'>
|
||||||
<div class='font-medium text-slate-400'>No {itemPluralLabel} to show</div>
|
<div class='font-medium text-slate-400'>No {itemPluralLabel} to show</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -43,7 +43,12 @@ export default function MainFiles({ initialDirectories, initialFiles, initialPat
|
|||||||
const directories = useSignal<Directory[]>(initialDirectories);
|
const directories = useSignal<Directory[]>(initialDirectories);
|
||||||
const files = useSignal<DirectoryFile[]>(initialFiles);
|
const files = useSignal<DirectoryFile[]>(initialFiles);
|
||||||
const path = useSignal<string>(initialPath);
|
const path = useSignal<string>(initialPath);
|
||||||
const areNewOptionsOption = useSignal<boolean>(false);
|
const chosenDirectories = useSignal<Pick<Directory, 'parent_path' | 'directory_name'>[]>([]);
|
||||||
|
const chosenFiles = useSignal<Pick<DirectoryFile, 'parent_path' | 'file_name'>[]>([]);
|
||||||
|
const isAnyItemChosen = chosenDirectories.value.length > 0 || chosenFiles.value.length > 0;
|
||||||
|
const bulkItemsCount = chosenDirectories.value.length + chosenFiles.value.length;
|
||||||
|
const areNewOptionsOpen = useSignal<boolean>(false);
|
||||||
|
const areBulkOptionsOpen = useSignal<boolean>(false);
|
||||||
const isNewDirectoryModalOpen = useSignal<boolean>(false);
|
const isNewDirectoryModalOpen = useSignal<boolean>(false);
|
||||||
const renameDirectoryOrFileModal = useSignal<
|
const renameDirectoryOrFileModal = useSignal<
|
||||||
{ isOpen: boolean; isDirectory: boolean; parentPath: string; name: string } | null
|
{ isOpen: boolean; isDirectory: boolean; parentPath: string; name: string } | null
|
||||||
@@ -70,7 +75,7 @@ export default function MainFiles({ initialDirectories, initialFiles, initialPat
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
areNewOptionsOption.value = false;
|
areNewOptionsOpen.value = false;
|
||||||
|
|
||||||
const requestBody = new FormData();
|
const requestBody = new FormData();
|
||||||
requestBody.set('parent_path', path.value);
|
requestBody.set('parent_path', path.value);
|
||||||
@@ -116,7 +121,7 @@ export default function MainFiles({ initialDirectories, initialFiles, initialPat
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
areNewOptionsOption.value = false;
|
areNewOptionsOpen.value = false;
|
||||||
isAdding.value = true;
|
isAdding.value = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -149,7 +154,11 @@ export default function MainFiles({ initialDirectories, initialFiles, initialPat
|
|||||||
}
|
}
|
||||||
|
|
||||||
function toggleNewOptionsDropdown() {
|
function toggleNewOptionsDropdown() {
|
||||||
areNewOptionsOption.value = !areNewOptionsOption.value;
|
areNewOptionsOpen.value = !areNewOptionsOpen.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleBulkOptionsDropdown() {
|
||||||
|
areBulkOptionsOpen.value = !areBulkOptionsOpen.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
function onClickOpenRenameDirectory(parentPath: string, name: string) {
|
function onClickOpenRenameDirectory(parentPath: string, name: string) {
|
||||||
@@ -328,9 +337,9 @@ export default function MainFiles({ initialDirectories, initialFiles, initialPat
|
|||||||
moveDirectoryOrFileModal.value = null;
|
moveDirectoryOrFileModal.value = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onClickDeleteDirectory(parentPath: string, name: string) {
|
async function onClickDeleteDirectory(parentPath: string, name: string, isBulkDeleting = false) {
|
||||||
if (confirm('Are you sure you want to delete this directory?')) {
|
if (isBulkDeleting || confirm('Are you sure you want to delete this directory?')) {
|
||||||
if (isDeleting.value) {
|
if (!isBulkDeleting && isDeleting.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -360,9 +369,9 @@ export default function MainFiles({ initialDirectories, initialFiles, initialPat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onClickDeleteFile(parentPath: string, name: string) {
|
async function onClickDeleteFile(parentPath: string, name: string, isBulkDeleting = false) {
|
||||||
if (confirm('Are you sure you want to delete this file?')) {
|
if (isBulkDeleting || confirm('Are you sure you want to delete this file?')) {
|
||||||
if (isDeleting.value) {
|
if (!isBulkDeleting && isDeleting.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -392,12 +401,122 @@ export default function MainFiles({ initialDirectories, initialFiles, initialPat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onClickChooseDirectory(parentPath: string, name: string) {
|
||||||
|
if (parentPath === '/' && name === '.Trash') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const chosenDirectoryIndex = chosenDirectories.value.findIndex((directory) =>
|
||||||
|
directory.parent_path === parentPath && directory.directory_name === name
|
||||||
|
);
|
||||||
|
|
||||||
|
if (chosenDirectoryIndex === -1) {
|
||||||
|
chosenDirectories.value = [...chosenDirectories.value, { parent_path: parentPath, directory_name: name }];
|
||||||
|
} else {
|
||||||
|
const newChosenDirectories = chosenDirectories.peek();
|
||||||
|
newChosenDirectories.splice(chosenDirectoryIndex, 1);
|
||||||
|
chosenDirectories.value = [...newChosenDirectories];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onClickChooseFile(parentPath: string, name: string) {
|
||||||
|
const chosenFileIndex = chosenFiles.value.findIndex((file) =>
|
||||||
|
file.parent_path === parentPath && file.file_name === name
|
||||||
|
);
|
||||||
|
|
||||||
|
if (chosenFileIndex === -1) {
|
||||||
|
chosenFiles.value = [...chosenFiles.value, { parent_path: parentPath, file_name: name }];
|
||||||
|
} else {
|
||||||
|
const newChosenFiles = chosenFiles.peek();
|
||||||
|
newChosenFiles.splice(chosenFileIndex, 1);
|
||||||
|
chosenFiles.value = [...newChosenFiles];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onClickBulkDelete() {
|
||||||
|
if (
|
||||||
|
confirm(
|
||||||
|
`Are you sure you want to delete ${bulkItemsCount === 1 ? 'this' : 'these'} ${bulkItemsCount} item${
|
||||||
|
bulkItemsCount === 1 ? '' : 's'
|
||||||
|
}?`,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
if (isDeleting.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isDeleting.value = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (const directory of chosenDirectories.value) {
|
||||||
|
await onClickDeleteDirectory(directory.parent_path, directory.directory_name, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const file of chosenFiles.value) {
|
||||||
|
await onClickDeleteDirectory(file.parent_path, file.file_name, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
chosenDirectories.value = [];
|
||||||
|
chosenFiles.value = [];
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
isDeleting.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<section class='flex flex-row items-center justify-between mb-4'>
|
<section class='flex flex-row items-center justify-between mb-4'>
|
||||||
<section class='relative inline-block text-left mr-2'>
|
<section class='relative inline-block text-left mr-2'>
|
||||||
<section class='flex flex-row items-center justify-start'>
|
<section class='flex flex-row items-center justify-start'>
|
||||||
<SearchFiles />
|
<SearchFiles />
|
||||||
|
|
||||||
|
{isAnyItemChosen
|
||||||
|
? (
|
||||||
|
<section class='relative inline-block text-left ml-2'>
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
class='inline-block justify-center gap-x-1.5 rounded-md bg-[#51A4FB] px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-sky-400 ml-2 w-11 h-9'
|
||||||
|
type='button'
|
||||||
|
title='Bulk actions'
|
||||||
|
id='bulk-button'
|
||||||
|
aria-expanded='true'
|
||||||
|
aria-haspopup='true'
|
||||||
|
onClick={() => toggleBulkOptionsDropdown()}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={`/images/${areBulkOptionsOpen.value ? 'hide-options' : 'show-options'}.svg`}
|
||||||
|
alt='Bulk actions'
|
||||||
|
class={`white w-5 max-w-5`}
|
||||||
|
width={20}
|
||||||
|
height={20}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class={`absolute left-0 z-10 mt-2 w-44 origin-top-left rounded-md bg-slate-700 shadow-lg ring-1 ring-black ring-opacity-15 focus:outline-none ${
|
||||||
|
!areBulkOptionsOpen.value ? 'hidden' : ''
|
||||||
|
}`}
|
||||||
|
role='menu'
|
||||||
|
aria-orientation='vertical'
|
||||||
|
aria-labelledby='bulk-button'
|
||||||
|
tabindex={-1}
|
||||||
|
>
|
||||||
|
<div class='py-1'>
|
||||||
|
<button
|
||||||
|
class={`text-white block px-4 py-2 text-sm w-full text-left hover:bg-slate-600`}
|
||||||
|
onClick={() => onClickBulkDelete()}
|
||||||
|
>
|
||||||
|
Delete {bulkItemsCount} item{bulkItemsCount === 1 ? '' : 's'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
: null}
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -427,7 +546,7 @@ export default function MainFiles({ initialDirectories, initialFiles, initialPat
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
class={`absolute right-0 z-10 mt-2 w-44 origin-top-right rounded-md bg-slate-700 shadow-lg ring-1 ring-black ring-opacity-15 focus:outline-none ${
|
class={`absolute right-0 z-10 mt-2 w-44 origin-top-right rounded-md bg-slate-700 shadow-lg ring-1 ring-black ring-opacity-15 focus:outline-none ${
|
||||||
!areNewOptionsOption.value ? 'hidden' : ''
|
!areNewOptionsOpen.value ? 'hidden' : ''
|
||||||
}`}
|
}`}
|
||||||
role='menu'
|
role='menu'
|
||||||
aria-orientation='vertical'
|
aria-orientation='vertical'
|
||||||
@@ -457,6 +576,10 @@ export default function MainFiles({ initialDirectories, initialFiles, initialPat
|
|||||||
<ListFiles
|
<ListFiles
|
||||||
directories={directories.value}
|
directories={directories.value}
|
||||||
files={files.value}
|
files={files.value}
|
||||||
|
chosenDirectories={chosenDirectories.value}
|
||||||
|
chosenFiles={chosenFiles.value}
|
||||||
|
onClickChooseDirectory={onClickChooseDirectory}
|
||||||
|
onClickChooseFile={onClickChooseFile}
|
||||||
onClickOpenRenameDirectory={onClickOpenRenameDirectory}
|
onClickOpenRenameDirectory={onClickOpenRenameDirectory}
|
||||||
onClickOpenRenameFile={onClickOpenRenameFile}
|
onClickOpenRenameFile={onClickOpenRenameFile}
|
||||||
onClickOpenMoveDirectory={onClickOpenMoveDirectory}
|
onClickOpenMoveDirectory={onClickOpenMoveDirectory}
|
||||||
|
|||||||
@@ -25,8 +25,8 @@
|
|||||||
"./": "./",
|
"./": "./",
|
||||||
"xml": "https://deno.land/x/xml@2.1.3/mod.ts",
|
"xml": "https://deno.land/x/xml@2.1.3/mod.ts",
|
||||||
"mrmime": "https://deno.land/x/mrmime@v2.0.0/mod.ts",
|
"mrmime": "https://deno.land/x/mrmime@v2.0.0/mod.ts",
|
||||||
"fresh/": "https://deno.land/x/fresh@1.6.8/",
|
"fresh/": "https://deno.land/x/fresh@1.7.1/",
|
||||||
"$fresh/": "https://deno.land/x/fresh@1.6.8/",
|
"$fresh/": "https://deno.land/x/fresh@1.7.1/",
|
||||||
"preact": "https://esm.sh/preact@10.23.2",
|
"preact": "https://esm.sh/preact@10.23.2",
|
||||||
"preact/": "https://esm.sh/preact@10.23.2/",
|
"preact/": "https://esm.sh/preact@10.23.2/",
|
||||||
"@preact/signals": "https://esm.sh/*@preact/signals@1.3.0",
|
"@preact/signals": "https://esm.sh/*@preact/signals@1.3.0",
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ import * as $news_Feeds from './islands/news/Feeds.tsx';
|
|||||||
import * as $notes_Note from './islands/notes/Note.tsx';
|
import * as $notes_Note from './islands/notes/Note.tsx';
|
||||||
import * as $notes_NotesWrapper from './islands/notes/NotesWrapper.tsx';
|
import * as $notes_NotesWrapper from './islands/notes/NotesWrapper.tsx';
|
||||||
import * as $photos_PhotosWrapper from './islands/photos/PhotosWrapper.tsx';
|
import * as $photos_PhotosWrapper from './islands/photos/PhotosWrapper.tsx';
|
||||||
import { type Manifest } from '$fresh/server.ts';
|
import type { Manifest } from '$fresh/server.ts';
|
||||||
|
|
||||||
const manifest = {
|
const manifest = {
|
||||||
routes: {
|
routes: {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { createHandler, ServeHandlerInfo } from 'fresh/server.ts';
|
|||||||
import manifest from './fresh.gen.ts';
|
import manifest from './fresh.gen.ts';
|
||||||
import config from './fresh.config.ts';
|
import config from './fresh.config.ts';
|
||||||
|
|
||||||
|
// @ts-ignore Deno's newer types seem to have messed this up
|
||||||
const CONN_INFO: ServeHandlerInfo = {
|
const CONN_INFO: ServeHandlerInfo = {
|
||||||
remoteAddr: { hostname: '127.0.0.1', port: 53496, transport: 'tcp' },
|
remoteAddr: { hostname: '127.0.0.1', port: 53496, transport: 'tcp' },
|
||||||
};
|
};
|
||||||
|
|||||||
1
static/images/hide-options.svg
Normal file
1
static/images/hide-options.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 512 512"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="m352 296l-96-96l-96 96"/><path fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32" d="M256 64C150 64 64 150 64 256s86 192 192 192s192-86 192-192S362 64 256 64Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 384 B |
1
static/images/show-options.svg
Normal file
1
static/images/show-options.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 512 512"><path fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32" d="M256 64C150 64 64 150 64 256s86 192 192 192s192-86 192-192S362 64 256 64Z"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="m352 216l-96 96l-96-96"/></svg>
|
||||||
|
After Width: | Height: | Size: 384 B |
Reference in New Issue
Block a user