jevstrudel.git / website / src / repl / util.mjs
util.mjsannotatedutil.mjssource205 lines · 6.3 KB · raw
1import { code2hash, errorLogger, evalScope, hash2code, logger } from '@strudel/core';
2import { settingPatterns } from '../settings.mjs';
3import { setVersionDefaults } from '@strudel/webaudio';
4import { getMetadata } from '../metadata_parser';
5import { isTauri } from '../tauri.mjs';
6import './Repl.css';
7import { createClient } from '@supabase/supabase-js';
8import { writeText } from '@tauri-apps/plugin-clipboard-manager';
9import { $featuredPatterns /* , loadDBPatterns */ } from '@src/user_pattern_utils.mjs';
10
11// Create a single supabase client for interacting with your database
12export const supabase = createClient(
13  'https://pidxdsxphlhzjnzmifth.supabase.co',
14  'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InBpZHhkc3hwaGxoempuem1pZnRoIiwicm9sZSI6ImFub24iLCJpYXQiOjE2NTYyMzA1NTYsImV4cCI6MTk3MTgwNjU1Nn0.bqlw7802fsWRnqU5BLYtmXk_k-D1VFmbkHMywWc15NM',
15);
16
17let dbLoaded;
18/* if (typeof window !== 'undefined') {
19  dbLoaded = loadDBPatterns();
20} */
21
22export async function initCode() {
23  // load code from url hash (either short hash from database or decode long hash)
24  try {
25    const initialUrl = window.location.href;
26    const hash = initialUrl.split('?')[1]?.split('#')?.[0]?.split('&')[0];
27    const codeParam = window.location.href.split('#')[1] || '';
28    if (codeParam) {
29      // looking like https://strudel.cc/#ImMzIGUzIg%3D%3D (hash length depends on code length)
30      return hash2code(codeParam);
31    } else if (hash) {
32      // looking like https://strudel.cc/?J01s5i1J0200 (fixed hash length)
33      return supabase
34        .from('code_v1')
35        .select('code')
36        .eq('hash', hash)
37        .then(({ data, error }) => {
38          if (error) {
39            console.warn('failed to load hash', error);
40          }
41          if (data.length) {
42            //console.log('load hash from database', hash);
43            return data[0].code;
44          }
45        });
46    }
47  } catch (err) {
48    console.warn('failed to decode', err);
49  }
50}
51
52export const parseJSON = (json) => {
53  json = json != null && json.length ? json : '{}';
54  try {
55    return JSON.parse(json);
56  } catch {
57    return '{}';
58  }
59};
60
61export async function getRandomTune() {
62  await dbLoaded;
63  const featuredTunes = Object.entries($featuredPatterns.get());
64  const randomItem = (arr) => arr[Math.floor(Math.random() * arr.length)];
65  const [_, data] = randomItem(featuredTunes);
66  return data;
67}
68
69export function loadModules() {
70  let modules = [
71    import('@strudel/core'),
72    import('@strudel/draw'),
73    import('@strudel/edo'),
74    import('@strudel/tonal'),
75    import('@strudel/mini'),
76    import('@strudel/xen'),
77    import('@strudel/webaudio'),
78    import('@strudel/codemirror'),
79    import('@strudel/hydra'),
80    import('@strudel/serial'),
81    import('@strudel/soundfonts'),
82    import('@strudel/csound'),
83    import('@strudel/tidal'),
84    import('@strudel/gamepad'),
85    import('@strudel/motion'),
86    import('@strudel/mqtt'),
87    import('@strudel/mondo'),
88    import('@strudel/dough'),
89    // jevstrudel: `jev({...})` and TypeSafe's choice/score/noul
90    import('../jev/jev.mjs'),
91  ];
92  if (isTauri()) {
93    modules = modules.concat([
94      import('@strudel/desktopbridge/loggerbridge.mjs'),
95      import('@strudel/desktopbridge/midibridge.mjs'),
96      import('@strudel/desktopbridge/oscbridge.mjs'),
97    ]);
98  } else {
99    modules = modules.concat([import('@strudel/midi'), import('@strudel/osc')]);
100  }
101
102  return evalScope(settingPatterns, ...modules);
103}
104// confirm dialog is a promise in webkit and a boolean in other browsers... normalize it to be a promise everywhere
105export function confirmDialog(msg) {
106  const confirmed = confirm(msg);
107  if (confirmed instanceof Promise) {
108    return confirmed;
109  }
110  return new Promise((resolve) => {
111    resolve(confirmed);
112  });
113}
114export const SETTING_CHANGE_RELOAD_MSG = 'Changing this setting requires the window to reload itself. OK?';
115
116export function confirmAndReloadPage(onSuccess) {
117  confirmDialog(SETTING_CHANGE_RELOAD_MSG).then((r) => {
118    if (r == true) {
119      try {
120        onSuccess();
121        return window.location.reload();
122      } catch (e) {
123        errorLogger(e);
124      }
125    }
126  });
127}
128//RIP due to SPAM
129// let lastShared;
130// export async function shareCode(codeToShare) {
131//   // const codeToShare = activeCode || code;
132//   if (lastShared === codeToShare) {
133//     logger(`Link already generated!`, 'error');
134//     return;
135//   }
136
137//   confirmDialog(
138//     'Do you want your pattern to be public? If no, press cancel and you will get just a private link.',
139//   ).then(async (isPublic) => {
140//     const hash = nanoid(12);
141//     const shareUrl = window.location.origin + window.location.pathname + '?' + hash;
142//     const { error } = await supabase.from('code_v1').insert([{ code: codeToShare, hash, ['public']: isPublic }]);
143//     if (!error) {
144//       lastShared = codeToShare;
145//       // copy shareUrl to clipboard
146//       if (isTauri()) {
147//         await writeText(shareUrl);
148//       } else {
149//         await navigator.clipboard.writeText(shareUrl);
150//       }
151//       const message = `Link copied to clipboard: ${shareUrl}`;
152//       alert(message);
153//       // alert(message);
154//       logger(message, 'highlight');
155//     } else {
156//       console.log('error', error);
157//       const message = `Error: ${error.message}`;
158//       // alert(message);
159//       logger(message);
160//     }
161//   });
162// }
163
164export async function shareCode(codeToShare) {
165  try {
166    const hash = '#' + code2hash(codeToShare);
167    const shareUrl = window.location.origin + window.location.pathname + hash;
168    if (isTauri()) {
169      await writeText(shareUrl);
170    } else {
171      await navigator.clipboard.writeText(shareUrl);
172    }
173    const message = `Link copied to clipboard!`;
174    alert(message);
175    logger(message, 'highlight');
176  } catch (e) {
177    console.error(e);
178  }
179}
180
181export const isIframe = () => window.location !== window.parent.location;
182function isCrossOriginFrame() {
183  try {
184    return !window.top.location.hostname;
185  } catch (e) {
186    return true;
187  }
188}
189
190export const isUdels = () => {
191  if (isCrossOriginFrame()) {
192    return false;
193  }
194  return window.top?.location?.pathname.includes('udels');
195};
196
197export function setVersionDefaultsFrom(code) {
198  try {
199    const metadata = getMetadata(code);
200    setVersionDefaults(metadata.version);
201  } catch (err) {
202    console.error('Error parsing metadata..');
203    console.error(err);
204  }
205}