diff --git a/app/web/src/api/sdf/dal/change_set.ts b/app/web/src/api/sdf/dal/change_set.ts index 529d988057..4990cea70e 100644 --- a/app/web/src/api/sdf/dal/change_set.ts +++ b/app/web/src/api/sdf/dal/change_set.ts @@ -1,12 +1,13 @@ // TODO: remove import { ActionInstance } from "@/store/actions.store"; +import { UserId } from "@/store/auth.store"; export enum ChangeSetStatus { Open = "Open", - Closed = "Closed", - Abandoned = "Abandoned", Applied = "Applied", Failed = "Failed", + Closed = "Closed", + Abandoned = "Abandoned", } export type ChangeSetId = string; @@ -16,6 +17,8 @@ export interface ChangeSet { name: string; actions: ActionInstance[]; status: ChangeSetStatus; + appliedByUserId?: UserId; + appliedAt?: IsoDateString; } export type ChangeStatus = "added" | "deleted" | "modified" | "unmodified"; diff --git a/app/web/src/components/ApplyChangeSetButton.vue b/app/web/src/components/ApplyChangeSetButton.vue index ba41475ade..838a87a971 100644 --- a/app/web/src/components/ApplyChangeSetButton.vue +++ b/app/web/src/components/ApplyChangeSetButton.vue @@ -42,6 +42,10 @@ @click="applyChangeSet" /> + @@ -55,10 +59,14 @@ import ActionSprite from "@/components/ActionSprite.vue"; import { useChangeSetsStore } from "@/store/change_sets.store"; import { useStatusStore } from "@/store/status.store"; import { useActionsStore } from "@/store/actions.store"; +import ChangeSetApplyVotingPopover from "./layout/navbar/ChangeSetApplyVotingPopover.vue"; const createModalRef = ref | null>(null); +const changeSetApplyVotingPopoverRef = ref(); const maybeOpenModal = () => { + // TODO(Wendy) - voting Popover needs to be put into this flow + if (!changeSetsStore.selectedChangeSet?.actions?.length) { applyChangeSet(); } else { @@ -66,6 +74,17 @@ const maybeOpenModal = () => { } }; +// TODO(Wendy) - implement the code that invokes this Popover when appropriate +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const openVotingPopover = () => { + const applyButtonRect = applyButtonRef.value.$el.getBoundingClientRect(); + + changeSetApplyVotingPopoverRef.value.openAt({ + x: applyButtonRect.x + applyButtonRect.width / 2 - 16, + y: applyButtonRect.bottom, + }); +}; + const changeSetsStore = useChangeSetsStore(); const actionsStore = useActionsStore(); const router = useRouter(); diff --git a/app/web/src/components/Popover.vue b/app/web/src/components/Popover.vue index 529d75c7ed..bc7b520f19 100644 --- a/app/web/src/components/Popover.vue +++ b/app/web/src/components/Popover.vue @@ -3,7 +3,13 @@
@@ -42,6 +48,9 @@ const props = defineProps({ // go on top of all elements, including the navbar and statusbar onTopOfEverything: { type: Boolean }, + + // act like a Modal that cannot be closed + noExit: { type: Boolean }, }); const internalRef = ref(); @@ -52,8 +61,11 @@ const anchorPos = ref<{ x: number; y: number }>(); function onWindowMousedown(e: MouseEvent) { requestAnimationFrame(() => { - if (e.target instanceof Element && internalRef.value?.contains(e.target)) { - return; // Don't close on click inside popover + if ( + (e.target instanceof Element && internalRef.value?.contains(e.target)) || + props.noExit + ) { + return; // Don't close on click inside popover or if noExit is set } close(); @@ -62,6 +74,7 @@ function onWindowMousedown(e: MouseEvent) { function onKeyboardEvent(e: KeyboardEvent) { if (e.key === "Escape") { + if (props.noExit) return; close(); } } @@ -107,6 +120,15 @@ function open(e?: MouseEvent, anchorToMouse?: boolean) { nextFrame(finishOpening); } +function openAt(pos: { x: number; y: number }) { + anchorPos.value = pos; + + isRepositioning.value = true; + isOpen.value = true; + + nextFrame(finishOpening); +} + function finishOpening() { startListening(); readjustPosition(); @@ -198,5 +220,5 @@ onUnmounted(() => { window.removeEventListener("resize", closeOnResize); }); -defineExpose({ open, close, isOpen }); +defineExpose({ open, openAt, close, isOpen }); diff --git a/app/web/src/components/layout/navbar/ChangeSetAppliedPopover.vue b/app/web/src/components/layout/navbar/ChangeSetAppliedPopover.vue new file mode 100644 index 0000000000..88067ab716 --- /dev/null +++ b/app/web/src/components/layout/navbar/ChangeSetAppliedPopover.vue @@ -0,0 +1,68 @@ + + + diff --git a/app/web/src/components/layout/navbar/ChangeSetApplyVotingPopover.vue b/app/web/src/components/layout/navbar/ChangeSetApplyVotingPopover.vue new file mode 100644 index 0000000000..464ee49e44 --- /dev/null +++ b/app/web/src/components/layout/navbar/ChangeSetApplyVotingPopover.vue @@ -0,0 +1,322 @@ + + + diff --git a/app/web/src/components/layout/navbar/ChangeSetPanel.vue b/app/web/src/components/layout/navbar/ChangeSetPanel.vue index 16b740a515..c3b6e8f08b 100644 --- a/app/web/src/components/layout/navbar/ChangeSetPanel.vue +++ b/app/web/src/components/layout/navbar/ChangeSetPanel.vue @@ -5,6 +5,7 @@
+
@@ -98,11 +103,15 @@ import { Modal, useValidatedInputGroup, } from "@si/vue-lib/design-system"; +import { storeToRefs } from "pinia"; import { nilId } from "@/utils/nilId"; import { useChangeSetsStore } from "@/store/change_sets.store"; import { useFixesStore } from "@/store/fixes.store"; import Wipe from "../../Wipe.vue"; +import ChangeSetAppliedPopover from "./ChangeSetAppliedPopover.vue"; +const dropdownRef = ref(); +const changeSetAppliedRef = ref(); const wipeRef = ref>(); const changeSetsStore = useChangeSetsStore(); @@ -187,4 +196,21 @@ function openCreateModal() { createChangeSetName.value = changeSetsStore.getGeneratedChangesetName(); createModalRef.value?.open(); } + +const openChangeSetAppliedPopover = () => { + const dropDownRect = dropdownRef.value.inputRef.getBoundingClientRect(); + + changeSetAppliedRef.value.openAt({ + x: dropDownRect.x + dropDownRect.width / 2 - 16, + y: dropDownRect.bottom, + }); +}; + +// TODO(Wendy) - Eventually we should replace this to not be reliant on the WSEvent +const { postApplyActor } = storeToRefs(changeSetsStore); +watch(postApplyActor, () => { + if (postApplyActor.value !== null) { + openChangeSetAppliedPopover(); + } +}); diff --git a/app/web/src/components/layout/navbar/Collaborators.vue b/app/web/src/components/layout/navbar/Collaborators.vue index 184e270805..b6963264cf 100644 --- a/app/web/src/components/layout/navbar/Collaborators.vue +++ b/app/web/src/components/layout/navbar/Collaborators.vue @@ -8,6 +8,7 @@ ) " > + +
+ + @@ -96,6 +101,7 @@ import { usePresenceStore } from "@/store/presence.store"; import { useChangeSetsStore } from "@/store/change_sets.store"; import UserIcon from "./UserIcon.vue"; import UserCard from "./UserCard.vue"; +import ChangeSetApplyVotingPopover from "./ChangeSetApplyVotingPopover.vue"; const presenceStore = usePresenceStore(); const changeSetsStore = useChangeSetsStore(); @@ -112,7 +118,7 @@ const moreUsersPopoverRef = ref(); const moreUsersButtonRef = ref(); const users = computed(() => { - const list = []; + const list = [] as UserInfo[]; for (const user of _.values(presenceStore.usersById)) { list.push({ name: user.name, @@ -123,6 +129,19 @@ const users = computed(() => { }); } + list.push({ + name: "test user", + color: "red", + status: "active", + changeset: changeSetsStore.selectedChangeSetId || undefined, + }); + list.push({ + name: "test user 2", + color: "green", + status: "active", + changeset: changeSetsStore.selectedChangeSetId || undefined, + }); + return list; }); @@ -257,4 +276,16 @@ const filteredUsers = computed(() => { ); } else return sortedUsers.value; }); + +// Code for the ChangeSetApplyVotingPopover +// TODO(Wendy) - Currently this is triggered by clicking on a user, should be tied to Apply Changes via the store + +const changeSetApplyVotingPopoverRef = ref(); + +// TODO(Wendy) - invoke this function when it's time to open the voting Popover for a particular user +// Also probably need to have functionality to pull a user to the front of the list if they aren't visible on the bar +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const openChangeSetApplyVotingPopover = (e: MouseEvent) => { + changeSetApplyVotingPopoverRef.value.open(e); +}; diff --git a/app/web/src/components/layout/navbar/NavbarPanelRight.vue b/app/web/src/components/layout/navbar/NavbarPanelRight.vue index 2b975e9752..6e222e9750 100644 --- a/app/web/src/components/layout/navbar/NavbarPanelRight.vue +++ b/app/web/src/components/layout/navbar/NavbarPanelRight.vue @@ -10,55 +10,22 @@ - - - - - + diff --git a/app/web/src/components/layout/navbar/ProfileButton.vue b/app/web/src/components/layout/navbar/ProfileButton.vue new file mode 100644 index 0000000000..2c99815194 --- /dev/null +++ b/app/web/src/components/layout/navbar/ProfileButton.vue @@ -0,0 +1,41 @@ + + + diff --git a/app/web/src/components/layout/navbar/UserCard.vue b/app/web/src/components/layout/navbar/UserCard.vue index 17fa7bf05c..bc618a7285 100644 --- a/app/web/src/components/layout/navbar/UserCard.vue +++ b/app/web/src/components/layout/navbar/UserCard.vue @@ -1,14 +1,22 @@