Skip to content

Commit

Permalink
feat: handle shortcuttiles
Browse files Browse the repository at this point in the history
  • Loading branch information
acezard committed Aug 14, 2024
1 parent 4435b4f commit 2e92b27
Show file tree
Hide file tree
Showing 17 changed files with 261 additions and 34 deletions.
9 changes: 9 additions & 0 deletions react/AppIcon/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import React, { Component } from 'react'
import { withClient } from 'cozy-client'

import styles from './styles.styl'
import { isShortcutFile } from '../AppSections/helpers'
import Icon, { iconPropType } from '../Icon'
import CubeIcon from '../Icons/Cube'
import { ShortcutTile } from '../ShortcutTile'
import palette from '../palette'
import { AppDoctype } from '../proptypes'

Expand Down Expand Up @@ -45,6 +47,9 @@ export class AppIcon extends Component {
fetchIcon() {
const { app, type, priority, client } = this.props

// Shortcut files used in cozy-store have their own icon in their doctype metadata
if (isShortcutFile(app)) return

return client.getStackClient().getIconURL({
type,
slug: app.slug || app,
Expand Down Expand Up @@ -93,6 +98,10 @@ export class AppIcon extends Component {
const { alt, className, fallbackIcon } = this.props
const { icon, status } = this.state

if (isShortcutFile(this.props.app)) {
return <ShortcutTile file={this.props.app} />
}

switch (status) {
case FETCHING:
return (
Expand Down
49 changes: 48 additions & 1 deletion react/AppSections/Sections.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@ import cx from 'classnames'
import PropTypes from 'prop-types'
import React, { Component } from 'react'

import flag from 'cozy-flags'
import { useExtendI18n } from 'cozy-ui/transpiled/react/providers/I18n'

import styles from './Sections.styl'
import * as catUtils from './categories'
import AppsSection from './components/AppsSection'
import DropdownFilter from './components/DropdownFilter'
import { APP_TYPE } from './constants'
import { generateI18nConfig } from './generateI18nConfig'
import { isShortcutFile } from './helpers'
import en from './locales/en.json'
import fr from './locales/fr.json'
import * as searchUtils from './search'
Expand Down Expand Up @@ -105,12 +110,19 @@ export class Sections extends Component {
const webAppGroups = catUtils.groupApps(
filteredApps.filter(a => a.type === APP_TYPE.WEBAPP)
)
const shortcutsGroups = catUtils.groupApps(
filteredApps.filter(a => isShortcutFile(a))
)

const webAppsCategories = Object.keys(webAppGroups)
.map(cat => catUtils.addLabel({ value: cat }, t))
.sort(catUtils.sorter)
const konnectorsCategories = Object.keys(konnectorGroups)
.map(cat => catUtils.addLabel({ value: cat }, t))
.sort(catUtils.sorter)
const shortcutsCategories = Object.keys(shortcutsGroups)
.map(cat => catUtils.addLabel({ value: cat }, t))
.sort(catUtils.sorter)

const dropdownDisplayed =
hasNav && (isMobile || isTablet) && showFilterDropdown
Expand Down Expand Up @@ -154,6 +166,33 @@ export class Sections extends Component {
})}
</div>
)}
{!!shortcutsCategories.length && (
<div>
{showSubTitles && (
<SectionSubtitle>{t('sections.shortcuts')}</SectionSubtitle>
)}

{shortcutsCategories.map(cat => {
return (
<AppsSection
key={cat.value}
{...componentsProps?.appsSection}
appsList={shortcutsGroups[cat.value]}
subtitle={
showSubSubTitles ? (
<SectionSubSubtitle>{cat.label}</SectionSubSubtitle>
) : null
}
IconComponent={IconComponent}
onAppClick={onAppClick}
displaySpecificMaintenanceStyle={
displaySpecificMaintenanceStyle
}
/>
)
})}
</div>
)}
{!!konnectorsCategories.length && (
<div>
{showSubTitles && (
Expand Down Expand Up @@ -186,6 +225,14 @@ export class Sections extends Component {
}
}

const SectionsWrapper = props => {
const config = flag('store.alternative-source')
const i18nConfig = generateI18nConfig(config?.categories)
useExtendI18n(i18nConfig)

return <Sections {...props} />
}

Sections.propTypes = {
t: PropTypes.func.isRequired,

Expand Down Expand Up @@ -230,6 +277,6 @@ Sections.defaultProps = {
})
}

export const Untranslated = withBreakpoints()(Sections)
export const Untranslated = withBreakpoints()(SectionsWrapper)

export default withLocales(locales)(translate()(Untranslated))
10 changes: 10 additions & 0 deletions react/AppSections/__snapshots__/index.spec.jsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -1075,6 +1075,11 @@ exports[`AppsSection component should render dropdown filter on mobile if no nav
"type": "webapp",
"value": "partners",
},
Object {
"label": "Shortcuts",
"secondary": false,
"value": "shortcuts",
},
Object {
"label": "Services",
"secondary": false,
Expand Down Expand Up @@ -1476,6 +1481,11 @@ exports[`AppsSection component should render dropdown filter on tablet if no nav
"type": "webapp",
"value": "partners",
},
Object {
"label": "Shortcuts",
"secondary": false,
"value": "shortcuts",
},
Object {
"label": "Services",
"secondary": false,
Expand Down
46 changes: 35 additions & 11 deletions react/AppSections/categories.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,21 +38,37 @@ export const groupApps = apps => multiGroupBy(apps, getAppCategory)
* Alphabetical sort on label except for
* - 'all' value always at the beginning
* - 'others' value always at the end
* - 'cozy' value should be near the beginning, right after 'all'
* - items of type 'file' should appear alphabetically between 'webapp' and 'konnector'
*
* @param {CategoryOption} categoryA
* @param {CategoryOption} categoryB
* @return {Number}
*/
export const sorter = (categoryA, categoryB) => {
return (
(categoryA.value === 'all' && -1) ||
(categoryB.value === 'all' && 1) ||
(categoryA.value === 'others' && 1) ||
(categoryB.value === 'others' && -1) ||
(categoryA.value === 'cozy' && -1) ||
(categoryB.value === 'cozy' && 1) ||
categoryA.label.localeCompare(categoryB.label)
)
// Always keep 'all' at the beginning
if (categoryA.value === 'all') return -1
if (categoryB.value === 'all') return 1

// Always keep 'others' at the end
if (categoryA.value === 'others') return 1
if (categoryB.value === 'others') return -1

// Keep 'cozy' near the beginning, right after 'all'
if (categoryA.value === 'cozy') return -1
if (categoryB.value === 'cozy') return 1

// Sort by type order: webapp < file < konnector
const typeOrder = ['webapp', 'file', 'konnector']
const typeAIndex = typeOrder.indexOf(categoryA.type)
const typeBIndex = typeOrder.indexOf(categoryB.type)

if (typeAIndex !== typeBIndex) {
return typeAIndex - typeBIndex
}

// Alphabetical sort on label for the rest
return categoryA.label.localeCompare(categoryB.label)
}

export const addLabel = (cat, t) => ({
Expand Down Expand Up @@ -82,7 +98,7 @@ export const generateOptionsFromApps = (apps, options = {}) => {
]
: []

for (const type of [APP_TYPE.WEBAPP, APP_TYPE.KONNECTOR]) {
for (const type of [APP_TYPE.WEBAPP, APP_TYPE.FILE, APP_TYPE.KONNECTOR]) {
const catApps = groupApps(apps.filter(a => a.type === type))
// Add an entry to filter by all konnectors
if (type === APP_TYPE.KONNECTOR) {
Expand All @@ -93,11 +109,19 @@ export const generateOptionsFromApps = (apps, options = {}) => {
})
)
}
if (type === APP_TYPE.FILE) {
allCategoryOptions.push(
addLabel({
value: 'shortcuts',
secondary: false
})
)
}
const categoryOptions = Object.keys(catApps).map(cat => {
return addLabel({
value: cat,
type: type,
secondary: type === APP_TYPE.KONNECTOR
secondary: type === APP_TYPE.KONNECTOR || type === APP_TYPE.FILE
})
})

Expand Down
6 changes: 6 additions & 0 deletions react/AppSections/categories.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ describe('generateOptionsFromApps', () => {
type: 'webapp',
value: 'others'
},
{ label: 'Shortcuts', secondary: false, value: 'shortcuts' },
{
label: 'Services',
secondary: false,
Expand Down Expand Up @@ -156,6 +157,11 @@ describe('generateOptionsFromApps', () => {
type: 'webapp',
value: 'others'
},
{
label: 'Shortcuts',
secondary: false,
value: 'shortcuts'
},
{
label: 'Services',
secondary: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Array [
"title": "Cozy Photos",
},
Object {
"developer": null,
"developer": "By undefined",
"status": "Update available",
"title": "Tasky",
},
Expand Down
7 changes: 6 additions & 1 deletion react/AppSections/constants.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
export const APP_TYPE = {
KONNECTOR: 'konnector',
WEBAPP: 'webapp'
WEBAPP: 'webapp',
FILE: 'file'
}

export const APP_CLASS = {
SHORTCUT: 'shortcut'
}
23 changes: 23 additions & 0 deletions react/AppSections/generateI18nConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export const generateI18nConfig = (categories?: {
[key: string]: string
}): {
en: Record<string, string>
fr: Record<string, string>
} => {
if (!categories) return { en: {}, fr: {} }

const i18nConfig: Record<string, string> = {}

for (const [key, value] of Object.entries(categories)) {
// Extract the final part of the path as the display name
const displayName =
value?.split('/').pop() ?? ''.replace(/([A-Z])/g, ' $1').trim()

i18nConfig[`app_categories.${key}`] = displayName
}

return {
en: i18nConfig,
fr: i18nConfig
}
}
8 changes: 8 additions & 0 deletions react/AppSections/helpers.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import _get from 'lodash/get'

import { APP_CLASS, APP_TYPE } from './constants'

export const getTranslatedManifestProperty = (app, path, t) => {
if (!t || !app || !path) return _get(app, path, '')
return t(`apps.${app.slug}.${path}`, {
_: _get(app, path, '')
})
}

export const isShortcutFile = app => {
if (!app) return false

return app.type === APP_TYPE.FILE && app.class === APP_CLASS.SHORTCUT
}
6 changes: 4 additions & 2 deletions react/AppSections/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,12 @@
"tech": "Tech",
"telecom": "Telecom",
"transport": "Transportation",
"pro": "Work"
"pro": "Work",
"shortcuts": "Shortcuts"
},
"sections": {
"applications": "Applications",
"konnectors": "Services"
"konnectors": "Services",
"shortcuts": "Shortcuts"
}
}
6 changes: 4 additions & 2 deletions react/AppSections/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,12 @@
"tech": "Tech",
"telecom": "Mobile",
"transport": "Voyage et transport",
"pro": "Travail"
"pro": "Travail",
"shortcuts": "Raccourcis"
},
"sections": {
"applications": "Applications",
"konnectors": "Services"
"konnectors": "Services",
"shortcuts": "Raccourcis"
}
}
7 changes: 4 additions & 3 deletions react/AppTile/AppTile.spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@
import { render } from '@testing-library/react'
import React from 'react'

import CozyClient, { CozyProvider } from 'cozy-client'
import CozyClient from 'cozy-client'

import AppTile from '.'
import en from '../AppSections/locales/en.json'
import DemoProvider from '../providers/DemoProvider'
import I18n from '../providers/I18n'

const appMock = {
Expand Down Expand Up @@ -41,11 +42,11 @@ const appMock2 = {
const client = new CozyClient({})
const Wrapper = props => {
return (
<CozyProvider client={client}>
<DemoProvider client={client}>
<I18n dictRequire={() => en} lang="en">
<AppTile {...props} />
</I18n>
</CozyProvider>
</DemoProvider>
)
}

Expand Down
Loading

0 comments on commit 2e92b27

Please sign in to comment.