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/SnippetsTable/SnippetsTable.tsx +107 -84 4.0.0-beta.1 → trunk View file →
@@ -1,19 +1,21 @@
1 1 import { __, sprintf } from '@wordpress/i18n'
2 2 import { createInterpolateElement } from '@wordpress/element'
3 -import React, { useState } from 'react'
3 +import React, { useCallback, useMemo } from 'react'
4 4 import classnames from 'classnames'
5 +import { useHorizontalScrollOverflow } from '../../../hooks/useHorizontalScrollOverflow'
5 6 import { WithRestAPIContext } from '../../../hooks/useRestAPI'
7 +import { useSnippetView } from '../../../hooks/useSnippetView'
6 8 import { WithSnippetsAPIContext } from '../../../hooks/useSnippetsAPI'
7 -import { WithSnippetsListContext } from '../../../hooks/useSnippetsList'
9 +import { WithSnippetsListContext, useSnippetsList } from '../../../hooks/useSnippetsList'
8 10 import { SNIPPET_TYPES } from '../../../types/Snippet'
9 11 import { isLicensed } from '../../../utils/screen'
10 -import { SNIPPET_TYPE_LABELS, getSnippetEditUrl, isProType } from '../../../utils/snippets/snippets'
12 +import { SNIPPET_TYPE_LABELS, getSnippetAddNewUrl, getSnippetType, isProType } from '../../../utils/snippets/snippets'
11 13 import { buildUrl } from '../../../utils/urls'
12 14 import { Badge } from '../../common/Badge'
13 -import { Button } from '../../common/Button'
14 15 import { Notice } from '../../common/Notice'
15 -import { UpsellDialog } from '../../common/UpsellDialog'
16 +import { ScreenMetaSlot } from '../../common/ScreenMetaSlot'
17 +import { UpsellPage } from '../../common/UpsellDialog'
16 18 import { WithSnippetsTableFilters, useSnippetsFilters } from './WithSnippetsTableFilters'
17 19 import { WithFilteredSnippetsContext } from './WithFilteredSnippetsContext'
18 20 import { SnippetsListTable } from './SnippetsListTable'
19 21 import type { SnippetType } from '../../../types/Snippet'
@@ -19,84 +21,49 @@
19 21 import type { SnippetType } from '../../../types/Snippet'
20 22
21 23 interface SnippetTypeTabProps {
22 24 type?: SnippetType
23 - setIsUpgradeDialogOpen: (isOpen: boolean) => void
25 + count?: number
24 26 }
25 27
26 -const SnippetTypeTab: React.FC<SnippetTypeTabProps> = ({ type, setIsUpgradeDialogOpen }) => {
28 +const SnippetTypeTab: React.FC<SnippetTypeTabProps> = ({ type, count }) => {
27 29 const { currentType, setCurrentType } = useSnippetsFilters()
28 30 const tabName = type ?? 'all'
29 31
32 + const isActive = type === currentType
33 + const isProLocked = type && isProType(type) && !isLicensed()
34 +
30 35 return (
31 - <a
32 - href={buildUrl(window.location.href, { type: tabName })}
33 - className={classnames('nav-tab', `${tabName}-tab`, {
34 - 'nav-tab-active': type === currentType,
35 - 'nav-tab-inactive': type && type !== currentType && !isLicensed() && isProType(type)
36 - })}
37 - aria-current={type === currentType ? 'page' : undefined}
38 - onClick={event => {
39 - event.preventDefault()
40 -
41 - if (type && !isLicensed() && isProType(type)) {
42 - setIsUpgradeDialogOpen(true)
43 - } else {
36 + <li>
37 + <a
38 + href={buildUrl(window.location.href, { type: tabName })}
39 + className={classnames('snippet-type-link', `${tabName}-type-link`, {
40 + 'active-type': isActive,
41 + 'pro-locked-type': isProLocked && !isActive
42 + })}
43 + aria-current={isActive ? 'page' : undefined}
44 + onClick={event => {
45 + event.preventDefault()
44 46 setCurrentType(type)
45 - }
46 - }}
47 - >
48 - <span className={`${tabName}-label`}>
49 - {type ? SNIPPET_TYPE_LABELS[type] : __('All Snippets', 'code-snippets')}
50 - </span>
51 - {type && <Badge name={type} />
52 - }
53 - </a>
47 + }}
48 + >
49 + {type && <Badge name={type} />}
50 + <span className={classnames(`${tabName}-label`, 'snippet-type-name')}>
51 + {type
52 + ? SNIPPET_TYPE_LABELS[type]
53 + : <>
54 + <span className="snippet-type-name-full">{__('All Snippets', 'code-snippets')}</span>
55 + <span className="snippet-type-name-short">{__('All', 'code-snippets')}</span>
56 + </>}
57 + </span>
58 + {count !== undefined && !isProLocked && <span className="subnav-count">{count}</span>}
59 + {isProLocked && <span className="pro-chip">{__('Pro', 'code-snippets')}</span>}
60 + </a>
61 + </li>
54 62 )
55 63 }
56 64
57 -const PageHeading = () => {
58 - const { searchQueryText, searchLineNumber, currentTag, setSearchQuery, setCurrentTag } = useSnippetsFilters()
59 - return (
60 - <>
61 - <a href={getSnippetEditUrl()} className="button button-primary button-large create-snippet-button">
62 - {__('Create new snippet', 'code-snippets')}
63 - </a>
64 65
65 - <h2>
66 - {__('Manage Code Snippets', 'code-snippets')}
67 -
68 - {searchQueryText || currentTag
69 - ? <span className="subtitle">
70 - {__('Search results', 'code-snippets')}
71 -
72 - {/* translators: %s: search query. */}
73 - {searchQueryText && sprintf(__(' for “%s”', 'code-snippets'), searchQueryText)}
74 -
75 - {/* translators: %d: code line number. */}
76 - {searchLineNumber && sprintf(__(' on line “%d”', 'code-snippets'), searchLineNumber)}
77 -
78 - {/* translators: %s: tag name. */}
79 - {currentTag && sprintf(__(' in tag “%s”', 'code-snippets'), currentTag)}
80 -
81 - {' '}
82 - <Button small className="clear-filters" onClick={() => {
83 - setSearchQuery()
84 - setCurrentTag()
85 - }}>
86 - {__('Clear Filters', 'code-snippets')}
87 - </Button>
88 - </span>
89 - : null}
90 - </h2>
91 -
92 - <hr className="wp-header-end" />
93 -
94 - <SafeModeNotice />
95 - </>
96 - )
97 -}
98 -
99 66 const SafeModeNotice = () =>
100 67 window.CODE_SNIPPETS_MANAGE?.isSafeModeActive
101 68 ? <Notice type="error">
102 69 <p>
@@ -116,29 +83,85 @@
116 83 </p>
117 84 </Notice>
118 85 : null
119 86
87 +// Counts render immediately from localized values, then switch to live values.
88 +const useSnippetTypeCounts = () => {
89 + const { snippetsList } = useSnippetsList()
90 +
91 + const countedSnippets = useMemo(
92 + () => snippetsList?.filter(snippet => !snippet.trashed),
93 + [snippetsList]
94 + )
95 +
96 + const typeCounts = useMemo(
97 + () => countedSnippets?.reduce((counts, snippet) => {
98 + const type = getSnippetType(snippet)
99 + return counts.set(type, (counts.get(type) ?? 0) + 1)
100 + }, new Map<SnippetType, number>()),
101 + [countedSnippets]
102 + )
103 +
104 + const localized = window.CODE_SNIPPETS_MANAGE?.typeCounts
105 +
106 + const getCount = useCallback(
107 + (type?: SnippetType) => countedSnippets
108 + ? type ? typeCounts?.get(type) ?? 0 : countedSnippets.length
109 + : localized?.[type ?? 'all'],
110 + [countedSnippets, typeCounts, localized]
111 + )
112 +
113 + return { getCount }
114 +}
115 +
120 116 const SnippetsTableInner = () => {
121 - const [isUpgradeDialogOpen, setIsUpgradeDialogOpen] = useState(false)
117 + const { snippetView, setSnippetView } = useSnippetView()
118 + const { currentType } = useSnippetsFilters()
119 + const { getCount } = useSnippetTypeCounts()
120 + const { atStart, atEnd, scrollRef } = useHorizontalScrollOverflow()
122 121
123 122 return (
124 123 <>
125 - <PageHeading />
124 + <div
125 + className={classnames('snippet-type-nav-wrapper', {
126 + 'has-scroll-start': !atStart,
127 + 'has-scroll-end': !atEnd
128 + })}
129 + >
130 + <nav
131 + ref={scrollRef}
132 + className="snippet-type-nav"
133 + aria-label={__('Snippet types', 'code-snippets')}
134 + >
135 + <ul>
136 + <SnippetTypeTab count={getCount()} />
137 + {SNIPPET_TYPES.map(type => <SnippetTypeTab key={type} type={type} count={getCount(type)} />)}
138 + </ul>
139 + </nav>
140 + </div>
126 141
127 - <nav
128 - className="nav-tab-wrapper snippet-type-tabs"
129 - aria-label={__('Snippet types', 'code-snippets')}
130 - >
131 - <SnippetTypeTab setIsUpgradeDialogOpen={setIsUpgradeDialogOpen} />
132 - {SNIPPET_TYPES.map(type =>
133 - <SnippetTypeTab key={type} type={type} setIsUpgradeDialogOpen={setIsUpgradeDialogOpen} />)}
134 - </nav>
142 + <ScreenMetaSlot />
135 143
136 - <WithFilteredSnippetsContext>
137 - <SnippetsListTable />
138 - </WithFilteredSnippetsContext>
144 + <div className="snippets-page-header">
145 + <h1>{sprintf(
146 + // translators: %s: label of the currently selected snippet type.
147 + __('Local Snippets: %s', 'code-snippets'),
148 + currentType ? SNIPPET_TYPE_LABELS[currentType] : __('All Snippets', 'code-snippets')
149 + )}</h1>
150 + <a href={getSnippetAddNewUrl(currentType)} className="button button-primary">
151 + {__('Add Snippet', 'code-snippets')}
152 + </a>
153 + </div>
139 154
140 - <UpsellDialog isOpen={isUpgradeDialogOpen} setIsOpen={setIsUpgradeDialogOpen} />
155 + <hr className="wp-header-end" />
156 +
157 + <SafeModeNotice />
158 +
159 + {currentType && !isLicensed() && isProType(currentType)
160 + ? <UpsellPage />
161 + : <WithFilteredSnippetsContext>
162 + <SnippetsListTable snippetView={snippetView} setSnippetView={setSnippetView} />
163 + </WithFilteredSnippetsContext>}
141 164 </>
142 165 )
143 166 }
144 167