Skip to content

Commit d9e81a5

Browse files
committed
feat: 页面 JS 增加光标同步功能
1 parent 949c395 commit d9e81a5

7 files changed

Lines changed: 167 additions & 12 deletions

File tree

packages/canvas/container/src/CanvasContainer.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ import {
9696
getRect
9797
} from './container'
9898
import { initHook, HOOK_NAME } from '@opentiny/tiny-engine-meta-register'
99-
import useCollabSchema from '@opentiny/tiny-engine-multi-person-collaboration'
99+
import { useCollabSchema } from '@opentiny/tiny-engine-multi-person-collaboration'
100100
import { useRealtimeCollab } from '@opentiny/tiny-engine-meta-register'
101101
102102
export default {

packages/multi-person-collaboration/package.json

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,28 +35,31 @@
3535
"author": "OpenTiny Team",
3636
"license": "MIT",
3737
"dependencies": {
38+
"@opentiny/tiny-engine-canvas": "workspace:*",
3839
"@opentiny/tiny-engine-common": "workspace:*",
3940
"@opentiny/tiny-engine-meta-register": "workspace:*",
4041
"@opentiny/tiny-engine-utils": "workspace:*",
41-
"@opentiny/tiny-engine-canvas": "workspace:*"
42+
"monaco-editor": "0.51.0"
4243
},
4344
"devDependencies": {
45+
"glob": "^10.0.0",
46+
"lib0": "^0.2.85",
47+
"lodash-es": "^4.17.21",
48+
"mitt": "^3.0.1",
49+
"uuid": "^9.0.1",
4450
"vite": "^5.4.2",
4551
"vitest": "^1.4.0",
4652
"vue": "^3.4.21",
47-
"glob": "^10.0.0",
48-
"yjs": "^13.6.8",
49-
"y-websocket": "^1.5.0",
53+
"y-monaco": "^0.1.6",
5054
"y-protocols": "^1.0.6",
51-
"lib0": "^0.2.85",
52-
"uuid": "^9.0.1",
53-
"lodash-es": "^4.17.21",
54-
"mitt": "^3.0.1"
55+
"y-websocket": "^1.5.0",
56+
"yjs": "^13.6.8"
5557
},
5658
"peerDependencies": {
5759
"vue": "^3.4.21",
58-
"yjs": "^13.6.8",
60+
"y-monaco": "^0.1.6",
61+
"y-protocols": "^1.0.6",
5962
"y-websocket": "^1.5.0",
60-
"y-protocols": "^1.0.6"
63+
"yjs": "^13.6.8"
6164
}
6265
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import { computed, onUnmounted, ref, watch } from 'vue'
2+
import { PORT } from '../config'
3+
import { useYjs } from './useYjs'
4+
import { MonacoBinding } from 'y-monaco'
5+
import type { UserAwareness } from '../type'
6+
7+
interface UseCollabMonacoOptions {
8+
currentUser: UserAwareness
9+
editorRef: any
10+
roomId: string
11+
fieldName: string
12+
}
13+
14+
function makeTransparent(hex: string, alpha: number): string {
15+
// hex: "#RRGGBB"
16+
if (!hex.startsWith('#') || (hex.length !== 7 && hex.length !== 4)) return hex
17+
let r: number, g: number, b: number
18+
19+
if (hex.length === 7) {
20+
r = parseInt(hex.slice(1, 3), 16)
21+
g = parseInt(hex.slice(3, 5), 16)
22+
b = parseInt(hex.slice(5, 7), 16)
23+
} else {
24+
// "#RGB" 短写
25+
r = parseInt(hex[1] + hex[1], 16)
26+
g = parseInt(hex[2] + hex[2], 16)
27+
b = parseInt(hex[3] + hex[3], 16)
28+
}
29+
30+
return `rgba(${r},${g},${b},${alpha})`
31+
}
32+
33+
export function useCollabMonaco(options: UseCollabMonacoOptions) {
34+
const { currentUser, editorRef, roomId, fieldName } = options
35+
const { ydoc, awareness } = useYjs(roomId, { websocketUrl: `ws://localhost:${PORT}` })
36+
37+
const monacoBinding = ref<MonacoBinding | null>(null)
38+
39+
watch(
40+
() => [awareness.value, editorRef.value],
41+
([yjsAwareness, monacoComponent]) => {
42+
if (yjsAwareness && monacoComponent && !monacoBinding.value) {
43+
const editor = monacoComponent.getEditor()
44+
if (!editor) {
45+
// eslint-disable-next-line no-console
46+
console.error('[useMonacoCollab] 无法从 monacoComponent 获取 editor 实例。')
47+
return
48+
}
49+
50+
const model = editor.getModel()
51+
if (!model) return
52+
53+
yjsAwareness.setLocalStateField('user', {
54+
name: currentUser.name,
55+
id: currentUser.id,
56+
color: currentUser.color,
57+
colorLight: makeTransparent(currentUser.color, 0.2)
58+
})
59+
60+
monacoBinding.value = new MonacoBinding(ydoc.getText(fieldName), model, new Set([editor]), yjsAwareness)
61+
62+
// eslint-disable-next-line no-console
63+
console.log('[useMonacoCollab] Yjs 绑定成功!光标同步已自动激活。')
64+
65+
// 监听 awareness,添加用户名标签
66+
// const handleAwarenessUpdate = () => {
67+
// const states = (yjsAwareness as Awareness).getStates()
68+
// const decorations: monaco.editor.IModelDeltaDecoration[] = []
69+
70+
// states.forEach((state, clientId) => {
71+
// // 跳过自己和没有用户和光标信息的
72+
// if (clientId === yjsAwareness.clientId || !state.selection || !state.user) {
73+
// return
74+
// }
75+
76+
// // y-monaco 已经创建了光标 找到其位置即可
77+
// const headPos = model.getPositionAt(state.selection.head)
78+
79+
// // 添加一个 "用户标签" 的 decoration
80+
// decorations.push({
81+
// range: new monaco.Range(headPos.lineNumber, headPos.column, headPos.lineNumber, headPos.column),
82+
// options: {
83+
// after: {
84+
// content: state.user.name,
85+
// // 添加一个类名,用于设置样式
86+
// inlineClassName: 'y-remote-cursor-username'
87+
// },
88+
// // 设置一个唯一的 CSS 类名,用于动态应用颜色
89+
// inlineClassName: `y-remote-user-color-${clientId}`
90+
// }
91+
// })
92+
// })
93+
94+
// editor.deltaDecorations([], decorations)
95+
// }
96+
97+
// yjsAwareness.on('update', handleAwarenessUpdate)
98+
// handleAwarenessUpdate() // 初始渲染
99+
}
100+
},
101+
{ immediate: true }
102+
)
103+
104+
onUnmounted(() => {
105+
monacoBinding.value?.destroy()
106+
if (awareness.value) {
107+
awareness.value.setLocalStateField('user', null)
108+
}
109+
})
110+
111+
return {
112+
isBindingReady: computed(() => monacoBinding.value !== null)
113+
}
114+
}
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
import { useCollabSchema } from './composables/useCollabSchema'
2-
export default useCollabSchema
2+
import { useCollabMonaco } from './composables/useCollabMonaco'
3+
import { useYjs } from './composables/useYjs'
4+
export { useCollabSchema, useCollabMonaco, useYjs }

packages/plugins/script/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"@opentiny/tiny-engine-utils": "workspace:*"
3131
},
3232
"devDependencies": {
33+
"@opentiny/tiny-engine-multi-person-collaboration": "workspace:*",
3334
"@opentiny/tiny-engine-vite-plugin-meta-comments": "workspace:*",
3435
"@vitejs/plugin-vue": "^5.1.2",
3536
"@vitejs/plugin-vue-jsx": "^4.0.1",

packages/plugins/script/src/Main.vue

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,4 +189,25 @@ export default {
189189
:deep(.monaco-editor .editorPlaceholder) {
190190
font-size: 12px !important;
191191
}
192+
:global(.yRemoteSelection) {
193+
background-color: rgba(250, 129, 0, 0.5);
194+
}
195+
196+
:global(.yRemoteSelectionHead) {
197+
position: absolute;
198+
border-left: orange solid 2px;
199+
border-top: orange solid 2px;
200+
border-bottom: orange solid 2px;
201+
height: 100%;
202+
box-sizing: border-box;
203+
}
204+
205+
:global(.yRemoteSelectionHead::after) {
206+
position: absolute;
207+
content: ' ';
208+
border: 3px solid orange;
209+
border-radius: 4px;
210+
left: -4px;
211+
top: -5px;
212+
}
192213
</style>

packages/plugins/script/src/js/method.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { string2Ast, ast2String, insertName, formatString } from '@opentiny/tiny
1717
import { constants } from '@opentiny/tiny-engine-utils'
1818
import { lint } from '@opentiny/tiny-engine-common/js/linter'
1919
import { isFunction } from '@opentiny/vue-renderless/grid/static'
20+
import { useCollabMonaco } from '@opentiny/tiny-engine-multi-person-collaboration'
2021

2122
const { SCHEMA_DATA_TYPE } = constants
2223

@@ -230,6 +231,19 @@ export default ({ emit }) => {
230231
state.script = getScriptString()
231232
monaco.value?.focus()
232233
window.dispatchEvent(new Event('resize'))
234+
const currentUser = {
235+
id: 2,
236+
name: 'Bob',
237+
color: '#ff0000ff',
238+
avatarUrl: 'https://i.pravatar.cc/150?img=2'
239+
}
240+
// 代码的协同编辑
241+
useCollabMonaco({
242+
currentUser,
243+
editorRef: monaco,
244+
roomId: 'page-js-Yjs',
245+
fieldName: 'page-js'
246+
})
233247
})
234248
})
235249

0 commit comments

Comments
 (0)