Skip to content
11 changes: 5 additions & 6 deletions src/main/store/constants.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import type { EditorSettings, NotesEditorSettings } from './types'

export const APP_DEFAULTS = {
sizes: {
sidebar: 180,
snippetList: 250,
tagsList: 50, // в %
},
export const LAYOUT_DEFAULTS = {
sidebar: { width: 200, min: 120 },
list: { width: 300, min: 150 },
editor: { min: 300 },
tags: { height: 200, min: 80 },
}

export const EDITOR_DEFAULTS: EditorSettings = {
Expand Down
56 changes: 32 additions & 24 deletions src/main/store/module/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type {
SpaceLayoutMode,
} from '../types'
import Store from 'electron-store'
import { APP_DEFAULTS } from '../constants'
import { LAYOUT_DEFAULTS } from '../constants'
import {
asRecord,
isRecord,
Expand All @@ -28,15 +28,15 @@ const APP_STORE_DEFAULTS: AppStore = {
selection: {},
layout: {
mode: 'all-panels',
tagsListHeight: APP_DEFAULTS.sizes.tagsList,
tagsListHeight: LAYOUT_DEFAULTS.tags.height,
},
},
notes: {
selection: {},
editorMode: 'livePreview',
layout: {
mode: 'all-panels',
tagsListHeight: APP_DEFAULTS.sizes.tagsList,
tagsListHeight: LAYOUT_DEFAULTS.tags.height,
},
},
notifications: {
Expand Down Expand Up @@ -139,21 +139,25 @@ function sanitizeAppStore(value: unknown): AppStore {
['all-panels', 'list-editor', 'editor-only'] as const,
getLegacyCodeLayoutMode(asRecord(source.state)),
),
tagsListHeight: readNumber(
codeLayoutSource,
'tagsListHeight',
readNumber(
legacySizes,
tagsListHeight: (() => {
const raw = readNumber(
codeLayoutSource,
'tagsListHeight',
APP_STORE_DEFAULTS.code.layout.tagsListHeight,
),
),
readNumber(
legacySizes,
'tagsListHeight',
LAYOUT_DEFAULTS.tags.height,
),
)
return raw < 100 ? LAYOUT_DEFAULTS.tags.height : raw
})(),
threePanel:
readOptionalNumberArray(codeLayoutSource, 'threePanel')
|| readOptionalNumberArray(legacySizes, 'layout'),
twoPanel:
readOptionalNumberArray(codeLayoutSource, 'twoPanel')
|| readOptionalNumberArray(legacySizes, 'codeListLayout'),
readOptionalNumber(codeLayoutSource, 'twoPanel')
?? readOptionalNumber(legacySizes, 'codeListLayout')
?? undefined,
},
},
notes: {
Expand All @@ -180,21 +184,25 @@ function sanitizeAppStore(value: unknown): AppStore {
['all-panels', 'list-editor', 'editor-only'] as const,
getLegacyNotesLayoutMode(asRecord(source.notesState)),
),
tagsListHeight: readNumber(
notesLayoutSource,
'tagsListHeight',
readNumber(
legacySizes,
'notesTagsListHeight',
APP_STORE_DEFAULTS.notes.layout.tagsListHeight,
),
),
tagsListHeight: (() => {
const raw = readNumber(
notesLayoutSource,
'tagsListHeight',
readNumber(
legacySizes,
'notesTagsListHeight',
LAYOUT_DEFAULTS.tags.height,
),
)
return raw < 100 ? LAYOUT_DEFAULTS.tags.height : raw
})(),
threePanel:
readOptionalNumberArray(notesLayoutSource, 'threePanel')
|| readOptionalNumberArray(legacySizes, 'notesLayout'),
twoPanel:
readOptionalNumberArray(notesLayoutSource, 'twoPanel')
|| readOptionalNumberArray(legacySizes, 'notesLayoutWithoutSidebar'),
readOptionalNumber(notesLayoutSource, 'twoPanel')
?? readOptionalNumber(legacySizes, 'notesLayoutWithoutSidebar')
?? undefined,
},
},
notifications: {
Expand Down
4 changes: 2 additions & 2 deletions src/main/store/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export interface AppStore {
mode: SpaceLayoutMode
tagsListHeight: number
threePanel?: number[]
twoPanel?: number[]
twoPanel?: number
}
}
notes: {
Expand All @@ -39,7 +39,7 @@ export interface AppStore {
mode: SpaceLayoutMode
tagsListHeight: number
threePanel?: number[]
twoPanel?: number[]
twoPanel?: number
}
}
notifications: {
Expand Down
96 changes: 31 additions & 65 deletions src/renderer/components/code-space-layout/CodeSpaceLayout.vue
Original file line number Diff line number Diff line change
@@ -1,85 +1,51 @@
<script setup lang="ts">
import * as Resizable from '@/components/ui/shadcn/resizable'
import { useApp } from '@/composables'
import { getCodePanels } from '@/composables/layoutModes'
import { store } from '@/electron'

const { codeLayoutMode } = useApp()

const storedThreePanelLayout = store.app.get('code.layout.threePanel') as
| number[]
| undefined
const storedTwoPanelLayout = store.app.get('code.layout.twoPanel') as
const panels = computed(() => getCodePanels(codeLayoutMode.value))

const storedThreePanel = store.app.get('code.layout.threePanel') as
| number[]
| undefined
const defaultThreePanelLayout = storedThreePanelLayout || [15, 20, 65]
const defaultTwoPanelLayout = storedTwoPanelLayout || [35, 65]
const panels = computed(() => getCodePanels(codeLayoutMode.value))

function onLayout(layout: number[]) {
if (layout.length === 3) {
store.app.set('code.layout.threePanel', layout)
return
}
const sidebarWidth
= storedThreePanel?.length === 2 ? storedThreePanel[0] : undefined
const listWidth = (() => {
if (storedThreePanel?.length === 2)
return storedThreePanel[1]
const twoPanel = store.app.get('code.layout.twoPanel') as number | undefined
return twoPanel ?? undefined
})()

if (layout.length === 2) {
store.app.set('code.layout.twoPanel', layout)
}
function onResizeEnd(sw: number, lw: number) {
store.app.set('code.layout.threePanel', [sw, lw])
}

function onTwoPanelResize(lw: number) {
store.app.set('code.layout.twoPanel', lw)
}
</script>

<template>
<div
v-if="!panels.showList"
class="h-screen"
>
<Editor />
</div>
<Resizable.ResizablePanelGroup
v-else-if="!panels.showSidebar"
direction="horizontal"
class="h-screen"
@layout="onLayout"
>
<Resizable.ResizablePanel
:default-size="defaultTwoPanelLayout[0]"
:min-size="15"
>
<SnippetList />
</Resizable.ResizablePanel>
<Resizable.ResizableHandle />
<Resizable.ResizablePanel
:default-size="defaultTwoPanelLayout[1]"
:min-size="30"
>
<Editor />
</Resizable.ResizablePanel>
</Resizable.ResizablePanelGroup>
<Resizable.ResizablePanelGroup
v-else
direction="horizontal"
class="h-screen"
@layout="onLayout"
<LayoutThreeColumn
:show-sidebar="panels.showSidebar"
:show-list="panels.showList"
:sidebar-width="sidebarWidth"
:list-width="listWidth"
@resize-end="onResizeEnd"
@two-panel-resize="onTwoPanelResize"
>
<Resizable.ResizablePanel
:default-size="defaultThreePanelLayout[0]"
:min-size="10"
>
<template #sidebar>
<Sidebar />
</Resizable.ResizablePanel>
<Resizable.ResizableHandle />
<Resizable.ResizablePanel
:default-size="defaultThreePanelLayout[1]"
:min-size="10"
>
</template>
<template #list>
<SnippetList />
</Resizable.ResizablePanel>
<Resizable.ResizableHandle />
<Resizable.ResizablePanel
:default-size="defaultThreePanelLayout[2]"
:min-size="30"
>
</template>
<template #editor>
<Editor />
</Resizable.ResizablePanel>
</Resizable.ResizablePanelGroup>
</template>
</LayoutThreeColumn>
</template>
122 changes: 122 additions & 0 deletions src/renderer/components/layout/ThreeColumn.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
<script setup lang="ts">
import { useResizeHandle } from '@/composables'
import { LAYOUT_DEFAULTS } from '~/main/store/constants'

const props = withDefaults(
defineProps<{
showSidebar: boolean
showList: boolean
sidebarWidth?: number
listWidth?: number
}>(),
{
sidebarWidth: LAYOUT_DEFAULTS.sidebar.width,
listWidth: LAYOUT_DEFAULTS.list.width,
},
)

const emit = defineEmits<{
resizeEnd: [sidebarWidth: number, listWidth: number]
twoPanelResize: [listWidth: number]
}>()

const containerRef = ref<HTMLElement>()
const sidebarHandleRef = ref<HTMLElement>()
const listHandleRef = ref<HTMLElement>()

const internalSidebarWidth = ref(props.sidebarWidth)
const internalListWidth = ref(props.listWidth)

function clampWidth(value: number, min: number, max: number) {
return Math.min(max, Math.max(min, value))
}

function getMaxWidth(excludeWidth: number) {
const total = containerRef.value?.clientWidth || window.innerWidth
return total - excludeWidth - LAYOUT_DEFAULTS.editor.min
}

const { isResizing: isSidebarResizing } = useResizeHandle(sidebarHandleRef, {
direction: 'horizontal',
onMove(dx) {
const max = getMaxWidth(internalListWidth.value)
internalSidebarWidth.value = clampWidth(
internalSidebarWidth.value + dx,
LAYOUT_DEFAULTS.sidebar.min,
max,
)
},
onEnd() {
emit('resizeEnd', internalSidebarWidth.value, internalListWidth.value)
},
})

const { isResizing: isListResizing } = useResizeHandle(listHandleRef, {
direction: 'horizontal',
onMove(dx) {
const exclude = props.showSidebar ? internalSidebarWidth.value : 0
const max = getMaxWidth(exclude)
internalListWidth.value = clampWidth(
internalListWidth.value + dx,
LAYOUT_DEFAULTS.list.min,
max,
)
},
onEnd() {
if (props.showSidebar) {
emit('resizeEnd', internalSidebarWidth.value, internalListWidth.value)
}
else {
emit('twoPanelResize', internalListWidth.value)
}
},
})

const isResizing = computed(
() => isSidebarResizing.value || isListResizing.value,
)
</script>

<template>
<div
v-if="!showList"
class="h-screen"
>
<slot name="editor" />
</div>
<div
v-else
ref="containerRef"
class="flex h-screen"
>
<div
v-if="showSidebar"
:style="{ width: `${internalSidebarWidth}px` }"
class="shrink-0 overflow-hidden"
>
<slot name="sidebar" />
</div>
<div
v-if="showSidebar"
ref="sidebarHandleRef"
class="before:bg-border hover:before:bg-primary data-[resizing]:before:bg-primary relative z-10 flex w-px shrink-0 cursor-col-resize items-center justify-center bg-transparent before:absolute before:inset-y-0 before:left-1/2 before:w-px before:-translate-x-1/2 before:transition-[background-color,width] before:duration-150 before:content-[''] after:absolute after:inset-y-0 after:left-1/2 after:w-3 after:-translate-x-1/2 after:content-[''] hover:before:w-0.5 hover:before:delay-200 data-[resizing]:before:w-0.5"
/>
<div
:style="{ width: `${internalListWidth}px` }"
class="shrink-0 overflow-hidden"
>
<slot name="list" />
</div>
<div
ref="listHandleRef"
class="before:bg-border hover:before:bg-primary data-[resizing]:before:bg-primary relative z-10 flex w-px shrink-0 cursor-col-resize items-center justify-center bg-transparent before:absolute before:inset-y-0 before:left-1/2 before:w-px before:-translate-x-1/2 before:transition-[background-color,width] before:duration-150 before:content-[''] after:absolute after:inset-y-0 after:left-1/2 after:w-3 after:-translate-x-1/2 after:content-[''] hover:before:w-0.5 hover:before:delay-200 data-[resizing]:before:w-0.5"
/>
<div class="min-w-0 flex-1 overflow-hidden">
<slot name="editor" />
</div>
<div
v-if="isResizing"
class="fixed inset-0 z-50 cursor-col-resize"
/>
</div>
</template>
Loading