4
4
* See License-AGPL.txt in the project root for license information.
5
5
*/
6
6
7
- import React from "react" ;
7
+ import { useContext , useEffect , useState } from "react" ;
8
8
import { WhitelistedRepository , Workspace , WorkspaceInfo } from "@gitpod/gitpod-protocol" ;
9
9
import Header from "../components/Header" ;
10
10
import DropDown from "../components/DropDown" ;
11
11
import exclamation from "../images/exclamation.svg" ;
12
12
import { WorkspaceModel } from "./workspace-model" ;
13
13
import { WorkspaceEntry } from "./WorkspaceEntry" ;
14
14
import { getGitpodService , gitpodHostUrl } from "../service/service" ;
15
- import { StartWorkspaceModal , WsStartEntry } from "./StartWorkspaceModal" ;
15
+ import { StartWorkspaceModal , WsStartEntry } from "./StartWorkspaceModal" ;
16
16
import { Item , ItemField , ItemFieldContextMenu , ItemFieldIcon , ItemsList } from "../components/ItemsList" ;
17
+ import { getCurrentTeam , TeamsContext } from "../teams/teams-context" ;
18
+ import { useLocation } from "react-router" ;
17
19
18
20
export interface WorkspacesProps {
19
21
}
@@ -24,144 +26,36 @@ export interface WorkspacesState {
24
26
repos : WhitelistedRepository [ ] ;
25
27
}
26
28
27
- export default class Workspaces extends React . Component < WorkspacesProps , WorkspacesState > {
29
+ export default function ( ) {
30
+ const location = useLocation ( ) ;
28
31
29
- protected workspaceModel : WorkspaceModel | undefined ;
32
+ const { teams } = useContext ( TeamsContext ) ;
33
+ const team = getCurrentTeam ( location , teams ) ;
34
+ const [ workspaces , setWorkspaces ] = useState < WorkspaceInfo [ ] > ( [ ] ) ;
35
+ const [ repos , setRepos ] = useState < WhitelistedRepository [ ] > ( [ ] ) ;
36
+ const [ isTemplateModelOpen , setIsTemplateModelOpen ] = useState < boolean > ( false ) ;
37
+ const [ workspaceModel , setWorkspaceModel ] = useState < WorkspaceModel > ( ) ;
30
38
31
- constructor ( props : WorkspacesProps ) {
32
- super ( props ) ;
33
- this . state = {
34
- workspaces : [ ] ,
35
- isTemplateModelOpen : false ,
36
- repos : [ ] ,
37
- } ;
38
- }
39
-
40
- async componentDidMount ( ) {
41
- this . workspaceModel = new WorkspaceModel ( this . setWorkspaces ) ;
42
- const repos = await getGitpodService ( ) . server . getFeaturedRepositories ( ) ;
43
- this . setState ( {
44
- repos
45
- } ) ;
46
- }
39
+ const updateWorkspaces = async ( ) => {
40
+ getGitpodService ( ) . server . getFeaturedRepositories ( ) . then ( setRepos ) ;
41
+ const workspaceModel = ! ! team ?
42
+ new WorkspaceModel ( setWorkspaces , getGitpodService ( ) . server . getTeamProjects ( team ?. id ) . then ( projects => projects . map ( p => p . id ) ) , false ) :
43
+ new WorkspaceModel ( setWorkspaces , getGitpodService ( ) . server . getUserProjects ( ) . then ( projects => projects . map ( p => p . id ) ) , true ) ;
47
44
48
- protected setWorkspaces = ( workspaces : WorkspaceInfo [ ] ) => {
49
- this . setState ( {
50
- workspaces
51
- } ) ;
45
+ setWorkspaceModel ( workspaceModel ) ;
52
46
}
47
+ useEffect ( ( ) => {
48
+ updateWorkspaces ( ) ;
49
+ } , [ teams , location ] ) ;
53
50
54
- protected showStartWSModal = ( ) => this . setState ( {
55
- isTemplateModelOpen : true
56
- } ) ;
57
-
58
- protected hideStartWSModal = ( ) => this . setState ( {
59
- isTemplateModelOpen : false
60
- } ) ;
61
-
62
- render ( ) {
63
- const wsModel = this . workspaceModel ;
64
- const onActive = ( ) => wsModel ! . active = true ;
65
- const onAll = ( ) => wsModel ! . active = false ;
66
- return < >
67
- < Header title = "Workspaces" subtitle = "Manage recent and stopped workspaces." />
51
+ const showStartWSModal = ( ) => setIsTemplateModelOpen ( true ) ;
52
+ const hideStartWSModal = ( ) => setIsTemplateModelOpen ( false ) ;
68
53
69
- < div className = "lg:px-28 px-10 pt-8 flex" >
70
- < div className = "flex" >
71
- < div className = "py-4" >
72
- < svg xmlns = "http://www.w3.org/2000/svg" fill = "none" viewBox = "0 0 16 16" width = "16" height = "16" > < path fill = "#A8A29E" d = "M6 2a4 4 0 100 8 4 4 0 000-8zM0 6a6 6 0 1110.89 3.477l4.817 4.816a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 010 6z" /> </ svg >
73
- </ div >
74
- < input type = "search" placeholder = "Search Workspaces" onChange = { ( v ) => { if ( wsModel ) wsModel . setSearch ( v . target . value ) } } />
75
- </ div >
76
- < div className = "flex-1" />
77
- < div className = "py-3" >
78
- < DropDown prefix = "Filter: " contextMenuWidth = "w-32" activeEntry = { wsModel ?. active ? 'Active' : 'All' } entries = { [ {
79
- title : 'Active' ,
80
- onClick : onActive
81
- } , {
82
- title : 'All' ,
83
- onClick : onAll
84
- } ] } />
85
- </ div >
86
- < div className = "py-3 pl-3" >
87
- < DropDown prefix = "Limit: " contextMenuWidth = "w-32" activeEntry = { wsModel ? wsModel ?. limit + '' : undefined } entries = { [ {
88
- title : '50' ,
89
- onClick : ( ) => { if ( wsModel ) wsModel . limit = 50 ; }
90
- } , {
91
- title : '100' ,
92
- onClick : ( ) => { if ( wsModel ) wsModel . limit = 100 ; }
93
- } , {
94
- title : '200' ,
95
- onClick : ( ) => { if ( wsModel ) wsModel . limit = 200 ; }
96
- } ] } />
97
- </ div >
98
- { wsModel && this . state ?. workspaces . length > 0 ?
99
- < button onClick = { this . showStartWSModal } className = "ml-2" > New Workspace</ button >
100
- : null
101
- }
102
- </ div >
103
- { wsModel && (
104
- this . state ?. workspaces . length > 0 || wsModel . searchTerm ?
105
- < ItemsList className = "lg:px-28 px-10" >
106
- < Item header = { true } className = "px-6" >
107
- < ItemFieldIcon />
108
- < ItemField className = "w-3/12" > Name</ ItemField >
109
- < ItemField className = "w-4/12" > Context</ ItemField >
110
- < ItemField className = "w-2/12" > Pending Changes</ ItemField >
111
- < ItemField className = "w-2/12" > Last Start</ ItemField >
112
- < ItemFieldContextMenu />
113
- </ Item >
114
- {
115
- wsModel . active || wsModel . searchTerm ? null :
116
- < Item className = "w-full bg-gitpod-kumquat-light py-6 px-6" >
117
- < ItemFieldIcon >
118
- < img src = { exclamation } alt = "Exclamation Mark" className = "m-auto" />
119
- </ ItemFieldIcon >
120
- < ItemField className = " flex flex-col" >
121
- < div className = "text-gitpod-red font-semibold" > Garbage Collection</ div >
122
- < p className = "text-gray-500" > Unpinned workspaces that have been stopped for more than 14 days will be automatically deleted. < a className = "gp-link" href = "https://www.gitpod.io/docs/life-of-workspace/#garbage-collection" > Learn more</ a > </ p >
123
- </ ItemField >
124
- </ Item >
125
- }
126
- {
127
- this . state ?. workspaces . map ( e => {
128
- return < WorkspaceEntry key = { e . workspace . id } desc = { e } model = { wsModel } stopWorkspace = { wsId => getGitpodService ( ) . server . stopWorkspace ( wsId ) } />
129
- } )
130
- }
131
- </ ItemsList >
132
- :
133
- < div className = "lg:px-28 px-10 flex flex-col space-y-2" >
134
- < div className = "px-6 py-3 flex justify-between space-x-2 text-gray-400 border-t border-gray-200 dark:border-gray-800 h-96" >
135
- < div className = "flex flex-col items-center w-96 m-auto" >
136
- < h3 className = "text-center pb-3 text-gray-500 dark:text-gray-400" > No Active Workspaces</ h3 >
137
- < div className = "text-center pb-6 text-gray-500" > Prefix any git repository URL with gitpod.io/# or create a new workspace for a recently used project. < a className = "gp-link" href = "https://www.gitpod.io/docs/getting-started/" > Learn more</ a > </ div >
138
- < span >
139
- < button onClick = { this . showStartWSModal } > New Workspace</ button >
140
- { wsModel . getAllFetchedWorkspaces ( ) . size > 0 ? < button className = "secondary ml-2" onClick = { onAll } > View All Workspaces</ button > :null }
141
- </ span >
142
- </ div >
143
- </ div >
144
- </ div >
145
- ) }
146
- < StartWorkspaceModal
147
- onClose = { this . hideStartWSModal }
148
- visible = { ! ! this . state ?. isTemplateModelOpen }
149
- examples = { this . state ?. repos && this . state . repos . map ( r => ( {
150
- title : r . name ,
151
- description : r . description || r . url ,
152
- startUrl : gitpodHostUrl . withContext ( r . url ) . toString ( )
153
- } ) ) }
154
- recent = { wsModel && this . state ?. workspaces ?
155
- this . getRecentSuggestions ( )
156
- : [ ] } />
157
- </ > ;
158
- }
159
-
160
- protected getRecentSuggestions ( ) : WsStartEntry [ ] {
161
- if ( this . workspaceModel ) {
162
- const all = this . workspaceModel . getAllFetchedWorkspaces ( ) ;
54
+ const getRecentSuggestions : ( ) => WsStartEntry [ ] = ( ) => {
55
+ if ( workspaceModel ) {
56
+ const all = workspaceModel . getAllFetchedWorkspaces ( ) ;
163
57
if ( all && all . size > 0 ) {
164
- const index = new Map < string , WsStartEntry & { lastUse : string } > ( ) ;
58
+ const index = new Map < string , WsStartEntry & { lastUse : string } > ( ) ;
165
59
for ( const ws of Array . from ( all . values ( ) ) ) {
166
60
const repoUrl = Workspace . getFullRepositoryUrl ( ws . workspace ) ;
167
61
if ( repoUrl ) {
@@ -183,10 +77,109 @@ export default class Workspaces extends React.Component<WorkspacesProps, Workspa
183
77
}
184
78
}
185
79
const list = Array . from ( index . values ( ) ) ;
186
- list . sort ( ( a , b ) => b . lastUse . localeCompare ( a . lastUse ) ) ;
80
+ list . sort ( ( a , b ) => b . lastUse . localeCompare ( a . lastUse ) ) ;
187
81
return list ;
188
82
}
189
83
}
190
84
return [ ] ;
191
85
}
86
+ if ( ! workspaceModel ) {
87
+ return < div />
88
+ }
89
+
90
+ const onActive = ( ) => workspaceModel . active = true ;
91
+ const onAll = ( ) => workspaceModel . active = false ;
92
+ return < >
93
+ < Header title = "Workspaces" subtitle = "Manage recent and stopped workspaces." />
94
+
95
+ < div className = "lg:px-28 px-10 pt-8 flex" >
96
+ < div className = "flex" >
97
+ < div className = "py-4" >
98
+ < svg xmlns = "http://www.w3.org/2000/svg" fill = "none" viewBox = "0 0 16 16" width = "16" height = "16" > < path fill = "#A8A29E" d = "M6 2a4 4 0 100 8 4 4 0 000-8zM0 6a6 6 0 1110.89 3.477l4.817 4.816a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 010 6z" /> </ svg >
99
+ </ div >
100
+ < input type = "search" placeholder = "Search Workspaces" onChange = { ( v ) => { if ( workspaceModel ) workspaceModel . setSearch ( v . target . value ) } } />
101
+ </ div >
102
+ < div className = "flex-1" />
103
+ < div className = "py-3" >
104
+ < DropDown prefix = "Filter: " contextMenuWidth = "w-32" activeEntry = { workspaceModel ?. active ? 'Active' : 'All' } entries = { [ {
105
+ title : 'Active' ,
106
+ onClick : onActive
107
+ } , {
108
+ title : 'All' ,
109
+ onClick : onAll
110
+ } ] } />
111
+ </ div >
112
+ < div className = "py-3 pl-3" >
113
+ < DropDown prefix = "Limit: " contextMenuWidth = "w-32" activeEntry = { workspaceModel ? workspaceModel ?. limit + '' : undefined } entries = { [ {
114
+ title : '50' ,
115
+ onClick : ( ) => { if ( workspaceModel ) workspaceModel . limit = 50 ; }
116
+ } , {
117
+ title : '100' ,
118
+ onClick : ( ) => { if ( workspaceModel ) workspaceModel . limit = 100 ; }
119
+ } , {
120
+ title : '200' ,
121
+ onClick : ( ) => { if ( workspaceModel ) workspaceModel . limit = 200 ; }
122
+ } ] } />
123
+ </ div >
124
+ { workspaceModel && workspaces . length > 0 ?
125
+ < button onClick = { showStartWSModal } className = "ml-2" > New Workspace</ button >
126
+ : null
127
+ }
128
+ </ div >
129
+ { workspaceModel && (
130
+ workspaces . length > 0 || workspaceModel . searchTerm ?
131
+ < ItemsList className = "lg:px-28 px-10" >
132
+ < Item header = { true } className = "px-6" >
133
+ < ItemFieldIcon />
134
+ < ItemField className = "w-3/12" > Name</ ItemField >
135
+ < ItemField className = "w-4/12" > Context</ ItemField >
136
+ < ItemField className = "w-2/12" > Pending Changes</ ItemField >
137
+ < ItemField className = "w-2/12" > Last Start</ ItemField >
138
+ < ItemFieldContextMenu />
139
+ </ Item >
140
+ {
141
+ workspaceModel . active || workspaceModel . searchTerm ? null :
142
+ < Item className = "w-full bg-gitpod-kumquat-light py-6 px-6" >
143
+ < ItemFieldIcon >
144
+ < img src = { exclamation } alt = "Exclamation Mark" className = "m-auto" />
145
+ </ ItemFieldIcon >
146
+ < ItemField className = " flex flex-col" >
147
+ < div className = "text-gitpod-red font-semibold" > Garbage Collection</ div >
148
+ < p className = "text-gray-500" > Unpinned workspaces that have been stopped for more than 14 days will be automatically deleted. < a className = "gp-link" href = "https://www.gitpod.io/docs/life-of-workspace/#garbage-collection" > Learn more</ a > </ p >
149
+ </ ItemField >
150
+ </ Item >
151
+ }
152
+ {
153
+ workspaces . map ( e => {
154
+ return < WorkspaceEntry key = { e . workspace . id } desc = { e } model = { workspaceModel } stopWorkspace = { wsId => getGitpodService ( ) . server . stopWorkspace ( wsId ) } />
155
+ } )
156
+ }
157
+ </ ItemsList >
158
+ :
159
+ < div className = "lg:px-28 px-10 flex flex-col space-y-2" >
160
+ < div className = "px-6 py-3 flex justify-between space-x-2 text-gray-400 border-t border-gray-200 dark:border-gray-800 h-96" >
161
+ < div className = "flex flex-col items-center w-96 m-auto" >
162
+ < h3 className = "text-center pb-3 text-gray-500 dark:text-gray-400" > No Active Workspaces</ h3 >
163
+ < div className = "text-center pb-6 text-gray-500" > Prefix any git repository URL with gitpod.io/# or create a new workspace for a recently used project. < a className = "gp-link" href = "https://www.gitpod.io/docs/getting-started/" > Learn more</ a > </ div >
164
+ < span >
165
+ < button onClick = { showStartWSModal } > New Workspace</ button >
166
+ { workspaceModel . getAllFetchedWorkspaces ( ) . size > 0 ? < button className = "secondary ml-2" onClick = { onAll } > View All Workspaces</ button > : null }
167
+ </ span >
168
+ </ div >
169
+ </ div >
170
+ </ div >
171
+ ) }
172
+ < StartWorkspaceModal
173
+ onClose = { hideStartWSModal }
174
+ visible = { ! ! isTemplateModelOpen }
175
+ examples = { repos && repos . map ( r => ( {
176
+ title : r . name ,
177
+ description : r . description || r . url ,
178
+ startUrl : gitpodHostUrl . withContext ( r . url ) . toString ( )
179
+ } ) ) }
180
+ recent = { workspaceModel && workspaces ?
181
+ getRecentSuggestions ( )
182
+ : [ ] } />
183
+ </ > ;
184
+
192
185
}
0 commit comments