swisseph-wasm 0.0.2 → 0.0.4
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/README.md +57 -0
- package/examples/api-explorer.html +829 -0
- package/examples/demo.html +307 -70
- package/examples/playground.html +434 -0
- package/examples/tests.html +363 -0
- package/package.json +6 -15
- package/src/swisseph.js +571 -137
- package/wsam/swisseph.js +2 -3558
- package/wsam/swisseph.wasm +0 -0
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Swiss Ephemeris Playground</title>
|
|
7
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
8
|
+
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
|
|
9
|
+
</head>
|
|
10
|
+
<body class="bg-gray-50 min-h-screen">
|
|
11
|
+
<div id="app">
|
|
12
|
+
<!-- Header -->
|
|
13
|
+
<header class="bg-white border-b border-gray-200">
|
|
14
|
+
<div class="container mx-auto px-4 py-6">
|
|
15
|
+
<div class="flex items-center justify-between">
|
|
16
|
+
<div>
|
|
17
|
+
<h1 class="text-2xl font-bold text-gray-900">
|
|
18
|
+
Swiss Ephemeris Playground
|
|
19
|
+
</h1>
|
|
20
|
+
<p class="text-gray-600 mt-1">Write and execute custom astronomical programs</p>
|
|
21
|
+
</div>
|
|
22
|
+
<div class="text-right">
|
|
23
|
+
<div class="flex gap-3 mb-2">
|
|
24
|
+
<a href="api-explorer.html" class="text-sm text-blue-600 hover:text-blue-800">API Explorer</a>
|
|
25
|
+
<a href="tests.html" class="text-sm text-blue-600 hover:text-blue-800">Tests</a>
|
|
26
|
+
</div>
|
|
27
|
+
<div class="text-sm" :class="status.color">{{ status.text }}</div>
|
|
28
|
+
<div class="text-xs text-gray-400 mt-1">{{ version }}</div>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
</header>
|
|
33
|
+
|
|
34
|
+
<div class="container mx-auto px-4 py-6">
|
|
35
|
+
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
|
36
|
+
<!-- Left: Code Editor -->
|
|
37
|
+
<div class="space-y-4">
|
|
38
|
+
<div class="bg-white rounded border border-gray-200 p-4">
|
|
39
|
+
<div class="flex items-center justify-between mb-3">
|
|
40
|
+
<h2 class="text-lg font-semibold text-gray-900">Code Editor</h2>
|
|
41
|
+
<button @click="runCode"
|
|
42
|
+
class="px-4 py-2 bg-green-600 text-white rounded hover:bg-green-700 transition-all font-medium text-sm">
|
|
43
|
+
Run Code
|
|
44
|
+
</button>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<textarea v-model="code"
|
|
48
|
+
class="w-full h-[500px] bg-gray-50 border border-gray-300 rounded p-3 font-mono text-sm text-gray-900 focus:outline-none focus:border-blue-500 resize-none"
|
|
49
|
+
placeholder="Write your code here..."></textarea>
|
|
50
|
+
|
|
51
|
+
<div class="mt-2 text-xs text-gray-500">
|
|
52
|
+
Tip: Use <code class="bg-gray-100 px-2 py-0.5 rounded">swisseph</code> to access all methods
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<!-- Quick Reference -->
|
|
57
|
+
<div class="bg-white rounded border border-gray-200 p-3">
|
|
58
|
+
<h3 class="text-sm font-semibold text-gray-900 mb-2">Quick Reference</h3>
|
|
59
|
+
<div class="space-y-1 text-xs">
|
|
60
|
+
<div><code class="text-blue-600">swisseph.julday(2000, 1, 1, 12.0)</code> - Convert date to JD</div>
|
|
61
|
+
<div><code class="text-blue-600">swisseph.calc(jd, swisseph.SE_SUN, ...)</code> - Planet position</div>
|
|
62
|
+
<div><code class="text-blue-600">swisseph.houses(jd, lat, lon, 'P')</code> - House cusps</div>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
<!-- Right: Templates + Output + Saved -->
|
|
68
|
+
<div class="space-y-4">
|
|
69
|
+
<!-- Program Templates - Compact Batch -->
|
|
70
|
+
<div class="bg-white rounded border border-gray-200 p-3">
|
|
71
|
+
<h2 class="text-sm font-semibold text-gray-900 mb-2">Program Templates</h2>
|
|
72
|
+
<div class="grid grid-cols-3 gap-2">
|
|
73
|
+
<button v-for="template in templates" :key="template.name"
|
|
74
|
+
@click="loadTemplate(template)"
|
|
75
|
+
class="p-2 bg-gray-50 rounded border border-gray-200 hover:border-blue-400 hover:bg-blue-50 transition-all text-center">
|
|
76
|
+
<div class="font-medium text-gray-900 text-xs">{{ template.name }}</div>
|
|
77
|
+
</button>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
<!-- Output -->
|
|
82
|
+
<div class="bg-white rounded border border-gray-200 p-4">
|
|
83
|
+
<div class="flex items-center justify-between mb-3">
|
|
84
|
+
<h2 class="text-lg font-semibold text-gray-900">Output</h2>
|
|
85
|
+
<button @click="clearOutput"
|
|
86
|
+
class="px-3 py-1 bg-gray-200 text-gray-700 rounded hover:bg-gray-300 transition-all text-sm">
|
|
87
|
+
Clear
|
|
88
|
+
</button>
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
<div class="bg-gray-50 rounded border border-gray-200 p-3 h-80 overflow-y-auto font-mono text-sm">
|
|
92
|
+
<div v-if="output.length === 0" class="text-gray-400 italic">
|
|
93
|
+
No output yet. Run some code to see results.
|
|
94
|
+
</div>
|
|
95
|
+
<div v-for="(item, idx) in output" :key="idx" class="mb-2">
|
|
96
|
+
<div v-if="item.type === 'log'" class="text-blue-600">
|
|
97
|
+
<span class="text-gray-400">›</span> {{ item.value }}
|
|
98
|
+
</div>
|
|
99
|
+
<div v-else-if="item.type === 'result'" class="text-green-600">
|
|
100
|
+
<span class="text-gray-400">→</span> {{ item.value }}
|
|
101
|
+
</div>
|
|
102
|
+
<div v-else-if="item.type === 'error'" class="text-red-600">
|
|
103
|
+
<span class="text-gray-400">✗</span> {{ item.value }}
|
|
104
|
+
</div>
|
|
105
|
+
<div v-else-if="item.type === 'object'" class="text-gray-700">
|
|
106
|
+
<pre class="whitespace-pre-wrap">{{ item.value }}</pre>
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
|
|
112
|
+
<!-- Saved Programs -->
|
|
113
|
+
<div class="bg-white rounded border border-gray-200 p-3">
|
|
114
|
+
<div class="flex items-center justify-between mb-2">
|
|
115
|
+
<h2 class="text-sm font-semibold text-gray-900">Saved Programs</h2>
|
|
116
|
+
<button @click="saveProgram"
|
|
117
|
+
class="px-2 py-1 bg-blue-600 text-white rounded hover:bg-blue-700 transition-all text-xs">
|
|
118
|
+
+ Save
|
|
119
|
+
</button>
|
|
120
|
+
</div>
|
|
121
|
+
|
|
122
|
+
<div v-if="savedPrograms.length === 0" class="text-gray-400 text-xs italic">
|
|
123
|
+
No saved programs yet
|
|
124
|
+
</div>
|
|
125
|
+
<div v-else class="space-y-2 max-h-40 overflow-y-auto">
|
|
126
|
+
<div v-for="(program, idx) in savedPrograms" :key="idx"
|
|
127
|
+
class="bg-gray-50 rounded border border-gray-200 p-2 hover:bg-gray-100 transition-all">
|
|
128
|
+
<div class="flex items-center justify-between">
|
|
129
|
+
<div class="flex-1 min-w-0">
|
|
130
|
+
<div class="font-medium text-gray-900 text-xs truncate">{{ program.name }}</div>
|
|
131
|
+
<div class="text-xs text-gray-500">{{ program.date }}</div>
|
|
132
|
+
</div>
|
|
133
|
+
<div class="flex gap-1 ml-2">
|
|
134
|
+
<button @click="loadSavedProgram(program)"
|
|
135
|
+
class="px-2 py-1 bg-blue-100 text-blue-700 rounded hover:bg-blue-200 text-xs">
|
|
136
|
+
Load
|
|
137
|
+
</button>
|
|
138
|
+
<button @click="deleteSavedProgram(idx)"
|
|
139
|
+
class="px-2 py-1 bg-red-100 text-red-700 rounded hover:bg-red-200 text-xs">
|
|
140
|
+
Del
|
|
141
|
+
</button>
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
|
|
152
|
+
<script type="module">
|
|
153
|
+
import SwissEph from '../src/swisseph.js';
|
|
154
|
+
|
|
155
|
+
const { createApp } = Vue;
|
|
156
|
+
|
|
157
|
+
createApp({
|
|
158
|
+
data() {
|
|
159
|
+
return {
|
|
160
|
+
swisseph: null,
|
|
161
|
+
status: { text: 'Loading...', color: 'text-yellow-600' },
|
|
162
|
+
version: '',
|
|
163
|
+
code: '',
|
|
164
|
+
output: [],
|
|
165
|
+
savedPrograms: [],
|
|
166
|
+
templates: [
|
|
167
|
+
{
|
|
168
|
+
name: 'Birth Chart',
|
|
169
|
+
description: 'Calculate natal chart positions',
|
|
170
|
+
code: `// Birth Chart Calculator
|
|
171
|
+
const birthDate = { year: 1990, month: 6, day: 15, hour: 14.5 };
|
|
172
|
+
const birthPlace = { lat: 40.7128, lon: -74.0060 }; // New York
|
|
173
|
+
|
|
174
|
+
// Convert to Julian Day
|
|
175
|
+
const jd = swisseph.julday(birthDate.year, birthDate.month, birthDate.day, birthDate.hour);
|
|
176
|
+
console.log('Julian Day:', jd);
|
|
177
|
+
|
|
178
|
+
// Calculate Sun position
|
|
179
|
+
const sun = swisseph.calc(jd, swisseph.SE_SUN, swisseph.SEFLG_SWIEPH);
|
|
180
|
+
console.log('Sun:', sun.longitude.toFixed(2) + '°');
|
|
181
|
+
|
|
182
|
+
// Calculate Moon position
|
|
183
|
+
const moon = swisseph.calc(jd, swisseph.SE_MOON, swisseph.SEFLG_SWIEPH);
|
|
184
|
+
console.log('Moon:', moon.longitude.toFixed(2) + '°');
|
|
185
|
+
|
|
186
|
+
// Calculate Ascendant
|
|
187
|
+
const houses = swisseph.houses(jd, birthPlace.lat, birthPlace.lon, 'P');
|
|
188
|
+
console.log('Ascendant:', houses.ascmc[0].toFixed(2) + '°');
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
sun: sun.longitude,
|
|
192
|
+
moon: moon.longitude,
|
|
193
|
+
ascendant: houses.ascmc[0]
|
|
194
|
+
};`
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
name: 'Planet Positions',
|
|
198
|
+
description: 'Get all planet positions',
|
|
199
|
+
code: `// All Planet Positions
|
|
200
|
+
const jd = swisseph.julday(2024, 1, 1, 12.0);
|
|
201
|
+
|
|
202
|
+
const planets = [
|
|
203
|
+
{ id: swisseph.SE_SUN, name: 'Sun' },
|
|
204
|
+
{ id: swisseph.SE_MOON, name: 'Moon' },
|
|
205
|
+
{ id: swisseph.SE_MERCURY, name: 'Mercury' },
|
|
206
|
+
{ id: swisseph.SE_VENUS, name: 'Venus' },
|
|
207
|
+
{ id: swisseph.SE_MARS, name: 'Mars' },
|
|
208
|
+
{ id: swisseph.SE_JUPITER, name: 'Jupiter' },
|
|
209
|
+
{ id: swisseph.SE_SATURN, name: 'Saturn' }
|
|
210
|
+
];
|
|
211
|
+
|
|
212
|
+
const positions = {};
|
|
213
|
+
|
|
214
|
+
planets.forEach(planet => {
|
|
215
|
+
const pos = swisseph.calc(jd, planet.id, swisseph.SEFLG_SWIEPH);
|
|
216
|
+
positions[planet.name] = pos.longitude.toFixed(2) + '°';
|
|
217
|
+
console.log(planet.name + ':', positions[planet.name]);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
return positions;`
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
name: 'House System',
|
|
224
|
+
description: 'Calculate house cusps',
|
|
225
|
+
code: `// House System Calculator
|
|
226
|
+
const jd = swisseph.julday(2024, 1, 1, 12.0);
|
|
227
|
+
const lat = 51.5074; // London
|
|
228
|
+
const lon = -0.1278;
|
|
229
|
+
|
|
230
|
+
// Calculate houses (Placidus)
|
|
231
|
+
const houses = swisseph.houses(jd, lat, lon, 'P');
|
|
232
|
+
|
|
233
|
+
console.log('House Cusps (Placidus):');
|
|
234
|
+
for (let i = 1; i <= 12; i++) {
|
|
235
|
+
console.log('House ' + i + ':', houses.cusps[i].toFixed(2) + '°');
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
console.log('\\nAngles:');
|
|
239
|
+
console.log('Ascendant:', houses.ascmc[0].toFixed(2) + '°');
|
|
240
|
+
console.log('MC:', houses.ascmc[1].toFixed(2) + '°');
|
|
241
|
+
|
|
242
|
+
return {
|
|
243
|
+
cusps: houses.cusps.slice(1, 13),
|
|
244
|
+
ascendant: houses.ascmc[0],
|
|
245
|
+
mc: houses.ascmc[1]
|
|
246
|
+
};`
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
name: 'Sidereal Time',
|
|
250
|
+
description: 'Calculate sidereal time',
|
|
251
|
+
code: `// Sidereal Time Calculator
|
|
252
|
+
const now = new Date();
|
|
253
|
+
const jd = swisseph.julday(
|
|
254
|
+
now.getFullYear(),
|
|
255
|
+
now.getMonth() + 1,
|
|
256
|
+
now.getDate(),
|
|
257
|
+
now.getHours() + now.getMinutes() / 60
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
const sidtime = swisseph.sidtime(jd);
|
|
261
|
+
const hours = Math.floor(sidtime);
|
|
262
|
+
const minutes = Math.floor((sidtime - hours) * 60);
|
|
263
|
+
const seconds = Math.floor(((sidtime - hours) * 60 - minutes) * 60);
|
|
264
|
+
|
|
265
|
+
console.log('Current Date:', now.toLocaleDateString());
|
|
266
|
+
console.log('Julian Day:', jd.toFixed(4));
|
|
267
|
+
console.log('Sidereal Time:', hours + 'h ' + minutes + 'm ' + seconds + 's');
|
|
268
|
+
|
|
269
|
+
return {
|
|
270
|
+
julianDay: jd,
|
|
271
|
+
siderealTime: sidtime,
|
|
272
|
+
formatted: hours + ':' + minutes + ':' + seconds
|
|
273
|
+
};`
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
name: 'Ayanamsa',
|
|
277
|
+
description: 'Calculate ayanamsa values',
|
|
278
|
+
code: `// Ayanamsa Calculator
|
|
279
|
+
const jd = swisseph.julday(2024, 1, 1, 0.0);
|
|
280
|
+
|
|
281
|
+
// Set Lahiri ayanamsa
|
|
282
|
+
swisseph.set_sid_mode(swisseph.SE_SIDM_LAHIRI, 0, 0);
|
|
283
|
+
|
|
284
|
+
const ayanamsa = swisseph.get_ayanamsa(jd);
|
|
285
|
+
const name = swisseph.get_ayanamsa_name(swisseph.SE_SIDM_LAHIRI);
|
|
286
|
+
|
|
287
|
+
console.log('Ayanamsa System:', name);
|
|
288
|
+
console.log('Ayanamsa Value:', ayanamsa.toFixed(6) + '°');
|
|
289
|
+
|
|
290
|
+
// Calculate tropical vs sidereal Sun
|
|
291
|
+
const tropicalSun = swisseph.calc(jd, swisseph.SE_SUN, swisseph.SEFLG_SWIEPH);
|
|
292
|
+
const siderealSun = tropicalSun.longitude - ayanamsa;
|
|
293
|
+
|
|
294
|
+
console.log('\\nSun Position:');
|
|
295
|
+
console.log('Tropical:', tropicalSun.longitude.toFixed(2) + '°');
|
|
296
|
+
console.log('Sidereal:', siderealSun.toFixed(2) + '°');
|
|
297
|
+
|
|
298
|
+
return {
|
|
299
|
+
ayanamsa: ayanamsa,
|
|
300
|
+
tropical: tropicalSun.longitude,
|
|
301
|
+
sidereal: siderealSun
|
|
302
|
+
};`
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
name: 'Date Conversion',
|
|
306
|
+
description: 'Convert between date formats',
|
|
307
|
+
code: `// Date Conversion Examples
|
|
308
|
+
const date = { year: 2024, month: 6, day: 21, hour: 12.0 };
|
|
309
|
+
|
|
310
|
+
// Convert to Julian Day
|
|
311
|
+
const jd = swisseph.julday(date.year, date.month, date.day, date.hour);
|
|
312
|
+
console.log('Julian Day:', jd);
|
|
313
|
+
|
|
314
|
+
// Convert back to calendar
|
|
315
|
+
const revDate = swisseph.revjul(jd, 1);
|
|
316
|
+
console.log('Reverse:', revDate.year + '-' + revDate.month + '-' + revDate.day + ' ' + revDate.hour + 'h');
|
|
317
|
+
|
|
318
|
+
// Calculate day of week
|
|
319
|
+
const dow = swisseph.day_of_week(jd);
|
|
320
|
+
const days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
|
|
321
|
+
console.log('Day of Week:', days[dow]);
|
|
322
|
+
|
|
323
|
+
// Delta T
|
|
324
|
+
const deltaT = swisseph.deltat(jd);
|
|
325
|
+
console.log('Delta T:', (deltaT * 86400).toFixed(2) + ' seconds');
|
|
326
|
+
|
|
327
|
+
return {
|
|
328
|
+
julianDay: jd,
|
|
329
|
+
dayOfWeek: days[dow],
|
|
330
|
+
deltaT: deltaT
|
|
331
|
+
};`
|
|
332
|
+
}
|
|
333
|
+
]
|
|
334
|
+
};
|
|
335
|
+
},
|
|
336
|
+
async mounted() {
|
|
337
|
+
await this.init();
|
|
338
|
+
this.loadSavedPrograms();
|
|
339
|
+
},
|
|
340
|
+
methods: {
|
|
341
|
+
async init() {
|
|
342
|
+
try {
|
|
343
|
+
this.status = { text: 'Initializing...', color: 'text-yellow-600' };
|
|
344
|
+
this.swisseph = new SwissEph();
|
|
345
|
+
await this.swisseph.initSwissEph();
|
|
346
|
+
|
|
347
|
+
this.version = 'Swiss Ephemeris v' + this.swisseph.version();
|
|
348
|
+
this.status = { text: 'Ready', color: 'text-green-600' };
|
|
349
|
+
|
|
350
|
+
// Load first template by default
|
|
351
|
+
this.loadTemplate(this.templates[0]);
|
|
352
|
+
} catch (error) {
|
|
353
|
+
this.status = { text: 'Error: ' + error.message, color: 'text-red-600' };
|
|
354
|
+
}
|
|
355
|
+
},
|
|
356
|
+
loadTemplate(template) {
|
|
357
|
+
this.code = template.code;
|
|
358
|
+
this.output = [];
|
|
359
|
+
},
|
|
360
|
+
runCode() {
|
|
361
|
+
this.output = [];
|
|
362
|
+
|
|
363
|
+
// Create a custom console
|
|
364
|
+
const customConsole = {
|
|
365
|
+
log: (...args) => {
|
|
366
|
+
args.forEach(arg => {
|
|
367
|
+
if (typeof arg === 'object') {
|
|
368
|
+
this.output.push({
|
|
369
|
+
type: 'object',
|
|
370
|
+
value: JSON.stringify(arg, null, 2)
|
|
371
|
+
});
|
|
372
|
+
} else {
|
|
373
|
+
this.output.push({ type: 'log', value: String(arg) });
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
try {
|
|
380
|
+
// Create function with swisseph and console in scope
|
|
381
|
+
const func = new Function('swisseph', 'console', this.code);
|
|
382
|
+
const result = func(this.swisseph, customConsole);
|
|
383
|
+
|
|
384
|
+
if (result !== undefined) {
|
|
385
|
+
if (typeof result === 'object') {
|
|
386
|
+
this.output.push({
|
|
387
|
+
type: 'object',
|
|
388
|
+
value: JSON.stringify(result, null, 2)
|
|
389
|
+
});
|
|
390
|
+
} else {
|
|
391
|
+
this.output.push({ type: 'result', value: String(result) });
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
} catch (error) {
|
|
395
|
+
this.output.push({ type: 'error', value: error.message });
|
|
396
|
+
}
|
|
397
|
+
},
|
|
398
|
+
clearOutput() {
|
|
399
|
+
this.output = [];
|
|
400
|
+
},
|
|
401
|
+
saveProgram() {
|
|
402
|
+
const name = prompt('Enter a name for this program:');
|
|
403
|
+
if (!name) return;
|
|
404
|
+
|
|
405
|
+
const program = {
|
|
406
|
+
name: name,
|
|
407
|
+
code: this.code,
|
|
408
|
+
date: new Date().toLocaleString()
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
this.savedPrograms.push(program);
|
|
412
|
+
localStorage.setItem('swisseph_programs', JSON.stringify(this.savedPrograms));
|
|
413
|
+
},
|
|
414
|
+
loadSavedProgram(program) {
|
|
415
|
+
this.code = program.code;
|
|
416
|
+
this.output = [];
|
|
417
|
+
},
|
|
418
|
+
deleteSavedProgram(idx) {
|
|
419
|
+
if (confirm('Delete this program?')) {
|
|
420
|
+
this.savedPrograms.splice(idx, 1);
|
|
421
|
+
localStorage.setItem('swisseph_programs', JSON.stringify(this.savedPrograms));
|
|
422
|
+
}
|
|
423
|
+
},
|
|
424
|
+
loadSavedPrograms() {
|
|
425
|
+
const saved = localStorage.getItem('swisseph_programs');
|
|
426
|
+
if (saved) {
|
|
427
|
+
this.savedPrograms = JSON.parse(saved);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}).mount('#app');
|
|
432
|
+
</script>
|
|
433
|
+
</body>
|
|
434
|
+
</html>
|