diff --git a/src/boot/app/app-loader-functions.ts b/src/boot/app/app-loader-functions.ts index b68ced88..fb836479 100644 --- a/src/boot/app/app-loader-functions.ts +++ b/src/boot/app/app-loader-functions.ts @@ -60,6 +60,7 @@ import { import { getSoapFetch, getXmlSoapFetch } from '../../network/fetch'; import { getTag, getTags, useTag, useTags } from '../../store/tags'; import { useNotify, useRefresh } from '../../store/network'; +import { changeTagColor, createTag, deleteTag, renameTag } from '../../network/tags'; // eslint-disable-next-line @typescript-eslint/ban-types export const getAppFunctions = (pkg: CarbonioModule): Record => ({ @@ -118,6 +119,11 @@ export const getAppFunctions = (pkg: CarbonioModule): Record = pushHistory, goBackHistory, replaceHistory, + // TAGS + createTag, + renameTag, + changeTagColor, + deleteTag, // STUFF useIsMobile, getBridgedFunctions: (): unknown => { diff --git a/src/network/fetch.ts b/src/network/fetch.ts index 71a2e913..b152d104 100644 --- a/src/network/fetch.ts +++ b/src/network/fetch.ts @@ -4,9 +4,15 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { find } from 'lodash'; +import { find, forEach, map } from 'lodash'; import { goToLogin } from './go-to-login'; -import { Account, ErrorSoapResponse, SoapResponse, SuccessSoapResponse } from '../../types'; +import { + Account, + ErrorSoapResponse, + SoapContext, + SoapResponse, + SuccessSoapResponse +} from '../../types'; import { userAgent } from './user-agent'; import { report } from '../reporting'; import { useAccountStore } from '../store/account'; @@ -73,12 +79,23 @@ const getXmlSession = (context?: any): string => { return ''; }; +const normalizeContext = (context: any): SoapContext => { + if (context.notify) { + // eslint-disable-next-line no-param-reassign + context.notify = map(context.notify, (notify) => ({ + ...notify, + deleted: notify.deleted?.id?.split(',') + })); + } + return context; +}; + const handleResponse = (api: string, res: SoapResponse): R => { - const { pollingInterval, context } = useNetworkStore.getState(); + const { pollingInterval, context, noOpTimeout } = useNetworkStore.getState(); const { usedQuota } = useAccountStore.getState(); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - clearTimeout(get().noOpTimeout); + clearTimeout(noOpTimeout); if (res?.Body?.Fault) { if ( find( @@ -97,7 +114,8 @@ const handleResponse = (api: string, res: SoapResponse): R => { if (res?.Header?.context) { const responseUsedQuota = res.Header.context?.refresh?.mbx?.[0]?.s ?? res.Header.context?.notify?.[0]?.mbx?.[0]?.s; - handleTagSync(res.Header.context); + const _context = normalizeContext(res.Header.context); + handleTagSync(_context); useAccountStore.setState({ usedQuota: responseUsedQuota ?? usedQuota }); diff --git a/src/network/tags.ts b/src/network/tags.ts index ee3eff8c..af16db2e 100644 --- a/src/network/tags.ts +++ b/src/network/tags.ts @@ -17,44 +17,24 @@ import { useTagStore } from '../store/tags'; import { getSoapFetch } from './fetch'; const set = useTagStore.setState; -export const createTag = (tag: Omit): Promise => +export const createTag = (tag: Omit): Promise => getSoapFetch(SHELL_APP_ID)('CreateTag', { _jsns: 'urn:zimbraMail', tag - }).then((r: CreateTagResponse) => { - set((state) => ({ tags: { ...state.tags, [r.tag[0].id]: r.tag[0] } })); - return r.tag[0].id; }); - export const deleteTag = (id: string): Promise => getSoapFetch(SHELL_APP_ID)('TagAction', { _jsns: 'urn:zimbraMail', action: { op: 'delete', id } }); -export const renameTag = (id: string, name: string): Promise => +export const renameTag = (id: string, name: string): Promise => getSoapFetch(SHELL_APP_ID)('TagAction', { _jsns: 'urn:zimbraMail', action: { op: 'rename', id, name } - }).then((r: TagActionResponse) => { - set( - produce((state) => { - state.tags[id].name = name; - }) - ); }); -export const changeTagColor = (id: string, color: string | number): Promise => +export const changeTagColor = (id: string, color: string | number): Promise => getSoapFetch(SHELL_APP_ID)('TagAction', { _jsns: 'urn:zimbraMail', action: typeof color === 'number' ? { op: 'color', color, id } : { op: 'color', rgb: color, id } - }).then((r: TagActionResponse) => { - set( - produce((state) => { - if (color === 'number') { - state.tags[id].color = color; - } else { - state.tags[id].rgb = color; - } - }) - ); }); diff --git a/src/shell/shell-view.jsx b/src/shell/shell-view.jsx index 49a4db65..973fbe96 100644 --- a/src/shell/shell-view.jsx +++ b/src/shell/shell-view.jsx @@ -18,6 +18,7 @@ import { useUserSettings } from '../store/account'; import { ShellUtilityBar, ShellUtilityPanel } from '../utility-bar'; import { useCurrentRoute } from '../history/hooks'; import { useTagStore } from '../store/tags/store'; +import { createTag } from '../network/tags'; const Background = styled.div` background: ${({ theme }) => theme.palette.gray6.regular}; @@ -45,9 +46,6 @@ function DarkReaderListener() { export function Shell() { const [mobileNavOpen, setMobileNavOpen] = useState(false); const activeRoute = useCurrentRoute(); - const tagStore = useTagStore(); - console.log('@@ tagStore', tagStore); - return ( diff --git a/src/store/account/hooks.ts b/src/store/account/hooks.ts index 33e73558..01dd57e8 100644 --- a/src/store/account/hooks.ts +++ b/src/store/account/hooks.ts @@ -34,7 +34,6 @@ export const useUserRight = (right: AccountRightName): Array export const useUserSettings = (): AccountSettings => useAccountStore((s) => s.settings); export const useUserSetting = (...path: Array): string | T => useAccountStore((s) => get(s.settings, join(path, '.'))); -// @@ export const useTags = (): Array => useAccountStore((s) => s.tags); export const getUserAccount = (): Account => useAccountStore.getState().account as Account; export const getUserAccounts = (): Array => [ @@ -43,7 +42,6 @@ export const getUserAccounts = (): Array => [ export const getUserSettings = (): AccountSettings => useAccountStore.getState().settings; export const getUserSetting = (...path: Array): string | T => get(useAccountStore.getState().settings, join(path, '.')); -// @@ export const getTags = (): Array => useAccountStore.getState().tags; export const getUserRights = (): AccountRights => useAccountStore.getState().account?.rights ?? { targets: [] }; diff --git a/src/store/network/hooks.ts b/src/store/network/hooks.ts index 5e29012e..26a9d835 100644 --- a/src/store/network/hooks.ts +++ b/src/store/network/hooks.ts @@ -3,11 +3,11 @@ * * SPDX-License-Identifier: AGPL-3.0-only */ -import { NotifyObject } from '../../../types'; +import { SoapNotify, SoapRefresh } from '../../../types'; import { useNetworkStore } from './store'; -export const useNotify = (): NotifyObject[] => { +export const useNotify = (): SoapNotify[] => { const notify = useNetworkStore((s) => s.context.notify ?? []); return notify; }; -export const useRefresh = (): NotifyObject => useNetworkStore((s) => s.context.refresh ?? {}); +export const useRefresh = (): SoapRefresh => useNetworkStore((s) => s.context.refresh ?? {}); diff --git a/src/store/tags/sync.ts b/src/store/tags/sync.ts index fa674c11..bc07df89 100644 --- a/src/store/tags/sync.ts +++ b/src/store/tags/sync.ts @@ -4,25 +4,76 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { reduce } from 'lodash'; +import { forEach, reduce } from 'lodash'; import { SoapContext, Tag, Tags } from '../../../types'; import { useTagStore } from './store'; +export const handleTagRefresh = (tags: Array): Tags => + reduce( + tags, + (acc: Tags, val: Tag): Tags => { + // eslint-disable-next-line no-param-reassign + acc[val.id] = val; + return acc; + }, + {} + ); + +export const handleTagCreated = (tags: Tags, created: Array): Tags => + reduce( + created, + (acc: Tags, val: Tag): Tags => { + // eslint-disable-next-line no-param-reassign + acc[val.id] = val; + return acc; + }, + tags + ); +export const handleTagModified = (tags: Tags, modified: Array>): Tags => + reduce( + modified, + (acc: Tags, val: Partial): Tags => { + if (val.id) { + // eslint-disable-next-line no-param-reassign + acc[val.id] = { ...tags[val.id], ...val }; + } + return acc; + }, + tags + ); +export const handleTagDeleted = (tags: Tags, deleted: string[]): Tags => + reduce( + deleted, + (acc, val) => { + // eslint-disable-next-line no-param-reassign + delete acc[val]; + return acc; + }, + tags + ); export const handleTagSync = (context: SoapContext): void => { if (context.refresh?.tags?.tag) { useTagStore.setState({ - tags: reduce( - context.refresh.tags?.tag, - (acc: Tags, val: Tag): Tags => { - // eslint-disable-next-line no-param-reassign - acc[val.id] = val; - return acc; - }, - {} - ) + tags: handleTagRefresh(context.refresh.tags.tag) }); } if (context.notify) { - console.log(context.notify); + forEach(context.notify, (notify) => { + if (notify.created?.tag) { + useTagStore.setState({ + tags: handleTagCreated(useTagStore.getState().tags, notify.created?.tag) + }); + } + if (notify.modified?.tag) { + useTagStore.setState({ + tags: handleTagModified(useTagStore.getState().tags, notify.modified?.tag) + }); + } + if (notify.deleted) { + useTagStore.setState({ + tags: handleTagDeleted(useTagStore.getState().tags, notify.deleted) + }); + } + }); } }; diff --git a/tsconfig.json b/tsconfig.json index ce2c92d9..182d4b77 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,13 +1,8 @@ { - "extends": "@zextras/carbonio-ui-configs/rules/typescript.json", - "compilerOptions": { - "skipLibCheck": true - }, - "include": [ - "types/**/*.d.ts", - "src/**/*.ts", - "src/**/*.tsx"], - "exclude": [ - "node_modules" - ] + "extends": "@zextras/carbonio-ui-configs/rules/typescript.json", + "compilerOptions": { + "skipLibCheck": true + }, + "include": ["types/**/*.d.ts", "src/**/*.ts", "src/**/*.tsx"], + "exclude": ["node_modules"] } diff --git a/types/exports/index.d.ts b/types/exports/index.d.ts index ebc33f42..d7a3a06b 100644 --- a/types/exports/index.d.ts +++ b/types/exports/index.d.ts @@ -24,16 +24,15 @@ import { import { ActionFactory, AnyFunction, CombinedActionFactory, Action } from '../integrations'; import { AccountSettings, - // @@ Tag, Account, - NotifyObject, AccountRights, AccountRightName, AccountRightTarget, SoapFetch } from '../account'; -import { Mods } from '../network'; +import { Mods, TagActionResponse, CreateTagResponse, SoapNotify, SoapRefresh } from '../network'; import { HistoryParams, ShellModes } from '../misc'; +import { Tag, Tags } from '../tags'; export const getBridgedFunctions: () => { addBoard: (path: string, context?: unknown | { app: string }) => void; @@ -93,8 +92,14 @@ export const getUserAccount: () => Account; export const getUserAccounts: () => Array; export const getUserRights: () => AccountRights; export const getUserRight: (right: AccountRightName) => Array; -// @@ export const useTags: () => Array; -// @@ export const getTags: () => Array; +export const useTags: () => Tags; +export const getTags: () => Tags; +export const useTag: (id: string) => Tag; +export const getTag: (id: string) => Tag; +export const createTag: (tag: Omit) => Promise; +export const renameTag: (id: string, name: string) => Promise; +export const deleteTag: (id: string) => Promise; +export const changeTagColor: (id: string, color: number | string) => Promise; export const useUserSettings: () => AccountSettings; export const useUserSetting: (...path: Array) => string | T; export const getUserSettings: () => AccountSettings; @@ -103,8 +108,8 @@ export const store: { store: Store; setReducer(nextReducer: Reducer): void; }; -export const useNotify: () => Array; -export const useRefresh: () => NotifyObject; +export const useNotify: () => Array; +export const useRefresh: () => SoapRefresh; export const Applink: FC; export const Spinner: FC; export const useAddBoardCallback: () => ( diff --git a/types/network/index.d.ts b/types/network/index.d.ts index 29ee435d..dc120932 100644 --- a/types/network/index.d.ts +++ b/types/network/index.d.ts @@ -126,13 +126,13 @@ export type AvailableLocalesResponse = { locale: Array; }; export type SoapContext = { - refresh?: NotifyObject; - notify?: [NotifyObject]; + refresh?: SoapRefresh; + notify?: Array; change?: { token: number }; session?: { id: number; _content: number }; }; -export type NotifyObject = { +export type SoapRefresh = { seq?: number; version?: string; mbx?: [{ s: number }]; @@ -140,6 +140,23 @@ export type NotifyObject = { tags?: { tag: Array }; }; +export type SoapNotify = { + seq?: number; + created?: { + m?: Array; + c?: Array; + folder?: Array; + tag?: Array; + }; + modified?: { + m?: Array; + c?: Array; + folder?: Array; + tag?: Array>; + }; + deleted: string[]; +}; + export type NetworkState = { noOpTimeout: unknown; context: SoapContext;