实践[前端] 实现一个类似于gnome交互的页面桌面

116 阅读3分钟

曾经有个梦想,那就是实现一个自己的桌面环境,我对gnome桌面是比较喜欢的,于是我打算复刻一个gnome的web版桌面页面,具有的功能包括workspace切换,桌面内应用的打开,键盘+鼠标实现类似switch gnome工作区,效果如下

桌面状态 image.png

工作区切换状态

image.png

应用程序小窗口[窗口实现layear聚焦提升]

image.png

带应用程序的缩略框

image.png

程序最大化

image.png

实现思路

我将该界面分为了如下几个区,分别是

  • WorkspaceContainer(存放workspace的容器)

  • Workspace: 主要的工作区,也是应用创建的目标位置

  • Application: 应用程序,可挂载页面或链接

    • TopBar 应用顶部的导航栏,负责关闭,最大化,最小化页面
  • TopBar: 顶部导航栏,展示工作区的位置和时间信息,顶部导航栏分为了left right content,采用响应式布局进行分区,每个区需要单独实现组件,方便管理.

对于应用的管理则是采用了redux状态管理,应用则是根据我定义的JSON进行序列化,每个工作区都会根据自己的工作区id创建和管理属于自己的应用程序

应用程序状态的抽象

一个应用程序主要包括一下属性,

  1. 程序所在的工作区,这也是为什么每个工作区可以单独渲染应用的原因
  2. 应用名称
  3. 最大化标志位
  4. 是否展示在顶层
  5. 创建的唯一标识,通过id可以精确销毁应用
  6. 窗口的尺寸信息,包括宽度,高度等,如果后期需要也能记录位置信息
  7. 窗口的所在层级,如果需要实现聚焦展示第一层,则需要对json进行遍历并去获取当前最大zIndex,并设置Zindex+1,提升需要展示在第一层的应用
export interface IAppContainer {
  workspace: string | number; //  工作区
  appName?: string; //            app名称
  big: 0 | 1; //                  是否最大化
  showTop?: 0 | 1; //             是否显示在第一层
  id: string | number; //         唯一标识
  width?: string; //              窗口宽度
  height?: string; //             窗口高度
  zIndex: 1; //                   窗口层级
}

状态管理方法

抽象了app的属性,那么我们需要对这些属性进行操作,我们需要提供对程序内容数组的增删改查进行设计,包括

  • 根据id删除窗口
  • 根据id最大化窗口
  • 根据id取消最大化
  • 根据id和工作区id设置窗口对象
  • 根据id最大化窗口
  • 添加一个App应用数据

interface InitialState {
  appContainer: IAppContainer[];
}
const initialState: InitialState = {
  appContainer: [],
};

const counterSlice = createSlice({
  name: "container",
  initialState,
  reducers: {
    /**
     * 根据id删除窗口
     * @param state // 状态
     * @param prop  // 参数
     */
    removeById(state: InitialState, prop) {
      const index = state.appContainer.findIndex(
        (item) => item.id === prop.payload.id && item.workspace === prop.payload.workspace
      );
      console.log(index);
      if (-1 !== index) {
        state.appContainer.splice(index, 1);
      }
    },
    /**
     * 根据id最大化窗口
     * @param state 
     * @param prop 
     */
    big1ById(state: InitialState, prop) {
      const obj = state.appContainer.find(
        (item) => item.id === prop.payload.id && item.workspace === prop.payload.workspace
      );
      if (obj) {
        obj.big = 1;
      }
    },
    /**
     * 根据id取消最大化
     * @param state 
     * @param prop 
     */
    big0ById(state: InitialState, prop) {
      const obj = state.appContainer.find(
        (item) => item.id === prop.payload.id && item.workspace === prop.payload.workspace
      );
      if (obj) {
        obj.big = 1;
      }
    },
    /**
     * 根据id和工作区id设置窗口对象
     * @param state 
     * @param prop 
     */
    setObjByIdAndWorkspaceId(state: InitialState, prop) {
      const obj = state.appContainer.find(
        (item) => item.id === prop.payload.id && item.workspace === prop.payload.workspace
      );
      if (obj) {
        obj.big = prop.payload.big;
        obj.height = prop.payload.height;
        obj.id = prop.payload.id;
        obj.width = prop.payload.width;
        obj.workspace = prop.payload.workspace;
        obj.zIndex = prop.payload.zIndex;
      }
    },
    /**
     * 根据id切换最大化窗口
     * @param state 
     * @param prop 
     */
    toggleBigById(state: InitialState, prop) {
      console.log(prop);
      const obj = state.appContainer.find(
        (item) => item.id === prop.payload.id && item.workspace === prop.payload.workspace
      );
      if (obj) {
        obj.big = obj.big ? 0 : 1;
      }
    },
    /**
     * 添加一个app对象
     * @param state 
     * @param prop 
     */
    add(state: InitialState, prop: { payload: IAppContainer; type: string }) {
      const obj = state.appContainer.find(
        (item) => item.id === prop.payload.id && prop.payload.workspace === item.workspace
      );
      if (!obj) {
        state.appContainer.push(prop.payload);
      }
    },
  },
});
// 控制alt标志位
export const { removeById, toggleBigById, add, setObjByIdAndWorkspaceId } = counterSlice.actions;

export default counterSlice.reducer;