Skip to content

Commit

Permalink
feat(UI): allow changing layout horizontal/vertical
Browse files Browse the repository at this point in the history
  • Loading branch information
hatemhosny committed Mar 29, 2024
1 parent 14c48ed commit df3796f
Show file tree
Hide file tree
Showing 11 changed files with 189 additions and 70 deletions.
8 changes: 8 additions & 0 deletions docs/docs/configuration/configuration-object.md
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,14 @@ Default: `false`

If `true`, the code is automatically [formatted](../features/code-format.md) on saving the project.

### `layout`

Type: [`"horizontal" | "vertical" | undefined`](../api/interfaces/Config.md#layout)

Default: `undefined`

Sets the app layout to horizontal or vertical. If `undefined` (the default), the layout is responsive (on small screens the layout is vertical when the playground height is larger than its width).

### `theme`

Type: [`"light" | "dark"`](../api/interfaces/Config.md#theme)
Expand Down
3 changes: 3 additions & 0 deletions src/livecodes/UI/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,9 @@ export const getEmmetToggle = /* @__PURE__ */ () =>
export const getThemeToggle = /* @__PURE__ */ () =>
document.querySelector('#settings-menu input#theme') as HTMLInputElement;

export const getLayoutToggle = /* @__PURE__ */ () =>
document.querySelector('#settings-menu input#layout') as HTMLInputElement;

export const getShowWelcomeToggle = /* @__PURE__ */ () =>
document.querySelector('#settings-menu input#welcome') as HTMLInputElement;

Expand Down
84 changes: 54 additions & 30 deletions src/livecodes/UI/split-panes.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,46 @@
import Split from 'split.js';
import { customEvents } from '../events';

export const createSplitPanes = () => {
const gutterSize = 10;
export const createSplitPanes = (layout: 'vertical' | 'horizontal' = 'horizontal') => {
let destroyed = false;
const split = Split(['#editors', '#output'], {
minSize: [0, 0],
gutterSize,
elementStyle: (_dimension, size, gutterSize) => {
window.dispatchEvent(new Event(customEvents.resizeEditor));
return {
'flex-basis': `calc(${size}% - ${gutterSize}px)`,
};
},
gutterStyle: (_dimension, gutterSize) => ({
'flex-basis': `${gutterSize}px`,
}),
onDragStart() {
setAnimation(false);
},
onDragEnd() {
setAnimation(true);
},
});
let split: Split.Instance;

const gutter = document.querySelector('.gutter');
if (gutter) {
const handle = document.createElement('div');
handle.id = 'handle';
gutter.appendChild(handle);
}
const init = () => {
destroy(false, false);
setLayout(layout);
destroyed = false;
const gutterSize = layout === 'vertical' ? 8 : 10;
split = Split(['#editors', '#output'], {
direction: layout,
minSize: [0, 0],
gutterSize,
elementStyle: (_dimension, size, gutterSize) => {
window.dispatchEvent(new Event(customEvents.resizeEditor));
return {
'flex-basis': `calc(${size}% - ${gutterSize}px)`,
};
},
gutterStyle: (_dimension, gutterSize) => ({
'flex-basis': `${gutterSize}px`,
}),
onDragStart() {
setAnimation(false);
},
onDragEnd() {
setAnimation(true);
},
});

const gutter = document.querySelector('.gutter');
if (gutter) {
if (!gutter.querySelector('#handle')) {
const handle = document.createElement('div');
handle.id = 'handle';
gutter.appendChild(handle);
}
}
setAnimation(true);
};

const setAnimation = (animate: boolean) => {
const editorsElement: HTMLElement | null = document.querySelector('#editors');
Expand All @@ -46,7 +57,8 @@ export const createSplitPanes = () => {
};

const show = (pane: 'code' | 'output', full?: boolean) => {
const smallScreen = window.innerWidth < 800;
if (!split) init();
const smallScreen = layout === 'horizontal' && window.innerWidth < 800;
const codeOpen = full || (smallScreen && full !== false) ? [100, 0] : [50, 50];
const outputOpen = full || (smallScreen && full !== false) ? [0, 100] : [50, 50];
if (pane === 'code' && (split.getSizes()[0] < 10 || full)) {
Expand All @@ -61,17 +73,29 @@ export const createSplitPanes = () => {
}
};

const getLayout = () => layout;

const setLayout = (newLayout: 'vertical' | 'horizontal') => {
document.documentElement.classList.toggle('layout-vertical', layout === 'vertical');
if (newLayout === layout) return;
layout = newLayout;
destroy();
init();
};

const destroy = (preserveStyles?: boolean | undefined, preserveGutters?: boolean | undefined) => {
if (!destroyed) {
split.destroy(preserveStyles, preserveGutters);
split?.destroy(preserveStyles, preserveGutters);
destroyed = true;
}
};

setAnimation(true);
init();

return {
show,
getLayout,
setLayout,
destroy,
};
};
72 changes: 38 additions & 34 deletions src/livecodes/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,42 +50,46 @@ export const getAppConfig = (config: Config | AppConfig): AppConfig =>
zoom: config.zoom,
});

export const getUserConfig = (config: Config | UserConfig): UserConfig => ({
autoupdate: config.autoupdate,
autosave: config.autosave,
autotest: config.autotest,
delay: config.delay,
formatOnsave: config.formatOnsave,
recoverUnsaved: config.recoverUnsaved,
welcome: config.welcome,
showSpacing: config.showSpacing,
...getEditorConfig(config),
...getFormatterConfig(config),
});
export const getUserConfig = (config: Config | UserConfig): UserConfig =>
cloneObject({
autoupdate: config.autoupdate,
autosave: config.autosave,
autotest: config.autotest,
delay: config.delay,
formatOnsave: config.formatOnsave,
layout: config.layout,
recoverUnsaved: config.recoverUnsaved,
welcome: config.welcome,
showSpacing: config.showSpacing,
...getEditorConfig(config),
...getFormatterConfig(config),
});

export const getEditorConfig = (config: Config | UserConfig): EditorConfig => ({
editor: config.editor ?? ((config as Config).readonly === true ? 'codejar' : undefined),
theme: config.theme,
editorTheme: config.editorTheme,
fontFamily: config.fontFamily,
fontSize: config.fontSize,
useTabs: config.useTabs,
tabSize: config.tabSize,
lineNumbers: config.lineNumbers,
wordWrap: config.wordWrap,
closeBrackets: config.closeBrackets,
emmet: config.emmet,
enableAI: config.enableAI,
editorMode: config.editorMode,
});
export const getEditorConfig = (config: Config | UserConfig): EditorConfig =>
cloneObject({
editor: config.editor ?? ((config as Config).readonly === true ? 'codejar' : undefined),
theme: config.theme,
editorTheme: config.editorTheme,
fontFamily: config.fontFamily,
fontSize: config.fontSize,
useTabs: config.useTabs,
tabSize: config.tabSize,
lineNumbers: config.lineNumbers,
wordWrap: config.wordWrap,
closeBrackets: config.closeBrackets,
emmet: config.emmet,
enableAI: config.enableAI,
editorMode: config.editorMode,
});

export const getFormatterConfig = (config: Config | UserConfig): FormatterConfig => ({
useTabs: config.useTabs,
tabSize: config.tabSize,
semicolons: config.semicolons,
singleQuote: config.singleQuote,
trailingComma: config.trailingComma,
});
export const getFormatterConfig = (config: Config | UserConfig): FormatterConfig =>
cloneObject({
useTabs: config.useTabs,
tabSize: config.tabSize,
semicolons: config.semicolons,
singleQuote: config.singleQuote,
trailingComma: config.trailingComma,
});

export const upgradeAndValidate = (config: Partial<Config>) =>
validateConfig(upgradeConfig(config as any));
1 change: 1 addition & 0 deletions src/livecodes/config/default-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const defaultConfig: Config = {
formatOnsave: false,
mode: 'full',
theme: 'dark',
layout: undefined,
editorTheme: undefined,
recoverUnsaved: true,
showSpacing: false,
Expand Down
2 changes: 2 additions & 0 deletions src/livecodes/config/validate-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export const validateConfig = (config: Partial<Config>): Partial<Config> => {

const modes: Array<Config['mode']> = ['full', 'editor', 'codeblock', 'result'];
const themes: Array<Config['theme']> = ['light', 'dark'];
const layout: Array<Config['layout']> = ['horizontal', 'vertical'];
const editorModes: Array<Config['editorMode']> = ['vim', 'emacs'];
const tools: Array<Tool['name']> = ['console', 'compiled', 'tests'];
const toolsPaneStatus: ToolsPaneStatus[] = ['', 'full', 'closed', 'open', 'none'];
Expand Down Expand Up @@ -101,6 +102,7 @@ export const validateConfig = (config: Partial<Config>): Partial<Config> => {
...(is(config.formatOnsave, 'boolean') ? { formatOnsave: config.formatOnsave } : {}),
...(includes(modes, config.mode) ? { mode: config.mode } : {}),
...(includes(themes, config.theme) ? { theme: config.theme } : {}),
...(includes(layout, config.layout) ? { layout: config.layout } : {}),
...(is(config.editorTheme, 'array', 'string') || is(config.editorTheme, 'string')
? { editorTheme: config.editorTheme }
: {}),
Expand Down
32 changes: 32 additions & 0 deletions src/livecodes/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1664,6 +1664,25 @@ const setTheme = (theme: Theme, editorTheme: Config['editorTheme']) => {
});
};

const setLayout = (layout: Config['layout']) => {
const newLayout =
layout ??
(window.innerWidth < 768 && window.innerHeight > window.innerWidth ? 'vertical' : 'horizontal');
split?.setLayout(newLayout);
const layoutToggle = UI.getLayoutToggle();
if (layoutToggle) {
const layoutSwitch = layoutToggle.closest('.switch') as HTMLElement;
if (layout === undefined) {
layoutToggle.readOnly = layoutToggle.indeterminate = true;
layoutSwitch.dataset.hint = 'Responsive layout';
} else {
layoutToggle.checked = layout === 'vertical';
layoutToggle.readOnly = layoutToggle.indeterminate = false;
layoutSwitch.dataset.hint = layout === 'vertical' ? 'Vertical layout' : 'Horizontal layout';
}
}
};

const loadSettings = (config: Config) => {
const processorToggles = UI.getProcessorToggles();
processorToggles.forEach((toggle) => {
Expand Down Expand Up @@ -1696,6 +1715,9 @@ const loadSettings = (config: Config) => {
const themeToggle = UI.getThemeToggle();
themeToggle.checked = config.theme === 'dark';

const layoutToggle = UI.getLayoutToggle();
layoutToggle.checked = config.layout === 'vertical';

const recoverToggle = UI.getRecoverToggle();
recoverToggle.checked = config.recoverUnsaved;

Expand Down Expand Up @@ -1931,6 +1953,9 @@ const handleTitleEdit = () => {

const handleResize = () => {
resizeEditors();
setLayout(getConfig().layout);

eventsManager.addEventListener(window, 'resize', () => setLayout(getConfig().layout), false);
eventsManager.addEventListener(window, 'resize', resizeEditors, false);
eventsManager.addEventListener(window, customEvents.resizeEditor, resizeEditors, false);
};
Expand Down Expand Up @@ -2292,6 +2317,13 @@ const handleSettings = () => {
if (configKey === 'theme') {
setConfig({ ...getConfig(), theme: toggle.checked ? 'dark' : 'light' });
setTheme(getConfig().theme, getConfig().editorTheme);
} else if (configKey === 'layout') {
const newLayout = toggle.readOnly ? 'vertical' : !toggle.checked ? 'horizontal' : undefined;
setConfig({
...getConfig(),
layout: newLayout,
});
setLayout(newLayout);
} else if (configKey === 'autosync') {
const syncData = (await getUserData())?.sync;
if (syncData?.repo) {
Expand Down
18 changes: 14 additions & 4 deletions src/livecodes/html/settings-menu.html
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,10 @@
<li>
<a href="#" id="broadcast-link">Broadcast …</a>
</li>
<li>&nbsp;</li>
<li>
<label class="switch">
<span>Auto update</span>
<span>Auto Update</span>
<div>
<input id="autoupdate" type="checkbox" data-config="autoupdate" />
<span class="slider round"></span>
Expand All @@ -83,7 +84,7 @@
</li>
<li>
<label class="switch">
<span>Auto save</span>
<span>Auto Save</span>
<div>
<input id="autosave" type="checkbox" data-config="autosave" />
<span class="slider round"></span>
Expand All @@ -108,16 +109,25 @@
</li>
<li>
<label class="switch">
<span>Format on-save</span>
<span>Format On-save</span>
<div>
<input id="formatOnsave" type="checkbox" data-config="formatOnsave" />
<span class="slider round"></span>
</div>
</label>
</li>
<li>
<label class="switch hint--bottom">
<span>Vertical Layout</span>
<div>
<input id="layout" type="checkbox" data-config="layout" />
<span class="slider round"></span>
</div>
</label>
</li>
<li>
<label class="switch">
<span>Dark theme</span>
<span>Dark Theme</span>
<div>
<input id="theme" type="checkbox" data-config="theme" />
<span class="slider round"></span>
Expand Down

0 comments on commit df3796f

Please sign in to comment.