MailSpring
workspace-store
constructor
它的constructor注册了很多事件
constructor() {super();this._resetInstanceVars();this._preferredLayoutMode = AppEnv.config.get('core.workspace.mode');//当用户选择根工作表(root sheet)时,比如从侧边栏点击"收件箱"、"已发送"等主要视图,该方法会清空当前的工作表栈,并将选中的根工作表推入栈中。这是工作区导航的基础操作this.listenTo(Actions.selectRootSheet, this._onSelectRootSheet);//处理焦点设置事件,比如用户点击某个邮件线程或文,在列表模式下,会根据焦点变化自动推入或弹出相应的工作表,例如:点击邮件线程时自动推入 Thread 工作表,取消选择时弹出this.listenTo(Actions.setFocus, this._onSetFocus);// 处理工作区位置的显示/隐藏切换, 比如用户隐藏侧边栏、工具栏等界面元素, 将隐藏状态保存到配置中,并在界面上反映这些变化this.listenTo(Actions.toggleWorkspaceLocationHidden, this._onToggleLocationHidden);//处理工作区位置的打开操作 ,如果某个位置被隐藏了,打开操作会取消隐藏状态,确保用户请求的界面元素能够正常显示this.listenTo(Actions.openWorkspaceLocation, this._onOpenWorkspaceLocation);//处理工作表栈的弹出操作,比如用户点击返回按钮,从当前工作表返回到上一个工作表,这是导航栈的标准操作this.listenTo(Actions.popSheet, this.popSheet);//处理返回到根工作表的操作,比如用户按 Home 键或点击"返回主页"按钮,清空工作表栈,只保留根工作表this.listenTo(Actions.popToRootSheet, this.popToRootSheet);//处理工作表栈的推入操作,比如用户点击某个邮件打开详情页面,将新的工作表推入栈中,实现页面导航this.listenTo(Actions.pushSheet, this.pushSheet);//从应用环境获取窗口类型设置 比如 'main' - 主窗口 'emptyWindow' - 空窗口(用于特殊用途) 'onboarding' - 引导窗口const { windowType } = AppEnv.getLoadSettings();// 禁用视觉缩放功能
//设置缩放级别的最小值和最大值
//参数 (1, 1) 表示:
//最小缩放级别 = 1(100%)
//最大缩放级别 = 1(100%)
//作用:禁用双击缩放和捏合缩放功能,确保界面保持固定的缩放比例webFrame.setVisualZoomLevelLimits(1, 1);//立即应用用户设置的界面缩放this._applyDesiredScale();//监听界面缩放配置的变化AppEnv.config.observe('core.workspace.interfaceZoom', this._applyDesiredScale);if (windowType === 'emptyWindow') {
//空窗口通常用于特殊用途,比如弹出窗口或模态框 ,为空窗口设置窗口属性接收回调AppEnv.onWindowPropsReceived(this._applyDesiredScale);}if (AppEnv.isMainWindow()) {
//重建快捷键绑定
//调用 _rebuildShortcuts 方法来重新设置快捷键
//只有在主窗口中才需要设置快捷键,因为快捷键主要在主界面中使用this._rebuildShortcuts();}}
this._resetInstanceVars()
_resetInstanceVars() {this.Location = Location = {} as any;this.Sheet = Sheet = {} as SheetSet;// 新增 AccountSidebarSearchBar 插入点,补全 Toolbar 字段避免类型报错Location.AccountSidebarSearchBar = { id: 'AccountSidebarSearchBar', Toolbar: {} };// 新增 AccountSidebarClassification 插入点,补全 Toolbar 字段避免类型报错Location.AccountSidebarClassification = { id: 'AccountSidebarClassification', Toolbar: {} };this._hiddenLocations = AppEnv.config.get('core.workspace.hiddenLocations') || {};this._sheetStack = [];if (AppEnv.isMainWindow()) {//判断是主窗口this.defineSheet('Global');this.defineSheet('Threads',{ root: true },{list: ['RootSidebar', 'ThreadList'],split: ['RootSidebar', 'ThreadList', 'MessageList', 'MessageListSidebar'],splitVertical: ['RootSidebar', 'ThreadList', 'MessageListSidebar'],});this.defineSheet('Thread', {}, { list: ['MessageList', 'MessageListSidebar'] });} else {this.defineSheet('Global');}}
this.defineSheet
defineSheet(id,options: Partial<SheetDeclaration> = {},columns: { [mode: string]: string[] } = {}) {Sheet[id] = {id,columns: {},supportedModes: Object.keys(columns),//支持的模式,比如:list、split、splitVerticalicon: options.icon,name: options.name,root: options.root,//是否是根工作表sidebarComponent: options.sidebarComponent,// 支持自定义侧边栏内容Toolbar: {Left: { id: `Sheet:${id}:Toolbar:Left` },Right: { id: `Sheet:${id}:Toolbar:Right` },},Header: { id: `Sheet:${id}:Header` },Footer: { id: `Sheet:${id}:Footer` },};// Make sure all the locations have definitions so that packages// can register things into these locations and their toolbars.for (const [mode, cols] of Object.entries(columns)) {Sheet[id].columns[mode] = [];for (const col of cols) {if (Location[col] == null) {Location[col] = { id: `${col}`, Toolbar: { id: `${col}:Toolbar` } };}Sheet[id].columns[mode].push(Location[col]);}}if (options.root && !this.rootSheet() && !(options as any).silent) {this._onSelectRootSheet(Sheet[id]);}//当短时间内多次调用 triggerDebounced() 时,只有最后一次调用会在 1 毫秒延迟后执行 //this.trigger(this)。this.triggerDebounced();}
代码解析:
if (options.root && !this.rootSheet() && !(options as any).silent) {this._onSelectRootSheet(Sheet[id]);
}
条件判断详解
1. options.root
含义:检查当前定义的工作表是否为根工作表
作用:
- 只有根工作表才会被自动选中
- 根工作表通常显示在侧边栏中,如"收件箱"、"已发送"等
- 非根工作表(如邮件详情页面)不会被自动选中
2. !this.rootSheet()
含义:检查当前是否还没有根工作表被选中
作用:
- this.rootSheet() 返回当前工作表栈中的第一个工作表(根工作表)
- !this.rootSheet() 表示当前没有根工作表
- 确保只有在没有根工作表时才自动选择一个
3. !(options as any).silent
含义:检查是否不是静默模式
作用:
- silent 选项用于控制是否静默创建工作表
- 静默模式下不会触发自动选择
- 通常用于初始化时避免不必要的状态变化
整体逻辑
这个条件判断确保:
- 只处理根工作表:只有标记为 root: true 的工作表才会被考虑
- 避免重复选择:如果已经有根工作表被选中,就不再自动选择新的
- 支持静默模式:通过 silent 选项可以控制是否自动选择
执行操作
当条件满足时,调用:
this._onSelectRootSheet(Sheet[id]);
这会:
- 清空当前工作表栈:this._sheetStack = []
- 将新定义的根工作表推入栈中:this._sheetStack.push(sheet)
- 触发状态更新:this.trigger(this)
实际应用场景
场景1:应用启动时
// 在 _resetInstanceVars() 中
this.defineSheet('Threads',{ root: true }, // 根工作表{list: ['RootSidebar', 'ThreadList'],split: ['RootSidebar', 'ThreadList', 'MessageList', 'MessageListSidebar'],splitVertical: ['RootSidebar', 'ThreadList', 'MessageListSidebar'],}
);
- 由于 options.root 为 true
- 且当前没有根工作表(!this.rootSheet() 为 true)
- 且不是静默模式(!(options as any).silent 为 true)
- 所以会自动选择 Threads 工作表作为初始视图
场景2:静默创建
this.defineSheet('Global', { root: true, silent: true });
- 即使 root 为 true,但由于 silent 为 true
- 不会自动选择该工作表
- 用于初始化时避免触发不必要的状态变化
场景3:已有根工作表
// 假设当前已经有 Threads 作为根工作表this.defineSheet('Preferences', { root: true });
- 由于 !this.rootSheet() 为 false(已有根工作表)
- 不会自动选择 Preferences 工作表
- 用户需要手动选择才能切换到偏好设置
设计目的
这种设计确保了:
- 应用启动时有默认视图:自动选择第一个根工作表
- 避免状态冲突:不会同时选择多个根工作表
- 支持灵活初始化:通过 silent 选项控制初始化行为
- 用户体验良好:应用启动后立即显示有用的界面
这是 Mailspring 工作区管理系统的核心逻辑之一,确保了应用启动时的正确初始化和用户界面的合理展示。
this.defineSheet('Threads',{ root: true }, // 根工作表{list: ['RootSidebar', 'ThreadList'],split: ['RootSidebar', 'ThreadList', 'MessageList', 'MessageListSidebar'],splitVertical: ['RootSidebar', 'ThreadList', 'MessageListSidebar'],}
);通过defineSheet转换Sheet.Threads = {id: 'Threads',columns: {list: [RootSidebar, ThreadList],split: [RootSidebar, ThreadList, MessageList, MessageListSidebar],splitVertical: [RootSidebar, ThreadList, MessageListSidebar]},supportedModes: ['list', 'split', 'splitVertical'],icon: undefined, // 从 options 传入name: undefined, // 从 options 传入root: true,sidebarComponent: undefined, // 从 options 传入Toolbar: {Left: { id: 'Sheet:Threads:Toolbar:Left' },Right: { id: 'Sheet:Threads:Toolbar:Right' }},Header: { id: 'Sheet:Threads:Header' },Footer: { id: 'Sheet:Threads:Footer' }
}defineSheet('Global')
defineSheet('Threads',{ root: true },{list: ['RootSidebar', 'ThreadList'],split: ['RootSidebar', 'ThreadList', 'MessageList', 'MessageListSidebar'],splitVertical: ['RootSidebar', 'ThreadList', 'MessageListSidebar'],}
);
defineSheet('Thread', {}, { list: ['MessageList', 'MessageListSidebar'] });{"Global": {"id": "Global","columns": {},"supportedModes": [],"Toolbar": {"Left": {"id": "Sheet:Global:Toolbar:Left"},"Right": {"id": "Sheet:Global:Toolbar:Right"}},"Header": {"id": "Sheet:Global:Header"},"Footer": {"id": "Sheet:Global:Footer"}},"Threads": {"id": "Threads","columns": {"list": [{"id": "RootSidebar","Toolbar": {"id": "RootSidebar:Toolbar"}},{"id": "ThreadList","Toolbar": {"id": "ThreadList:Toolbar"}}],"split": [{"id": "RootSidebar","Toolbar": {"id": "RootSidebar:Toolbar"}},{"id": "ThreadList","Toolbar": {"id": "ThreadList:Toolbar"}},{"id": "MessageList","Toolbar": {"id": "MessageList:Toolbar"}},{"id": "MessageListSidebar","Toolbar": {"id": "MessageListSidebar:Toolbar"}}],"splitVertical": [{"id": "RootSidebar","Toolbar": {"id": "RootSidebar:Toolbar"}},{"id": "ThreadList","Toolbar": {"id": "ThreadList:Toolbar"}},{"id": "MessageListSidebar","Toolbar": {"id": "MessageListSidebar:Toolbar"}}]},"supportedModes": ["list","split","splitVertical"],"root": true,"Toolbar": {"Left": {"id": "Sheet:Threads:Toolbar:Left"},"Right": {"id": "Sheet:Threads:Toolbar:Right"}},"Header": {"id": "Sheet:Threads:Header"},"Footer": {"id": "Sheet:Threads:Footer"}},"Thread": {"id": "Thread","columns": {"list": [{"id": "MessageList","Toolbar": {"id": "MessageList:Toolbar"}},{"id": "MessageListSidebar","Toolbar": {"id": "MessageListSidebar:Toolbar"}}]},"supportedModes": ["list"],"Toolbar": {"Left": {"id": "Sheet:Thread:Toolbar:Left"},"Right": {"id": "Sheet:Thread:Toolbar:Right"}},"Header": {"id": "Sheet:Thread:Header"},"Footer": {"id": "Sheet:Thread:Footer"}}
}