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
3 changes: 2 additions & 1 deletion frontend/src/components/FilterDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { TagFilterToggle } from './TagFilterToggle'
import { FilterSelector } from './FilterSelector'
import { AccordionMenu } from './AccordionMenu'
import { selectTags } from '../selectors/tags'
import { byName } from '../helpers/utilHelper'
import { useLabel } from '../hooks/useLabel'
import { Drawer } from './Drawer'

Expand Down Expand Up @@ -135,7 +136,7 @@ export const FilterDrawer: React.FC = () => {
filterList={platformFilter.concat(
Object.keys(platforms.nameLookup)
.map(p => ({ value: parseInt(p), name: platforms.nameLookup[p] }))
.sort((a, b) => (a.name?.toLowerCase() > b.name?.toLowerCase() ? 1 : -1))
.sort(byName)
)}
/>
),
Expand Down
8 changes: 2 additions & 6 deletions frontend/src/components/OrganizationGuestList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { useGuests } from '../hooks/useGuests'
import { LoadingMessage } from './LoadingMessage'
import { Pagination } from '@mui/lab'
import { Gutters } from './Gutters'
import { alphaSort } from '../helpers/utilHelper'
import { Avatar } from './Avatar'
import { Icon } from './Icon'

Expand All @@ -15,7 +16,7 @@ export const OrganizationGuestList: React.FC = () => {
const perPage = 20
const pageCount = Math.ceil(guests.length / perPage)
const start = (page - 1) * perPage
const pageGuests = [...guests].sort(alphaEmailSort).slice(start, start + perPage)
const pageGuests = [...guests].sort((a, b) => alphaSort(a.email, b.email)).slice(start, start + perPage)

if (!guestsLoaded) return <LoadingMessage />

Expand Down Expand Up @@ -70,8 +71,3 @@ export const OrganizationGuestList: React.FC = () => {
)
}

function alphaEmailSort(a, b) {
const aa = a.email.toLowerCase()
const bb = b.email.toLowerCase()
return aa > bb ? 1 : aa < bb ? -1 : 0
}
8 changes: 2 additions & 6 deletions frontend/src/components/OrganizationMemberList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ import React from 'react'
import { useSelector } from 'react-redux'
import { selectAvailableUsers } from '../selectors/organizations'
import { OrganizationMember } from '../components/OrganizationMember'
import { alphaSort } from '../helpers/utilHelper'
import { IOrganizationState } from '../models/organization'
import { List } from '@mui/material'

type Props = { organization?: IOrganizationState; owner?: IOrganizationMember; enterprise?: boolean }

export const OrganizationMemberList: React.FC<Props> = ({ organization, owner, enterprise }) => {
const freeUsers = useSelector(selectAvailableUsers)
const members = organization?.members ? [...organization.members].sort(alphaEmailSort) : []
const members = organization?.members ? [...organization.members].sort((a, b) => alphaSort(a.user.email, b.user.email)) : []
return (
<List>
{owner && (
Expand All @@ -35,8 +36,3 @@ export const OrganizationMemberList: React.FC<Props> = ({ organization, owner, e
)
}

function alphaEmailSort(a, b) {
const aa = a.user.email.toLowerCase()
const bb = b.user.email.toLowerCase()
return aa > bb ? 1 : aa < bb ? -1 : 0
}
3 changes: 2 additions & 1 deletion frontend/src/components/OrganizationSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { selectAllConnectionSessions } from '../selectors/connections'
import { selectOrganization } from '../selectors/organizations'
import { GuideBubble } from './GuideBubble'
import { fontSizes } from '../styling'
import { byName } from '../helpers/utilHelper'
import { Avatar } from './Avatar'
import { Icon } from './Icon'

Expand Down Expand Up @@ -146,7 +147,7 @@ export const OrganizationSelect: React.FC = () => {
}
}

options.sort((a, b) => (a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1))
options.sort(byName)
if (!options.length) return null

const mySessions = sessions.filter(s => s.target.accountId === ownOrg?.id).length
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/components/OrganizationSelectList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { ListItemButton, ListSubheader, ListItemIcon, ListItemText, Chip } from
import { getOwnOrganization } from '../models/organization'
import { selectOrganization } from '../selectors/organizations'
import { IconButton } from '../buttons/IconButton'
import { byName } from '../helpers/utilHelper'
import { Avatar } from './Avatar'

const AVATAR_SIZE = 28
Expand Down Expand Up @@ -45,7 +46,7 @@ export const OrganizationSelectList: React.FC = () => {
}
}

options.sort((a, b) => (a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1))
options.sort(byName)
if (!options.length) return null

return (
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/components/SharedUsersLists.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { selectMembersWithAccess } from '../selectors/organizations'
import { selectOrganization } from '../selectors/organizations'
import { ShareButton } from '../buttons/ShareButton'
import { IconButton } from '../buttons/IconButton'
import { alphaSort } from '../helpers/utilHelper'
import { Gutters } from './Gutters'
import { Icon } from './Icon'

Expand Down Expand Up @@ -93,4 +94,4 @@ export const SharedUsersLists: React.FC<Props> = ({ device, network, connected =
)
}

const sort = (users: IUser[]) => users.sort((a, b) => (a.email > b.email ? 1 : b.email > a.email ? -1 : 0))
const sort = (users: IUser[]) => users.sort((a, b) => alphaSort(a.email, b.email))
7 changes: 3 additions & 4 deletions frontend/src/components/SortServices.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { selectDeviceModelAttributes } from '../selectors/devices'
import { IconButton, Menu, MenuItem } from '@mui/material'
import { useDispatch, useSelector } from 'react-redux'
import { State, Dispatch } from '../store'
import { byName } from '../helpers/utilHelper'
import { Icon } from './Icon'

export function getSortOptions(key: ISortServiceType) {
Expand All @@ -13,14 +14,12 @@ export function getSortOptions(key: ISortServiceType) {
const optionSortServices: IOptionServiceSort = {
ATOZ: {
name: 'Alpha A-Z',
sortService: (a: IService, b: IService) =>
a.name.toLowerCase() > b.name.toLowerCase() ? 1 : a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 0,
sortService: byName,
icon: 'sort-alpha-down',
},
ZTOA: {
name: 'Alpha Z-A',
sortService: (a: IService, b: IService) =>
a.name.toLowerCase() < b.name.toLowerCase() ? 1 : a.name.toLowerCase() > b.name.toLowerCase() ? -1 : 0,
sortService: (a: IService, b: IService) => byName(b, a),
icon: 'sort-alpha-up',
},
NEWEST: {
Expand Down
6 changes: 2 additions & 4 deletions frontend/src/components/Tags.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { useMemo } from 'react'
import { Chip, BoxProps, Typography } from '@mui/material'
import { byName } from '../helpers/utilHelper'
import { Tag } from './Tag'

export type TagProps = BoxProps & {
Expand All @@ -14,7 +15,7 @@ export type TagProps = BoxProps & {

export const Tags: React.FC<TagProps> = ({ tags, small, max = 1, showEmpty, hideLabels, onClick, onDelete }) => {
const dot = tags.length > max && small
const sortedTags = useMemo(() => [...tags].sort(nameSort), [tags])
const sortedTags = useMemo(() => [...tags].sort(byName), [tags])

if (!tags.length && showEmpty)
return (
Expand All @@ -37,6 +38,3 @@ export const Tags: React.FC<TagProps> = ({ tags, small, max = 1, showEmpty, hide
return <>{dot ? <Chip size="small" label={tagElements} /> : tagElements}</>
}

function nameSort(a: ITag, b: ITag) {
return a.name.localeCompare(b.name)
}
3 changes: 2 additions & 1 deletion frontend/src/helpers/selectedHelper.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import structuredClone from '@ungap/structured-clone'
import { getDevices } from '../selectors/devices'
import { byName } from './utilHelper'
import { State } from '../store'

export function eachSelectedDevice(state: State, selected: IDevice['id'][], callback: (device: IDevice) => void) {
Expand All @@ -17,5 +18,5 @@ export function getSelectedTags(devices?: IDevice[], selected?: IDevice['id'][])
})
}
})
return result.sort((a, b) => a.name.localeCompare(b.name))
return result.sort(byName)
}
14 changes: 14 additions & 0 deletions frontend/src/helpers/utilHelper.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
import { REGEX_VALID_IP } from '../constants'

// One comparator behind every alphabetical sort in the UI, so "alphabetical" means the same
// thing everywhere: numeric so device2 sorts before device10, base sensitivity so case and
// accents don't split otherwise equal names. Held as a collator because localeCompare with an
// options object builds a new one on every call.
const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' })

export function alphaSort(a: string = '', b: string = '') {
return collator.compare(a, b)
}

export function byName<T extends { name?: string }>(a: T, b: T) {
return alphaSort(a.name, b.name)
}

export function toLookup<T>(array: T[], key: string): ILookup<T> {
return array.reduce((obj, item) => ({ ...obj, [item[key]]: item }), {})
}
Expand Down
7 changes: 2 additions & 5 deletions frontend/src/models/connections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import browser from '../services/browser'
import structuredClone from '@ungap/structured-clone'
import { createModel } from '@rematch/core'
import { parse as urlParse } from 'url'
import { pickTruthy } from '../helpers/utilHelper'
import { alphaSort, pickTruthy } from '../helpers/utilHelper'
import { DEFAULT_CONNECTION, IP_PRIVATE } from '@common/constants'
import { REGEX_HIDDEN_PASSWORD, CERTIFICATE_DOMAIN } from '../constants'
import {
Expand Down Expand Up @@ -570,7 +570,7 @@ export default createModel<RootModel>()({
},

async setAll(all: IConnection[]) {
all.sort((a, b) => nameSort(a.name || '', b.name || ''))
all.sort((a, b) => alphaSort(a.name, b.name))
dispatch.connections.set({ all: [...all] }) // to ensure we trigger update
},
}),
Expand All @@ -593,6 +593,3 @@ export default createModel<RootModel>()({
},
})

function nameSort(a: string, b: string) {
return a.toLowerCase() < b.toLowerCase() ? -1 : a.toLowerCase() > b.toLowerCase() ? 1 : 0
}
7 changes: 2 additions & 5 deletions frontend/src/models/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { removeDeviceName } from '@common/nameHelper'
import { graphQLBasicRequest } from '../services/graphQL'
import { selectActiveAccountId } from '../selectors/accounts'
import { selectDeviceModelAttributes } from '../selectors/devices'
import { alphaSort } from '../helpers/utilHelper'
import { RootModel } from '.'

type ISearchState = ILookup<any> & {
Expand Down Expand Up @@ -156,10 +157,6 @@ export default createModel<RootModel>()({
})

export function sortSearch(search: ISearch[]): ISearch[] {
const sorted = search.sort((a, b) => {
if (a.nodeName.toLowerCase() > b.nodeName.toLowerCase()) return 1
if (a.nodeName.toLowerCase() < b.nodeName.toLowerCase()) return -1
return 0
})
const sorted = search.sort((a, b) => alphaSort(a.nodeName, b.nodeName))
return sorted || []
}
3 changes: 2 additions & 1 deletion frontend/src/pages/NetworksPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,14 @@ import { IconButton } from '../buttons/IconButton'
import { Container } from '../components/Container'
import { Network } from '../components/Network'
import { Gutters } from '../components/Gutters'
import { byName } from '../helpers/utilHelper'
import { Title } from '../components/Title'
import { Icon } from '../components/Icon'

export const NetworksPage: React.FC = () => {
const { t } = useTranslation()
const dispatch = useDispatch<Dispatch>()
const all = [...useSelector(selectNetworks)].sort((a, b) => (a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1))
const all = [...useSelector(selectNetworks)].sort(byName)
const initialized = useSelector((state: State) => state.networks.initialized)
const permissions = useSelector(selectPermissions)
const loading = useSelector(selectDeviceModelAttributes).fetching
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/selectors/contacts.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { createSelector } from 'reselect'
import { State } from '../store'
import { selectOrganization } from './organizations'
import { alphaSort } from '../helpers/utilHelper'
import { isUserAccount } from './accounts'

const getContacts = (state: State) => state.contacts.all
Expand All @@ -24,6 +25,6 @@ export const selectContacts = createSelector(
seen.add(key)
return true
})
.sort((a, b) => a.email.localeCompare(b.email, undefined, { sensitivity: 'base' }))
.sort((a, b) => alphaSort(a.email, b.email))
}
)
3 changes: 2 additions & 1 deletion frontend/src/selectors/tags.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { getTags } from './state'
import { createSelector } from 'reselect'
import { selectActiveAccountId } from './accounts'
import { byName } from '../helpers/utilHelper'

export const selectTags = createSelector([getTags, selectActiveAccountId], (tags, accountId) =>
[...(tags[accountId] || [])].sort((a, b) => a.name.localeCompare(b.name))
[...(tags[accountId] || [])].sort(byName)
)
Loading