@@ -3,20 +3,22 @@ import {
3
3
Trigger as DropdownMenuTrigger ,
4
4
} from '@radix-ui/react-dropdown-menu'
5
5
import {
6
+ CubeIcon ,
6
7
DashboardIcon ,
7
8
DotsHorizontalIcon ,
8
9
HamburgerMenuIcon ,
9
10
MagnifyingGlassIcon ,
10
11
TrashIcon ,
11
- CubeIcon ,
12
12
} from '@radix-ui/react-icons'
13
- import React from 'react'
14
13
import { LoaderFunctionArgs , json } from '@remix-run/node'
15
14
import { useFetcher , useLoaderData } from '@remix-run/react'
15
+ import { motion } from 'framer-motion'
16
16
import moment from 'moment'
17
17
import { UserDetails } from 'prisma-client'
18
+ import React from 'react'
18
19
import { ProjectContextMenu } from '../components/projectActionContextMenu'
19
20
import { SortingContextMenu } from '../components/sortProjectsContextMenu'
21
+ import { useCleanupOperations } from '../hooks/useFetcherWithOperation'
20
22
import { useIsDarkMode } from '../hooks/useIsDarkMode'
21
23
import { listDeletedProjects , listProjects } from '../models/project.server'
22
24
import { getCollaborators } from '../models/projectCollaborators.server'
@@ -25,14 +27,14 @@ import { button } from '../styles/button.css'
25
27
import { newProjectButton } from '../styles/newProjectButton.css'
26
28
import { projectCategoryButton , userName } from '../styles/sidebarComponents.css'
27
29
import { sprinkles } from '../styles/sprinkles.css'
28
- import { Collaborator , CollaboratorsByProject , ProjectWithoutContent } from '../types'
30
+ import { Collaborator , CollaboratorsByProject , Operation , ProjectWithoutContent } from '../types'
29
31
import { requireUser } from '../util/api.server'
30
32
import { assertNever } from '../util/assertNever'
33
+ import { auth0LoginURL } from '../util/auth0.server'
31
34
import { projectEditorLink } from '../util/links'
32
35
import { when } from '../util/react-conditionals'
33
36
import { UnknownPlayerName , multiplayerInitialsFromName } from '../util/strings'
34
37
import { useProjectMatchesQuery , useSortCompareProject } from '../util/use-sort-compare-project'
35
- import { auth0LoginURL } from '../util/auth0.server'
36
38
37
39
const SortOptions = [ 'title' , 'dateCreated' , 'dateModified' ] as const
38
40
export type SortCriteria = ( typeof SortOptions ) [ number ]
@@ -74,6 +76,8 @@ export async function loader(args: LoaderFunctionArgs) {
74
76
}
75
77
76
78
const ProjectsPage = React . memo ( ( ) => {
79
+ useCleanupOperations ( )
80
+
77
81
const data = useLoaderData ( ) as unknown as {
78
82
projects : ProjectWithoutContent [ ]
79
83
user : UserDetails
@@ -134,6 +138,7 @@ const ProjectsPage = React.memo(() => {
134
138
< TopActionBar />
135
139
< ProjectsHeader projects = { filteredProjects } />
136
140
< Projects projects = { filteredProjects } collaborators = { data . collaborators } />
141
+ < ActiveOperations />
137
142
</ div >
138
143
</ div >
139
144
)
@@ -205,12 +210,11 @@ const Sidebar = React.memo(({ user }: { user: UserDetails }) => {
205
210
flexDirection : 'row' ,
206
211
alignItems : 'center' ,
207
212
border : `1px solid ${ isSearchFocused ? '#0075F9' : 'transparent' } ` ,
208
- borderBottomColor : isSearchFocused ? '#0075F9' : 'gray' ,
213
+ borderBottom : `1px solid ${ isSearchFocused ? '#0075F9' : 'gray' } ` ,
209
214
borderRadius : isSearchFocused ? 3 : undefined ,
210
215
overflow : 'visible' ,
211
216
padding : '0px 14px' ,
212
217
gap : 10 ,
213
- borderBottom : '1px solid gray' ,
214
218
} }
215
219
>
216
220
< MagnifyingGlassIcon />
@@ -819,3 +823,78 @@ const ProjectCardActions = React.memo(({ project }: { project: ProjectWithoutCon
819
823
)
820
824
} )
821
825
ProjectCardActions . displayName = 'ProjectCardActions'
826
+
827
+ const ActiveOperations = React . memo ( ( ) => {
828
+ const operations = useProjectsStore ( ( store ) =>
829
+ store . operations . sort ( ( a , b ) => b . startedAt - a . startedAt ) ,
830
+ )
831
+
832
+ return (
833
+ < div
834
+ style = { {
835
+ position : 'fixed' ,
836
+ bottom : 0 ,
837
+ right : 0 ,
838
+ margin : 10 ,
839
+ display : 'flex' ,
840
+ flexDirection : 'column' ,
841
+ gap : 10 ,
842
+ } }
843
+ >
844
+ { operations . map ( ( operation ) => {
845
+ return < ActiveOperation operation = { operation } key = { operation . key } />
846
+ } ) }
847
+ </ div >
848
+ )
849
+ } )
850
+ ActiveOperations . displayName = 'ActiveOperations'
851
+
852
+ const ActiveOperation = React . memo ( ( { operation } : { operation : Operation } ) => {
853
+ function getOperationVerb ( op : Operation ) {
854
+ switch ( op . type ) {
855
+ case 'delete' :
856
+ return 'Deleting'
857
+ case 'destroy' :
858
+ return 'Destroying'
859
+ case 'rename' :
860
+ return 'Renaming'
861
+ case 'restore' :
862
+ return 'Restoring'
863
+ default :
864
+ assertNever ( op . type )
865
+ }
866
+ }
867
+
868
+ return (
869
+ < div
870
+ style = { {
871
+ padding : 10 ,
872
+ display : 'flex' ,
873
+ gap : 10 ,
874
+ alignItems : 'center' ,
875
+ animation : 'spin 2s linear infinite' ,
876
+ } }
877
+ className = { sprinkles ( {
878
+ boxShadow : 'shadow' ,
879
+ borderRadius : 'small' ,
880
+ backgroundColor : 'primary' ,
881
+ color : 'white' ,
882
+ } ) }
883
+ >
884
+ < motion . div
885
+ style = { {
886
+ width : 8 ,
887
+ height : 8 ,
888
+ } }
889
+ className = { sprinkles ( { backgroundColor : 'white' } ) }
890
+ initial = { { rotate : 0 } }
891
+ animate = { { rotate : 100 } }
892
+ transition = { { ease : 'linear' , repeatType : 'loop' , repeat : Infinity } }
893
+ />
894
+ < div >
895
+ { getOperationVerb ( operation ) } project { operation . projectName }
896
+ </ div >
897
+ </ div >
898
+ )
899
+ } )
900
+ ActiveOperation . displayName = 'ActiveOperation'
0 commit comments