Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions Components/Repo/Repo.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,26 @@
0 10px 30px var(--shadow-md);
}

.cardArchived {
background: var(--surface-2);
border-style: dashed;
box-shadow: none;
opacity: 0.62;
filter: grayscale(0.7);
}
.cardArchived::before {
background: var(--text-faint);
opacity: 0.6;
}
.cardArchived:hover {
opacity: 1;
filter: grayscale(0);
border-color: var(--border);
box-shadow:
0 1px 2px var(--shadow-sm),
0 6px 16px -8px var(--shadow-md);
}

/* Left thiny rail */
.card::before {
content: '';
Expand Down Expand Up @@ -127,6 +147,9 @@
animation: pulseRed 4s infinite;
animation-delay: 0.5s;
}
.statusArchived {
background: var(--text-muted);
}

@keyframes pulseGreen {
0% {
Expand Down Expand Up @@ -286,6 +309,28 @@
align-items: center;
justify-content: flex-end;
align-self: center;
gap: 4px;
}

.compactButton {
display: inline-flex;
align-items: center;
justify-content: center;
height: 30px;
width: 30px;
border: none;
border-radius: 8px;
background: transparent;
color: var(--primary);
cursor: pointer;
transition: background 0.15s ease;
}
.compactButton:hover:not(:disabled) {
background: #6d4aff14;
}
.compactButton:disabled {
opacity: 0.5;
cursor: not-allowed;
}

@media all and (max-width: 1000px) {
Expand Down
60 changes: 48 additions & 12 deletions Components/Repo/Repo.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { useState, useMemo } from 'react';
import classes from './Repo.module.css';
import { IconSettings, IconChevronDown, IconBellOff, IconLockPlus, IconCloud } from '@tabler/icons-react';
import { IconSettings, IconChevronDown, IconBellOff, IconLockPlus, IconCloud, IconPackage, IconArchive } from '@tabler/icons-react';
import StorageBar from '../UI/StorageBar/StorageBar';
import InfoTooltip from '../UI/InfoTooltip/InfoTooltip';
import RepoIcon from './RepoIcon';
import QuickCommands from './QuickCommands/QuickCommands';
import { Repository, WizardEnvType, Optional, DateFormatEnum } from '~/types';
import { fromUnixTime, formatDistanceStrict } from 'date-fns';
import { formatDate } from '~/helpers/functions';
import { compactRepository } from '~/helpers/functions/compactRepository';
import useMedia from 'use-media';

type RepoProps = Omit<Repository, 'unixUser' | 'displayDetails'> & {
Expand Down Expand Up @@ -50,6 +51,15 @@ export default function Repo(props: RepoProps) {

//States
const [displayDetails, setDisplayDetails] = useState(displayDetailsFromLS);
const [isCompacting, setIsCompacting] = useState(false);

const compactEnabled = props.wizardEnv?.DISABLE_COMPACT_REPO !== 'true' && !props.archived;

const compactHandler = async () => {
setIsCompacting(true);
await compactRepository(props.repositoryName);
setIsCompacting(false);
};

//BUTTON : Display or not repo details for ONE repo
const displayDetailsForOneHandler = (boolean: boolean) => {
Expand All @@ -69,19 +79,34 @@ export default function Repo(props: RepoProps) {
props.lastSave === 0 ? undefined : formatDate(props.lastSave, props.dateFormat);

//Repo identity: gradient icon avatar with status badge
const repoIdentity = () => (
<div className={classes.avatar} aria-hidden='true'>
<RepoIcon name={props.icon} size={20} stroke={1.75} />
<span
className={`${classes.statusBadge} ${props.status ? classes.statusOk : classes.statusKo}`}
title={props.status ? 'Status OK' : 'Status error'}
/>
</div>
);
const getStatusInfo = (): { className: string; title: string } => {
if (props.archived) {
return { className: classes.statusArchived, title: 'Archived' };
}
if (props.status) {
return { className: classes.statusOk, title: 'Status OK' };
}
return { className: classes.statusKo, title: 'Status error' };
};

const repoIdentity = () => {
const status = getStatusInfo();
return (
<div className={classes.avatar} aria-hidden='true'>
<RepoIcon name={props.icon} size={20} stroke={1.75} />
<span className={`${classes.statusBadge} ${status.className}`} title={status.title} />
</div>
);
};

//Indicator chips (append-only, alert, comment)
const indicatorChips = () => (
<>
{props.archived && (
<div className={classes.chip} title='Archived (read-only, frozen)'>
<IconArchive size={16} />
</div>
)}
{props.appendOnlyMode && (
<div className={classes.chip} title='Append-only mode enabled'>
<IconLockPlus size={16} />
Expand Down Expand Up @@ -114,7 +139,7 @@ export default function Repo(props: RepoProps) {
// ---------- MOBILE ----------
if (isMobile) {
return (
<div className={classes.card}>
<div className={`${classes.card} ${props.archived ? classes.cardArchived : ''}`}>
<div className={classes.header}>
<div className={classes.titleGroup}>
{repoIdentity()}
Expand All @@ -133,7 +158,7 @@ export default function Repo(props: RepoProps) {

// ---------- DESKTOP ----------
return (
<div className={classes.card}>
<div className={`${classes.card} ${props.archived ? classes.cardArchived : ''}`}>
<div className={classes.header}>
<div className={classes.titleGroup}>
{repoIdentity()}
Expand Down Expand Up @@ -195,6 +220,17 @@ export default function Repo(props: RepoProps) {
</div>

<div className={classes.statActions}>
{compactEnabled && (
<button
className={classes.compactButton}
onClick={compactHandler}
disabled={isCompacting}
title='Compact repository (reclaim disk space)'
aria-label='Compact repository'
>
<IconPackage size={22} />
</button>
)}
<button
className={classes.editButton}
onClick={() => props.repoManageEditHandler()}
Expand Down
46 changes: 26 additions & 20 deletions Containers/RepoList/RepoList.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

.containerRepoList {
display: flex;
flex-direction: row;
flex-direction: column;
}

.containerAddRepo {
Expand Down Expand Up @@ -88,36 +88,42 @@
margin: 5px auto;
}

.unfoldButton {
cursor: pointer;
position: sticky;
/* Toolbar toggle button for archived repositories */
.archivedToggleBtn {
display: inline-flex;
align-items: center;
gap: 6px;
height: 36px;
padding: 0 13px;
background: var(--surface-2);
border: 1px solid var(--border-strong);
border-radius: 20px;
color: var(--text-faint);
padding-top: 49px;
align-self: flex-start;
top: 0;
cursor: pointer;
font-size: 0.8rem;
font-weight: 700;
box-sizing: border-box;
transition:
color 0.2s ease,
border-color 0.2s ease,
background 0.2s ease;
}

.foldButton {
cursor: pointer;
position: sticky;
color: var(--text-faint);
padding-top: 49px;
align-self: flex-start;
top: 0;
.archivedToggleBtn:hover {
color: var(--primary);
border-color: var(--primary);
}

.unfoldButton:active,
.foldButton:active {
transform: scale(0.96);
.archivedToggleBtnActive {
color: var(--primary);
border-color: var(--primary);
background: var(--primary-soft);
}

@media all and (max-width: 1000px) {
.newRepoButton {
display: none;
}
.chevron {
display: none;
}
.containerAddRepo {
display: none;
}
Expand Down
72 changes: 53 additions & 19 deletions Containers/RepoList/RepoList.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,30 @@
import classes from './RepoList.module.css';
import React, { useState, useMemo } from 'react';
import {
IconArchive,
IconCalendarDown,
IconCalendarUp,
IconPlus,
IconRefresh,
IconSearch,
IconSortAscendingLetters,
IconSortDescendingLetters,
IconSortAscending2,
IconSortDescending2,
IconDatabase,
IconX,
IconClock,
IconCalendarUp,
IconCalendarDown,
IconSortAscendingSmallBig,
IconSortDescendingSmallBig,
IconSortDescending2,
IconSortDescending2Filled,
IconRefresh,
IconSearch,
IconSortDescendingLetters,
IconSortDescendingSmallBig,
IconX,
} from '@tabler/icons-react';
import { useRouter } from 'next/router';
import Link from 'next/link';
import useSWR, { useSWRConfig } from 'swr';
import { useRouter } from 'next/router';
import React, { useMemo, useState } from 'react';
import { ToastContainer, ToastOptions, toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import useSWR, { useSWRConfig } from 'swr';
import classes from './RepoList.module.css';

import Repo from '~/Components/Repo/Repo';
import RepoManage from '../RepoManage/RepoManage';
import ShimmerRepoList from '~/Components/UI/ShimmerRepoList/ShimmerRepoList';
import { Repository, WizardEnvType, DateFormatEnum, StorageTarget } from '~/types';
import { DateFormatEnum, Repository, StorageTarget, WizardEnvType } from '~/types';
import RepoManage from '../RepoManage/RepoManage';

type SortOption =
| 'alias-asc'
Expand All @@ -50,6 +48,17 @@ export default function RepoList() {
return (savedSort as SortOption) || 'alias-asc';
});
const [isRefreshing, setIsRefreshing] = useState(false);
const [showArchived, setShowArchived] = useState(
() => localStorage.getItem('repoShowArchived') === 'true'
);

const toggleShowArchived = () => {
setShowArchived((prev) => {
const next = !prev;
localStorage.setItem('repoShowArchived', String(next));
return next;
});
};

const [searchQuery, setSearchQuery] = useState(() => {
const savedSearch = localStorage.getItem('repoSearch');
Expand Down Expand Up @@ -177,7 +186,7 @@ export default function RepoList() {
}
};

const renderRepoList = getSortedRepoList().map((repo: Repository) => (
const renderRepo = (repo: Repository) => (
<Repo
key={repo.id}
id={repo.id}
Expand All @@ -197,11 +206,20 @@ export default function RepoList() {
storageTargetName={
repo.storageTarget ? storageTargetNames.get(repo.storageTarget) : undefined
}
archived={repo.archived}
repoManageEditHandler={() => manageRepoEditHandler(repo.id)}
wizardEnv={wizardEnv}
dateFormat={dateFormat}
/>
));
);

const sortedRepoList = getSortedRepoList();
const archivedCount = data.repoList.filter((repo: Repository) => repo.archived).length;
const effectiveShowArchived = showArchived && archivedCount > 0;
const visibleRepoList = sortedRepoList.filter((repo: Repository) =>
effectiveShowArchived ? repo.archived : !repo.archived
);
const renderRepoList = visibleRepoList.map(renderRepo);

return (
<>
Expand Down Expand Up @@ -291,6 +309,22 @@ export default function RepoList() {
title='Manually refresh status & storage. Does not replace a scheduled cron job.'
size={18}
/>
{archivedCount > 0 && (
<button
type='button'
className={`${classes.archivedToggleBtn} ${effectiveShowArchived ? classes.archivedToggleBtnActive : ''}`}
onClick={toggleShowArchived}
aria-pressed={effectiveShowArchived}
title={
effectiveShowArchived
? 'Back to active repositories'
: 'Show archived repositories'
}
>
<IconArchive size={16} />
<span>{archivedCount}</span>
</button>
)}
</div>
</div>

Expand Down
Loading
Loading