@catandbox/schrodinger-web-adapter 0.1.26 → 0.1.28
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.
- package/dist/client/ticket-list.js +176 -6
- package/package.json +1 -1
|
@@ -1,5 +1,169 @@
|
|
|
1
1
|
import { renderStatusBadge } from "./status-badge";
|
|
2
2
|
import { setInnerHtml } from "./dom-utils";
|
|
3
|
+
function loadThree() {
|
|
4
|
+
return new Promise((resolve, reject) => {
|
|
5
|
+
if (typeof THREE !== "undefined") {
|
|
6
|
+
resolve();
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
const s = document.createElement("script");
|
|
10
|
+
s.src = "https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js";
|
|
11
|
+
s.onload = () => resolve();
|
|
12
|
+
s.onerror = reject;
|
|
13
|
+
document.head.appendChild(s);
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
function initHydrogenLogo(el) {
|
|
17
|
+
// Math helpers
|
|
18
|
+
function factorial(n) {
|
|
19
|
+
let r = 1;
|
|
20
|
+
for (let i = 2; i <= n; i++)
|
|
21
|
+
r *= i;
|
|
22
|
+
return r;
|
|
23
|
+
}
|
|
24
|
+
function assocLaguerre(p, q, x) {
|
|
25
|
+
if (p === 0)
|
|
26
|
+
return 1;
|
|
27
|
+
if (p === 1)
|
|
28
|
+
return 1 + q - x;
|
|
29
|
+
let lm2 = 1, lm1 = 1 + q - x, val = 0;
|
|
30
|
+
for (let k = 2; k <= p; k++) {
|
|
31
|
+
val = ((2 * k - 1 + q - x) * lm1 - (k - 1 + q) * lm2) / k;
|
|
32
|
+
lm2 = lm1;
|
|
33
|
+
lm1 = val;
|
|
34
|
+
}
|
|
35
|
+
return val;
|
|
36
|
+
}
|
|
37
|
+
function assocLegendre(l, m, x) {
|
|
38
|
+
let pmm = 1.0;
|
|
39
|
+
if (m > 0) {
|
|
40
|
+
const s = Math.sqrt((1 - x) * (1 + x));
|
|
41
|
+
let f = 1;
|
|
42
|
+
for (let i = 1; i <= m; i++) {
|
|
43
|
+
pmm *= -f * s;
|
|
44
|
+
f += 2;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (l === m)
|
|
48
|
+
return pmm;
|
|
49
|
+
let pmmp1 = x * (2 * m + 1) * pmm;
|
|
50
|
+
if (l === m + 1)
|
|
51
|
+
return pmmp1;
|
|
52
|
+
let pll = 0;
|
|
53
|
+
for (let ll = m + 2; ll <= l; ll++) {
|
|
54
|
+
pll = (x * (2 * ll - 1) * pmmp1 - (ll + m - 1) * pmm) / (ll - m);
|
|
55
|
+
pmm = pmmp1;
|
|
56
|
+
pmmp1 = pll;
|
|
57
|
+
}
|
|
58
|
+
return pll;
|
|
59
|
+
}
|
|
60
|
+
function sphericalHarmonicReal(l, m, theta, phi) {
|
|
61
|
+
const absM = Math.abs(m);
|
|
62
|
+
const norm = Math.sqrt(((2 * l + 1) / (4 * Math.PI)) * (factorial(l - absM) / factorial(l + absM)));
|
|
63
|
+
const plm = assocLegendre(l, absM, Math.cos(theta));
|
|
64
|
+
if (m > 0)
|
|
65
|
+
return norm * plm * Math.cos(m * phi) * Math.SQRT2;
|
|
66
|
+
if (m < 0)
|
|
67
|
+
return norm * plm * Math.sin(absM * phi) * Math.SQRT2;
|
|
68
|
+
return norm * plm;
|
|
69
|
+
}
|
|
70
|
+
function radialWaveFunction(n, l, r) {
|
|
71
|
+
const rho = (2 * r) / n;
|
|
72
|
+
const norm = Math.sqrt(Math.pow(2 / n, 3) * factorial(n - l - 1) / (2 * n * Math.pow(factorial(n + l), 3)));
|
|
73
|
+
return norm * Math.exp(-rho / 2) * Math.pow(rho, l) * assocLaguerre(n - l - 1, 2 * l + 1, rho);
|
|
74
|
+
}
|
|
75
|
+
function psiProb(n, l, m, r, theta, phi) {
|
|
76
|
+
const R = radialWaveFunction(n, l, r);
|
|
77
|
+
const Y = sphericalHarmonicReal(l, m, theta, phi);
|
|
78
|
+
return R * R * Y * Y;
|
|
79
|
+
}
|
|
80
|
+
// Generate points via rejection sampling
|
|
81
|
+
const n = 4, l = 3, m = 1, maxR = 45, numPoints = 80000;
|
|
82
|
+
let maxProb = 0;
|
|
83
|
+
for (let i = 0; i < 5000; i++) {
|
|
84
|
+
const r = Math.random() * maxR;
|
|
85
|
+
const theta = Math.acos(2 * Math.random() - 1);
|
|
86
|
+
const phi = Math.random() * 2 * Math.PI;
|
|
87
|
+
const p = psiProb(n, l, m, r, theta, phi) * r * r;
|
|
88
|
+
if (p > maxProb)
|
|
89
|
+
maxProb = p;
|
|
90
|
+
}
|
|
91
|
+
const pts = [], cols = [], szs = [];
|
|
92
|
+
let accepted = 0;
|
|
93
|
+
const sc = 0.7;
|
|
94
|
+
for (let i = 0; i < numPoints * 30 && accepted < numPoints; i++) {
|
|
95
|
+
const r = Math.random() * maxR;
|
|
96
|
+
const theta = Math.acos(2 * Math.random() - 1);
|
|
97
|
+
const phi = Math.random() * 2 * Math.PI;
|
|
98
|
+
const prob = psiProb(n, l, m, r, theta, phi) * r * r;
|
|
99
|
+
const thr = prob / maxProb;
|
|
100
|
+
if (Math.random() < thr) {
|
|
101
|
+
pts.push(r * Math.sin(theta) * Math.cos(phi) * sc, r * Math.sin(theta) * Math.sin(phi) * sc, r * Math.cos(theta) * sc);
|
|
102
|
+
const t = r / maxR;
|
|
103
|
+
cols.push(0.3 + 0.6 * t, 0.5 * (1 - t) + 0.2 * t, 0.95 - 0.2 * t);
|
|
104
|
+
szs.push(0.08 + 0.15 * thr);
|
|
105
|
+
accepted++;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const scene = new THREE.Scene();
|
|
109
|
+
const camera = new THREE.PerspectiveCamera(50, 1, 0.1, 1000);
|
|
110
|
+
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
|
|
111
|
+
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
|
|
112
|
+
renderer.setSize(80, 80);
|
|
113
|
+
renderer.setClearColor(0x08090d, 1);
|
|
114
|
+
el.appendChild(renderer.domElement);
|
|
115
|
+
const geometry = new THREE.BufferGeometry();
|
|
116
|
+
geometry.setAttribute("position", new THREE.Float32BufferAttribute(pts, 3));
|
|
117
|
+
geometry.setAttribute("color", new THREE.Float32BufferAttribute(cols, 3));
|
|
118
|
+
geometry.setAttribute("size", new THREE.Float32BufferAttribute(szs, 1));
|
|
119
|
+
const material = new THREE.ShaderMaterial({
|
|
120
|
+
vertexShader: `
|
|
121
|
+
attribute float size;
|
|
122
|
+
varying vec3 vColor;
|
|
123
|
+
void main() {
|
|
124
|
+
vColor = color;
|
|
125
|
+
vec4 mv = modelViewMatrix * vec4(position, 1.0);
|
|
126
|
+
gl_PointSize = size * (200.0 / -mv.z);
|
|
127
|
+
gl_Position = projectionMatrix * mv;
|
|
128
|
+
}
|
|
129
|
+
`,
|
|
130
|
+
fragmentShader: `
|
|
131
|
+
varying vec3 vColor;
|
|
132
|
+
void main() {
|
|
133
|
+
float d = length(gl_PointCoord - vec2(0.5));
|
|
134
|
+
if (d > 0.5) discard;
|
|
135
|
+
float a = smoothstep(0.5, 0.1, d) * 0.7;
|
|
136
|
+
gl_FragColor = vec4(vColor, a);
|
|
137
|
+
}
|
|
138
|
+
`,
|
|
139
|
+
vertexColors: true,
|
|
140
|
+
transparent: true,
|
|
141
|
+
depthWrite: false,
|
|
142
|
+
blending: THREE.AdditiveBlending,
|
|
143
|
+
});
|
|
144
|
+
scene.add(new THREE.Points(geometry, material));
|
|
145
|
+
const nucGeo = new THREE.SphereGeometry(0.25, 12, 12);
|
|
146
|
+
scene.add(new THREE.Mesh(nucGeo, new THREE.MeshBasicMaterial({ color: 0xffffff })));
|
|
147
|
+
let theta = 0;
|
|
148
|
+
const phi = 0.85, radius = 42;
|
|
149
|
+
function updateCam() {
|
|
150
|
+
camera.position.set(radius * Math.sin(phi) * Math.sin(theta), radius * Math.cos(phi), radius * Math.sin(phi) * Math.cos(theta));
|
|
151
|
+
camera.lookAt(0, 0, 0);
|
|
152
|
+
}
|
|
153
|
+
let rafId;
|
|
154
|
+
function animate() {
|
|
155
|
+
rafId = requestAnimationFrame(animate);
|
|
156
|
+
theta += 0.003;
|
|
157
|
+
updateCam();
|
|
158
|
+
renderer.render(scene, camera);
|
|
159
|
+
}
|
|
160
|
+
updateCam();
|
|
161
|
+
animate();
|
|
162
|
+
return () => {
|
|
163
|
+
cancelAnimationFrame(rafId);
|
|
164
|
+
renderer.dispose();
|
|
165
|
+
};
|
|
166
|
+
}
|
|
3
167
|
function formatDate(timestamp) {
|
|
4
168
|
return new Date(timestamp * 1000).toLocaleDateString(undefined, {
|
|
5
169
|
month: "short",
|
|
@@ -35,10 +199,11 @@ const ORDER_OPTIONS = [
|
|
|
35
199
|
{ value: "oldest", label: "Oldest first" }
|
|
36
200
|
];
|
|
37
201
|
export async function renderTicketList(container, client, emitter) {
|
|
38
|
-
// Load tickets and
|
|
202
|
+
// Load tickets, categories and Three.js in parallel
|
|
39
203
|
const [result, portalConfig] = await Promise.all([
|
|
40
204
|
client.listTickets({}).catch(() => ({ items: [] })),
|
|
41
|
-
client.getPortalConfig().catch(() => ({ categories: [], aliases: [] }))
|
|
205
|
+
client.getPortalConfig().catch(() => ({ categories: [], aliases: [] })),
|
|
206
|
+
loadThree().catch(() => { })
|
|
42
207
|
]);
|
|
43
208
|
const allTickets = result.items;
|
|
44
209
|
const categories = portalConfig.categories;
|
|
@@ -57,10 +222,10 @@ export async function renderTicketList(container, client, emitter) {
|
|
|
57
222
|
<s-box padding="large">
|
|
58
223
|
<s-stack gap="large">
|
|
59
224
|
<div style="display:flex; justify-content:space-between; align-items:center;">
|
|
60
|
-
<
|
|
61
|
-
<
|
|
62
|
-
<
|
|
63
|
-
</
|
|
225
|
+
<div style="display:flex; align-items:center; gap:12px;">
|
|
226
|
+
<div id="sch-logo-el" style="width:80px; height:80px; border-radius:8px; overflow:hidden; flex-shrink:0; background:#08090d;"></div>
|
|
227
|
+
<span style="font-size:18px; font-weight:700; color:#0f172a;">Schrödinger helpdesk by Cat and box</span>
|
|
228
|
+
</div>
|
|
64
229
|
<s-button variant="primary" id="sch-new-ticket-btn">
|
|
65
230
|
<span style="display:inline-flex; align-items:center; gap:4px;">
|
|
66
231
|
<svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor"><path d="M10 3a1 1 0 0 1 1 1v5h5a1 1 0 1 1 0 2h-5v5a1 1 0 1 1-2 0v-5H4a1 1 0 1 1 0-2h5V4a1 1 0 0 1 1-1z"/></svg>
|
|
@@ -96,6 +261,11 @@ export async function renderTicketList(container, client, emitter) {
|
|
|
96
261
|
container
|
|
97
262
|
.querySelector("#sch-new-ticket-btn")
|
|
98
263
|
?.addEventListener("click", () => emitter.emit("ticket:create", undefined));
|
|
264
|
+
// Init hydrogen orbital logo
|
|
265
|
+
const logoEl = container.querySelector("#sch-logo-el");
|
|
266
|
+
if (logoEl && typeof THREE !== "undefined") {
|
|
267
|
+
initHydrogenLogo(logoEl);
|
|
268
|
+
}
|
|
99
269
|
function getSelectValue(id) {
|
|
100
270
|
const el = container.querySelector(`#${id}`);
|
|
101
271
|
return el?.value ?? "";
|