Skip to content

Commit

Permalink
merge: #2930
Browse files Browse the repository at this point in the history
2930: feat(web,sdf,dal): share code-editor cursor r=paulocsanz a=paulocsanz



Co-authored-by: Paulo Cabral <[email protected]>
  • Loading branch information
si-bors-ng[bot] and paulocsanz authored Nov 1, 2023
2 parents de260a0 + 1e0b1e4 commit 14ff10d
Show file tree
Hide file tree
Showing 8 changed files with 270 additions and 154 deletions.
64 changes: 61 additions & 3 deletions app/web/src/components/CodeEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,12 @@
import { computed, ref, watch } from "vue";
import * as _ from "lodash-es";
import { basicSetup, EditorView } from "codemirror";
import { Compartment, EditorState, StateEffect } from "@codemirror/state";
import {
Compartment,
EditorState,
StateEffect,
EditorSelection,
} from "@codemirror/state";
import {
ViewUpdate,
keymap,
Expand All @@ -46,6 +51,7 @@ import { useTheme, VButton } from "@si/vue-lib/design-system";
import { vim, Vim } from "@replit/codemirror-vim";
import storage from "local-storage-fallback";
import beautify from "js-beautify";
import { UserId, useCursorStore } from "@/store/cursor.store";
import { useChangeSetsStore } from "@/store/change_sets.store";
import {
createTypescriptSource,
Expand All @@ -67,11 +73,29 @@ const emit = defineEmits<{
"update:modelValue": [v: string];
blur: [v: string];
change: [v: string];
explicitSave: [];
close: [];
}>();
const changeSetsStore = useChangeSetsStore();
const cursorStore = useCursorStore();
const cursorEntersDiagram = () => {
cursorStore.setContainer("code-editor", props.id ?? null);
setTimeout(() => {
const selection = view.state.selection.asSingle();
updatePointer({ x: selection.main.head, y: selection.main.head });
}, 200);
};
const cursorLeavesDiagram = () => cursorStore.setContainer(null, null);
const updatePointer = (pos: { x: number; y: number }) => {
if (
cursorStore.container !== "code-editor" ||
cursorStore.containerKey !== props.id
)
return;
cursorStore.updateCursor(pos.x, pos.y);
};
const editorMount = ref(); // div (template ref) where we will mount the editor
let view: EditorView; // instance of the CodeMirror editor
Expand Down Expand Up @@ -366,8 +390,16 @@ const mountEditor = async () => {
view.contentDOM.onblur = () => {
draftValue.value = autoformat(draftValue.value);
cursorLeavesDiagram();
emit("blur", draftValue.value);
};
view.contentDOM.onfocus = () => cursorEntersDiagram();
view.contentDOM.onkeydown = () => {
const selection = view.state.selection.asSingle();
updatePointer({ x: selection.main.head, y: selection.main.head });
manageSelections();
};
manageSelections();
for (const key in window.localStorage) {
if (key.startsWith("code-mirror-state-")) {
Expand All @@ -384,6 +416,32 @@ const mountEditor = async () => {
}
};
const cursorsSelection = ref<Record<UserId, number>>({});
const manageSelections = () => {
if (!view) return;
let selection = view.state.selection.asSingle();
if (selection.ranges.length === 0) {
selection = selection.addRange(EditorSelection.cursor(0), true);
}
for (const cursor of Object.values(cursorStore.cursors)) {
const range = EditorSelection.cursor(cursor.x);
const head = cursorsSelection.value[cursor.userPk];
const index = selection.ranges.findIndex(
(range) => range.head === head && range.anchor === head,
);
if (index !== -1) {
selection = selection.replaceRange(range, index);
} else {
selection = selection.addRange(range, false);
}
cursorsSelection.value[cursor.userPk] = cursor.x;
}
view.dispatch({ selection });
};
watch(() => cursorStore.cursors, manageSelections, { immediate: true });
watch(
[
() => props.typescript,
Expand All @@ -398,7 +456,7 @@ watch(
function onLocalSave() {
draftValue.value = autoformat(draftValue.value);
emitUpdatedValue();
emit("explicitSave");
emit("blur", draftValue.value);
return true; // codemirror needs this when used as a "command"
}
Expand Down
5 changes: 0 additions & 5 deletions app/web/src/components/FuncEditor/FuncEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
v-model="editingFunc"
:typescript="selectedFuncDetails?.types"
@change="updateFuncCode"
@explicitSave="execFunc"
@close="emit('close')"
/>
</template>
Expand Down Expand Up @@ -77,10 +76,6 @@ const updateFuncCode = (code: string) => {
funcStore.updateFuncCode(props.funcId, code);
};
const execFunc = () => {
funcStore.SAVE_AND_EXEC_FUNC(props.funcId);
};
const emit = defineEmits<{
(e: "close"): void;
}>();
Expand Down
111 changes: 9 additions & 102 deletions app/web/src/components/Workspace/WorkspaceModelAndView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
:customConfig="diagramCustomConfig"
:nodes="diagramNodes"
:edges="diagramEdges"
:cursors="cursors"
:cursors="cursorStore.cursors"
@insert-element="onDiagramInsertElement"
@hover-element="onDiagramHoverElement"
@move-element="onDiagramMoveElement"
Expand Down Expand Up @@ -140,7 +140,7 @@

<script lang="ts" setup>
import * as _ from "lodash-es";
import { onBeforeUnmount, computed, onMounted, ref, watch } from "vue";
import { computed, onMounted, ref, watch } from "vue";
import plur from "plur";
import {
Collapsible,
Expand All @@ -159,9 +159,8 @@ import {
} from "@/store/components.store";
import { useFixesStore } from "@/store/fixes.store";
import { useChangeSetsStore } from "@/store/change_sets.store";
import { useRealtimeStore } from "@/store/realtime/realtime.store";
import FixProgressOverlay from "@/components/FixProgressOverlay.vue";
import { useAuthStore } from "@/store/auth.store";
import { useCursorStore } from "@/store/cursor.store";
import GenericDiagram from "../GenericDiagram/GenericDiagram.vue";
import AssetPalette from "../AssetPalette.vue";
import {
Expand All @@ -178,7 +177,6 @@ import {
DiagramEdgeData,
HoverElementEvent,
MovePointerEvent,
DiagramCursorDef,
} from "../GenericDiagram/diagram_types";
import ComponentOutline from "../ComponentOutline/ComponentOutline.vue";
import EdgeDetailsPanel from "../EdgeDetailsPanel.vue";
Expand All @@ -187,108 +185,18 @@ import ComponentCard from "../ComponentCard.vue";
import EdgeCard from "../EdgeCard.vue";
import NoSelectionDetailsPanel from "../NoSelectionDetailsPanel.vue";

const realtimeStore = useRealtimeStore();
const changeSetStore = useChangeSetsStore();
const fixesStore = useFixesStore();
const authStore = useAuthStore();

const MOUSE_REFRESH_RATE = 20;
const mouse = ref<{
x: number | null;
y: number | null;
container: "diagram" | null;
}>({ x: null, y: null, container: null });
const mouseOverDiagram = () => {
if (mouse.value.container === "diagram") return;
cursors.value = {};
mouse.value = { container: "diagram", x: null, y: null };
};
const mouseOutDiagram = () => {
if (mouse.value.container === null) return;
cursors.value = {};
mouse.value = { container: null, x: 0, y: 0 };
};
const cursors = ref<Record<string, DiagramCursorDef>>({});
const streamCursor = _.debounce(() => {
const toDelete = [];
for (const key in cursors.value) {
const cursor = cursors.value[key];
if (!cursor) return;

if (new Date().getTime() - cursor.timestamp.getTime() > 5000) {
toDelete.push(key);
}
}
for (const key of toDelete) {
delete cursors.value[key];
}
const cursorStore = useCursorStore();

if (mouse.value.x === null || mouse.value.y === null) return;
const mouseOverDiagram = () => cursorStore.setContainer("diagram", null);
const mouseOutDiagram = () => cursorStore.setContainer(null, null);

const changeSetPk = changeSetStore.selectedChangeSetId;
if (mouse.value.container === "diagram") {
realtimeStore.sendCursor({
changeSetPk,
container: mouse.value.container,
x: `${mouse.value.x}`,
y: `${mouse.value.y}`,
});
} else {
// Avoids sending the cursor twice if outside of the diagram for now
mouse.value.x = null;
mouse.value.y = null;

realtimeStore.sendCursor({
changeSetPk,
container: null,
x: "0",
y: "0",
});
}
}, MOUSE_REFRESH_RATE);
const interval = setInterval(streamCursor, 4500);
const updatePointer = (pos: MovePointerEvent) => {
if (mouse.value.container === "diagram") {
mouse.value.x = pos.x;
mouse.value.y = pos.y;
}
streamCursor();
if (cursorStore.container !== "diagram" || cursorStore.containerKey !== null)
return;
cursorStore.updateCursor(pos.x, pos.y);
};
watch(
() => changeSetStore.selectedChangeSetId,
(changeSetId) => {
realtimeStore.unsubscribe("workspace-model-and-view");
realtimeStore.subscribe(
"workspace-model-and-view",
`changeset/${changeSetId}`,
{
eventType: "Cursor",
callback: (payload) => {
if (payload.userPk === authStore.user?.pk) return;
if (payload.container !== "diagram") {
delete cursors.value[payload.userPk];
} else {
/* eslint-disable no-empty */
try {
cursors.value[payload.userPk] = {
x: parseInt(payload.x),
y: parseInt(payload.y),
userPk: payload.userPk,
userName: payload.userName,
timestamp: new Date(),
};
} catch {}
}
},
},
);
},
{ immediate: true },
);
onBeforeUnmount(() => {
realtimeStore.unsubscribe("workspace-model-and-view");
clearInterval(interval);
});

const fixesAreRunning = computed(
() =>
Expand All @@ -299,7 +207,6 @@ const fixesAreRunning = computed(
const openCollapsible = ref(true);

onMounted(() => {
cursors.value = {};
if (changeSetStore.headSelected) {
openCollapsible.value = !!window.localStorage.getItem("applied-changes");
window.localStorage.removeItem("applied-changes");
Expand Down
Loading

0 comments on commit 14ff10d

Please sign in to comment.