Files
8bitworkshop/src/ide/layout.ts
T

156 lines
5.1 KiB
TypeScript

// Pane layout for the IDE.
//
// On wide screens the sidebar, editor and emulator sit side by side as three
// resizable panes (Split.js). On small screens they become a tab bar instead,
// showing one pane at a time; switching tabs just moves the splits
// programmatically with setSizes() (hence the zero minSize in tab mode).
import Split = require('split.js');
import { isMobileDevice } from "./views/baseviews";
declare var $: JQueryStatic; // use browser jquery
// panes, in Split.js order
const PANES = ['#sidebar', '#workspace', '#emulator'];
// split sizes for each tab (collapse the other two)
const TAB_SIZES = {
sidebar: [100, 0, 0],
editor: [0, 100, 0],
emulator: [0, 0, 100],
};
// ui.ts injects these so this module doesn't have to import (and cycle with) it
export interface LayoutHooks {
getPlatformId(): string;
isEmbed: boolean;
hasLocalStorage: boolean;
resizePlatform(): void;
resizeWindows(): void;
}
var hooks: LayoutHooks = null;
// Split.js instance; kept around so tabs can move the splits
var notebookSplit: any = null;
// true when the panes are shown as tabs (one at a time) instead of side by side
var tabMode = false;
var activeTab = 'workspace';
// the side-by-side sizes to restore when we leave tab mode
var desktopSizes: number[] = null;
var splitStorageName: string = null;
// live query (isMobileDevice in baseviews is computed once at load and
// wouldn't react to rotating the device or resizing the window)
var mobileMediaQuery = (typeof window !== 'undefined' && window.matchMedia)
? window.matchMedia("only screen and (max-width: 760px)") : null;
var wired = false;
function isTabLayout(): boolean {
return mobileMediaQuery ? mobileMediaQuery.matches : isMobileDevice;
}
function createSplit(sizes: number[]) {
if (notebookSplit) {
notebookSplit.destroy();
notebookSplit = null;
}
notebookSplit = Split(PANES, {
sizes: sizes,
// tab mode collapses two panes to 0, so the desktop minSize can't apply
minSize: tabMode ? [0, 0, 0] : [0, 250, 250],
onDrag: () => {
hooks.resizePlatform();
},
onDragEnd: () => {
if (!tabMode && hooks.hasLocalStorage) localStorage.setItem(splitStorageName, JSON.stringify(notebookSplit.getSizes()))
hooks.resizeWindows();
},
});
}
function applyTabSizes() {
notebookSplit.setSizes(TAB_SIZES[activeTab] || TAB_SIZES.editor);
}
function setActiveTabClass(tab: string) {
activeTab = TAB_SIZES[tab] ? tab : 'editor';
$("#mobiletabs .mobiletab").removeClass("active");
$("#mobiletabs .mobiletab[data-tab='" + activeTab + "']").addClass("active");
$("#sidebar,#workspace,#emulator").removeClass("active");
var sel = activeTab === 'sidebar' ? '#sidebar' : activeTab === 'emulator' ? '#emulator' : '#workspace';
$(sel).addClass("active");
}
function setTabMode(on: boolean) {
tabMode = on;
if (typeof document === 'undefined') return;
$("body").toggleClass("tab-layout", on);
}
export function showTab(tab: string) {
setActiveTabClass(tab);
if (!tabMode || !notebookSplit) return;
applyTabSizes();
// the revealed pane was 0px wide; let it relayout once it's visible
setTimeout(() => {
if (activeTab === 'emulator') hooks.resizePlatform();
if (activeTab === 'editor') hooks.resizeWindows();
}, 0);
}
function updateSplitLayout() {
var wantTabs = isTabLayout();
if (wantTabs === tabMode) return;
if (wantTabs) {
// entering tab mode: remember the side-by-side sizes, recreate with 0 mins
if (notebookSplit) desktopSizes = notebookSplit.getSizes();
setTabMode(true);
createSplit(desktopSizes || [0, 55, 45]);
showTab(activeTab);
} else {
// leaving tab mode: restore the side-by-side panes and their sizes
setTabMode(false);
$("#sidebar,#workspace,#emulator").removeClass("active");
createSplit(desktopSizes || [12, 44, 44]);
hooks.resizePlatform();
hooks.resizeWindows();
}
}
export function setupSplits(h: LayoutHooks) {
hooks = h;
var platform_id = hooks.getPlatformId();
splitStorageName = 'workspace-split3-' + platform_id;
if (hooks.isEmbed) splitStorageName = 'embed-' + splitStorageName;
var sizes;
if (platform_id.startsWith('vcs'))
sizes = [0, 50, 50];
else if (hooks.isEmbed || isMobileDevice)
sizes = [0, 55, 45];
else
sizes = [12, 44, 44];
var sizesStr = hooks.hasLocalStorage && localStorage.getItem(splitStorageName);
if (sizesStr) {
try {
sizes = JSON.parse(sizesStr);
} catch (e) { console.log(e); }
}
if (isTabLayout()) desktopSizes = sizes; // these came from localStorage; keep them for desktop
setTabMode(isTabLayout());
createSplit(sizes);
if (tabMode) {
applyTabSizes();
setActiveTabClass(activeTab);
} else {
$("#sidebar,#workspace,#emulator").removeClass("active");
}
if (!wired) {
wired = true;
$("#mobiletabs").on("click", ".mobiletab", function () {
showTab($(this).attr("data-tab"));
});
if (mobileMediaQuery) {
if (mobileMediaQuery.addEventListener) mobileMediaQuery.addEventListener("change", updateSplitLayout);
else if (mobileMediaQuery.addListener) mobileMediaQuery.addListener(updateSplitLayout);
}
}
}