Skip to content
This repository was archived by the owner on Feb 6, 2026. It is now read-only.

Commit 9bb82c9

Browse files
authored
Merge pull request #8314 from systeminit/wendy/apply-change-set-button-tooltip
Better UX when Apply Change Set button is disabled, navbar improvements
2 parents 71cb778 + 19ab82e commit 9bb82c9

9 files changed

Lines changed: 227 additions & 97 deletions

File tree

app/web/src/App.vue

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,15 @@ watch([selectedWorkspace, user], () => {
171171
max-width: 380px;
172172
}
173173
174+
.v-popper--theme-apply-button > .v-popper__wrapper > .v-popper__inner {
175+
max-width: 390px;
176+
font-size: 13px;
177+
line-height: 1.25;
178+
background-color: #262626 !important;
179+
border: 1px solid #5a5a5a !important;
180+
border-radius: 0.25rem !important;
181+
}
182+
174183
.v-popper--theme-attribute-docs > .v-popper__wrapper > .v-popper__inner {
175184
border-radius: 0.5rem !important;
176185
max-width: 420px;

app/web/src/newhotness/ApplyChangeSetButton.vue

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,16 @@
33
<NewButton
44
ref="applyButtonRef"
55
tone="action"
6-
label="Apply Change Set"
6+
:label="squish ? 'Apply' : 'Apply Change Set'"
77
:pill="proposedActions.length"
88
class="ml-2xs mr-xs"
9-
loadingText="Applying Changes"
9+
:loadingText="squish ? 'Applying' : 'Applying Changes'"
1010
:loading="applyInFlight"
11-
:disabled="!ctx.queriesEnabled.value"
11+
:disabled="buttonDisabled"
12+
:icon="buttonDisabled ? 'loader' : undefined"
13+
:tooltip="buttonTooltipText"
14+
tooltipPlacement="bottom-end"
15+
tooltipTheme="apply-button"
1216
@click="openApplyChangeSetModal"
1317
/>
1418
<ApplyChangeSetModal ref="applyChangeSetModalRef" votingKind="merge" :actions="proposedActions" />
@@ -26,6 +30,11 @@ import { bifrost, useMakeArgs, useMakeKey } from "@/store/realtime/heimdall";
2630
import ApplyChangeSetModal from "./ApplyChangeSetModal.vue";
2731
import { useApplyChangeSet } from "./logic_composables/change_set";
2832
import { useContext } from "./logic_composables/context";
33+
import { useStatus } from "./logic_composables/status";
34+
35+
const props = defineProps<{
36+
squish?: boolean;
37+
}>();
2938
3039
const ctx = useContext();
3140
@@ -70,4 +79,19 @@ const actions = computed(() => actionsRaw.data.value?.actions ?? []);
7079
const proposedActions = computed(() =>
7180
actions.value.filter((action) => action.originatingChangeSetId === ctx.changeSetId.value),
7281
);
82+
83+
const status = useStatus();
84+
const buttonDisabled = computed(() => {
85+
// Need a change set to apply...
86+
if (!ctx.changeSet.value) return true;
87+
88+
// If we are on HEAD, we cannot apply.
89+
if (ctx.onHead.value) return true;
90+
91+
// If the change set is churning on work on flight, do not allow the ability to apply.
92+
if (status[ctx.changeSet.value.id] === "syncing") return true;
93+
94+
return !ctx.queriesEnabled.value;
95+
});
96+
const buttonTooltipText = computed(() => buttonDisabled.value ? "We are updating this change set based on recent changes. Applying will be available once the updates are finished." : undefined);
7397
</script>

app/web/src/newhotness/nav/NavbarPanelRight.vue

Lines changed: 19 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -5,40 +5,11 @@
55
<Notifications :changeSetsNeedingApproval="changeSetsNeedingApproval" />
66
</template>
77

8-
<template v-if="featureFlagsStore.SQLITE_TOOLS">
9-
<NavbarButton icon="odin" size="sm">
10-
<template #dropdownContent>
11-
<DropdownMenuItem
12-
v-if="props.workspaceId && props.changeSetId"
13-
icon="niflheim"
14-
label="Re-do Cold Start"
15-
@click="heimdall.muspelheim(props.workspaceId, true)"
16-
/>
17-
<DropdownMenuItem
18-
v-if="props.workspaceId && props.changeSetId"
19-
icon="refresh"
20-
label="Rebuild Index"
21-
@click="rebuild(props.workspaceId, props.changeSetId)"
22-
/>
23-
<DropdownMenuItem icon="mjolnir" label="Throw Hammer" @click="() => modalRef.open()" />
24-
<DropdownMenuItem
25-
v-if="props.changeSetId"
26-
icon="odin"
27-
label="Log Sqlite"
28-
@click="() => props.changeSetId && heimdall.odin(props.changeSetId)"
29-
/>
30-
<DropdownMenuItem icon="trash" label="Bobby Drop Tables" @click="() => heimdall.bobby()" />
31-
<DropdownMenuItem
32-
v-if="props.workspaceId && props.changeSetId"
33-
icon="trash"
34-
label="Ragnarok"
35-
@click="
36-
() => heimdall.ragnarok(props.workspaceId!, props.changeSetId!)
37-
"
38-
/>
39-
</template>
40-
</NavbarButton>
41-
</template>
8+
<SQLiteToolsButton
9+
v-if="featureFlagsStore.SQLITE_TOOLS && !collapse"
10+
:changeSetId="changeSetId"
11+
:workspaceId="workspaceId"
12+
/>
4213

4314
<template v-if="!collapse">
4415
<NavbarButton tooltipText="Documentation" icon="question-circle" externalLinkTo="https://docs.systeminit.com/" />
@@ -52,35 +23,31 @@
5223
<WorkspaceSettingsMenu />
5324
</template>
5425

55-
<ProfileButton :showTopLevelMenuItems="collapse" />
56-
57-
<ApplyChangeSetButton v-if="!invalidWorkspace" />
26+
<ProfileButton
27+
:showTopLevelMenuItems="collapse"
28+
:changeSetId="changeSetId"
29+
:workspaceId="workspaceId"
30+
/>
5831

59-
<Modal ref="modalRef" title="Throw">
60-
<Stack>
61-
<VormInput v-model="entityKind" label="Entity Kind" type="text" />
62-
<VormInput v-model="entityId" label="ID" type="text" />
63-
<NewButton label="Mjolnir!" tone="action" @click="hammer" />
64-
</Stack>
65-
</Modal>
32+
<ApplyChangeSetButton
33+
v-if="!invalidWorkspace"
34+
:squish="windowWidthReactive < 820"
35+
/>
6636
</div>
6737
</template>
6838

6939
<script lang="ts" setup>
70-
import { computed, onBeforeUnmount, onMounted, ref } from "vue";
71-
import { DropdownMenuItem, VormInput, Modal, Stack, NewButton } from "@si/vue-lib/design-system";
72-
import { URLPattern, describePattern } from "@si/vue-lib";
40+
import { computed } from "vue";
7341
import { useFeatureFlagsStore } from "@/store/feature_flags.store";
74-
import * as heimdall from "@/store/realtime/heimdall";
75-
import { sdfApiInstance } from "@/store/apis.web";
76-
import { ChangeSetId, ChangeSet } from "@/api/sdf/dal/change_set";
77-
import { EntityKind } from "@/workers/types/entity_kind_types";
42+
import { ChangeSet } from "@/api/sdf/dal/change_set";
7843
import ApplyChangeSetButton from "@/newhotness/ApplyChangeSetButton.vue";
7944
import NavbarButton from "@/components/layout/navbar/NavbarButton.vue";
8045
import Collaborators from "./Collaborators.vue";
8146
import Notifications from "./Notifications.vue";
8247
import WorkspaceSettingsMenu from "./WorkspaceSettingsMenu.vue";
8348
import ProfileButton from "./ProfileButton.vue";
49+
import SQLiteToolsButton from "./SQLiteToolsButton.vue";
50+
import { windowWidthReactive } from "../logic_composables/emitters";
8451
8552
const props = defineProps<{
8653
changeSetId: string;
@@ -90,43 +57,5 @@ const props = defineProps<{
9057
}>();
9158
9259
const featureFlagsStore = useFeatureFlagsStore();
93-
const modalRef = ref();
94-
const entityId = ref("");
95-
const entityKind = ref("");
96-
97-
const windowWidth = ref(window.innerWidth);
98-
const collapse = computed(() => windowWidth.value < 1200);
99-
100-
const windowResizeHandler = () => {
101-
windowWidth.value = window.innerWidth;
102-
};
103-
104-
const rebuild = (workspaceId: string, changeSetId: ChangeSetId) => {
105-
const pattern = [
106-
"v2",
107-
"workspaces",
108-
{ workspaceId },
109-
"change-sets",
110-
{ changeSetId },
111-
"index",
112-
"rebuild",
113-
] as URLPattern;
114-
const [url] = describePattern(pattern);
115-
sdfApiInstance.post(url);
116-
};
117-
118-
const hammer = () => {
119-
if (props.workspaceId && props.changeSetId) {
120-
heimdall.mjolnir(props.workspaceId, props.changeSetId, entityKind.value as EntityKind, entityId.value);
121-
modalRef.value.close();
122-
}
123-
};
124-
125-
onMounted(() => {
126-
windowResizeHandler();
127-
window.addEventListener("resize", windowResizeHandler);
128-
});
129-
onBeforeUnmount(() => {
130-
window.removeEventListener("resize", windowResizeHandler);
131-
});
60+
const collapse = computed(() => windowWidthReactive.value < 1200);
13261
</script>

app/web/src/newhotness/nav/ProfileButton.vue

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
<!-- eslint-disable vue/no-multiple-template-root -->
12
<template>
23
<NavbarButton ref="navbarButtonRef" class="flex-none py-xs" tooltipText="Profile">
34
<template #default="{ open, hovered }">
@@ -38,6 +39,12 @@
3839
<DropdownMenuItem icon="question-circle" label="Documentation" href="https://docs.systeminit.com/" />
3940
<DropdownMenuItem icon="logo-discord" label="Discord Community" href="https://discord.gg/system-init" />
4041
<DropdownMenuItem icon="settings" label="Workspace Settings" @click="openSettings" />
42+
<DropdownMenuItem
43+
v-if="featureFlagsStore.SQLITE_TOOLS"
44+
icon="odin"
45+
label="SQLite Tools"
46+
@click="openSQLiteTools"
47+
/>
4148
</template>
4249
<DropdownMenuItem class="profile-dropdown-menu-logout" icon="logout" label="Logout" linkToNamedRoute="logout" />
4350
</template>
@@ -48,6 +55,12 @@
4855
@openExportModal="exportModalRef?.open()"
4956
@openIntegrationsModal="integrationsModalRef?.open()"
5057
/>
58+
<SQLiteToolsMenuItems
59+
v-if="secondaryMenu === 'sqlite'"
60+
:changeSetId="changeSetId"
61+
:workspaceId="workspaceId"
62+
@hammer="hammerModalRef.open"
63+
/>
5164
<template v-else>
5265
<DropdownMenuItem checkable :checked="!userOverrideTheme" icon="bolt" @select="userOverrideTheme = null">
5366
System theme
@@ -71,6 +84,11 @@
7184
</template>
7285
</template>
7386
</NavbarButton>
87+
<SQLiteToolsHammerModal
88+
ref="hammerModalRef"
89+
:changeSetId="changeSetId"
90+
:workspaceId="workspaceId"
91+
/>
7492
</template>
7593

7694
<script lang="ts" setup>
@@ -87,14 +105,20 @@ import NavbarButton from "@/components/layout/navbar/NavbarButton.vue";
87105
import UserIcon from "@/components/layout/navbar/UserIcon.vue";
88106
import WorkspaceSettingsMenuItems from "@/components/layout/navbar/WorkspaceSettingsMenuItems.vue";
89107
import { useContext } from "../logic_composables/context";
108+
import SQLiteToolsMenuItems from "./SQLiteToolsMenuItems.vue";
109+
import SQLiteToolsHammerModal from "./SQLiteToolsHammerModal.vue";
90110
91111
const importModalRef = ref<InstanceType<typeof WorkspaceImportModal>>();
92112
const exportModalRef = ref<InstanceType<typeof WorkspaceExportModal>>();
93113
const integrationsModalRef = ref<InstanceType<typeof WorkspaceIntegrationsModal>>();
94114
95-
defineProps({
96-
showTopLevelMenuItems: { type: Boolean },
97-
});
115+
defineProps<{
116+
showTopLevelMenuItems?: boolean;
117+
changeSetId: string;
118+
workspaceId: string;
119+
}>();
120+
121+
const hammerModalRef = ref();
98122
99123
const featureFlagsStore = useFeatureFlagsStore();
100124
const ctx = useContext();
@@ -113,7 +137,7 @@ const themeIcon = computed(() => {
113137
else return "bolt";
114138
});
115139
116-
const secondaryMenu = ref<"theme" | "settings">("theme");
140+
const secondaryMenu = ref<"theme" | "settings" | "sqlite">("theme");
117141
118142
const changeTheme = () => {
119143
secondaryMenu.value = "theme";
@@ -124,4 +148,9 @@ const openSettings = () => {
124148
secondaryMenu.value = "settings";
125149
navbarButtonRef.value?.openSecondary();
126150
};
151+
152+
const openSQLiteTools = () => {
153+
secondaryMenu.value = "sqlite";
154+
navbarButtonRef.value?.openSecondary();
155+
};
127156
</script>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<!-- eslint-disable vue/no-multiple-template-root -->
2+
<template>
3+
<NavbarButton icon="odin" size="sm">
4+
<template #dropdownContent>
5+
<SQLiteToolsMenuItems
6+
:changeSetId="changeSetId"
7+
:workspaceId="workspaceId"
8+
@hammer="hammerModalRef.open"
9+
/>
10+
</template>
11+
</NavbarButton>
12+
<SQLiteToolsHammerModal
13+
ref="hammerModalRef"
14+
:changeSetId="changeSetId"
15+
:workspaceId="workspaceId"
16+
/>
17+
</template>
18+
19+
<script setup lang="ts">
20+
import NavbarButton from '@/components/layout/navbar/NavbarButton.vue';
21+
import SQLiteToolsMenuItems from "./SQLiteToolsMenuItems.vue";
22+
import SQLiteToolsHammerModal from './SQLiteToolsHammerModal.vue';
23+
import { ref } from 'vue';
24+
25+
const props = defineProps<{
26+
changeSetId: string;
27+
workspaceId: string;
28+
}>();
29+
30+
const hammerModalRef = ref();
31+
</script>
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<template>
2+
<Modal ref="modalRef" title="Throw">
3+
<Stack>
4+
<VormInput v-model="entityKind" label="Entity Kind" type="text" />
5+
<VormInput v-model="entityId" label="ID" type="text" />
6+
<NewButton label="Mjolnir!" tone="action" @click="hammer" />
7+
</Stack>
8+
</Modal>
9+
</template>
10+
11+
<script setup lang="ts">
12+
import * as heimdall from "@/store/realtime/heimdall";
13+
import { Modal, NewButton, Stack, VormInput } from '@si/vue-lib/design-system';
14+
import { EntityKind } from "@/workers/types/entity_kind_types";
15+
import { ref } from "vue";
16+
17+
const props = defineProps<{
18+
changeSetId: string;
19+
workspaceId: string;
20+
}>();
21+
22+
const modalRef = ref();
23+
const entityId = ref("");
24+
const entityKind = ref("");
25+
26+
const hammer = () => {
27+
if (props.workspaceId && props.changeSetId) {
28+
heimdall.mjolnir(props.workspaceId, props.changeSetId, entityKind.value as EntityKind, entityId.value);
29+
modalRef.value.close();
30+
}
31+
};
32+
33+
const open = () => {
34+
modalRef.value.open();
35+
};
36+
37+
defineExpose({
38+
open
39+
});
40+
</script>

0 commit comments

Comments
 (0)