opencube 0.3.1 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/main.js +76 -14
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencube",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "description": "A tiny Three.js desktop pet for opencode session activity.",
5
5
  "main": "src/main.js",
6
6
  "exports": {
package/src/main.js CHANGED
@@ -16,6 +16,7 @@ const ICON_PATH = process.env.OPENCODE_PET_ICON || path.join(__dirname, "..", "a
16
16
  const MAX_EVENTS = 100
17
17
  const MAX_INTERACTION_EVENTS = 80
18
18
  const IDLE_TTL_MS = 5 * 60 * 1000
19
+ const PET_WINDOW_SIZE = 120
19
20
 
20
21
  let petWindow = null
21
22
  let panelWindow = null
@@ -31,17 +32,68 @@ let pendingQuestionsByRequest = new Map()
31
32
  let cleanupTimer = null
32
33
  let dragState = null
33
34
 
35
+ function normalizeDragPoint(point) {
36
+ const screenX = Number(point?.screenX)
37
+ const screenY = Number(point?.screenY)
38
+ if (!Number.isFinite(screenX) || !Number.isFinite(screenY)) return null
39
+ return { screenX, screenY }
40
+ }
41
+
42
+ function clamp(value, min, max) {
43
+ return Math.max(min, Math.min(max, value))
44
+ }
45
+
46
+ function getVirtualWorkArea() {
47
+ const displays = screen.getAllDisplays()
48
+ if (!displays.length) return screen.getPrimaryDisplay().workArea
49
+
50
+ return displays.reduce((area, display) => {
51
+ const next = display.workArea
52
+ const left = Math.min(area.x, next.x)
53
+ const top = Math.min(area.y, next.y)
54
+ const right = Math.max(area.x + area.width, next.x + next.width)
55
+ const bottom = Math.max(area.y + area.height, next.y + next.height)
56
+ return { x: left, y: top, width: right - left, height: bottom - top }
57
+ }, displays[0].workArea)
58
+ }
59
+
60
+ function normalizeWindowPosition(x, y, win) {
61
+ if (!Number.isFinite(x) || !Number.isFinite(y) || !win || win.isDestroyed()) return null
62
+
63
+ const bounds = win.getBounds()
64
+ const area = getVirtualWorkArea()
65
+ const minX = area.x
66
+ const minY = area.y
67
+ const maxX = area.x + area.width - Math.max(1, bounds.width)
68
+ const maxY = area.y + area.height - Math.max(1, bounds.height)
69
+
70
+ return {
71
+ x: Math.round(clamp(x, Math.min(minX, maxX), Math.max(minX, maxX))),
72
+ y: Math.round(clamp(y, Math.min(minY, maxY), Math.max(minY, maxY))),
73
+ }
74
+ }
75
+
34
76
  ipcMain.on("opencube-drag-start", (event, point) => {
35
77
  if (!petWindow || petWindow.isDestroyed()) return
78
+ const dragPoint = normalizeDragPoint(point)
79
+ if (!dragPoint) return
36
80
  const [x, y] = petWindow.getPosition()
37
- dragState = { windowX: x, windowY: y, screenX: point.screenX, screenY: point.screenY }
81
+ dragState = { windowX: x, windowY: y, screenX: dragPoint.screenX, screenY: dragPoint.screenY }
38
82
  })
39
83
 
40
84
  ipcMain.on("opencube-drag-move", (event, point) => {
41
85
  if (!petWindow || petWindow.isDestroyed() || !dragState) return
42
- const nextX = Math.round(dragState.windowX + point.screenX - dragState.screenX)
43
- const nextY = Math.round(dragState.windowY + point.screenY - dragState.screenY)
44
- petWindow.setPosition(nextX, nextY, false)
86
+ const dragPoint = normalizeDragPoint(point)
87
+ if (!dragPoint) return
88
+ const nextX = Math.round(dragState.windowX + dragPoint.screenX - dragState.screenX)
89
+ const nextY = Math.round(dragState.windowY + dragPoint.screenY - dragState.screenY)
90
+ const nextPosition = normalizeWindowPosition(nextX, nextY, petWindow)
91
+ if (!nextPosition) return
92
+ try {
93
+ petWindow.setPosition(nextPosition.x, nextPosition.y, false)
94
+ } catch {
95
+ dragState = null
96
+ }
45
97
  })
46
98
 
47
99
  ipcMain.on("opencube-drag-end", () => {
@@ -680,6 +732,7 @@ function petHtml3D() {
680
732
  const initialStateJson = JSON.stringify(getPetState()).replaceAll("<", "\\u003c")
681
733
  const iconUrl = createIconDataUrl()
682
734
  const threeCjsPath = JSON.stringify(path.join(path.dirname(require.resolve("three")), "three.cjs"))
735
+ const renderSize = PET_WINDOW_SIZE
683
736
  return `<!doctype html>
684
737
  <html>
685
738
  <head>
@@ -704,8 +757,8 @@ function petHtml3D() {
704
757
  #scene {
705
758
  position: absolute;
706
759
  inset: 0;
707
- width: 100%;
708
- height: 100%;
760
+ width: ${renderSize}px;
761
+ height: ${renderSize}px;
709
762
  pointer-events: none;
710
763
  }
711
764
  #hit-layer {
@@ -734,6 +787,7 @@ function petHtml3D() {
734
787
  const THREE = require(${threeCjsPath})
735
788
  const { ipcRenderer } = require("electron")
736
789
 
790
+ const PET_RENDER_SIZE = ${renderSize}
737
791
  window.__PET_STATE = ${initialStateJson}
738
792
  const stage = document.querySelector(".stage")
739
793
  const hitLayer = document.getElementById("hit-layer")
@@ -765,7 +819,7 @@ function petHtml3D() {
765
819
  camera.position.set(0, 0.04, 3.25)
766
820
  const renderer = new THREE.WebGLRenderer({ canvas, alpha: true, antialias: true })
767
821
  renderer.setClearColor(0x000000, 0)
768
- renderer.setPixelRatio(Math.min((window.devicePixelRatio || 1) * 1.5, 3))
822
+ renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2))
769
823
  renderer.outputColorSpace = THREE.SRGBColorSpace
770
824
 
771
825
  const cubeGroup = new THREE.Group()
@@ -1093,10 +1147,8 @@ function petHtml3D() {
1093
1147
  }
1094
1148
 
1095
1149
  function resize() {
1096
- const width = Math.max(1, window.innerWidth)
1097
- const height = Math.max(1, window.innerHeight)
1098
- renderer.setSize(width, height, false)
1099
- camera.aspect = width / height
1150
+ renderer.setSize(PET_RENDER_SIZE, PET_RENDER_SIZE, false)
1151
+ camera.aspect = 1
1100
1152
  camera.updateProjectionMatrix()
1101
1153
  }
1102
1154
  window.addEventListener("resize", resize)
@@ -1631,6 +1683,16 @@ function petHtml3D() {
1631
1683
  busyFaces,
1632
1684
  helloFlashes,
1633
1685
  permissionGlows,
1686
+ renderSize: {
1687
+ fixed: PET_RENDER_SIZE,
1688
+ innerWidth: window.innerWidth,
1689
+ innerHeight: window.innerHeight,
1690
+ devicePixelRatio: window.devicePixelRatio || 1,
1691
+ canvasWidth: canvas.width,
1692
+ canvasHeight: canvas.height,
1693
+ canvasClientWidth: canvas.clientWidth,
1694
+ canvasClientHeight: canvas.clientHeight,
1695
+ },
1634
1696
  }
1635
1697
  requestAnimationFrame(tick)
1636
1698
  }
@@ -1658,7 +1720,7 @@ function restorePosition(win) {
1658
1720
  try {
1659
1721
  const raw = fs.readFileSync(STATE_FILE, "utf8")
1660
1722
  const state = JSON.parse(raw)
1661
- if (typeof state.x === "number" && typeof state.y === "number") {
1723
+ if (Number.isFinite(state.x) && Number.isFinite(state.y)) {
1662
1724
  win.setPosition(state.x, state.y, false)
1663
1725
  return
1664
1726
  }
@@ -1676,8 +1738,8 @@ function restorePosition(win) {
1676
1738
  function createPetWindow() {
1677
1739
  if (petWindow && !petWindow.isDestroyed()) return petWindow
1678
1740
  petWindow = new BrowserWindow({
1679
- width: 120,
1680
- height: 120,
1741
+ width: PET_WINDOW_SIZE,
1742
+ height: PET_WINDOW_SIZE,
1681
1743
  show: false,
1682
1744
  frame: false,
1683
1745
  resizable: false,