Skip to content

Commit

Permalink
feat: support layout manipulation
Browse files Browse the repository at this point in the history
  • Loading branch information
grantjbutler committed Feb 19, 2022
1 parent 48bdd9e commit 622b155
Show file tree
Hide file tree
Showing 10 changed files with 115 additions and 35 deletions.
2 changes: 1 addition & 1 deletion packages/renderer/src/components/ContextMenu/MenuItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export default defineComponent({
throw new Error();
}

label.value = firstChild.children as string;
label.value = (firstChild.children as string).trim();

return null;
};
Expand Down
64 changes: 44 additions & 20 deletions packages/renderer/src/components/Sidebar/Layout.vue
Original file line number Diff line number Diff line change
@@ -1,24 +1,40 @@
<template>
<div
v-click-away="exitEditing"
class="macos:mx-2 macos:rounded macos:px-2 macos:py-1"
:class="{'bg-system-background-selected-content': isSelected, 'text-white': isSelected}"
@click="select"
@click.self="exitEditing"
@dblclick.prevent="enterEditing"
>
<input
v-if="isEditing"
v-model.lazy="name"
v-focus
type="text"
class="p-0 text-sm border-0 bg-system-background-under-page"
@keydown.esc="exitEditing"
<context-menu-providing>
<div
v-click-away="exitEditing"
class="macos:mx-2 macos:rounded macos:px-2 macos:py-1"
:class="{'bg-system-background-selected-content': isSelected, 'text-white': isSelected}"
@click="select"
@click.self="exitEditing"
@dblclick.prevent="enterEditing"
>
<span
v-else
>{{ layout.name }}</span>
</div>
<input
v-if="isEditing"
v-model.lazy="name"
v-focus
type="text"
class="p-0 text-sm border-0 bg-system-background-under-page"
@keydown.esc="exitEditing"
>
<span
v-else
>{{ layout.name }}</span>
</div>

<template #menu>
<context-menu>
<menu-item @click="duplicate">
Duplicate Layout
</menu-item>
<menu-item
v-if="layoutsStore.layouts.length > 1"
@click="deleteLayout"
>
Delete Layout
</menu-item>
</context-menu>
</template>
</context-menu-providing>
</template>

<script lang="ts" setup>
Expand All @@ -45,7 +61,15 @@ const exitEditing = () => {
const select = () => {
if (isSelected.value) { return; }
layoutsStore.selectLayout(props.layout);
layoutsStore.selectLayout(props.layout.id);
};
const duplicate = () => {
layoutsStore.duplicateLayout(props.layout.id);
};
const deleteLayout = () => {
layoutsStore.deleteLayout(props.layout.id);
};
watch(isSelected, (value) => {
Expand Down
8 changes: 4 additions & 4 deletions packages/renderer/src/components/Sidebar/Layouts.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
>
<option
v-for="layout in layouts"
:key="layout.name"
:value="layout"
:key="layout.id"
:value="layout.id"
>
{{ layout.name }}
</option>
Expand All @@ -44,7 +44,7 @@
<div class="flex flex-col h-24 overflow-y-auto macos:border-b macos:border-system-separator macos:py-1">
<Layout
v-for="layout in layouts"
:key="layout.name"
:key="layout.id"
:layout="layout"
/>
</div>
Expand Down Expand Up @@ -75,7 +75,7 @@ import Layout from './Layout.vue';
const layoutsStore = useLayoutsStore();
const layouts = computed(() => layoutsStore.layouts);
const selectedLayout = computed({
get: () => layoutsStore.selectedLayout,
get: () => layoutsStore.selectedLayout.id,
set: (value) => layoutsStore.selectLayout(value),
});
Expand Down
4 changes: 4 additions & 0 deletions packages/renderer/src/layout/Component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,8 @@ export default class Component {
exerciseLayout(size: Size): LayoutNode {
throw new Error('Subclasses are expected to implement logic for exercising a layout.');
}

clone(): Component {
throw new Error('Subclasses are expected to implement logic for cloning.');
}
}
10 changes: 10 additions & 0 deletions packages/renderer/src/layout/FlexComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import ContainerComponent from './ContainerComponent';
import Frame from './Frame';
import Size from './Size';
import type LayoutNode from './LayoutNode';
import type Component from './Component';

export default class FlexComponent extends ContainerComponent {
direction: 'horizontal' | 'vertical' = 'horizontal';
Expand Down Expand Up @@ -105,4 +106,13 @@ export default class FlexComponent extends ContainerComponent {

return new ContainerLayoutNode(this.id, nodeFrame, childNodes);
}

clone(): Component {
const clone = new FlexComponent();
clone.direction = this.direction;
clone.spacing = this.spacing;
clone.distribution = this.distribution;
this.children.forEach(child => clone.addChild(child.clone()));
return clone;
}
}
7 changes: 7 additions & 0 deletions packages/renderer/src/layout/InsetComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,11 @@ export default class InsetComponent extends ContainerComponent {

return new ContainerLayoutNode(this.id, new Frame(0, 0, childNode.frame.width + this.insets.left + this.insets.right, childNode.frame.height + this.insets.top + this.insets.bottom), [childNode]);
}

clone(): Component {
const clone = new InsetComponent();
clone.insets = this.insets.clone();
this.children.forEach(child => clone.addChild(child.clone()));
return clone;
}
}
11 changes: 10 additions & 1 deletion packages/renderer/src/layout/Insets.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export default class {
export default class Insets {
top: number;
left: number;
bottom: number;
Expand All @@ -10,4 +10,13 @@ export default class {
this.bottom = bottom;
this.right = right;
}

clone(): Insets {
return new Insets(
this.top,
this.left,
this.bottom,
this.right,
);
}
}
7 changes: 7 additions & 0 deletions packages/renderer/src/layout/SourceComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,11 @@ export default class SourceComponent extends Component {

return new SourceLayoutNode(this.id, new Frame(0, 0, croppedSize.width * scaleRatio, croppedSize.height * scaleRatio), this.source);
}

clone(): Component {
const clone = new SourceComponent();
clone.source = this.source;
clone.crop = this.crop.clone();
return clone;
}
}
1 change: 1 addition & 0 deletions packages/renderer/src/layout/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const containerComponents: { [index: string]: typeof ContainerComponent }
};

export interface Layout {
id: string
name: string
rootComponent: ContainerComponent
}
Expand Down
36 changes: 27 additions & 9 deletions packages/renderer/src/store/layouts.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { defineStore } from 'pinia';
import { v4 as uuidv4 } from 'uuid';
import type { ContainerComponent, Layout } from '/@/layout';
import { FlexComponent } from '/@/layout';

Expand All @@ -10,6 +11,7 @@ interface State {
export const useLayoutsStore = defineStore('layouts', {
state: (): State => {
const initialLayout: Layout = {
id: uuidv4(),
name: 'Untitled Layout',
rootComponent: new FlexComponent(),
};
Expand All @@ -20,31 +22,47 @@ export const useLayoutsStore = defineStore('layouts', {
};
},
actions: {
selectLayout(layout: Layout) {
selectLayout(id: string) {
const layout = this.layouts.find(layout => layout.id === id);
if (!layout) { return; }
this.selectedLayout = layout;
},
setRootComponent(component: ContainerComponent) {
this.selectedLayout.rootComponent = component;
},
createLayout(name: string, rootComponent: ContainerComponent) {
// TODO: Show an alert that there's already a layout with a given name.
if (this.layouts.find(layout => layout.name === name)) { return; }

const layout = {
id: uuidv4(),
name,
rootComponent,
};

this.layouts.push(layout);
this.selectedLayout = layout;
},
renameLayout(existing: string, newName: string) {
// TODO: Show an alert that there's already a layout with a given name.
if (this.layouts.find(layout => layout.name === newName)) { return; }

const index = this.layouts.findIndex(layout => layout.name === existing);
renameLayout(id: string, newName: string) {
const index = this.layouts.findIndex(layout => layout.id === id);
if (index < 0) { return; }
this.layouts[index].name = newName;
},
duplicateLayout(id: string) {
const layout = this.layouts.find(layout => layout.id === id);
if (!layout) { return; }
const baseName = layout.name + ' copy';
let possibleName = baseName;
let iterator = 1;

while (this.layouts.find(layout => layout.name == possibleName)) {
possibleName = baseName + ` ${++iterator}`;
}

this.createLayout(possibleName, layout.rootComponent.clone() as ContainerComponent);
},
deleteLayout(id: string) {
if (this.layouts.length == 1) { return; }
const index = this.layouts.findIndex(layout => layout.id === id);
if (index < 0) { return; }
this.layouts.splice(index, 1);
},
},
});

0 comments on commit 622b155

Please sign in to comment.