@ledgerhq/live-common
Version:
Common ground for the Ledger Live apps
132 lines • 5.28 kB
JavaScript
import { useState, useReducer, useEffect, useMemo } from "react";
import { useSortedFilteredApps } from "./filtering";
import { reducer, initState, getNextAppOp, isOutOfMemoryState, predictOptimisticState, } from "./logic";
import { runAppOp } from "./runner";
import { useFeatureFlags } from "../featureFlags";
// use for React apps. support dynamic change of the state.
export const useAppsRunner = (listResult, exec, appsToRestore) => {
// $FlowFixMe for ledger-live-mobile older react/flow version
const [state, dispatch] = useReducer(reducer, null, () => initState(listResult, appsToRestore));
const nextAppOp = useMemo(() => getNextAppOp(state), [state]);
const appOp = state.currentAppOp || nextAppOp;
useEffect(() => {
dispatch({
type: "reset",
initialState: initState(listResult, appsToRestore),
});
}, [listResult, appsToRestore]);
useEffect(() => {
if (appOp) {
const sub = runAppOp({ state, appOp, exec }).subscribe(event => {
dispatch({
type: "onRunnerEvent",
event,
});
});
return () => {
sub.unsubscribe();
};
} // we only want to redo the effect on appOp changes here
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [listResult, appOp, exec]);
return [state, dispatch];
};
export function useNotEnoughMemoryToInstall(state, name) {
return useMemo(() => isOutOfMemoryState(predictOptimisticState(reducer(state, {
type: "install",
name,
}))), [name, state]);
}
export function useAppsSections(state, opts) {
const { updateAllQueue, appByName, installed, installQueue, apps } = state;
const appsUpdating = useMemo(() => updateAllQueue.map(name => appByName[name]).filter(Boolean), [appByName, updateAllQueue]);
const updatableAppList = useMemo(() => apps.filter(({ name }) => installed.some(i => i.name === name && !i.updated)), [apps, installed]);
const { getFeature, isFeature } = useFeatureFlags();
const update = appsUpdating.length > 0 ? appsUpdating : updatableAppList;
const filterParam = useMemo(() => ({
query: opts.query,
installedApps: installed,
type: [opts.appFilter],
getFeature,
isFeature,
}), [getFeature, installed, isFeature, opts.appFilter, opts.query]);
const catalog = useSortedFilteredApps(apps, filterParam, opts.sort);
const installedAppList = useSortedFilteredApps(apps, {
query: opts.query,
installedApps: installed,
installQueue,
type: ["installed"],
getFeature,
isFeature,
}, {
type: "default",
order: "asc",
});
const device = installedAppList.sort(({ name: _name }, { name }) => {
// place install queue on top of list
// with the app being installed at the top
let pos1 = installQueue.indexOf(_name);
let pos2 = installQueue.indexOf(name);
pos1 = pos1 < 0 ? Number.MAX_VALUE : pos1;
pos2 = pos2 < 0 ? Number.MAX_VALUE : pos2;
return pos1 - pos2;
});
return {
update,
catalog,
device,
};
}
export function useAppInstallProgress(state, name) {
const { currentProgressSubject, currentAppOp } = state;
const [progress, setProgress] = useState(0);
useEffect(() => {
if (!currentAppOp || !currentProgressSubject || currentAppOp.name !== name) {
setProgress(0);
return;
}
const sub = currentProgressSubject.subscribe(setProgress);
return () => sub.unsubscribe();
}, [currentProgressSubject, currentAppOp, name]);
if (currentProgressSubject && currentAppOp && currentAppOp.name === name) {
return progress;
}
return 0;
}
// if the app needs deps to be installed, we want to display a modal
// this should returns all params the modal also need (so we don't do things twice)
export function useAppInstallNeedsDeps(state, app) {
const { appByName, installed: installedList, installQueue } = state;
const res = useMemo(() => {
const dependencies = (app.dependencies || [])
.map(name => appByName[name])
.filter(dep => dep &&
!installedList.some(app => app.name === dep.name) &&
!installQueue.includes(dep.name));
if (dependencies.length) {
return {
app,
dependencies,
};
}
return null;
}, [appByName, app, installQueue, installedList]);
return res;
}
// if the app needs deps to be installed, we want to display a modal
// this should returns all params the modal also need (so we don't do things twice)
export function useAppUninstallNeedsDeps(state, app) {
const { apps, installed } = state;
const res = useMemo(() => {
const dependents = apps.filter(a => installed.some(i => i.name === a.name) && a.dependencies.includes(app.name));
if (dependents.length) {
return {
dependents,
app,
};
}
return null;
}, [app, apps, installed]);
return res;
}
//# sourceMappingURL=react.js.map