Skip to content

Commit

Permalink
Merge pull request #4006 from manuelmeister/feature/column-wrap
Browse files Browse the repository at this point in the history
Optimize column layout experience
  • Loading branch information
manuelmeister committed Oct 22, 2023
2 parents adf2452 + 18bda6a commit e12da72
Show file tree
Hide file tree
Showing 24 changed files with 311 additions and 173 deletions.
19 changes: 16 additions & 3 deletions frontend/src/components/activity/CardContentNode.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
<template>
<v-card :elevation="draggable ? 4 : 0" :class="{ 'mx-2 my-2': draggable }">
<v-card
:elevation="draggable ? 4 : 0"
:tile="draggable ? null : true"
class="d-flex flex-column"
:class="{ 'mx-2 my-2': draggable }"
>
<v-card-title hide-actions class="pa-0 pr-sm-2">
<v-toolbar dense flat>
<v-icon class="mr-2">{{ icon }}</v-icon>
Expand Down Expand Up @@ -56,7 +61,7 @@
</v-toolbar>
</v-card-title>
<slot name="outer">
<v-card-text>
<v-card-text class="flex-grow-1">
<slot />
</v-card-text>
</slot>
Expand Down Expand Up @@ -108,7 +113,7 @@ export default {
}
</script>

<style scoped>
<style scoped lang="scss">
.v-card:not(:hover):deep(button.visible-on-hover),
.v-card:not(:hover):deep(button.tooltip-activator) {
opacity: 0;
Expand All @@ -127,4 +132,12 @@ export default {
opacity 0.2s linear,
width 0.3s steps(1, start);
}
::v-deep {
.e-form-container,
.v-input,
.v-input__control {
height: 100%;
}
}
</style>
1 change: 0 additions & 1 deletion frontend/src/components/activity/ContentNode.vue
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ export default {

<style lang="scss">
.content-node:not(.draggable) + .content-node:not(.draggable) {
border-top: 1px solid rgba(0, 0, 0, 0.12) !important;
border-radius: 0 !important;
}
Expand Down
43 changes: 38 additions & 5 deletions frontend/src/components/activity/DraggableContentNodes.vue
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
<template>
<div>
<div class="d-flex flex-column flex-grow-1">
<draggable
v-if="contentNodeIds"
v-model="localContentNodeIds"
:disabled="!draggingEnabled"
group="contentNodes"
class="draggable-area d-flex flex-column pb-10"
:class="{ 'min-height': layoutMode }"
:invert-swap="true"
class="draggable-area flex-grow-1"
:class="{
'min-height draggable-area--layout-mode pb-2': layoutMode,
'draggable-area--read-mode': !layoutMode,
'draggable-area--root': isRoot,
'draggable-area--column d-flex flex-column': direction === 'column',
'draggable-area--row d-flex flex-row flex-wrap': direction === 'row',
}"
:swap-threshold="0.65"
:inverted-swap-threshold="0.65"
@start="startDrag"
@add="finishDrag"
@update="finishDrag"
Expand All @@ -23,10 +30,16 @@
:draggable="draggingEnabled"
:disabled="disabled"
/>
<v-sheet
v-if="!layoutMode && draggableContentNodeIds.length === 0"
elevation="0"
class="content-node placeholder-node"
></v-sheet>
</draggable>

<button-nested-content-node-add
v-if="layoutMode"
class="flex-grow-0"
:layout-mode="layoutMode"
:parent-content-node="parentContentNode"
:slot-name="slotName"
Expand All @@ -53,6 +66,8 @@ export default {
slotName: { type: String, required: true },
parentContentNode: { type: Object, required: true },
disabled: { type: Boolean, default: false },
direction: { type: String, default: 'column' },
isRoot: { type: Boolean, default: false },
},
data() {
return {
Expand Down Expand Up @@ -138,6 +153,24 @@ export default {
min-height: 10rem;
}
.draggable-area ::v-deep .content-node {
margin: 0 !important;
flex-grow: 1;
}
.draggable-area--row ::v-deep .content-node {
flex: 1 0 320px;
}
.draggable-area--layout-mode {
display: flex !important;
gap: 4px;
}
.draggable-area--read-mode {
gap: 1px;
}
.dragging-content-node .draggable-area {
position: relative;
z-index: 100;
Expand All @@ -151,7 +184,7 @@ export default {
left: 4px;
right: 4px;
border-radius: 5px;
border: 2px dashed map-get($blue-grey, 'base');
border: 2px dotted map-get($blue-grey, 'base');
background: map-get($blue-grey, 'lighten-4');
opacity: 40%;
content: '';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>
<v-menu bottom left offset-y>
<template #activator="{ on, attrs }">
<v-btn icon class="float-right mr-4 mt-3" v-bind="attrs" v-on="on">
<v-btn icon v-bind="attrs" v-on="on">
<v-icon>mdi-dots-vertical</v-icon>
</v-btn>
</template>
Expand Down
4 changes: 0 additions & 4 deletions frontend/src/components/activity/ScheduleEntry.vue
Original file line number Diff line number Diff line change
Expand Up @@ -361,10 +361,6 @@ export default {
margin-bottom: 0;
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
padding: 1.5rem 16px;
@media #{map-get($display-breakpoints, 'sm-and-down')} {
border-bottom: none;
}
}
.e-category-chip-save-icon {
Expand Down
110 changes: 73 additions & 37 deletions frontend/src/components/activity/content/ColumnLayout.vue
Original file line number Diff line number Diff line change
@@ -1,38 +1,54 @@
<template>
<v-row v-if="!contentNode.loading" no-gutters>
<resizable-column
v-for="(_, slot) in columns"
:key="slot"
:parent-content-node="contentNode"
:layout-mode="layoutMode"
:width-left="relativeColumnWidths[slot][0]"
:width="relativeColumnWidths[slot][1]"
:width-right="relativeColumnWidths[slot][2]"
:num-columns="numColumns"
:last="slot === lastColumn"
:min-width="minWidth(slot)"
:max-width="maxWidth(slot)"
:color="color"
:show-header="!isRoot"
@resizing="(newWidth) => resizeColumn(slot, newWidth)"
@resize-stop="saveColumnWidths"
>
<draggable-content-nodes
:slot-name="slot"
:layout-mode="layoutMode"
:parent-content-node="contentNode"
:disabled="disabled"
/>

<template #menu>
<LayoutCard
v-resizeobserver.debounce="onResize"
class="ec-column-layout"
:class="{ 'my-2': !isRoot && layoutMode }"
:is-root="isRoot"
:layout-mode="layoutMode"
>
<template #header>
{{ $tc('contentNode.columnLayout.name') }}
<MenuCardlessContentNode :content-node="contentNode">
<column-operations
:content-node="contentNode"
:min-column-width="minWidth(slot)"
:total-width="12"
:min-column-width="minWidth"
:total-width="totalWidth"
/>
</template>
</resizable-column>
</v-row>
</MenuCardlessContentNode>
</template>
<div
v-if="!contentNode.loading"
class="d-flex flex-wrap ec-column-layout__container"
:class="{ 'px-1 gap-4': layoutMode, 'h-full': !layoutMode }"
>
<resizable-column
v-for="(_, slot) in columns"
:key="slot"
:parent-content-node="contentNode"
:layout-mode="layoutMode"
:width-left="relativeColumnWidths[slot][0]"
:width="relativeColumnWidths[slot][1]"
:width-right="relativeColumnWidths[slot][2]"
:num-columns="numColumns"
:last="slot === lastColumn"
:min-width="minWidth"
:max-width="maxWidth(slot)"
:color="color"
:show-header="!isRoot"
:is-default-variant="isDefaultVariant"
@resizing="(newWidth) => resizeColumn(slot, newWidth)"
@resize-stop="saveColumnWidths"
>
<draggable-content-nodes
:slot-name="slot"
:layout-mode="layoutMode"
:parent-content-node="contentNode"
:disabled="disabled"
:is-root="isRoot"
/>
</resizable-column>
</div>
</LayoutCard>
</template>

<script>
Expand All @@ -43,6 +59,9 @@ import DraggableContentNodes from '@/components/activity/DraggableContentNodes.v
import ColumnOperations from '@/components/activity/content/columnLayout/ColumnOperations.vue'
import { idToColor } from '@/common/helpers/colors.js'
import { errorToMultiLineToast } from '@/components/toast/toasts'
import MenuCardlessContentNode from '@/components/activity/MenuCardlessContentNode.vue'
import LayoutCard from '@/components/activity/content/layout/LayoutCard.vue'
import camelCase from 'lodash/camelCase.js'
function cumulativeSumReducer(cumSum, nextElement) {
cumSum.push(cumSum[cumSum.length - 1] + nextElement)
Expand All @@ -52,13 +71,18 @@ function cumulativeSumReducer(cumSum, nextElement) {
export default {
name: 'ColumnLayout',
components: {
LayoutCard,
MenuCardlessContentNode,
ColumnOperations,
DraggableContentNodes,
ResizableColumn,
},
mixins: [contentNodeMixin],
data() {
return {
clientWidth: 1000,
minWidth: 3,
totalWidth: 12,
localColumnWidths: {},
}
},
Expand Down Expand Up @@ -98,6 +122,11 @@ export default {
isRoot() {
return this.contentNode._meta.self === this.contentNode.root()._meta.self
},
isDefaultVariant() {
// 260 is between 870/3 and 870/4.
// We don't want average column widths of below 260px as the readability suffers.
return this.clientWidth / this.numColumns > 260
},
},
watch: {
columns: {
Expand All @@ -108,6 +137,7 @@ export default {
},
},
methods: {
camelCase,
setLocalColumnWidths() {
this.localColumnWidths = mapValues(this.columns, 'width')
},
Expand All @@ -124,18 +154,16 @@ export default {
if (index === -1 || index === slots.length - 1) return undefined
return slots[index + 1]
},
minWidth() {
return 3
},
maxWidth(slot) {
const nextSlot = this.next(slot)
if (nextSlot === undefined) return this.localColumnWidths[slot]
return (
this.localColumnWidths[slot] +
this.localColumnWidths[nextSlot] -
this.minWidth(nextSlot)
this.localColumnWidths[slot] + this.localColumnWidths[nextSlot] - this.minWidth
)
},
onResize({ width }) {
this.clientWidth = width
},
async saveColumnWidths() {
const payload = {
data: {
Expand All @@ -154,3 +182,11 @@ export default {
},
}
</script>
<style scoped>
.ec-column-layout__container {
background-color: #ccc;
border-bottom-left-radius: 9px;
border-bottom-right-radius: 9px;
}
</style>
16 changes: 7 additions & 9 deletions frontend/src/components/activity/content/Material.vue
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
<template>
<card-content-node v-bind="$props">
<div class="mb-3">
<material-table
:camp="camp()"
:material-node="contentNode"
:layout-mode="layoutMode"
:material-item-collection="materialItemCollection"
:disabled="disabled"
/>
</div>
<material-table
:camp="camp()"
:material-node="contentNode"
:layout-mode="layoutMode"
:material-item-collection="materialItemCollection"
:disabled="disabled"
/>
</card-content-node>
</template>

Expand Down
26 changes: 10 additions & 16 deletions frontend/src/components/activity/content/Notes.vue
Original file line number Diff line number Diff line change
@@ -1,33 +1,27 @@
<template>
<card-content-node v-bind="$props">
<div class="mb-3">
<api-form :entity="contentNode">
<api-richtext
fieldname="data.html"
:label="$tc('contentNode.notes.name')"
rows="4"
auto-grow
:disabled="layoutMode || disabled"
:filled="layoutMode"
/>
</api-form>
</div>
<api-richtext
:uri="contentNode._meta.self"
fieldname="data.html"
:label="$tc('contentNode.notes.name')"
rows="4"
auto-grow
:disabled="layoutMode || disabled"
:filled="layoutMode"
height="100%"
/>
</card-content-node>
</template>

<script>
import ApiForm from '@/components/form/api/ApiForm.vue'
import CardContentNode from '@/components/activity/CardContentNode.vue'
import { contentNodeMixin } from '@/mixins/contentNodeMixin.js'
export default {
name: 'Notes',
components: {
CardContentNode,
ApiForm,
},
mixins: [contentNodeMixin],
}
</script>

<style scoped></style>
Loading

0 comments on commit e12da72

Please sign in to comment.