Skip to content

Commit

Permalink
fix: make it possible to drag items into groups
Browse files Browse the repository at this point in the history
closes #25
  • Loading branch information
simonwep committed Dec 28, 2023
1 parent dfed7cb commit 5c2433c
Show file tree
Hide file tree
Showing 8 changed files with 80 additions and 20 deletions.
1 change: 1 addition & 0 deletions src/app/components/base/draggable/Draggable.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export interface DraggableEvent {
}

export interface ReorderEvent {
group: string;
source: string;
target: string;
type: DropOrder;
Expand Down
34 changes: 23 additions & 11 deletions src/app/components/base/draggable/Draggable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
@drag="drag"
@dragstart="dragStart"
>
<Button :color="store.group === props.name ? 'primary' : 'dimmed'" :icon="icon" textual />
<Button :color="matched && store.source !== id ? 'primary' : 'dimmed'" :icon="icon" textual />

<div
v-if="store.source === props.id && top && left && label"
Expand All @@ -29,15 +29,17 @@
import { computed, ref } from 'vue';
import Button from '@components/base/button/Button.vue';
import { AppIcon } from '@components/base/icon/Icon.types';
import { DraggableEvent, ReorderEvent } from './Draggable.types';
import { store } from './store';
import { ReorderEvent } from './Draggable.types';
import { DraggableStore, store } from './store';
const emit = defineEmits<{
(e: 'drop', data: ReorderEvent): void;
}>();
const props = defineProps<{
text: (store: DraggableEvent) => string | undefined;
text: (store: DraggableStore) => string | undefined;
icon?: (store: DraggableStore) => AppIcon | undefined;
target?: string[];
name: string;
id: string;
}>();
Expand All @@ -48,19 +50,23 @@ const left = ref(0);
const top = ref(0);
const active = computed(() => props.id === store.target && store.type);
const targets = computed(() => props.target ?? [props.name]);
const matched = computed(() => store.targets?.includes(props.name));
const icon = computed((): AppIcon => {
return active.value ? (store.type === 'before' ? 'skip-up-line' : 'skip-down-line') : 'draggable';
});
const icon = computed(
(): AppIcon =>
active.value ? props.icon?.(store) ?? (store.type === 'before' ? 'skip-up-line' : 'skip-down-line') : 'draggable'
);
const label = computed(() => {
return store.target && store.target && store.source ? props.text?.(store as DraggableEvent) : undefined;
return store.target && store.target && store.source ? props.text?.(store) : undefined;
});
const dragStart = (evt: DragEvent) => {
if (evt.dataTransfer && element.value) {
store.group = props.name;
store.source = props.id;
store.targets = targets.value;
evt.dataTransfer.effectAllowed = 'move';
evt.dataTransfer.setDragImage(element.value, Infinity, Infinity);
evt.dataTransfer.setData('text/plain', props.name);
Expand All @@ -73,7 +79,7 @@ const drag = (evt: DragEvent) => {
};
const dragOver = (evt: DragEvent) => {
if (store.group === props.name) {
if (store.group && store.targets?.includes(store.group)) {
const rect = draggable.value?.getBoundingClientRect();
evt.preventDefault();
Expand All @@ -95,13 +101,19 @@ const dragLeave = (evt: DragEvent) => {
const dragEnd = () => {
store.type = undefined;
store.target = undefined;
store.targets = undefined;
store.source = undefined;
store.group = undefined;
};
const drop = (evt: DragEvent) => {
if (store.target && store.target && store.source) {
emit('drop', store as Required<DraggableEvent>);
if (store.type && store.target && store.source && store.group) {
emit('drop', {
group: store.group,
source: store.source,
target: store.target,
type: store.type
});
}
evt.preventDefault();
Expand Down
5 changes: 4 additions & 1 deletion src/app/components/base/draggable/store.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import { reactive } from 'vue';
import { DropOrder } from './Draggable.types';

export interface DraggableStore {
group?: string;
source?: string;
target?: string;
type?: string;
targets?: string[];
type?: DropOrder;
}

export const store = reactive<DraggableStore>({
group: undefined,
source: undefined,
target: undefined,
targets: undefined,
type: undefined
});
20 changes: 17 additions & 3 deletions src/app/pages/shared/BudgetGroup.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,14 @@

<!-- Budgets -->
<template v-for="(budget, index) of group.budgets" :key="budget.id + index">
<Draggable :id="budget.id" name="budget-group" :text="buildDraggableText" @drop="reorder" />
<Draggable
:id="budget.id"
:target="['budget-group', 'budget-groups']"
name="budget-group"
:text="buildDraggableText"
@drop="reorder"
/>

<Button color="dimmed" icon="close-circle" textual @click="removeBudget(budget.id)" />

<span :class="$style.header">
Expand Down Expand Up @@ -68,8 +75,9 @@ import { useI18n } from 'vue-i18n';
import Button from '@components/base/button/Button.vue';
import Currency from '@components/base/currency/Currency.vue';
import CurrencyCell from '@components/base/currency-cell/CurrencyCell.vue';
import { ReorderEvent, DraggableEvent } from '@components/base/draggable/Draggable.types';
import { ReorderEvent } from '@components/base/draggable/Draggable.types';
import Draggable from '@components/base/draggable/Draggable.vue';
import { DraggableStore } from '@components/base/draggable/store';
import TextCell from '@components/base/text-cell/TextCell.vue';
import { useDataStore } from '@store/state';
import { BudgetGroup } from '@store/state/types';
Expand All @@ -83,6 +91,7 @@ const {
moveBudget,
removeBudgetGroup,
getBudget,
getBudgetGroup,
addBudget,
setBudgetName,
setBudgetGroupName,
Expand All @@ -108,9 +117,10 @@ const totals = computed(() => {
const totalAmount = computed(() => sum(totals.value));
const averageAmount = computed(() => average(totals.value));
const buildDraggableText = (store: DraggableEvent) => {
const buildDraggableText = (store: DraggableStore) => {
const [srcGroup, src] = store.source ? getBudget(store.source) ?? [] : [];
const [dstGroup, dst] = store.target ? getBudget(store.target) ?? [] : [];
const otherDist = store.target ? getBudgetGroup(store.target) : undefined;
if (src && srcGroup) {
if (dst && dstGroup) {
Expand All @@ -123,6 +133,10 @@ const buildDraggableText = (store: DraggableEvent) => {
: t('shared.append', { from: srcLabel, to: dstLabel });
}
if (otherDist) {
return t('shared.moveInto', { from: src.name, to: otherDist.name });
}
return t('shared.move', { from: src.name });
}
};
Expand Down
26 changes: 21 additions & 5 deletions src/app/pages/shared/BudgetGroups.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,13 @@

<!-- Body -->
<template v-for="group of groups" :key="group.id">
<Draggable :id="group.id" :text="buildDraggableText" name="budget-groups" @drop="reorder" />
<Draggable
:id="group.id"
:icon="buildDraggableIcon"
:text="buildDraggableText"
name="budget-groups"
@drop="reorder"
/>
<BudgetGroup :group="group" />
</template>

Expand All @@ -41,8 +47,10 @@ import { computed } from 'vue';
import { useI18n } from 'vue-i18n';
import Button from '@components/base/button/Button.vue';
import Currency from '@components/base/currency/Currency.vue';
import { DraggableEvent, ReorderEvent } from '@components/base/draggable/Draggable.types';
import { ReorderEvent } from '@components/base/draggable/Draggable.types';
import Draggable from '@components/base/draggable/Draggable.vue';
import { DraggableStore } from '@components/base/draggable/store';
import { AppIcon } from '@components/base/icon/Icon.types';
import { useMonthNames } from '@composables';
import { useDataStore } from '@store/state';
import BudgetGroup from './BudgetGroup.vue';
Expand All @@ -51,7 +59,7 @@ const props = defineProps<{
type: 'expenses' | 'income';
}>();
const { state, moveBudgetGroup, addBudgetGroup, getBudgetGroup, isCurrentMonth } = useDataStore();
const { state, moveBudgetGroup, moveBudgetIntoGroup, addBudgetGroup, getBudgetGroup, isCurrentMonth } = useDataStore();
const { t } = useI18n();
const groups = computed(() => state[props.type]);
Expand All @@ -71,7 +79,11 @@ const totals = computed(() => {
return totals;
});
const buildDraggableText = (store: DraggableEvent) => {
const buildDraggableIcon = (store: DraggableStore): AppIcon | undefined => {
return store.group === 'budget-group' ? 'skip-down-line' : undefined;
};
const buildDraggableText = (store: DraggableStore) => {
const src = store.source ? getBudgetGroup(store.source) : undefined;
const dst = store.target ? getBudgetGroup(store.target) : undefined;
Expand All @@ -87,7 +99,11 @@ const buildDraggableText = (store: DraggableEvent) => {
};
const reorder = (evt: ReorderEvent) => {
moveBudgetGroup(evt.source, evt.target, evt.type === 'after');
if (evt.group === 'budget-group') {
moveBudgetIntoGroup(evt.source, evt.target);
} else {
moveBudgetGroup(evt.source, evt.target, evt.type === 'after');
}
};
</script>

Expand Down
1 change: 1 addition & 0 deletions src/i18n/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@
"append": "Verschiebe „{from}“ nach „{to}“",
"average": "Durchschnitt",
"move": "Verschiebe „{from}“",
"moveInto": "Verschiebe „{from}“ in „{to}“",
"prepend": "Verschiebe „{from}“ vor „{to}“",
"total": "Gesamt",
"totals": "Summen",
Expand Down
1 change: 1 addition & 0 deletions src/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@
"append": "Move “{from}” after “{to}”",
"average": "Average",
"move": "Move “{from}”",
"moveInto": "Move “{from}” into “{to}”",
"prepend": "Move “{from}” before “{to}”",
"total": "Total",
"totals": "Totals",
Expand Down
12 changes: 12 additions & 0 deletions src/store/state/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ interface Store {
addBudget(group: string): void;

moveBudget(id: string, target: string, after?: boolean): void;
moveBudgetIntoGroup(id: string, target: string): void;
moveBudgetGroup(id: string, target: string, after?: boolean): void;

removeBudgetGroup(id: string): void;
Expand Down Expand Up @@ -248,6 +249,17 @@ export const createDataStore = (storage?: Storage): Store => {
moveInArrays(budgets, id, target, after);
},

moveBudgetIntoGroup(id: string, target: string) {
const allGroups = groups();
const sourceGroup = allGroups.find((g) => g.budgets.find((v) => v.id === id));
const budgetIndex = sourceGroup?.budgets.findIndex((v) => v.id === id) ?? -1;
const targetGroup = allGroups.find((g) => g.id === target);

if (targetGroup && sourceGroup && budgetIndex !== -1) {
targetGroup.budgets.push(...sourceGroup.budgets.splice(budgetIndex, 1));
}
},

moveBudgetGroup(id: string, target: string, after?: boolean) {
const { income, expenses } = getCurrentYear();
moveInArrays([income, expenses], id, target, after);
Expand Down

0 comments on commit 5c2433c

Please sign in to comment.