PluginProbe
Code Snippets / trunk
Code Snippets vtrunk
4.0.0-beta.2 3.10.2 3.10.1 3.10.0 3.10.0-beta.2 3.10.0-beta.1 4.0.0-beta.1 3.9.6 trunk 2.10.0 2.10.1 2.12.0 2.12.1 2.13.0 2.13.1 2.13.2 2.13.3 2.14.0 2.14.1 2.14.2 2.14.3 2.14.4 2.14.5 2.14.6 3.0.0 All 65 releases
← All changes | js/components/ManageMenu/CommunityCloud/CloudSearch.tsx +105 -70 3.10.0-beta.2 → trunk View file →
@@ -7,12 +7,14 @@
7 7 import { isCloudSnippetDownloadable } from '../../../utils/snippets/snippets'
8 8 import { TableNav } from '../../common/ListTable/TableNavigation'
9 9 import { LoadingStatusNotices } from '../../common/LoadingStatusNotices'
10 10 import { SnippetViewToggle } from '../../common/SnippetViewToggle'
11 +import { CloudSnippetAuthor, CloudSnippetCard } from '../../common/cloud/CloudSnippetCard'
11 12 import { CloudSnippetsTable } from './CloudSnippetsTable'
12 -import { CloudSnippetAuthor, SearchResult } from './SearchResult'
13 13 import { useCloudSearch } from './WithCloudSearchContext'
14 14 import { SearchFilters } from './SearchFilters'
15 +import type { CloudSearchResults } from './WithCloudSearchContext'
16 +import type { TableNavProps } from '../../common/ListTable/TableNavigation'
15 17 import type { CloudSnippetSchema } from '../../../types/schema/CloudSnippetSchema'
16 18 import type { SnippetView } from '../../../types/SnippetView'
17 19 import type { ListTableAction } from '../../common/ListTable'
18 20 import type { Dispatch, FormEventHandler, SetStateAction } from 'react'
@@ -73,38 +75,44 @@
73 75 selected: Set<CloudSnippetSchema['id']>
74 76 setSelected: Dispatch<SetStateAction<Set<CloudSnippetSchema['id']>>>
75 77 }
76 78
77 -const SearchResultsGrid: React.FC<SearchResultsGridProps> = ({ snippets, selected, setSelected }) =>
78 - <ul
79 - className={classnames('cloud-search-results', 'code-snippets-cards', {
80 - 'has-selection': snippets.some(snippet => selected.has(snippet.id))
81 - })}
82 - >
83 - {snippets.map(result =>
84 - <SearchResult
85 - key={result.id}
86 - snippet={result}
87 - author={<CloudSnippetAuthor codevaultSlug={result.codevault} />}
88 - isSelected={selected.has(result.id)}
89 - onSelectedChange={isCloudSnippetDownloadable(result)
90 - ? isSelected => {
91 - setSelected(previous => {
92 - const updated = new Set(previous)
79 +const SearchResultsGrid: React.FC<SearchResultsGridProps> = ({ snippets, selected, setSelected }) => {
80 + const { doSearch } = useCloudSearch()
93 81
94 - if (isSelected) {
95 - updated.add(result.id)
96 - } else {
97 - updated.delete(result.id)
98 - }
82 + return (
83 + <ul
84 + className={classnames('cloud-search-results', 'code-snippets-cards', {
85 + 'has-selection': snippets.some(snippet => selected.has(snippet.id))
86 + })}
87 + >
88 + {snippets.map(result =>
89 + <CloudSnippetCard
90 + key={result.id}
91 + snippet={result}
92 + author={<CloudSnippetAuthor codevaultSlug={result.codevault} />}
93 + isSelected={selected.has(result.id)}
94 + onSnippetDownloaded={() => doSearch()}
95 + onSelectedChange={isCloudSnippetDownloadable(result)
96 + ? isSelected => {
97 + setSelected(previous => {
98 + const updated = new Set(previous)
99 99
100 - return updated
101 - })
102 - }
103 - : undefined}
104 - />)}
105 - </ul>
100 + if (isSelected) {
101 + updated.add(result.id)
102 + } else {
103 + updated.delete(result.id)
104 + }
106 105
106 + return updated
107 + })
108 + }
109 + : undefined}
110 + />)}
111 + </ul>
112 + )
113 +}
114 +
107 115 interface SearchResultsViewProps {
108 116 snippetView: SnippetView
109 117 setSnippetView: (view: SnippetView) => void
110 118 }
@@ -114,23 +122,75 @@
114 122 const CLOUD_BULK_ACTIONS: ListTableAction<CloudSearchAction>[] = [
115 123 { key: 'download', label: __('Download', 'code-snippets') }
116 124 ]
117 125
118 -const useSearchResultsSelection = () => {
119 - const { api } = useRestAPI()
120 - const { searchResults, isSearching, doSearch } = useCloudSearch()
126 +interface CloudSearchSnippetsProps {
127 + snippetView: SnippetView
128 + snippets: CloudSnippetSchema[]
129 + selected: Set<CloudSnippetSchema['id']>
130 + setSelected: Dispatch<SetStateAction<Set<CloudSnippetSchema['id']>>>
131 +}
132 +
133 +const CloudSearchSnippets: React.FC<CloudSearchSnippetsProps> = ({ snippetView, ...props }) =>
134 + 'card' === snippetView
135 + ? <SearchResultsGrid {...props} />
136 + : <CloudSnippetsTable {...props} />
137 +
138 +interface SearchResultsTableNavProps extends Omit<TableNavProps<CloudSnippetSchema['id'], CloudSearchAction>,
139 + 'totalItems' | 'totalPages' | 'currentPage' | 'setCurrentPage'> {
140 + which: 'top' | 'bottom'
141 + doSearch: ReturnType<typeof useCloudSearch>['doSearch']
142 + isSearching: boolean
143 + searchResults: CloudSearchResults
144 +}
145 +
146 +const SearchResultsTableNav: React.FC<SearchResultsTableNavProps> = ({
147 + which,
148 + doSearch,
149 + isSearching,
150 + searchResults,
151 + ...props
152 +}) =>
153 + <TableNav
154 + which={which}
155 + disabled={isSearching}
156 + totalItems={searchResults.totalItems}
157 + totalPages={searchResults.totalPages}
158 + currentPage={searchResults.page}
159 + setCurrentPage={page => doSearch({ page })}
160 + {...props}
161 + />
162 +
163 +/**
164 + * Bulk selection for the current page of results. A new set of results replaces
165 + * the rows the selection referred to, so nothing carries over: a stale
166 + * selection would let a bulk action act on snippets no longer on screen.
167 + */
168 +const useResultsSelection = (snippets: CloudSnippetSchema[] | undefined) => {
121 169 const [selected, setSelected] = useState<Set<CloudSnippetSchema['id']>>(new Set())
122 170
123 171 useEffect(() => {
124 172 setSelected(new Set())
125 - }, [searchResults?.snippets])
173 + }, [snippets])
126 174
175 + return { selected, setSelected }
176 +}
177 +
178 +const SearchResultsTable: React.FC<SearchResultsViewProps> = ({ snippetView, setSnippetView }) => {
179 + const { api } = useRestAPI()
180 + const { searchResults, isSearching, doSearch } = useCloudSearch()
181 + const { selected, setSelected } = useResultsSelection(searchResults?.snippets)
182 +
183 + if (!searchResults) {
184 + return null
185 + }
186 +
127 187 const doAction = async (
128 188 action: CloudSearchAction | undefined,
129 189 selectedIds: Set<CloudSnippetSchema['id']>
130 190 ): Promise<void> => {
131 191 if ('download' === action) {
132 - await Promise.all((searchResults?.snippets ?? [])
192 + await Promise.all(searchResults.snippets
133 193 .filter(snippet => selectedIds.has(snippet.id) && isCloudSnippetDownloadable(snippet))
134 194 .map(({ id }) => api.post(`${REST_BASES.cloud.snippets}/${id}/download`)))
135 195
136 196 doSearch()
@@ -136,35 +196,13 @@
136 196 doSearch()
137 197 }
138 198 }
139 199
140 - return { doAction, doSearch, isSearching, searchResults, selected, setSelected }
141 -}
142 -
143 -const SearchResultsTable: React.FC<SearchResultsViewProps> = ({ snippetView, setSnippetView }) => {
144 - const { doAction, doSearch, isSearching, searchResults, selected, setSelected } =
145 - useSearchResultsSelection()
146 -
147 - if (!searchResults) {
148 - return null
149 - }
150 -
151 - const { totalItems, totalPages, page } = searchResults
152 -
153 - const navProps = {
154 - totalItems,
155 - totalPages,
156 - selected,
157 - setSelected,
158 - disabled: isSearching,
159 - currentPage: page,
160 - setCurrentPage: (newPage: number) => doSearch({ page: newPage })
161 - }
162 -
163 200 return (
164 201 <div className="snippets-list-view">
165 - <TableNav
202 + <SearchResultsTableNav
166 203 which="top"
204 + {...{ isSearching, searchResults, selected, setSelected, doSearch }}
167 205 actions={CLOUD_BULK_ACTIONS}
168 206 doAction={doAction}
169 207 selectAllKeys={'card' === snippetView
170 208 ? searchResults.snippets
@@ -175,24 +213,21 @@
175 213 endTableNav={which =>
176 214 'top' === which
177 215 ? <SnippetViewToggle snippetView={snippetView} setSnippetView={setSnippetView} />
178 216 : null}
179 - {...navProps}
180 217 />
181 218
182 - {'card' === snippetView
183 - ? <SearchResultsGrid
184 - snippets={searchResults.snippets}
185 - selected={selected}
186 - setSelected={setSelected}
187 - />
188 - : <CloudSnippetsTable
189 - snippets={searchResults.snippets}
190 - selected={selected}
191 - setSelected={setSelected}
192 - />}
219 + <CloudSearchSnippets
220 + snippets={searchResults.snippets}
221 + snippetView={snippetView}
222 + selected={selected}
223 + setSelected={setSelected}
224 + />
193 225
194 - <TableNav which="bottom" {...navProps} />
226 + <SearchResultsTableNav
227 + which="bottom"
228 + {...{ isSearching, searchResults, selected, setSelected, doSearch }}
229 + />
195 230 </div>
196 231 )
197 232 }
198 233