Skip to content

Commit

Permalink
feat(external-apps): list external apps in route and load them 🚀
Browse files Browse the repository at this point in the history
  • Loading branch information
mukuljainx committed May 17, 2021
1 parent b54889e commit 929a139
Show file tree
Hide file tree
Showing 11 changed files with 247 additions and 17 deletions.
46 changes: 46 additions & 0 deletions src/apps/appManager/store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import api from "utils/api";
import { IApp } from "./interface";

interface IAppManagerState {
apps: IApp[];
loading: boolean;
error?: any;
}

const initialState: IAppManagerState = { apps: [], loading: true };

const getAppsAsync = createAsyncThunk(
"appManager/getAppsAsync",
(_, thunkAPI) => {
return api
.get("/manager/apps/")
.then(({ data }) => data)
.catch((e) => {
thunkAPI.rejectWithValue(e);
});
}
);

const authSlice = createSlice({
name: "appManager",
initialState,
reducers: {
getApps: () => {},
},
extraReducers: {
// Add reducers for additional action types here, and handle loading state as needed
[getAppsAsync.fulfilled.type]: (state, action) => {
state.apps.push(action.payload);
state.loading = false;
},
[getAppsAsync.rejected.type]: (state, action) => {
state.loading = false;
state.error = action.error;
},
},
});

export const {} = authSlice.actions;
export { getAppsAsync };
export default authSlice.reducer;
60 changes: 60 additions & 0 deletions src/apps/externalAppShell/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import * as React from "react";
import AppShell from "apps/shell";
import styled from "styled-components";

import useAnimationEndRender from "utils/hooks/useAnimationEndRender";
import useScript from "utils/hooks/useScript";
import If from "atoms/If";
import Loader from "./loader";

const Wrapper = styled.div`
height: 100%;
width: 100%;
overflow: auto;
background: ${({ theme }) => theme.colors.plain};
`;

interface IProps {
instanceId: string;
appName: string;
onMouseDown: (event: React.MouseEvent, dragId: string) => void;
dragId: string;
data: Record<string, any>;
}

const ExternalAppShell = ({
appName,
instanceId,
onMouseDown,
dragId,
data,
}: IProps) => {
const { render } = useAnimationEndRender({ instanceId });
const status = useScript(
`http://localhost:8000/api/manager/assests/${data.appId}/main.js`,
true
);
console.log(data.options);
return (
<AppShell
height={data.options.height}
width={data.options.width}
dragId={dragId}
onMouseDown={onMouseDown}
appName={appName}
instanceId={instanceId}
icon={data.icon}
name={data.name}
>
<If condition={render > 0}>
<If condition={status !== "ready"}>
<Loader status={status} />
</If>
{/* keep it always ready */}
<Wrapper hidden={status !== "ready"} id={data.appId}></Wrapper>
</If>
</AppShell>
);
};

export default React.memo(ExternalAppShell);
43 changes: 43 additions & 0 deletions src/apps/externalAppShell/loader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import * as React from "react";
import styled from "styled-components";
import { ProgressIndicator } from "@fluentui/react";

import { Stack, Text, StackItem } from "atoms/styled";
import { StatusTypes } from "utils/hooks/useScript";

interface IProps {
status: StatusTypes;
}

const Wrapper = styled(Stack)`
background: ${({ theme }) => theme.colors.plain};
${StackItem} {
width: 320px;
}
`;

const ScriptStatus = ({ status }: IProps) => {
return (
<Wrapper
flexDirection="column"
gap={8}
fullHeight
justifyContent="center"
alignItems="center"
>
<StackItem>
<ProgressIndicator
label="Loading app..."
progressHidden={status === "error"}
/>
</StackItem>
<StackItem>
{status === "error" && (
<Text>Something went wrong while loading the app.</Text>
)}
</StackItem>
</Wrapper>
);
};

export default ScriptStatus;
36 changes: 36 additions & 0 deletions src/apps/folder/store/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { deleteChildren, refreshRoutes } from "./helper";
import { IFolder, IFolderRoutes, IFile } from "../interfaces";
import { folderPool, folderMap } from "./routes";
import { sortBy, get } from "lodash-es";
import { getAppsAsync } from "apps/appManager/store";
import { IApp } from "apps/appManager/interface";

export interface IBaseState {
folderPool: Record<string, IFolder>;
Expand Down Expand Up @@ -169,6 +171,40 @@ const folderSlice = createSlice({
refreshRoutes(state);
},
},
extraReducers: {
[getAppsAsync.fulfilled.type]: (
state,
{ payload }: PayloadAction<IApp[]>
) => {
//data: {
// id: "img2",
// name: "lady.jpg",
// icon: "image",
// path: "lady.jpg",
// },
// appName: "photo",
// order: 1,
const applicationsFolder = get(
state.root,
state.routeToFolder["/applications"]
).files;
const startingOrder = Object.keys(applicationsFolder).length;
payload.forEach((customApp, index) => {
applicationsFolder[customApp.appId] = {
data: {
appId: customApp.appId,
icon: customApp.icon,
name: customApp.name,
appType: "EXTERNAL",
options: customApp.options,
},
appName: customApp.appId,
order: startingOrder + index,
};
});
refreshRoutes(state);
},
},
});

export const {
Expand Down
21 changes: 21 additions & 0 deletions src/apps/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Photo from "apps/photo";
import AppManager from "apps/appManager";
import { IApp, IMetaData } from "base/interfaces";
import AnimatedFileWrapper from "atoms/animatedFileWrapper";
import ExternalAppShell from "apps/externalAppShell";

interface IProps {
app: IApp;
Expand Down Expand Up @@ -81,6 +82,26 @@ const App = (props: IProps) => {
}
}

if (app.data.appType === "EXTERNAL") {
return (
<AnimatedFileWrapper
appName={app.appName}
id={id}
style={style}
metaData={metaData}
weight={weight}
>
<ExternalAppShell
data={app.data}
instanceId={id}
dragId={dragId}
appName={app.appName}
onMouseDown={onMouseDown}
/>
</AnimatedFileWrapper>
);
}

return null;
};

Expand Down
2 changes: 1 addition & 1 deletion src/apps/photo/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ interface IProps {
path: string;
instanceId: string;
appName: string;
onMouseDown: (event: React.MouseEvent) => void;
onMouseDown: (event: React.MouseEvent, dragId: string) => void;
dragId: string;
}

Expand Down
7 changes: 6 additions & 1 deletion src/atoms/styled/appImage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,18 @@ type IProps = ReactHTMLElement<

const Image = ({ name, size, ref, height, width, ...rest }: IProps) => {
const iconName = availableIcons[name] || "generic.svg";
let href = "";

if (name && name.includes("http")) {
href = name;
}

return (
<img
{...rest}
height={height || size}
width={width || size}
src={require(`./assets/${iconName}`).default}
src={href ? href : require(`./assets/${iconName}`).default}
/>
);
};
Expand Down
8 changes: 5 additions & 3 deletions src/base/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { toggleStartMenu as toggleStartMenuAction } from "base/store";
import { toggleQuickActions } from "apps/actionCenter/store";
import { initRoutes } from "apps/folder/store";
import ActionCenter from "apps/actionCenter";
import { getAppsAsync } from "apps/appManager/store";

interface IProps {}

Expand All @@ -24,7 +25,7 @@ const Base = ({}: IProps) => {
const wrapperRef = React.useRef<HTMLDivElement>(null);

let user = useSelector((state) => state.auth.user);
const openedApps = useSelector((state) => state.base.apps);
const runningApp = useSelector((state) => state.base.runningApp);
const routes = useSelector((state) => state.folder.routes, shallowEqual);

const { store, handleMouseDown } = useDraggable({ wrapperRef });
Expand Down Expand Up @@ -52,6 +53,7 @@ const Base = ({}: IProps) => {
wrapper: wrapperRef.current!,
};
dispatch(initRoutes(user?.name || "Guest user"));
dispatch(getAppsAsync());
}, []);

const handleMouseDownEvent = React.useCallback(
Expand All @@ -66,7 +68,7 @@ const Base = ({}: IProps) => {
<Wrapper ref={wrapperRef}>
<Menu />
<ActionCenter />
{Object.values(openedApps).map((app) => {
{Object.values(runningApp).map((app) => {
return (
<>
{app.instances.map((instance, index) => {
Expand Down Expand Up @@ -101,7 +103,7 @@ const Base = ({}: IProps) => {
<AppBar
toggleQuickActions={dispatchToggleQuickActions}
toggleMenu={toggleStartMenu}
apps={openedApps}
apps={runningApp}
/>
</Wrapper>
</Desktop>
Expand Down
16 changes: 8 additions & 8 deletions src/base/store/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { IApp, IAppGroup } from "base/interfaces";

interface IBaseState {
apps: Record<string, IAppGroup>;
runningApp: Record<string, IAppGroup>;

currentWeight: number;
menu: {
Expand All @@ -11,7 +11,7 @@ interface IBaseState {
}

const initialState: IBaseState = {
apps: {},
runningApp: {},
currentWeight: 0,
menu: {
show: false,
Expand All @@ -28,7 +28,7 @@ const baseSlice = createSlice({
payload: { appName, instanceId },
}: PayloadAction<{ appName: string; instanceId: string }>
) => {
const runningApp = state.apps[appName];
const runningApp = state.runningApp[appName];
if (
runningApp.instances[runningApp.instances.length - 1].id === instanceId
) {
Expand Down Expand Up @@ -56,7 +56,7 @@ const baseSlice = createSlice({
openApp(state, action: PayloadAction<IApp>) {
const newApp = action.payload;
const instanceId = `${newApp.appName}-${newApp.id}`;
const runningApp = state.apps[newApp.appName];
const runningApp = state.runningApp[newApp.appName];

if (runningApp) {
runningApp.weight = state.currentWeight;
Expand All @@ -71,29 +71,29 @@ const baseSlice = createSlice({
}
runningApp.instances.push({ ...newApp, id: instanceId });
} else {
state.apps[newApp.appName] = {
state.runningApp[newApp.appName] = {
initialWeight: state.currentWeight,
weight: state.currentWeight,
id: newApp.id,
name: newApp.appName,
instances: [{ ...newApp, id: instanceId }],
};
}
// relying on this to never cross the css z-index limit - 10000
// relying on this, to never cross the css z-index limit - 10000
// TODO: if does, have to reset every app weight that's all
state.currentWeight++;
},
closeApp: (
state,
action: PayloadAction<{ appName: string; instanceId: string }>
) => {
const app = state.apps[action.payload.appName];
const app = state.runningApp[action.payload.appName];
if (!app) {
return;
}

if (app.instances.length === 1) {
delete state.apps[action.payload.appName];
delete state.runningApp[action.payload.appName];
} else {
app.instances = app.instances.filter(
(instance) => instance.id !== action.payload.instanceId
Expand Down
9 changes: 8 additions & 1 deletion src/store/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,15 @@ import auth from "auth/store";
import base from "base/store";
import folder from "apps/folder/store";
import actionCenter from "apps/actionCenter/store";
import appManager from "apps/appManager/store";

const rootReducer = combineReducers({ auth, base, folder, actionCenter });
const rootReducer = combineReducers({
auth,
base,
folder,
actionCenter,
appManager,
});

export type RootState = ReturnType<typeof rootReducer>;

Expand Down
Loading

0 comments on commit 929a139

Please sign in to comment.