Skip to content

Commit

Permalink
use ids in editable list items allowing straight forward views backen…
Browse files Browse the repository at this point in the history
…d sync
  • Loading branch information
rouk1 committed Oct 2, 2024
1 parent 26a6ea9 commit 3408ccb
Show file tree
Hide file tree
Showing 7 changed files with 70 additions and 27 deletions.
12 changes: 10 additions & 2 deletions frontend/src/components/EditableList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export interface EditableListItemModel {
name: string;
icon?: string;
isUnnamed?: boolean;
id: string;
}
export interface EditableListAction {
Expand All @@ -22,12 +23,18 @@ const props = defineProps<{
const emit = defineEmits<{
action: [payload: string, item: EditableListItemModel];
select: [name: string];
rename: [oldName: string, newName: string, item: EditableListItemModel];
}>();
const items = defineModel<EditableListItemModel[]>("items", { required: true });
const onAction = (payload: string, item: EditableListItemModel) => {
function onAction(payload: string, item: EditableListItemModel) {
emit("action", payload, item);
};
}
function onRename(oldName: string, newName: string, item: EditableListItemModel) {
emit("rename", oldName, newName, item);
}
</script>

<template>
Expand All @@ -39,6 +46,7 @@ const onAction = (payload: string, item: EditableListItemModel) => {
:actions="props.actions"
@action="onAction($event, item)"
@select="emit('select', item.name)"
@rename="onRename"
/>
</div>
</template>
Expand Down
9 changes: 6 additions & 3 deletions frontend/src/components/EditableListItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,19 @@ const props = defineProps<{ actions?: EditableListAction[] }>();
const emit = defineEmits<{
action: [payload: string];
select: [name: string];
select: [];
rename: [oldName: string, newName: string, item: EditableListItemModel];
}>();
const item = defineModel<EditableListItemModel>({ required: true });
const label = ref<HTMLSpanElement>();
function onItemNameEdited(e: Event) {
(e.target as HTMLInputElement).blur();
const oldName = item.value.name;
item.value.isUnnamed = false;
item.value.name = (e.target as HTMLSpanElement).textContent ?? "unnamed";
emit("rename", oldName, item.value.name, item.value);
}
onMounted(() => {
Expand All @@ -38,12 +41,12 @@ onMounted(() => {

<template>
<div class="editable-list-item">
<div class="label-container" @click="emit('select', item.name)">
<div class="label-container" @click="emit('select')">
<span class="icon" v-if="item.icon" :class="item.icon" />
<span
class="label"
:contenteditable="item.isUnnamed"
ref="label"
:contenteditable="item.isUnnamed"
@keydown.enter="onItemNameEdited"
>
{{ item.name }}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/services/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export async function deleteView(view: string) {
const r = await fetch(`${BASE_URL}/project/views/${view}`, {
method: "DELETE",
});
checkResponseStatus(r, 200);
checkResponseStatus(r, 202);
} catch (error) {
reportError(getErrorMessage(error));
}
Expand Down
5 changes: 4 additions & 1 deletion frontend/src/stores/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export const useProjectStore = defineStore("project", () => {
stopBackendPolling();
const realKey = key.replace(" (self)", "");
if (!isKeyDisplayed(view, realKey)) {
views.value[view].push(realKey);
views.value[view] = [...views.value[view], realKey];
await persistView(view, views.value[view]);
}
await startBackendPolling();
Expand Down Expand Up @@ -210,6 +210,9 @@ export const useProjectStore = defineStore("project", () => {
* @param name the name of the view to delete
*/
async function deleteView(name: string) {
if (name === currentView.value) {
currentView.value = null;
}
delete views.value[name];
await deleteViewApi(name);
}
Expand Down
40 changes: 25 additions & 15 deletions frontend/src/views/ComponentsView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -94,22 +94,27 @@ const fileTreeNodes: TreeAccordionNode[] = [
];
const items = ref<EditableListItemModel[]>([
{ name: "Item 1", icon: "icon-plot" },
{ name: "Item 2" },
{ name: "Item 3", icon: "icon-error" },
{ name: "Item 4", icon: "icon-error" },
{ name: "Item 5", icon: "icon-error" },
{ name: "Item 6", icon: "icon-error" },
{ name: "Item 7", icon: "icon-error" },
{ name: "Item 8", icon: "icon-error" },
{ name: "Item 9", icon: "icon-error" },
{ name: "Item 10", icon: "icon-error" },
{ name: "Item 11", icon: "icon-error" },
{ name: "Item 12", icon: "icon-error" },
{ name: "Item 1", icon: "icon-plot", id: generateRandomId() },
{ name: "Item 2", id: generateRandomId() },
{ name: "Item 3", icon: "icon-error", id: generateRandomId() },
{ name: "Item 4", icon: "icon-error", id: generateRandomId() },
{ name: "Item 5", icon: "icon-error", id: generateRandomId() },
{ name: "Item 6", icon: "icon-error", id: generateRandomId() },
{ name: "Item 7", icon: "icon-error", id: generateRandomId() },
{ name: "Item 8", icon: "icon-error", id: generateRandomId() },
{ name: "Item 9", icon: "icon-error", id: generateRandomId() },
{ name: "Item 10", icon: "icon-error", id: generateRandomId() },
{ name: "Item 11", icon: "icon-error", id: generateRandomId() },
{ name: "Item 12", icon: "icon-error", id: generateRandomId() },
]);
function onAddToEditableListAction() {
items.value.unshift({ name: "Unnamed", icon: "icon-plot", isUnnamed: true });
items.value.unshift({
name: "Unnamed",
icon: "icon-plot",
isUnnamed: true,
id: generateRandomId(),
});
}
function onEditableListAction(action: string, item: EditableListItemModel) {
Expand All @@ -120,7 +125,12 @@ function onEditableListAction(action: string, item: EditableListItemModel) {
break;
case "duplicate": {
const index = items.value.indexOf(item) ?? 0;
items.value.splice(index + 1, 0, { name: "Unnamed", icon: "icon-plot", isUnnamed: true });
items.value.splice(index + 1, 0, {
name: "Unnamed",
icon: "icon-plot",
isUnnamed: true,
id: generateRandomId(),
});
break;
}
case "delete":
Expand Down Expand Up @@ -351,7 +361,7 @@ const lastSelectedItem = ref<string | null>(null);
<div class="header">
Editable List as 2 way data binding... item list is:
<ul>
<li v-for="item in items" :key="item.name">{{ item.name }}</li>
<li v-for="item in items" :key="item.name">{{ item.name }} (id: {{ item.id }})</li>
</ul>
It also emit an event when an item is selected. Last selected item: {{ lastSelectedItem }}
</div>
Expand Down
24 changes: 20 additions & 4 deletions frontend/src/views/ProjectView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@ import SectionHeader from "@/components/SectionHeader.vue";
import SimpleButton from "@/components/SimpleButton.vue";
import TreeAccordion from "@/components/TreeAccordion.vue";
import { fetchShareableBlob } from "@/services/api";
import { saveBlob } from "@/services/utils";
import { generateRandomId, saveBlob } from "@/services/utils";
import { useProjectStore } from "@/stores/project";
const projectStore = useProjectStore();
const isDropIndicatorVisible = ref(false);
const editor = ref<HTMLDivElement>();
const isInFocusMode = ref(false);
const views = ref<EditableListItemModel[]>([]);
let unsavedViewsIds: string[] = [];
async function onShareReport() {
const currentView = projectStore.currentView;
Expand Down Expand Up @@ -68,12 +69,21 @@ function onViewSelected(view: string) {
}
}
async function onViewRenamed(oldName: string, newName: string, item: EditableListItemModel) {
// user can rename an existing view
// and rename a new view that is not known by the backend
if (unsavedViewsIds.includes(item.id)) {
await projectStore.createView(newName);
unsavedViewsIds = unsavedViewsIds.filter((id) => id !== item.id);
} else {
await projectStore.renameView(oldName, newName);
}
}
async function onViewsListAction(action: string, item: EditableListItemModel) {
switch (action) {
case "rename": {
item.isUnnamed = true;
// TODO: wait for end of namina ge rename
// await projectStore.renameView(item.name, item.name);
break;
}
case "duplicate": {
Expand All @@ -83,7 +93,9 @@ async function onViewsListAction(action: string, item: EditableListItemModel) {
name: newName,
icon: "icon-new-document",
isUnnamed: true,
id: generateRandomId(),
});
unsavedViewsIds.push(newName);
await projectStore.duplicateView(item.name, newName);
break;
}
Expand All @@ -96,7 +108,9 @@ async function onViewsListAction(action: string, item: EditableListItemModel) {
}
function onAddView() {
views.value.push({ name: "New view", icon: "icon-new-document", isUnnamed: true });
const id = generateRandomId();
views.value.push({ name: "New view", icon: "icon-new-document", isUnnamed: true, id });
unsavedViewsIds.push(id);
}
onMounted(async () => {
Expand All @@ -105,6 +119,7 @@ onMounted(async () => {
name: key,
icon: "icon-new-document",
isUnnamed: false,
id: generateRandomId(),
}));
});
onBeforeUnmount(() => {
Expand Down Expand Up @@ -132,6 +147,7 @@ onBeforeUnmount(() => {
]"
@action="onViewsListAction"
@select="onViewSelected"
@rename="onViewRenamed"
/>
</Simplebar>
<SectionHeader title="Elements" icon="icon-pie-chart" />
Expand Down
5 changes: 4 additions & 1 deletion frontend/tests/services/api.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,19 @@ describe("API Service", () => {
});

it("Can call endpoints", async () => {
mockedFetch.mockResolvedValue(createFetchResponse({}));
mockedFetch.mockResolvedValue(createFetchResponse({}, 200));
const project = await fetchProject();
expect(project).toBeDefined();

mockedFetch.mockResolvedValue(createFetchResponse({}, 201));
const view = await putView("test", []);
expect(view).toBeDefined();

mockedFetch.mockResolvedValue(createFetchResponse({}, 202));
const del = await deleteView("test");
expect(del).toBeUndefined();

mockedFetch.mockResolvedValue(createFetchResponse({}, 200));
const share = await fetchShareableBlob("test");
expect(share).toBeDefined();
});
Expand Down

0 comments on commit 3408ccb

Please sign in to comment.