Initial commit
Co-Authored-By: Eric Tuvesson <eric.tuvesson@gmail.com> Co-Authored-By: mikaeltellhed <2311083+mikaeltellhed@users.noreply.github.com> Co-Authored-By: kotte <14197736+mrtamagotchi@users.noreply.github.com> Co-Authored-By: Anders Larsson <64838990+anders-topp@users.noreply.github.com> Co-Authored-By: Johan <4934465+joolsus@users.noreply.github.com> Co-Authored-By: Tore Knudsen <18231882+torekndsn@users.noreply.github.com> Co-Authored-By: victoratndl <99176179+victoratndl@users.noreply.github.com>
9
packages/noodl-editor/src/editor/codicon.md
Normal file
@@ -0,0 +1,9 @@
|
||||
Font used by Monaco Editor for the icons.
|
||||
|
||||
The issue:
|
||||
|
||||
Monaco Editor is importing the font with CSS url.
|
||||
`url('./codicon.ttf')` and since we don't transform the urls because
|
||||
of the old templates this causes a problem here.
|
||||
|
||||
Easy solution was just to move this single font file here and everything works fine.
|
||||
BIN
packages/noodl-editor/src/editor/codicon.ttf
Normal file
28
packages/noodl-editor/src/editor/index.html
Normal file
@@ -0,0 +1,28 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Noodl</title>
|
||||
<link href="../assets/lib/fontawesome/css/font-awesome.min.css" rel="stylesheet" />
|
||||
<link href="../assets/css/style.css" rel="stylesheet" />
|
||||
|
||||
<!-- Libs that don't have npm packaged -->
|
||||
<script type="text/javascript" src="../assets/lib/jquery-min.js"></script>
|
||||
<script type="text/javascript" src="../assets/lib/jquery.autosize.min.js"></script>
|
||||
|
||||
<link rel="preload" href="../assets/Inter/Inter-Light.ttf" as="font" />
|
||||
<link rel="preload" href="../assets/Inter/Inter-Regular.ttf" as="font" />
|
||||
<link rel="preload" href="../assets/Inter/Inter-Medium.ttf" as="font" />
|
||||
<link rel="preload" href="../assets/Inter/Inter-Bold.ttf" as="font" />
|
||||
<link rel="preload" href="../assets/Inter/Inter-SemiBold.ttf" as="font" />
|
||||
|
||||
<script>
|
||||
const path = process.env.devMode !== 'yes' ? '.' : 'http://localhost:8080/src/editor';
|
||||
document.write(`<script defer src="${path}/index.bundle.js"><\/script>`);
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root" style="position: relative; width: 100vw; height: 100vh; overflow: hidden"></div>
|
||||
</body>
|
||||
</html>
|
||||
87
packages/noodl-editor/src/editor/index.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import * as remote from '@electron/remote';
|
||||
import { ipcRenderer } from 'electron';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
import './process-setup';
|
||||
|
||||
import { EventDispatcher } from '../shared/utils/EventDispatcher';
|
||||
import { NodeLibrary } from './src/models/nodelibrary';
|
||||
import { ProjectModel } from './src/models/projectmodel';
|
||||
|
||||
//Design tokens for later
|
||||
// import '../../../noodl-core-ui/src/styles/custom-properties/animations.css';
|
||||
// import '../../../noodl-core-ui/src/styles/custom-properties/fonts.css';
|
||||
// import '../../../noodl-core-ui/src/styles/custom-properties/colors.css';
|
||||
import '../editor/src/styles/custom-properties/animations.css';
|
||||
import '../editor/src/styles/custom-properties/fonts.css';
|
||||
import '../editor/src/styles/custom-properties/colors.css';
|
||||
|
||||
import Router from './src/router';
|
||||
|
||||
ipcRenderer.on('open-noodl-uri', async (event, uri) => {
|
||||
if (uri.startsWith('noodl:import/http')) {
|
||||
console.log('import: ', uri);
|
||||
EventDispatcher.instance.emit('importFromUrl', uri.substring('noodl:import/'.length));
|
||||
}
|
||||
});
|
||||
|
||||
ipcRenderer.on('import-projectmetadata', (event, data) => {
|
||||
ProjectModel.instance.mergeMetadata(data);
|
||||
});
|
||||
|
||||
function setupViewerIpc() {
|
||||
ipcRenderer.on('viewer-refreshed', () => {
|
||||
EventDispatcher.instance.emit('viewer-refreshed');
|
||||
});
|
||||
|
||||
ipcRenderer.on('viewer-closed', () => {
|
||||
EventDispatcher.instance.emit('viewer-closed');
|
||||
});
|
||||
}
|
||||
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
// Register node adapters
|
||||
require('./src/models/NodeTypeAdapters/registeradapters');
|
||||
|
||||
// Disable context menu
|
||||
$('body').on('contextmenu', function () {
|
||||
return false;
|
||||
});
|
||||
|
||||
ipcRenderer.on('showAutoUpdatePopup', () => {
|
||||
//@ts-expect-error
|
||||
window._hasNewAutoUpdateAvailable = true;
|
||||
});
|
||||
|
||||
setupViewerIpc();
|
||||
|
||||
ipcRenderer.on('window-focused', () => {
|
||||
EventDispatcher.instance.notifyListeners('window-focused');
|
||||
});
|
||||
|
||||
document.addEventListener('mousedown', () => {
|
||||
remote.getCurrentWindow().focus();
|
||||
});
|
||||
|
||||
// Activity detector
|
||||
let lastActiveTime = +new Date();
|
||||
document.addEventListener('mousedown', function (evt) {
|
||||
const now = +new Date();
|
||||
if (now > lastActiveTime + 10 * 60 * 1000) {
|
||||
// Wake up after 10 minutes of inactivity
|
||||
EventDispatcher.instance.notifyListeners('wakeup');
|
||||
}
|
||||
lastActiveTime = now;
|
||||
});
|
||||
|
||||
EventDispatcher.instance.on('ProjectModel.instanceWillChange', () => {
|
||||
//@ts-expect-error
|
||||
window.NodeLibraryData = undefined;
|
||||
NodeLibrary.instance.reload();
|
||||
}, null);
|
||||
|
||||
// Create the main element
|
||||
const rootElement = document.getElementById('root');
|
||||
ReactDOM.render(React.createElement(Router, { uri: remote.process.env.noodlURI }), rootElement);
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[128],{93128:(e,n,t)=>{t.r(n),t.d(n,{c:()=>m});var i=t(96539),l=Object.defineProperty,o=(e,n)=>l(e,"name",{value:n,configurable:!0});function r(e,n){return n.forEach((function(n){n&&"string"!=typeof n&&!Array.isArray(n)&&Object.keys(n).forEach((function(t){if("default"!==t&&!(t in e)){var i=Object.getOwnPropertyDescriptor(n,t);Object.defineProperty(e,t,i.get?i:{enumerable:!0,get:function(){return n[t]}})}}))})),Object.freeze(e)}o(r,"_mergeNamespaces");var a={exports:{}};!function(e){var n={},t=/[^\s\u00a0]/,i=e.Pos,l=e.cmpPos;function r(e){var n=e.search(t);return-1==n?0:n}function a(e,n,t){return/\bstring\b/.test(e.getTokenTypeAt(i(n.line,0)))&&!/^[\'\"\`]/.test(t)}function c(e,n){var t=e.getMode();return!1!==t.useInnerComments&&t.innerMode?e.getModeAt(n):t}o(r,"firstNonWS"),e.commands.toggleComment=function(e){e.toggleComment()},e.defineExtension("toggleComment",(function(e){e||(e=n);for(var t=this,l=1/0,o=this.listSelections(),r=null,a=o.length-1;a>=0;a--){var c=o[a].from(),m=o[a].to();c.line>=l||(m.line>=l&&(m=i(l,0)),l=c.line,null==r?t.uncomment(c,m,e)?r="un":(t.lineComment(c,m,e),r="line"):"un"==r?t.uncomment(c,m,e):t.lineComment(c,m,e))}})),o(a,"probablyInsideString"),o(c,"getMode"),e.defineExtension("lineComment",(function(e,l,o){o||(o=n);var m=this,s=c(m,e),f=m.getLine(e.line);if(null!=f&&!a(m,e,f)){var g=o.lineComment||s.lineComment;if(g){var u=Math.min(0!=l.ch||l.line==e.line?l.line+1:l.line,m.lastLine()+1),d=null==o.padding?" ":o.padding,h=o.commentBlankLines||e.line==l.line;m.operation((function(){if(o.indent){for(var n=null,l=e.line;l<u;++l){var a=(c=m.getLine(l)).slice(0,r(c));(null==n||n.length>a.length)&&(n=a)}for(l=e.line;l<u;++l){var c=m.getLine(l),s=n.length;(h||t.test(c))&&(c.slice(0,s)!=n&&(s=r(c)),m.replaceRange(n+g+d,i(l,0),i(l,s)))}}else for(l=e.line;l<u;++l)(h||t.test(m.getLine(l)))&&m.replaceRange(g+d,i(l,0))}))}else(o.blockCommentStart||s.blockCommentStart)&&(o.fullLines=!0,m.blockComment(e,l,o))}})),e.defineExtension("blockComment",(function(e,o,r){r||(r=n);var a=this,m=c(a,e),s=r.blockCommentStart||m.blockCommentStart,f=r.blockCommentEnd||m.blockCommentEnd;if(s&&f){if(!/\bcomment\b/.test(a.getTokenTypeAt(i(e.line,0)))){var g=Math.min(o.line,a.lastLine());g!=e.line&&0==o.ch&&t.test(a.getLine(g))&&--g;var u=null==r.padding?" ":r.padding;e.line>g||a.operation((function(){if(0!=r.fullLines){var n=t.test(a.getLine(g));a.replaceRange(u+f,i(g)),a.replaceRange(s+u,i(e.line,0));var c=r.blockCommentLead||m.blockCommentLead;if(null!=c)for(var d=e.line+1;d<=g;++d)(d!=g||n)&&a.replaceRange(c+u,i(d,0))}else{var h=0==l(a.getCursor("to"),o),p=!a.somethingSelected();a.replaceRange(f,o),h&&a.setSelection(p?o:a.getCursor("from"),o),a.replaceRange(s,e)}}))}}else(r.lineComment||m.lineComment)&&0!=r.fullLines&&a.lineComment(e,o,r)})),e.defineExtension("uncomment",(function(e,l,o){o||(o=n);var r,a=this,m=c(a,e),s=Math.min(0!=l.ch||l.line==e.line?l.line:l.line-1,a.lastLine()),f=Math.min(e.line,s),g=o.lineComment||m.lineComment,u=[],d=null==o.padding?" ":o.padding;e:if(g){for(var h=f;h<=s;++h){var p=a.getLine(h),v=p.indexOf(g);if(v>-1&&!/comment/.test(a.getTokenTypeAt(i(h,v+1)))&&(v=-1),-1==v&&t.test(p))break e;if(v>-1&&t.test(p.slice(0,v)))break e;u.push(p)}if(a.operation((function(){for(var e=f;e<=s;++e){var n=u[e-f],t=n.indexOf(g),l=t+g.length;t<0||(n.slice(l,l+d.length)==d&&(l+=d.length),r=!0,a.replaceRange("",i(e,t),i(e,l)))}})),r)return!0}var b=o.blockCommentStart||m.blockCommentStart,C=o.blockCommentEnd||m.blockCommentEnd;if(!b||!C)return!1;var k=o.blockCommentLead||m.blockCommentLead,L=a.getLine(f),x=L.indexOf(b);if(-1==x)return!1;var O=s==f?L:a.getLine(s),y=O.indexOf(C,s==f?x+b.length:0),S=i(f,x+1),R=i(s,y+1);if(-1==y||!/comment/.test(a.getTokenTypeAt(S))||!/comment/.test(a.getTokenTypeAt(R))||a.getRange(S,R,"\n").indexOf(C)>-1)return!1;var T=L.lastIndexOf(b,e.ch),E=-1==T?-1:L.slice(0,e.ch).indexOf(C,T+b.length);if(-1!=T&&-1!=E&&E+C.length!=e.ch)return!1;E=O.indexOf(C,l.ch);var M=O.slice(l.ch).lastIndexOf(b,E-l.ch);return T=-1==E||-1==M?-1:l.ch+M,(-1==E||-1==T||T==l.ch)&&(a.operation((function(){a.replaceRange("",i(s,y-(d&&O.slice(y-d.length,y)==d?d.length:0)),i(s,y+C.length));var e=x+b.length;if(d&&L.slice(e,e+d.length)==d&&(e+=d.length),a.replaceRange("",i(f,x),i(f,e)),k)for(var n=f+1;n<=s;++n){var l=a.getLine(n),o=l.indexOf(k);if(-1!=o&&!t.test(l.slice(0,o))){var r=o+k.length;d&&l.slice(r,r+d.length)==d&&(r+=d.length),a.replaceRange("",i(n,o),i(n,r))}}})),!0)}))}(i.a.exports);var c=a.exports,m=Object.freeze(r({__proto__:null,[Symbol.toStringTag]:"Module",default:c},[a.exports]))}}]);
|
||||
@@ -0,0 +1 @@
|
||||
"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[269,31],{81031:(e,o,t)=>{t.r(o),t.d(o,{a:()=>u,d:()=>l});var n=t(96539),r=Object.defineProperty,i=(e,o)=>r(e,"name",{value:o,configurable:!0});function a(e,o){return o.forEach((function(o){o&&"string"!=typeof o&&!Array.isArray(o)&&Object.keys(o).forEach((function(t){if("default"!==t&&!(t in e)){var n=Object.getOwnPropertyDescriptor(o,t);Object.defineProperty(e,t,n.get?n:{enumerable:!0,get:function(){return o[t]}})}}))})),Object.freeze(e)}i(a,"_mergeNamespaces");var u={exports:{}};!function(e){function o(o,t,n){var r,i=o.getWrapperElement();return(r=i.appendChild(document.createElement("div"))).className=n?"CodeMirror-dialog CodeMirror-dialog-bottom":"CodeMirror-dialog CodeMirror-dialog-top","string"==typeof t?r.innerHTML=t:r.appendChild(t),e.addClass(i,"dialog-opened"),r}function t(e,o){e.state.currentNotificationClose&&e.state.currentNotificationClose(),e.state.currentNotificationClose=o}i(o,"dialogDiv"),i(t,"closeNotification"),e.defineExtension("openDialog",(function(n,r,a){a||(a={}),t(this,null);var u=o(this,n,a.bottom),s=!1,l=this;function c(o){if("string"==typeof o)p.value=o;else{if(s)return;s=!0,e.rmClass(u.parentNode,"dialog-opened"),u.parentNode.removeChild(u),l.focus(),a.onClose&&a.onClose(u)}}i(c,"close");var f,p=u.getElementsByTagName("input")[0];return p?(p.focus(),a.value&&(p.value=a.value,!1!==a.selectValueOnOpen&&p.select()),a.onInput&&e.on(p,"input",(function(e){a.onInput(e,p.value,c)})),a.onKeyUp&&e.on(p,"keyup",(function(e){a.onKeyUp(e,p.value,c)})),e.on(p,"keydown",(function(o){a&&a.onKeyDown&&a.onKeyDown(o,p.value,c)||((27==o.keyCode||!1!==a.closeOnEnter&&13==o.keyCode)&&(p.blur(),e.e_stop(o),c()),13==o.keyCode&&r(p.value,o))})),!1!==a.closeOnBlur&&e.on(u,"focusout",(function(e){null!==e.relatedTarget&&c()}))):(f=u.getElementsByTagName("button")[0])&&(e.on(f,"click",(function(){c(),l.focus()})),!1!==a.closeOnBlur&&e.on(f,"blur",c),f.focus()),c})),e.defineExtension("openConfirm",(function(n,r,a){t(this,null);var u=o(this,n,a&&a.bottom),s=u.getElementsByTagName("button"),l=!1,c=this,f=1;function p(){l||(l=!0,e.rmClass(u.parentNode,"dialog-opened"),u.parentNode.removeChild(u),c.focus())}i(p,"close"),s[0].focus();for(var d=0;d<s.length;++d){var m=s[d];!function(o){e.on(m,"click",(function(t){e.e_preventDefault(t),p(),o&&o(c)}))}(r[d]),e.on(m,"blur",(function(){--f,setTimeout((function(){f<=0&&p()}),200)})),e.on(m,"focus",(function(){++f}))}})),e.defineExtension("openNotification",(function(n,r){t(this,c);var a,u=o(this,n,r&&r.bottom),s=!1,l=r&&void 0!==r.duration?r.duration:5e3;function c(){s||(s=!0,clearTimeout(a),e.rmClass(u.parentNode,"dialog-opened"),u.parentNode.removeChild(u))}return i(c,"close"),e.on(u,"click",(function(o){e.e_preventDefault(o),c()})),l&&(a=setTimeout(c,l)),c}))}(n.a.exports);var s=u.exports,l=Object.freeze(a({__proto__:null,[Symbol.toStringTag]:"Module",default:s},[u.exports]))},18269:(e,o,t)=>{t.r(o),t.d(o,{j:()=>c});var n=t(96539),r=t(81031),i=Object.defineProperty,a=(e,o)=>i(e,"name",{value:o,configurable:!0});function u(e,o){return o.forEach((function(o){o&&"string"!=typeof o&&!Array.isArray(o)&&Object.keys(o).forEach((function(t){if("default"!==t&&!(t in e)){var n=Object.getOwnPropertyDescriptor(o,t);Object.defineProperty(e,t,n.get?n:{enumerable:!0,get:function(){return o[t]}})}}))})),Object.freeze(e)}a(u,"_mergeNamespaces");var s={exports:{}};!function(e){function o(e,o,t,n,r){e.openDialog?e.openDialog(o,r,{value:n,selectValueOnOpen:!0,bottom:e.options.search.bottom}):r(prompt(t,n))}function t(e){return e.phrase("Jump to line:")+' <input type="text" style="width: 10em" class="CodeMirror-search-field"/> <span style="color: #888" class="CodeMirror-search-hint">'+e.phrase("(Use line:column or scroll% syntax)")+"</span>"}function n(e,o){var t=Number(o);return/^[-+]/.test(o)?e.getCursor().line+t:t-1}e.defineOption("search",{bottom:!1}),a(o,"dialog"),a(t,"getJumpDialog"),a(n,"interpretLine"),e.commands.jumpToLine=function(e){var r=e.getCursor();o(e,t(e),e.phrase("Jump to line:"),r.line+1+":"+r.ch,(function(o){var t;if(o)if(t=/^\s*([\+\-]?\d+)\s*\:\s*(\d+)\s*$/.exec(o))e.setCursor(n(e,t[1]),Number(t[2]));else if(t=/^\s*([\+\-]?\d+(\.\d+)?)\%\s*/.exec(o)){var i=Math.round(e.lineCount()*Number(t[1])/100);/^[-+]/.test(t[1])&&(i=r.line+i+1),e.setCursor(i-1,r.ch)}else(t=/^\s*\:?\s*([\+\-]?\d+)\s*/.exec(o))&&e.setCursor(n(e,t[1]),r.ch)}))},e.keyMap.default["Alt-G"]="jumpToLine"}(n.a.exports,r.a.exports);var l=s.exports,c=Object.freeze(u({__proto__:null,[Symbol.toStringTag]:"Module",default:l},[s.exports]))}}]);
|
||||
@@ -0,0 +1 @@
|
||||
"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[31],{81031:(e,o,n)=>{n.r(o),n.d(o,{a:()=>u,d:()=>c});var t=n(96539),r=Object.defineProperty,i=(e,o)=>r(e,"name",{value:o,configurable:!0});function a(e,o){return o.forEach((function(o){o&&"string"!=typeof o&&!Array.isArray(o)&&Object.keys(o).forEach((function(n){if("default"!==n&&!(n in e)){var t=Object.getOwnPropertyDescriptor(o,n);Object.defineProperty(e,n,t.get?t:{enumerable:!0,get:function(){return o[n]}})}}))})),Object.freeze(e)}i(a,"_mergeNamespaces");var u={exports:{}};!function(e){function o(o,n,t){var r,i=o.getWrapperElement();return(r=i.appendChild(document.createElement("div"))).className=t?"CodeMirror-dialog CodeMirror-dialog-bottom":"CodeMirror-dialog CodeMirror-dialog-top","string"==typeof n?r.innerHTML=n:r.appendChild(n),e.addClass(i,"dialog-opened"),r}function n(e,o){e.state.currentNotificationClose&&e.state.currentNotificationClose(),e.state.currentNotificationClose=o}i(o,"dialogDiv"),i(n,"closeNotification"),e.defineExtension("openDialog",(function(t,r,a){a||(a={}),n(this,null);var u=o(this,t,a.bottom),l=!1,c=this;function s(o){if("string"==typeof o)d.value=o;else{if(l)return;l=!0,e.rmClass(u.parentNode,"dialog-opened"),u.parentNode.removeChild(u),c.focus(),a.onClose&&a.onClose(u)}}i(s,"close");var f,d=u.getElementsByTagName("input")[0];return d?(d.focus(),a.value&&(d.value=a.value,!1!==a.selectValueOnOpen&&d.select()),a.onInput&&e.on(d,"input",(function(e){a.onInput(e,d.value,s)})),a.onKeyUp&&e.on(d,"keyup",(function(e){a.onKeyUp(e,d.value,s)})),e.on(d,"keydown",(function(o){a&&a.onKeyDown&&a.onKeyDown(o,d.value,s)||((27==o.keyCode||!1!==a.closeOnEnter&&13==o.keyCode)&&(d.blur(),e.e_stop(o),s()),13==o.keyCode&&r(d.value,o))})),!1!==a.closeOnBlur&&e.on(u,"focusout",(function(e){null!==e.relatedTarget&&s()}))):(f=u.getElementsByTagName("button")[0])&&(e.on(f,"click",(function(){s(),c.focus()})),!1!==a.closeOnBlur&&e.on(f,"blur",s),f.focus()),s})),e.defineExtension("openConfirm",(function(t,r,a){n(this,null);var u=o(this,t,a&&a.bottom),l=u.getElementsByTagName("button"),c=!1,s=this,f=1;function d(){c||(c=!0,e.rmClass(u.parentNode,"dialog-opened"),u.parentNode.removeChild(u),s.focus())}i(d,"close"),l[0].focus();for(var p=0;p<l.length;++p){var g=l[p];!function(o){e.on(g,"click",(function(n){e.e_preventDefault(n),d(),o&&o(s)}))}(r[p]),e.on(g,"blur",(function(){--f,setTimeout((function(){f<=0&&d()}),200)})),e.on(g,"focus",(function(){++f}))}})),e.defineExtension("openNotification",(function(t,r){n(this,s);var a,u=o(this,t,r&&r.bottom),l=!1,c=r&&void 0!==r.duration?r.duration:5e3;function s(){l||(l=!0,clearTimeout(a),e.rmClass(u.parentNode,"dialog-opened"),u.parentNode.removeChild(u))}return i(s,"close"),e.on(u,"click",(function(o){e.e_preventDefault(o),s()})),c&&(a=setTimeout(s,c)),s}))}(t.a.exports);var l=u.exports,c=Object.freeze(a({__proto__:null,[Symbol.toStringTag]:"Module",default:l},[u.exports]))}}]);
|
||||
@@ -0,0 +1 @@
|
||||
"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[590],{84590:(e,o,t)=>{t.r(o);var n=t(96539),i=(t(71254),t(27378),t(31542),Object.defineProperty),r=(e,o)=>i(e,"name",{value:o,configurable:!0});function u(e){return{options:e instanceof Function?{render:e}:!0===e?{}:e}}function m(e){const o=e.state.info.options;return(null==o?void 0:o.hoverTime)||500}function s(e,o){const t=e.state.info,i=o.target||o.srcElement;if(!(i instanceof HTMLElement))return;if("SPAN"!==i.nodeName||void 0!==t.hoverTimeout)return;const u=i.getBoundingClientRect(),s=r((function(){clearTimeout(t.hoverTimeout),t.hoverTimeout=setTimeout(p,c)}),"onMouseMove"),f=r((function(){n.C.off(document,"mousemove",s),n.C.off(e.getWrapperElement(),"mouseout",f),clearTimeout(t.hoverTimeout),t.hoverTimeout=void 0}),"onMouseOut"),p=r((function(){n.C.off(document,"mousemove",s),n.C.off(e.getWrapperElement(),"mouseout",f),t.hoverTimeout=void 0,a(e,u)}),"onHover"),c=m(e);t.hoverTimeout=setTimeout(p,c),n.C.on(document,"mousemove",s),n.C.on(e.getWrapperElement(),"mouseout",f)}function a(e,o){const t=e.coordsChar({left:(o.left+o.right)/2,top:(o.top+o.bottom)/2}),n=e.state.info.options,i=n.render||e.getHelper(t,"info");if(i){const r=e.getTokenAt(t,!0);if(r){const u=i(r,n,e,t);u&&f(e,o,u)}}}function f(e,o,t){const i=document.createElement("div");i.className="CodeMirror-info",i.appendChild(t),document.body.appendChild(i);const u=i.getBoundingClientRect(),m=window.getComputedStyle(i),s=u.right-u.left+parseFloat(m.marginLeft)+parseFloat(m.marginRight),a=u.bottom-u.top+parseFloat(m.marginTop)+parseFloat(m.marginBottom);let f=o.bottom;a>window.innerHeight-o.bottom-15&&o.top>window.innerHeight-o.bottom&&(f=o.top-a),f<0&&(f=o.bottom);let p,c=Math.max(0,window.innerWidth-s-15);c>o.left&&(c=o.left),i.style.opacity="1",i.style.top=f+"px",i.style.left=c+"px";const l=r((function(){clearTimeout(p)}),"onMouseOverPopup"),d=r((function(){clearTimeout(p),p=setTimeout(v,200)}),"onMouseOut"),v=r((function(){n.C.off(i,"mouseover",l),n.C.off(i,"mouseout",d),n.C.off(e.getWrapperElement(),"mouseout",d),i.style.opacity?(i.style.opacity="0",setTimeout((()=>{i.parentNode&&i.parentNode.removeChild(i)}),600)):i.parentNode&&i.parentNode.removeChild(i)}),"hidePopup");n.C.on(i,"mouseover",l),n.C.on(i,"mouseout",d),n.C.on(e.getWrapperElement(),"mouseout",d)}n.C.defineOption("info",!1,((e,o,t)=>{if(t&&t!==n.C.Init){const o=e.state.info.onMouseOver;n.C.off(e.getWrapperElement(),"mouseover",o),clearTimeout(e.state.info.hoverTimeout),delete e.state.info}if(o){const t=e.state.info=u(o);t.onMouseOver=s.bind(null,e),n.C.on(e.getWrapperElement(),"mouseover",t.onMouseOver)}})),r(u,"createState"),r(m,"getHoverTime"),r(s,"onMouseOver"),r(a,"onMouseHover"),r(f,"showPopup")}}]);
|
||||
@@ -0,0 +1 @@
|
||||
"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[688],{89663:(e,t,n)=>{n.r(t);var r=n(96539),l=n(71254),a=n(87756),i=(n(27378),n(31542),Object.defineProperty),s=(e,t)=>i(e,"name",{value:t,configurable:!0});function o(e,t){var n,r;const l=e.levels;return((l&&0!==l.length?l[l.length-1]-((null===(n=this.electricInput)||void 0===n?void 0:n.test(t))?1:0):e.indentLevel)||0)*((null===(r=this.config)||void 0===r?void 0:r.indentUnit)||0)}s(o,"indent");const u=s((e=>{const t=(0,a.o)({eatWhitespace:e=>e.eatWhile(l.i),lexRules:l.L,parseRules:l.P,editorConfig:{tabSize:e.tabSize}});return{config:e,startState:t.startState,token:t.token,indent:o,electricInput:/^\s*[})\]]/,fold:"brace",lineComment:"#",closeBrackets:{pairs:'()[]{}""',explode:"()[]{}"}}}),"graphqlModeFactory");r.C.defineMode("graphql",u)},87756:(e,t,n)=>{n.d(t,{o:()=>s});var r=n(71254),l=n(57554),a=Object.defineProperty,i=(e,t)=>a(e,"name",{value:t,configurable:!0});function s(e={eatWhitespace:e=>e.eatWhile(r.i),lexRules:r.L,parseRules:r.P,editorConfig:{}}){return{startState(){const t={level:0,step:0,name:null,kind:null,type:null,rule:null,needsSeparator:!1,prevState:null};return p(e.parseRules,t,l.h.DOCUMENT),t},token:(t,n)=>o(t,n,e)}}function o(e,t,n){var r;if(t.inBlockstring)return e.match(/.*"""/)?(t.inBlockstring=!1,"string"):(e.skipToEnd(),"string");const{lexRules:l,parseRules:a,eatWhitespace:i,editorConfig:s}=n;if(t.rule&&0===t.rule.length?d(t):t.needsAdvance&&(t.needsAdvance=!1,f(t,!0)),e.sol()){const n=(null==s?void 0:s.tabSize)||2;t.indentLevel=Math.floor(e.indentation()/n)}if(i(e))return"ws";const o=S(l,e);if(!o)return e.match(/\S+/)||e.match(/\s/),p(c,t,"Invalid"),"invalidchar";if("Comment"===o.kind)return p(c,t,"Comment"),"comment";const v=u({},t);if("Punctuation"===o.kind)if(/^[{([]/.test(o.value))void 0!==t.indentLevel&&(t.levels=(t.levels||[]).concat(t.indentLevel+1));else if(/^[})\]]/.test(o.value)){const e=t.levels=(t.levels||[]).slice(0,-1);t.indentLevel&&e.length>0&&e[e.length-1]<t.indentLevel&&(t.indentLevel=e[e.length-1])}for(;t.rule;){let n="function"==typeof t.rule?0===t.step?t.rule(o,e):null:t.rule[t.step];if(t.needsSeparator&&(n=null==n?void 0:n.separator),n){if(n.ofRule&&(n=n.ofRule),"string"==typeof n){p(a,t,n);continue}if(null===(r=n.match)||void 0===r?void 0:r.call(n,o))return n.update&&n.update(t,o),"Punctuation"===o.kind?f(t,!0):t.needsAdvance=!0,n.style}h(t)}return u(t,v),p(c,t,"Invalid"),"invalidchar"}function u(e,t){const n=Object.keys(t);for(let r=0;r<n.length;r++)e[n[r]]=t[n[r]];return e}i(s,"onlineParser"),i(o,"getToken"),i(u,"assign");const c={Invalid:[],Comment:[]};function p(e,t,n){if(!e[n])throw new TypeError("Unknown rule: "+n);t.prevState=Object.assign({},t),t.kind=n,t.name=null,t.type=null,t.rule=e[n],t.step=0,t.needsSeparator=!1}function d(e){e.prevState&&(e.kind=e.prevState.kind,e.name=e.prevState.name,e.type=e.prevState.type,e.rule=e.prevState.rule,e.step=e.prevState.step,e.needsSeparator=e.prevState.needsSeparator,e.prevState=e.prevState.prevState)}function f(e,t){var n;if(v(e)&&e.rule){const n=e.rule[e.step];if(n.separator){const t=n.separator;if(e.needsSeparator=!e.needsSeparator,!e.needsSeparator&&t.ofRule)return}if(t)return}for(e.needsSeparator=!1,e.step++;e.rule&&!(Array.isArray(e.rule)&&e.step<e.rule.length);)d(e),e.rule&&(v(e)?(null===(n=e.rule)||void 0===n?void 0:n[e.step].separator)&&(e.needsSeparator=!e.needsSeparator):(e.needsSeparator=!1,e.step++))}function v(e){const t=Array.isArray(e.rule)&&"string"!=typeof e.rule[e.step]&&e.rule[e.step];return t&&t.isList}function h(e){for(;e.rule&&(!Array.isArray(e.rule)||!e.rule[e.step].ofRule);)d(e);e.rule&&f(e,!1)}function S(e,t){const n=Object.keys(e);for(let r=0;r<n.length;r++){const l=t.match(e[n[r]]);if(l&&l instanceof Array)return{kind:n[r],value:l[0]}}}i(p,"pushRule"),i(d,"popRule"),i(f,"advanceRule"),i(v,"isList"),i(h,"unsuccessful"),i(S,"lex")}}]);
|
||||
@@ -0,0 +1 @@
|
||||
"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[725],{43725:(e,r,n)=>{n.r(r),n.d(r,{b:()=>s});var t=n(96539),i=Object.defineProperty,o=(e,r)=>i(e,"name",{value:r,configurable:!0});function l(e,r){return r.forEach((function(r){r&&"string"!=typeof r&&!Array.isArray(r)&&Object.keys(r).forEach((function(n){if("default"!==n&&!(n in e)){var t=Object.getOwnPropertyDescriptor(r,n);Object.defineProperty(e,n,t.get?t:{enumerable:!0,get:function(){return r[n]}})}}))})),Object.freeze(e)}o(l,"_mergeNamespaces");var f={exports:{}};!function(e){function r(r){return function(n,t){var i=t.line,l=n.getLine(i);function f(r){for(var o,f=t.ch,a=0;;){var s=f<=0?-1:l.lastIndexOf(r[0],f-1);if(-1!=s){if(1==a&&s<t.ch)break;if(o=n.getTokenTypeAt(e.Pos(i,s+1)),!/^(comment|string)/.test(o))return{ch:s+1,tokenType:o,pair:r};f=s-1}else{if(1==a)break;a=1,f=l.length}}}function a(r){var t,o,l=1,f=n.lastLine(),a=r.ch;e:for(var s=i;s<=f;++s)for(var u=n.getLine(s),c=s==i?a:0;;){var g=u.indexOf(r.pair[0],c),p=u.indexOf(r.pair[1],c);if(g<0&&(g=u.length),p<0&&(p=u.length),(c=Math.min(g,p))==u.length)break;if(n.getTokenTypeAt(e.Pos(s,c+1))==r.tokenType)if(c==g)++l;else if(!--l){t=s,o=c;break e}++c}return null==t||i==t?null:{from:e.Pos(i,a),to:e.Pos(t,o)}}o(f,"findOpening"),o(a,"findRange");for(var s=[],u=0;u<r.length;u++){var c=f(r[u]);c&&s.push(c)}for(s.sort((function(e,r){return e.ch-r.ch})),u=0;u<s.length;u++){var g=a(s[u]);if(g)return g}return null}}o(r,"bracketFolding"),e.registerHelper("fold","brace",r([["{","}"],["[","]"]])),e.registerHelper("fold","brace-paren",r([["{","}"],["[","]"],["(",")"]])),e.registerHelper("fold","import",(function(r,n){function t(n){if(n<r.firstLine()||n>r.lastLine())return null;var t=r.getTokenAt(e.Pos(n,1));if(/\S/.test(t.string)||(t=r.getTokenAt(e.Pos(n,t.end+1))),"keyword"!=t.type||"import"!=t.string)return null;for(var i=n,o=Math.min(r.lastLine(),n+10);i<=o;++i){var l=r.getLine(i).indexOf(";");if(-1!=l)return{startCh:t.end,end:e.Pos(i,l)}}}o(t,"hasImport");var i,l=n.line,f=t(l);if(!f||t(l-1)||(i=t(l-2))&&i.end.line==l-1)return null;for(var a=f.end;;){var s=t(a.line+1);if(null==s)break;a=s.end}return{from:r.clipPos(e.Pos(l,f.startCh+1)),to:a}})),e.registerHelper("fold","include",(function(r,n){function t(n){if(n<r.firstLine()||n>r.lastLine())return null;var t=r.getTokenAt(e.Pos(n,1));return/\S/.test(t.string)||(t=r.getTokenAt(e.Pos(n,t.end+1))),"meta"==t.type&&"#include"==t.string.slice(0,8)?t.start+8:void 0}o(t,"hasInclude");var i=n.line,l=t(i);if(null==l||null!=t(i-1))return null;for(var f=i;null!=t(f+1);)++f;return{from:e.Pos(i,l+1),to:r.clipPos(e.Pos(f))}}))}(t.a.exports);var a=f.exports,s=Object.freeze(l({__proto__:null,[Symbol.toStringTag]:"Module",default:a},[f.exports]))}}]);
|
||||
@@ -0,0 +1 @@
|
||||
"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[729],{68729:(e,n,r)=>{r.r(n);var t=r(96539),a=r(84553),i=(r(71254),r(27378),r(31542),Object.defineProperty),s=(e,n)=>i(e,"name",{value:n,configurable:!0});function c(e){o=e,u=e.length,l=d=f=-1,x(),O();const n=h();return g("EOF"),n}let o,u,l,d,f,p,b;function h(){const e=l,n=[];if(g("{"),!E("}")){do{n.push(m())}while(E(","));g("}")}return{kind:"Object",start:e,end:f,members:n}}function m(){const e=l,n="String"===b?y():null;g("String"),g(":");const r=v();return{kind:"Member",start:e,end:f,key:n,value:r}}function k(){const e=l,n=[];if(g("["),!E("]")){do{n.push(v())}while(E(","));g("]")}return{kind:"Array",start:e,end:f,values:n}}function v(){switch(b){case"[":return k();case"{":return h();case"String":case"Number":case"Boolean":case"Null":const e=y();return O(),e}g("Value")}function y(){return{kind:b,start:l,end:d,value:JSON.parse(o.slice(l,d))}}function g(e){if(b===e)return void O();let n;if("EOF"===b)n="[end of file]";else if(d-l>1)n="`"+o.slice(l,d)+"`";else{const e=o.slice(l).match(/^.+?\b/);n="`"+(e?e[0]:o[l])+"`"}throw N(`Expected ${e} but found ${n}.`)}s(c,"jsonParse"),s(h,"parseObj"),s(m,"parseMember"),s(k,"parseArr"),s(v,"parseVal"),s(y,"curToken"),s(g,"expect");class w extends Error{constructor(e,n){super(e),this.position=n}}function N(e){return new w(e,{start:l,end:d})}function E(e){if(b===e)return O(),!0}function x(){return d<u&&(d++,p=d===u?0:o.charCodeAt(d)),p}function O(){for(f=d;9===p||10===p||13===p||32===p;)x();if(0!==p){switch(l=d,p){case 34:return b="String",S();case 45:case 48:case 49:case 50:case 51:case 52:case 53:case 54:case 55:case 56:case 57:return b="Number",j();case 102:if("false"!==o.slice(l,l+5))break;return d+=4,x(),void(b="Boolean");case 110:if("null"!==o.slice(l,l+4))break;return d+=3,x(),void(b="Null");case 116:if("true"!==o.slice(l,l+4))break;return d+=3,x(),void(b="Boolean")}b=o[l],x()}else b="EOF"}function S(){for(x();34!==p&&p>31;)if(92===p)switch(p=x(),p){case 34:case 47:case 92:case 98:case 102:case 110:case 114:case 116:x();break;case 117:x(),$(),$(),$(),$();break;default:throw N("Bad character escape sequence.")}else{if(d===u)throw N("Unterminated string.");x()}if(34!==p)throw N("Unterminated string.");x()}function $(){if(p>=48&&p<=57||p>=65&&p<=70||p>=97&&p<=102)return x();throw N("Expected hexadecimal digit.")}function j(){45===p&&x(),48===p?x():F(),46===p&&(x(),F()),69!==p&&101!==p||(p=x(),43!==p&&45!==p||x(),F())}function F(){if(p<48||p>57)throw N("Expected decimal digit.");do{x()}while(p>=48&&p<=57)}function T(e,n,r){const t=[];return r.members.forEach((r=>{var a;if(r){const i=null===(a=r.key)||void 0===a?void 0:a.value,s=n[i];s?B(s,r.value).forEach((([n,r])=>{t.push(V(e,n,r))})):t.push(V(e,r.key,`Variable "$${i}" does not appear in any GraphQL query.`))}})),t}function B(e,n){if(!e||!n)return[];if(e instanceof a.bM)return"Null"===n.kind?[[n,`Type "${e}" is non-nullable and cannot be null.`]]:B(e.ofType,n);if("Null"===n.kind)return[];if(e instanceof a.p2){const r=e.ofType;return"Array"===n.kind?C(n.values||[],(e=>B(r,e))):B(r,n)}if(e instanceof a.sR){if("Object"!==n.kind)return[[n,`Type "${e}" must be an Object.`]];const r=Object.create(null),t=C(n.members,(n=>{var t;const a=null===(t=null==n?void 0:n.key)||void 0===t?void 0:t.value;r[a]=!0;const i=e.getFields()[a];return i?B(i?i.type:void 0,n.value):[[n.key,`Type "${e}" does not have a field "${a}".`]]}));return Object.keys(e.getFields()).forEach((i=>{r[i]||e.getFields()[i].type instanceof a.bM&&t.push([n,`Object of type "${e}" is missing required field "${i}".`])})),t}return"Boolean"===e.name&&"Boolean"!==n.kind||"String"===e.name&&"String"!==n.kind||"ID"===e.name&&"Number"!==n.kind&&"String"!==n.kind||"Float"===e.name&&"Number"!==n.kind||"Int"===e.name&&("Number"!==n.kind||(0|n.value)!==n.value)||(e instanceof a.mR||e instanceof a.n2)&&("String"!==n.kind&&"Number"!==n.kind&&"Boolean"!==n.kind&&"Null"!==n.kind||A(e.parseValue(n.value)))?[[n,`Expected value of type "${e}".`]]:[]}function V(e,n,r){return{message:r,severity:"error",type:"validation",from:e.posFromIndex(n.start),to:e.posFromIndex(n.end)}}function A(e){return null==e||e!=e}function C(e,n){return Array.prototype.concat.apply([],e.map(n))}s(w,"JSONSyntaxError"),s(N,"syntaxError"),s(E,"skip"),s(x,"ch"),s(O,"lex"),s(S,"readString"),s($,"readHex"),s(j,"readNumber"),s(F,"readDigits"),t.C.registerHelper("lint","graphql-variables",((e,n,r)=>{if(!e)return[];let t;try{t=c(e)}catch(e){if(e instanceof w)return[V(r,e.position,e.message)];throw e}const a=n.variableToType;return a?T(r,a,t):[]})),s(T,"validateVariables"),s(B,"validateValue"),s(V,"lintError"),s(A,"isNullish"),s(C,"mapCat")}}]);
|
||||
@@ -0,0 +1 @@
|
||||
"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[732],{95772:(e,t,r)=>{r.r(t),r.d(t,{c:()=>c});var n=r(96539),a=Object.defineProperty,i=(e,t)=>a(e,"name",{value:t,configurable:!0});function s(e,t){return t.forEach((function(t){t&&"string"!=typeof t&&!Array.isArray(t)&&Object.keys(t).forEach((function(r){if("default"!==r&&!(r in e)){var n=Object.getOwnPropertyDescriptor(t,r);Object.defineProperty(e,r,n.get?n:{enumerable:!0,get:function(){return t[r]}})}}))})),Object.freeze(e)}i(s,"_mergeNamespaces");var o={exports:{}};!function(e){var t={pairs:"()[]{}''\"\"",closeBefore:")]}'\":;>",triples:"",explode:"[]{}"},r=e.Pos;function n(e,r){return"pairs"==r&&"string"==typeof e?e:"object"==typeof e&&null!=e[r]?e[r]:t[r]}e.defineOption("autoCloseBrackets",!1,(function(t,r,i){i&&i!=e.Init&&(t.removeKeyMap(a),t.state.closeBrackets=null),r&&(s(n(r,"pairs")),t.state.closeBrackets=r,t.addKeyMap(a))})),i(n,"getOption");var a={Backspace:c,Enter:f};function s(e){for(var t=0;t<e.length;t++){var r=e.charAt(t),n="'"+r+"'";a[n]||(a[n]=o(r))}}function o(e){return function(t){return p(t,e)}}function l(e){var t=e.state.closeBrackets;return!t||t.override?t:e.getModeAt(e.getCursor()).closeBrackets||t}function c(t){var a=l(t);if(!a||t.getOption("disableInput"))return e.Pass;for(var i=n(a,"pairs"),s=t.listSelections(),o=0;o<s.length;o++){if(!s[o].empty())return e.Pass;var c=d(t,s[o].head);if(!c||i.indexOf(c)%2!=0)return e.Pass}for(o=s.length-1;o>=0;o--){var f=s[o].head;t.replaceRange("",r(f.line,f.ch-1),r(f.line,f.ch+1),"+delete")}}function f(t){var r=l(t),a=r&&n(r,"explode");if(!a||t.getOption("disableInput"))return e.Pass;for(var i=t.listSelections(),s=0;s<i.length;s++){if(!i[s].empty())return e.Pass;var o=d(t,i[s].head);if(!o||a.indexOf(o)%2!=0)return e.Pass}t.operation((function(){var e=t.lineSeparator()||"\n";t.replaceSelection(e+e,null),u(t,-1),i=t.listSelections();for(var r=0;r<i.length;r++){var n=i[r].head.line;t.indentLine(n,null,!0),t.indentLine(n+1,null,!0)}}))}function u(e,t){for(var r=[],n=e.listSelections(),a=0,i=0;i<n.length;i++){var s=n[i];s.head==e.getCursor()&&(a=i);var o=s.head.ch||t>0?{line:s.head.line,ch:s.head.ch+t}:{line:s.head.line-1};r.push({anchor:o,head:o})}e.setSelections(r,a)}function h(t){var n=e.cmpPos(t.anchor,t.head)>0;return{anchor:new r(t.anchor.line,t.anchor.ch+(n?-1:1)),head:new r(t.head.line,t.head.ch+(n?1:-1))}}function p(t,a){var i=l(t);if(!i||t.getOption("disableInput"))return e.Pass;var s=n(i,"pairs"),o=s.indexOf(a);if(-1==o)return e.Pass;for(var c,f=n(i,"closeBefore"),p=n(i,"triples"),d=s.charAt(o+1)==a,v=t.listSelections(),b=o%2==0,k=0;k<v.length;k++){var S,y=v[k],O=y.head,P=t.getRange(O,r(O.line,O.ch+1));if(b&&!y.empty())S="surround";else if(!d&&b||P!=a)if(d&&O.ch>1&&p.indexOf(a)>=0&&t.getRange(r(O.line,O.ch-2),O)==a+a){if(O.ch>2&&/\bstring/.test(t.getTokenTypeAt(r(O.line,O.ch-2))))return e.Pass;S="addFour"}else if(d){var x=0==O.ch?" ":t.getRange(r(O.line,O.ch-1),O);if(e.isWordChar(P)||x==a||e.isWordChar(x))return e.Pass;S="both"}else{if(!b||!(0===P.length||/\s/.test(P)||f.indexOf(P)>-1))return e.Pass;S="both"}else S=d&&g(t,O)?"both":p.indexOf(a)>=0&&t.getRange(O,r(O.line,O.ch+3))==a+a+a?"skipThree":"skip";if(c){if(c!=S)return e.Pass}else c=S}var A=o%2?s.charAt(o-1):a,m=o%2?a:s.charAt(o+1);t.operation((function(){if("skip"==c)u(t,1);else if("skipThree"==c)u(t,3);else if("surround"==c){for(var e=t.getSelections(),r=0;r<e.length;r++)e[r]=A+e[r]+m;for(t.replaceSelections(e,"around"),e=t.listSelections().slice(),r=0;r<e.length;r++)e[r]=h(e[r]);t.setSelections(e)}else"both"==c?(t.replaceSelection(A+m,null),t.triggerElectric(A+m),u(t,-1)):"addFour"==c&&(t.replaceSelection(A+A+A+A,"before"),u(t,1))}))}function d(e,t){var n=e.getRange(r(t.line,t.ch-1),r(t.line,t.ch+1));return 2==n.length?n:null}function g(e,t){var n=e.getTokenAt(r(t.line,t.ch+1));return/\bstring/.test(n.type)&&n.start==t.ch&&(0==t.ch||!/\bstring/.test(e.getTokenTypeAt(t)))}i(s,"ensureBound"),s(t.pairs+"`"),i(o,"handler"),i(l,"getConfig"),i(c,"handleBackspace"),i(f,"handleEnter"),i(u,"moveSel"),i(h,"contractSelection"),i(p,"handleChar"),i(d,"charsAround"),i(g,"stringStartsAfter")}(n.a.exports);var l=o.exports,c=Object.freeze(s({__proto__:null,[Symbol.toStringTag]:"Module",default:l},[o.exports]))}}]);
|
||||
@@ -0,0 +1 @@
|
||||
"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[783],{37783:(e,t,n)=>{n.r(t);var r=n(96539),l=n(71254),a=n(87756),u=(n(27378),n(31542),Object.defineProperty),i=(e,t)=>u(e,"name",{value:t,configurable:!0});function s(e,t){var n,r;const l=e.levels;return((l&&0!==l.length?l[l.length-1]-((null===(n=this.electricInput)||void 0===n?void 0:n.test(t))?1:0):e.indentLevel)||0)*((null===(r=this.config)||void 0===r?void 0:r.indentUnit)||0)}r.C.defineMode("graphql-variables",(e=>{const t=(0,a.o)({eatWhitespace:e=>e.eatSpace(),lexRules:o,parseRules:c,editorConfig:{tabSize:e.tabSize}});return{config:e,startState:t.startState,token:t.token,indent:s,electricInput:/^\s*[}\]]/,fold:"brace",closeBrackets:{pairs:'[]{}""',explode:"[]{}"}}})),i(s,"indent");const o={Punctuation:/^\[|]|\{|\}|:|,/,Number:/^-?(?:0|(?:[1-9][0-9]*))(?:\.[0-9]*)?(?:[eE][+-]?[0-9]+)?/,String:/^"(?:[^"\\]|\\(?:"|\/|\\|b|f|n|r|t|u[0-9a-fA-F]{4}))*"?/,Keyword:/^true|false|null/},c={Document:[(0,l.p)("{"),(0,l.l)("Variable",(0,l.o)((0,l.p)(","))),(0,l.p)("}")],Variable:[p("variable"),(0,l.p)(":"),"Value"],Value(e){switch(e.kind){case"Number":return"NumberValue";case"String":return"StringValue";case"Punctuation":switch(e.value){case"[":return"ListValue";case"{":return"ObjectValue"}return null;case"Keyword":switch(e.value){case"true":case"false":return"BooleanValue";case"null":return"NullValue"}return null}},NumberValue:[(0,l.t)("Number","number")],StringValue:[(0,l.t)("String","string")],BooleanValue:[(0,l.t)("Keyword","builtin")],NullValue:[(0,l.t)("Keyword","keyword")],ListValue:[(0,l.p)("["),(0,l.l)("Value",(0,l.o)((0,l.p)(","))),(0,l.p)("]")],ObjectValue:[(0,l.p)("{"),(0,l.l)("ObjectField",(0,l.o)((0,l.p)(","))),(0,l.p)("}")],ObjectField:[p("attribute"),(0,l.p)(":"),"Value"]};function p(e){return{style:e,match:e=>"String"===e.kind,update(e,t){e.name=t.value.slice(1,-1)}}}i(p,"namedKey")},87756:(e,t,n)=>{n.d(t,{o:()=>i});var r=n(71254),l=n(57554),a=Object.defineProperty,u=(e,t)=>a(e,"name",{value:t,configurable:!0});function i(e={eatWhitespace:e=>e.eatWhile(r.i),lexRules:r.L,parseRules:r.P,editorConfig:{}}){return{startState(){const t={level:0,step:0,name:null,kind:null,type:null,rule:null,needsSeparator:!1,prevState:null};return p(e.parseRules,t,l.h.DOCUMENT),t},token:(t,n)=>s(t,n,e)}}function s(e,t,n){var r;if(t.inBlockstring)return e.match(/.*"""/)?(t.inBlockstring=!1,"string"):(e.skipToEnd(),"string");const{lexRules:l,parseRules:a,eatWhitespace:u,editorConfig:i}=n;if(t.rule&&0===t.rule.length?d(t):t.needsAdvance&&(t.needsAdvance=!1,f(t,!0)),e.sol()){const n=(null==i?void 0:i.tabSize)||2;t.indentLevel=Math.floor(e.indentation()/n)}if(u(e))return"ws";const s=h(l,e);if(!s)return e.match(/\S+/)||e.match(/\s/),p(c,t,"Invalid"),"invalidchar";if("Comment"===s.kind)return p(c,t,"Comment"),"comment";const v=o({},t);if("Punctuation"===s.kind)if(/^[{([]/.test(s.value))void 0!==t.indentLevel&&(t.levels=(t.levels||[]).concat(t.indentLevel+1));else if(/^[})\]]/.test(s.value)){const e=t.levels=(t.levels||[]).slice(0,-1);t.indentLevel&&e.length>0&&e[e.length-1]<t.indentLevel&&(t.indentLevel=e[e.length-1])}for(;t.rule;){let n="function"==typeof t.rule?0===t.step?t.rule(s,e):null:t.rule[t.step];if(t.needsSeparator&&(n=null==n?void 0:n.separator),n){if(n.ofRule&&(n=n.ofRule),"string"==typeof n){p(a,t,n);continue}if(null===(r=n.match)||void 0===r?void 0:r.call(n,s))return n.update&&n.update(t,s),"Punctuation"===s.kind?f(t,!0):t.needsAdvance=!0,n.style}S(t)}return o(t,v),p(c,t,"Invalid"),"invalidchar"}function o(e,t){const n=Object.keys(t);for(let r=0;r<n.length;r++)e[n[r]]=t[n[r]];return e}u(i,"onlineParser"),u(s,"getToken"),u(o,"assign");const c={Invalid:[],Comment:[]};function p(e,t,n){if(!e[n])throw new TypeError("Unknown rule: "+n);t.prevState=Object.assign({},t),t.kind=n,t.name=null,t.type=null,t.rule=e[n],t.step=0,t.needsSeparator=!1}function d(e){e.prevState&&(e.kind=e.prevState.kind,e.name=e.prevState.name,e.type=e.prevState.type,e.rule=e.prevState.rule,e.step=e.prevState.step,e.needsSeparator=e.prevState.needsSeparator,e.prevState=e.prevState.prevState)}function f(e,t){var n;if(v(e)&&e.rule){const n=e.rule[e.step];if(n.separator){const t=n.separator;if(e.needsSeparator=!e.needsSeparator,!e.needsSeparator&&t.ofRule)return}if(t)return}for(e.needsSeparator=!1,e.step++;e.rule&&!(Array.isArray(e.rule)&&e.step<e.rule.length);)d(e),e.rule&&(v(e)?(null===(n=e.rule)||void 0===n?void 0:n[e.step].separator)&&(e.needsSeparator=!e.needsSeparator):(e.needsSeparator=!1,e.step++))}function v(e){const t=Array.isArray(e.rule)&&"string"!=typeof e.rule[e.step]&&e.rule[e.step];return t&&t.isList}function S(e){for(;e.rule&&(!Array.isArray(e.rule)||!e.rule[e.step].ofRule);)d(e);e.rule&&f(e,!1)}function h(e,t){const n=Object.keys(e);for(let r=0;r<n.length;r++){const l=t.match(e[n[r]]);if(l&&l instanceof Array)return{kind:n[r],value:l[0]}}}u(p,"pushRule"),u(d,"popRule"),u(f,"advanceRule"),u(v,"isList"),u(S,"unsuccessful"),u(h,"lex")}}]);
|
||||
@@ -0,0 +1 @@
|
||||
"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[820],{91820:(e,t,n)=>{n.r(t);var r=n(96539),l=n(71254),a=n(87756),u=(n(27378),n(31542),Object.defineProperty);function i(e,t){var n,r;const l=e.levels;return((l&&0!==l.length?l[l.length-1]-((null===(n=this.electricInput)||void 0===n?void 0:n.test(t))?1:0):e.indentLevel)||0)*((null===(r=this.config)||void 0===r?void 0:r.indentUnit)||0)}r.C.defineMode("graphql-results",(e=>{const t=(0,a.o)({eatWhitespace:e=>e.eatSpace(),lexRules:s,parseRules:o,editorConfig:{tabSize:e.tabSize}});return{config:e,startState:t.startState,token:t.token,indent:i,electricInput:/^\s*[}\]]/,fold:"brace",closeBrackets:{pairs:'[]{}""',explode:"[]{}"}}})),u(i,"name",{value:"indent",configurable:!0});const s={Punctuation:/^\[|]|\{|\}|:|,/,Number:/^-?(?:0|(?:[1-9][0-9]*))(?:\.[0-9]*)?(?:[eE][+-]?[0-9]+)?/,String:/^"(?:[^"\\]|\\(?:"|\/|\\|b|f|n|r|t|u[0-9a-fA-F]{4}))*"?/,Keyword:/^true|false|null/},o={Document:[(0,l.p)("{"),(0,l.l)("Entry",(0,l.p)(",")),(0,l.p)("}")],Entry:[(0,l.t)("String","def"),(0,l.p)(":"),"Value"],Value(e){switch(e.kind){case"Number":return"NumberValue";case"String":return"StringValue";case"Punctuation":switch(e.value){case"[":return"ListValue";case"{":return"ObjectValue"}return null;case"Keyword":switch(e.value){case"true":case"false":return"BooleanValue";case"null":return"NullValue"}return null}},NumberValue:[(0,l.t)("Number","number")],StringValue:[(0,l.t)("String","string")],BooleanValue:[(0,l.t)("Keyword","builtin")],NullValue:[(0,l.t)("Keyword","keyword")],ListValue:[(0,l.p)("["),(0,l.l)("Value",(0,l.p)(",")),(0,l.p)("]")],ObjectValue:[(0,l.p)("{"),(0,l.l)("ObjectField",(0,l.p)(",")),(0,l.p)("}")],ObjectField:[(0,l.t)("String","property"),(0,l.p)(":"),"Value"]}},87756:(e,t,n)=>{n.d(t,{o:()=>i});var r=n(71254),l=n(57554),a=Object.defineProperty,u=(e,t)=>a(e,"name",{value:t,configurable:!0});function i(e={eatWhitespace:e=>e.eatWhile(r.i),lexRules:r.L,parseRules:r.P,editorConfig:{}}){return{startState(){const t={level:0,step:0,name:null,kind:null,type:null,rule:null,needsSeparator:!1,prevState:null};return p(e.parseRules,t,l.h.DOCUMENT),t},token:(t,n)=>s(t,n,e)}}function s(e,t,n){var r;if(t.inBlockstring)return e.match(/.*"""/)?(t.inBlockstring=!1,"string"):(e.skipToEnd(),"string");const{lexRules:l,parseRules:a,eatWhitespace:u,editorConfig:i}=n;if(t.rule&&0===t.rule.length?d(t):t.needsAdvance&&(t.needsAdvance=!1,f(t,!0)),e.sol()){const n=(null==i?void 0:i.tabSize)||2;t.indentLevel=Math.floor(e.indentation()/n)}if(u(e))return"ws";const s=g(l,e);if(!s)return e.match(/\S+/)||e.match(/\s/),p(c,t,"Invalid"),"invalidchar";if("Comment"===s.kind)return p(c,t,"Comment"),"comment";const v=o({},t);if("Punctuation"===s.kind)if(/^[{([]/.test(s.value))void 0!==t.indentLevel&&(t.levels=(t.levels||[]).concat(t.indentLevel+1));else if(/^[})\]]/.test(s.value)){const e=t.levels=(t.levels||[]).slice(0,-1);t.indentLevel&&e.length>0&&e[e.length-1]<t.indentLevel&&(t.indentLevel=e[e.length-1])}for(;t.rule;){let n="function"==typeof t.rule?0===t.step?t.rule(s,e):null:t.rule[t.step];if(t.needsSeparator&&(n=null==n?void 0:n.separator),n){if(n.ofRule&&(n=n.ofRule),"string"==typeof n){p(a,t,n);continue}if(null===(r=n.match)||void 0===r?void 0:r.call(n,s))return n.update&&n.update(t,s),"Punctuation"===s.kind?f(t,!0):t.needsAdvance=!0,n.style}S(t)}return o(t,v),p(c,t,"Invalid"),"invalidchar"}function o(e,t){const n=Object.keys(t);for(let r=0;r<n.length;r++)e[n[r]]=t[n[r]];return e}u(i,"onlineParser"),u(s,"getToken"),u(o,"assign");const c={Invalid:[],Comment:[]};function p(e,t,n){if(!e[n])throw new TypeError("Unknown rule: "+n);t.prevState=Object.assign({},t),t.kind=n,t.name=null,t.type=null,t.rule=e[n],t.step=0,t.needsSeparator=!1}function d(e){e.prevState&&(e.kind=e.prevState.kind,e.name=e.prevState.name,e.type=e.prevState.type,e.rule=e.prevState.rule,e.step=e.prevState.step,e.needsSeparator=e.prevState.needsSeparator,e.prevState=e.prevState.prevState)}function f(e,t){var n;if(v(e)&&e.rule){const n=e.rule[e.step];if(n.separator){const t=n.separator;if(e.needsSeparator=!e.needsSeparator,!e.needsSeparator&&t.ofRule)return}if(t)return}for(e.needsSeparator=!1,e.step++;e.rule&&!(Array.isArray(e.rule)&&e.step<e.rule.length);)d(e),e.rule&&(v(e)?(null===(n=e.rule)||void 0===n?void 0:n[e.step].separator)&&(e.needsSeparator=!e.needsSeparator):(e.needsSeparator=!1,e.step++))}function v(e){const t=Array.isArray(e.rule)&&"string"!=typeof e.rule[e.step]&&e.rule[e.step];return t&&t.isList}function S(e){for(;e.rule&&(!Array.isArray(e.rule)||!e.rule[e.step].ofRule);)d(e);e.rule&&f(e,!1)}function g(e,t){const n=Object.keys(e);for(let r=0;r<n.length;r++){const l=t.match(e[n[r]]);if(l&&l instanceof Array)return{kind:n[r],value:l[0]}}}u(p,"pushRule"),u(d,"popRule"),u(f,"advanceRule"),u(v,"isList"),u(S,"unsuccessful"),u(g,"lex")}}]);
|
||||
@@ -0,0 +1 @@
|
||||
"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[829],{92823:(e,t,n)=>{function i(e,t){const n=[];let i=e;for(;null==i?void 0:i.kind;)n.push(i),i=i.prevState;for(let e=n.length-1;e>=0;e--)t(n[e])}n.d(t,{f:()=>i}),(0,Object.defineProperty)(i,"name",{value:"forEachState",configurable:!0})},63829:(e,t,n)=>{n.r(t);var i=n(96539),r=n(84553),l=n(62697),o=n(92823),s=(n(71254),n(27378),n(31542),Object.defineProperty),a=(e,t)=>s(e,"name",{value:t,configurable:!0});function c(e,t,n){const i=f(n,p(t.string));if(!i)return;const r=null!==t.type&&/"|\w/.test(t.string[0])?t.start:t.end;return{list:i,from:{line:e.line,ch:r},to:{line:e.line,ch:t.end}}}function f(e,t){return t?u(u(e.map((e=>({proximity:d(p(e.text),t),entry:e}))),(e=>e.proximity<=2)),(e=>!e.entry.isDeprecated)).sort(((e,t)=>(e.entry.isDeprecated?1:0)-(t.entry.isDeprecated?1:0)||e.proximity-t.proximity||e.entry.text.length-t.entry.text.length)).map((e=>e.entry)):u(e,(e=>!e.isDeprecated))}function u(e,t){const n=e.filter(t);return 0===n.length?e:n}function p(e){return e.toLowerCase().replace(/\W/g,"")}function d(e,t){let n=y(t,e);return e.length>t.length&&(n-=e.length-t.length-1,n+=0===e.indexOf(t)?0:.5),n}function y(e,t){let n,i;const r=[],l=e.length,o=t.length;for(n=0;n<=l;n++)r[n]=[n];for(i=1;i<=o;i++)r[0][i]=i;for(n=1;n<=l;n++)for(i=1;i<=o;i++){const l=e[n-1]===t[i-1]?0:1;r[n][i]=Math.min(r[n-1][i]+1,r[n][i-1]+1,r[n-1][i-1]+l),n>1&&i>1&&e[n-1]===t[i-2]&&e[n-2]===t[i-1]&&(r[n][i]=Math.min(r[n][i],r[n-2][i-2]+l))}return r[l][o]}function m(e,t,n){const i="Invalid"===t.state.kind?t.state.prevState:t.state,o=i.kind,s=i.step;if("Document"===o&&0===s)return c(e,t,[{text:"{"}]);const a=n.variableToType;if(!a)return;const f=g(a,t.state);if("Document"===o||"Variable"===o&&0===s)return c(e,t,Object.keys(a).map((e=>({text:`"${e}": `,type:a[e]}))));if(("ObjectValue"===o||"ObjectField"===o&&0===s)&&f.fields)return c(e,t,Object.keys(f.fields).map((e=>f.fields[e])).map((e=>({text:`"${e.name}": `,type:e.type,description:e.description}))));if("StringValue"===o||"NumberValue"===o||"BooleanValue"===o||"NullValue"===o||"ListValue"===o&&1===s||"ObjectField"===o&&2===s||"Variable"===o&&2===s){const n=f.type?(0,r.xC)(f.type):void 0;if(n instanceof r.sR)return c(e,t,[{text:"{"}]);if(n instanceof r.mR)return c(e,t,n.getValues().map((e=>({text:`"${e.name}"`,type:n,description:e.description}))));if(n===l.EZ)return c(e,t,[{text:"true",type:l.EZ,description:"Not false."},{text:"false",type:l.EZ,description:"Not true."}])}}function g(e,t){const n={type:null,fields:null};return(0,o.f)(t,(t=>{if("Variable"===t.kind)n.type=e[t.name];else if("ListValue"===t.kind){const e=n.type?(0,r.tf)(n.type):void 0;n.type=e instanceof r.p2?e.ofType:null}else if("ObjectValue"===t.kind){const e=n.type?(0,r.xC)(n.type):void 0;n.fields=e instanceof r.sR?e.getFields():null}else if("ObjectField"===t.kind){const e=t.name&&n.fields?n.fields[t.name]:null;n.type=null==e?void 0:e.type}})),n}a(c,"hintList"),a(f,"filterAndSortList"),a(u,"filterNonEmpty"),a(p,"normalizeText"),a(d,"getProximity"),a(y,"lexicalDistance"),i.C.registerHelper("hint","graphql-variables",((e,t)=>{const n=e.getCursor(),r=e.getTokenAt(n),l=m(n,r,t);return(null==l?void 0:l.list)&&l.list.length>0&&(l.from=i.C.Pos(l.from.line,l.from.ch),l.to=i.C.Pos(l.to.line,l.to.ch),i.C.signal(e,"hasCompletion",e,l,r)),l})),a(m,"getVariablesHint"),a(g,"getTypeInfo")}}]);
|
||||
@@ -0,0 +1 @@
|
||||
"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[906],{61906:(t,e,r)=>{r.r(e),r.d(e,{a:()=>o,m:()=>l});var n=r(96539),a=Object.defineProperty,c=(t,e)=>a(t,"name",{value:e,configurable:!0});function i(t,e){return e.forEach((function(e){e&&"string"!=typeof e&&!Array.isArray(e)&&Object.keys(e).forEach((function(r){if("default"!==r&&!(r in t)){var n=Object.getOwnPropertyDescriptor(e,r);Object.defineProperty(t,r,n.get?n:{enumerable:!0,get:function(){return e[r]}})}}))})),Object.freeze(t)}c(i,"_mergeNamespaces");var o={exports:{}};!function(t){var e=/MSIE \d/.test(navigator.userAgent)&&(null==document.documentMode||document.documentMode<8),r=t.Pos,n={"(":")>",")":"(<","[":"]>","]":"[<","{":"}>","}":"{<","<":">>",">":"<<"};function a(t){return t&&t.bracketRegex||/[(){}[\]]/}function i(t,e,c){var i=t.getLineHandle(e.line),s=e.ch-1,l=c&&c.afterCursor;null==l&&(l=/(^| )cm-fat-cursor($| )/.test(t.getWrapperElement().className));var h=a(c),u=!l&&s>=0&&h.test(i.text.charAt(s))&&n[i.text.charAt(s)]||h.test(i.text.charAt(s+1))&&n[i.text.charAt(++s)];if(!u)return null;var f=">"==u.charAt(1)?1:-1;if(c&&c.strict&&f>0!=(s==e.ch))return null;var g=t.getTokenTypeAt(r(e.line,s+1)),m=o(t,r(e.line,s+(f>0?1:0)),f,g,c);return null==m?null:{from:r(e.line,s),to:m&&m.pos,match:m&&m.ch==u.charAt(0),forward:f>0}}function o(t,e,c,i,o){for(var s=o&&o.maxScanLineLength||1e4,l=o&&o.maxScanLines||1e3,h=[],u=a(o),f=c>0?Math.min(e.line+l,t.lastLine()+1):Math.max(t.firstLine()-1,e.line-l),g=e.line;g!=f;g+=c){var m=t.getLine(g);if(m){var d=c>0?0:m.length-1,k=c>0?m.length:-1;if(!(m.length>s))for(g==e.line&&(d=e.ch-(c<0?1:0));d!=k;d+=c){var p=m.charAt(d);if(u.test(p)&&(void 0===i||(t.getTokenTypeAt(r(g,d+1))||"")==(i||""))){var v=n[p];if(v&&">"==v.charAt(1)==c>0)h.push(p);else{if(!h.length)return{pos:r(g,d),ch:p};h.pop()}}}}}return g-c!=(c>0?t.lastLine():t.firstLine())&&null}function s(t,n,a){for(var o=t.state.matchBrackets.maxHighlightLineLength||1e3,s=a&&a.highlightNonMatching,l=[],h=t.listSelections(),u=0;u<h.length;u++){var f=h[u].empty()&&i(t,h[u].head,a);if(f&&(f.match||!1!==s)&&t.getLine(f.from.line).length<=o){var g=f.match?"CodeMirror-matchingbracket":"CodeMirror-nonmatchingbracket";l.push(t.markText(f.from,r(f.from.line,f.from.ch+1),{className:g})),f.to&&t.getLine(f.to.line).length<=o&&l.push(t.markText(f.to,r(f.to.line,f.to.ch+1),{className:g}))}}if(l.length){e&&t.state.focused&&t.focus();var m=c((function(){t.operation((function(){for(var t=0;t<l.length;t++)l[t].clear()}))}),"clear");if(!n)return m;setTimeout(m,800)}}function l(t){t.operation((function(){t.state.matchBrackets.currentlyHighlighted&&(t.state.matchBrackets.currentlyHighlighted(),t.state.matchBrackets.currentlyHighlighted=null),t.state.matchBrackets.currentlyHighlighted=s(t,!1,t.state.matchBrackets)}))}function h(t){t.state.matchBrackets&&t.state.matchBrackets.currentlyHighlighted&&(t.state.matchBrackets.currentlyHighlighted(),t.state.matchBrackets.currentlyHighlighted=null)}c(a,"bracketRegex"),c(i,"findMatchingBracket"),c(o,"scanForBracket"),c(s,"matchBrackets"),c(l,"doMatchBrackets"),c(h,"clearHighlighted"),t.defineOption("matchBrackets",!1,(function(e,r,n){n&&n!=t.Init&&(e.off("cursorActivity",l),e.off("focus",l),e.off("blur",h),h(e)),r&&(e.state.matchBrackets="object"==typeof r?r:{},e.on("cursorActivity",l),e.on("focus",l),e.on("blur",h))})),t.defineExtension("matchBrackets",(function(){s(this,!0)})),t.defineExtension("findMatchingBracket",(function(t,e,r){return(r||"boolean"==typeof e)&&(r?(r.strict=e,e=r):e=e?{strict:!0}:null),i(this,t,e)})),t.defineExtension("scanForBracket",(function(t,e,r,n){return o(this,t,e,r,n)}))}(n.a.exports);var s=o.exports,l=Object.freeze(i({__proto__:null,[Symbol.toStringTag]:"Module",default:s},[o.exports]))}}]);
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
object-assign
|
||||
(c) Sindre Sorhus
|
||||
@license MIT
|
||||
*/
|
||||
|
||||
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
|
||||
|
||||
/**
|
||||
* Prism: Lightweight, robust, elegant syntax highlighting
|
||||
*
|
||||
* @license MIT <https://opensource.org/licenses/MIT>
|
||||
* @author Lea Verou <https://lea.verou.me>
|
||||
* @namespace
|
||||
* @public
|
||||
*/
|
||||
|
||||
/** @license React v0.19.1
|
||||
* scheduler.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/** @license React v16.13.1
|
||||
* react-is.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/** @license React v16.14.0
|
||||
* react-dom-server.browser.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/** @license React v16.14.0
|
||||
* react-dom.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/** @license React v16.14.0
|
||||
* react.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
object-assign
|
||||
(c) Sindre Sorhus
|
||||
@license MIT
|
||||
*/
|
||||
|
||||
/* @license
|
||||
Papa Parse
|
||||
v5.4.1
|
||||
https://github.com/mholt/PapaParse
|
||||
License: MIT
|
||||
*/
|
||||
|
||||
/*!
|
||||
* escape-html
|
||||
* Copyright(c) 2012-2013 TJ Holowaychuk
|
||||
* Copyright(c) 2015 Andreas Lubbe
|
||||
* Copyright(c) 2015 Tiancheng "Timothy" Gu
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/*!
|
||||
* is-primitive <https://github.com/jonschlinkert/is-primitive>
|
||||
*
|
||||
* Copyright (c) 2014-present, Jon Schlinkert.
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
|
||||
/*!
|
||||
* isobject <https://github.com/jonschlinkert/isobject>
|
||||
*
|
||||
* Copyright (c) 2014-2017, Jon Schlinkert.
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
|
||||
/*!
|
||||
* set-value <https://github.com/jonschlinkert/set-value>
|
||||
*
|
||||
* Copyright (c) Jon Schlinkert (https://github.com/jonschlinkert).
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
|
||||
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
|
||||
|
||||
/**
|
||||
* Prism: Lightweight, robust, elegant syntax highlighting
|
||||
*
|
||||
* @license MIT <https://opensource.org/licenses/MIT>
|
||||
* @author Lea Verou <https://lea.verou.me>
|
||||
* @namespace
|
||||
* @public
|
||||
*/
|
||||
|
||||
/** @license React v0.19.1
|
||||
* scheduler.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/** @license React v16.13.1
|
||||
* react-is.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/** @license React v16.14.0
|
||||
* react-dom.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/** @license React v16.14.0
|
||||
* react.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/** @license React v17.0.2
|
||||
* react-jsx-runtime.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 637 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 52 KiB |
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
object-assign
|
||||
(c) Sindre Sorhus
|
||||
@license MIT
|
||||
*/
|
||||
|
||||
/** @license React v0.19.1
|
||||
* scheduler.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/** @license React v16.14.0
|
||||
* react-dom.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/** @license React v16.14.0
|
||||
* react.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
object-assign
|
||||
(c) Sindre Sorhus
|
||||
@license MIT
|
||||
*/
|
||||
|
||||
/** @license React v0.19.1
|
||||
* scheduler.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/** @license React v16.14.0
|
||||
* react-dom.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/** @license React v16.14.0
|
||||
* react.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
After Width: | Height: | Size: 106 KiB |
|
After Width: | Height: | Size: 15 KiB |
16
packages/noodl-editor/src/editor/process-setup.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
/* eslint-disable */
|
||||
/*
|
||||
This file is added to .prettierignore too!
|
||||
|
||||
It is very important that the steps in here are done in the right order.
|
||||
*/
|
||||
|
||||
// Setup the platform before anything else is loading
|
||||
// This is a problem since we are calling the platform when importing
|
||||
import '@noodl/platform-electron';
|
||||
|
||||
// Setup the tracker to send to Mixpanel,
|
||||
// Very important that this is before anything else.
|
||||
import './src/utils/tracker';
|
||||
|
||||
import './src/utils/bugtracker';
|
||||
1063
packages/noodl-editor/src/editor/src/ViewerConnection.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export enum FeedbackType {
|
||||
Success = 'success',
|
||||
Notice = 'notice',
|
||||
Danger = 'danger'
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
export enum KeyCode {
|
||||
Up = 38,
|
||||
Down = 40,
|
||||
Left = 37,
|
||||
Right = 39,
|
||||
ESC = 27,
|
||||
Enter = 13,
|
||||
Space = 32
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Keybinding } from '@noodl-utils/keyboard/Keybinding';
|
||||
import { KeyCode, KeyMod } from '@noodl-utils/keyboard/KeyCode';
|
||||
|
||||
export namespace Keybindings {
|
||||
export const REFRESH_PREVIEW = new Keybinding(KeyMod.CtrlCmd, KeyCode.KEY_R);
|
||||
export const OPEN_DEVTOOLS = new Keybinding(KeyMod.CtrlCmd, KeyCode.KEY_D);
|
||||
export const OPEN_CLOUD_DEVTOOLS = new Keybinding(KeyMod.CtrlCmd, KeyMod.Shift, KeyCode.KEY_R);
|
||||
export const TOGGLE_PREVIEW_MODE = new Keybinding(KeyMod.CtrlCmd, KeyCode.KEY_T);
|
||||
|
||||
export const PROPERTY_PANEL_OPEN_DOCS = new Keybinding(KeyCode.F1);
|
||||
export const PROPERTY_PANEL_EDIT_LABEL = new Keybinding(KeyCode.Enter);
|
||||
export const PROPERTY_PANEL_EDIT_LABEL2 = new Keybinding(KeyCode.F2);
|
||||
export const PROPERTY_PANEL_DELETE = new Keybinding(KeyCode.Delete); // Actually node graph delete
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export enum LoadingState {
|
||||
Loading = 'is-loading',
|
||||
Completed = 'is-completed',
|
||||
Error = 'is-error'
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export const LocalStorageKey = {
|
||||
lastSeenChangelogDate: 'lastSeenChangelogDate',
|
||||
hasNewEditorVersionAvailable: 'hasNewEditorVersionAvailable',
|
||||
devModeSignInUsername: 'devModeSignInUsername'
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
export const NodeGraphColors = {
|
||||
yellow: '#ffa300',
|
||||
darkyellow: '#916311',
|
||||
orange: '#df691a',
|
||||
red: '#dc322f',
|
||||
magenta: '#d33682',
|
||||
violet: '#6c71c4',
|
||||
blue: '#268bd2',
|
||||
cyan: '#2aa198',
|
||||
green: '#859900',
|
||||
multiSelect: '#aaaaaa',
|
||||
base2: undefined
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
export enum NodeType {
|
||||
Visual = 'visual',
|
||||
Logic = 'logic',
|
||||
Data = 'data',
|
||||
Connection = 'component',
|
||||
Custom = 'javascript',
|
||||
None = 'none'
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { createContext } from 'react';
|
||||
|
||||
export interface IModule {
|
||||
id: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export interface IModuleLibraryContext {
|
||||
modules: IModule[];
|
||||
}
|
||||
|
||||
const ModuleLibraryContext = createContext<IModuleLibraryContext>({
|
||||
modules: null
|
||||
});
|
||||
@@ -0,0 +1,176 @@
|
||||
import React, { createContext, useContext, useCallback, useState, useEffect } from 'react';
|
||||
|
||||
import { ComponentModel } from '@noodl-models/componentmodel';
|
||||
import { SidebarModel } from '@noodl-models/sidebar';
|
||||
import { isComponentModel_CloudRuntime } from '@noodl-utils/NodeGraph';
|
||||
|
||||
import { Slot } from '@noodl-core-ui/types/global';
|
||||
|
||||
import { CenterToFitMode, NodeGraphEditor } from '../../views/nodegrapheditor';
|
||||
|
||||
type NodeGraphID = 'frontend' | 'backend';
|
||||
|
||||
interface NodeGraphControlSwitchOptions {
|
||||
pushHistory: boolean;
|
||||
selectSheet?: boolean;
|
||||
breadcrumbs?: boolean;
|
||||
node?: TSFixme;
|
||||
}
|
||||
|
||||
export interface NodeGraphControlContext {
|
||||
active: NodeGraphID;
|
||||
|
||||
nodeGraph: NodeGraphEditor;
|
||||
|
||||
switchToComponent(component: ComponentModel, options: NodeGraphControlSwitchOptions): void;
|
||||
}
|
||||
|
||||
const NodeGraphContext = createContext<NodeGraphControlContext>({
|
||||
active: null,
|
||||
nodeGraph: null,
|
||||
switchToComponent: null
|
||||
});
|
||||
|
||||
export interface NodeGraphContextProviderProps {
|
||||
children: Slot;
|
||||
}
|
||||
|
||||
export class NodeGraphContextTmp {
|
||||
public static active: NodeGraphControlContext['active'];
|
||||
public static nodeGraph: NodeGraphControlContext['nodeGraph'];
|
||||
public static switchToComponent: NodeGraphControlContext['switchToComponent'];
|
||||
}
|
||||
|
||||
export function NodeGraphContextProvider({ children }: NodeGraphContextProviderProps) {
|
||||
const [active, setActive] = useState<NodeGraphID>(null);
|
||||
const [nodeGraph, setNodeGraph] = useState<NodeGraphEditor>(null);
|
||||
|
||||
//create node graph, and support hot reloading it
|
||||
useEffect(() => {
|
||||
function createNodeGraph() {
|
||||
const newNodeGraph = new NodeGraphEditor({});
|
||||
newNodeGraph.render();
|
||||
return newNodeGraph;
|
||||
}
|
||||
|
||||
let currentInstance = createNodeGraph();
|
||||
setNodeGraph(currentInstance);
|
||||
if (import.meta.webpackHot) {
|
||||
import.meta.webpackHot.accept('../../views/nodegrapheditor', () => {
|
||||
const activeComponent = currentInstance.activeComponent;
|
||||
currentInstance.dispose();
|
||||
const newInstance = createNodeGraph();
|
||||
newInstance.switchToComponent(activeComponent);
|
||||
|
||||
setNodeGraph(newInstance);
|
||||
currentInstance = newInstance;
|
||||
});
|
||||
}
|
||||
|
||||
return () => {
|
||||
currentInstance.dispose();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const switchToComponent: NodeGraphControlContext['switchToComponent'] = useCallback(
|
||||
(component, options) => {
|
||||
if (!component) return;
|
||||
|
||||
nodeGraph.switchToComponent(component, options);
|
||||
},
|
||||
[nodeGraph]
|
||||
);
|
||||
|
||||
//switch sidebar panel when components are selected in the node graph
|
||||
useEffect(() => {
|
||||
if (!nodeGraph) return;
|
||||
|
||||
function _update(model: ComponentModel) {
|
||||
if (isComponentModel_CloudRuntime(model)) {
|
||||
setActive('backend');
|
||||
if (SidebarModel.instance.ActiveId === 'components') {
|
||||
SidebarModel.instance.switch('cloud-functions');
|
||||
}
|
||||
} else {
|
||||
setActive('frontend');
|
||||
if (SidebarModel.instance.ActiveId === 'cloud-functions') {
|
||||
SidebarModel.instance.switch('components');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const eventGroup = {};
|
||||
nodeGraph.on(
|
||||
'activeComponentChanged',
|
||||
({ model }: { model: ComponentModel }) => {
|
||||
_update(model);
|
||||
},
|
||||
eventGroup
|
||||
);
|
||||
|
||||
_update(nodeGraph.activeComponent);
|
||||
|
||||
return () => {
|
||||
nodeGraph.off(eventGroup);
|
||||
};
|
||||
}, [nodeGraph]);
|
||||
|
||||
// Screenshot helper
|
||||
useEffect(() => {
|
||||
function setupScreenshot() {
|
||||
window.resizeTo(1920, 1080);
|
||||
|
||||
// Something something Panel size = default
|
||||
|
||||
// Allow the resize to do it's thing first
|
||||
setTimeout(() => {
|
||||
nodeGraph.centerToFit(CenterToFitMode.AllNodes);
|
||||
}, 50);
|
||||
}
|
||||
|
||||
// @ts-expect-error
|
||||
window.setupScreenshot = setupScreenshot;
|
||||
return function () {
|
||||
// @ts-expect-error
|
||||
window.setupScreenshot = undefined;
|
||||
};
|
||||
}, []);
|
||||
|
||||
//set global on a singleton for code that can't access the react context
|
||||
useEffect(() => {
|
||||
if (!nodeGraph) return;
|
||||
|
||||
NodeGraphContextTmp.switchToComponent = switchToComponent;
|
||||
NodeGraphContextTmp.nodeGraph = nodeGraph;
|
||||
|
||||
return () => {
|
||||
NodeGraphContextTmp.active = null;
|
||||
NodeGraphContextTmp.nodeGraph = null;
|
||||
NodeGraphContextTmp.switchToComponent = null;
|
||||
};
|
||||
}, [nodeGraph]);
|
||||
|
||||
NodeGraphContextTmp.active = active;
|
||||
|
||||
return (
|
||||
<NodeGraphContext.Provider
|
||||
value={{
|
||||
active,
|
||||
nodeGraph,
|
||||
switchToComponent
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</NodeGraphContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useNodeGraphContext() {
|
||||
const context = useContext(NodeGraphContext);
|
||||
|
||||
if (context === undefined) {
|
||||
throw new Error('useNodeGraphContext must be a child of NodeGraphContextProvider');
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import { useModel } from '@noodl-hooks/useModel';
|
||||
import React, { createContext, useContext, useState, useEffect } from 'react';
|
||||
|
||||
import { ProjectModel } from '@noodl-models/projectmodel';
|
||||
import { StylesModel } from '@noodl-models/StylesModel';
|
||||
|
||||
import { Slot } from '@noodl-core-ui/types/global';
|
||||
|
||||
import { DesignTokenColor, extractProjectColors } from './extractProjectColors';
|
||||
|
||||
export interface ProjectDesignTokenContext {
|
||||
staticColors: DesignTokenColor[];
|
||||
dynamicColors: DesignTokenColor[];
|
||||
textStyles: TSFixme[];
|
||||
}
|
||||
|
||||
const ProjectDesignTokenContext = createContext<ProjectDesignTokenContext>({
|
||||
staticColors: [],
|
||||
dynamicColors: [],
|
||||
textStyles: []
|
||||
});
|
||||
|
||||
export interface ProjectDesignTokenContextProps {
|
||||
children: Slot;
|
||||
}
|
||||
|
||||
export function ProjectDesignTokenContextProvider({ children }: ProjectDesignTokenContextProps) {
|
||||
useModel(ProjectModel.instance);
|
||||
const [group] = useState({});
|
||||
|
||||
const [staticColors, setStaticColors] = useState<DesignTokenColor[]>([]);
|
||||
const [dynamicColors, setDynamicColors] = useState<DesignTokenColor[]>([]);
|
||||
const [textStyles, setTextStyles] = useState<TSFixme[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
const stylesModel = new StylesModel();
|
||||
|
||||
function extract() {
|
||||
const styles = stylesModel.getStyles('colors');
|
||||
const colors = extractProjectColors(ProjectModel.instance, styles);
|
||||
const textStyles = stylesModel.getStyles('text');
|
||||
|
||||
setStaticColors(colors.staticColors);
|
||||
setDynamicColors(colors.dynamicColors);
|
||||
setTextStyles(textStyles);
|
||||
}
|
||||
|
||||
stylesModel.on('stylesChanged', (args) => {
|
||||
if (['colors', 'text'].includes(args.type)) {
|
||||
extract();
|
||||
}
|
||||
});
|
||||
|
||||
extract();
|
||||
|
||||
return () => {
|
||||
stylesModel.dispose();
|
||||
ProjectModel.instance.off(group);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ProjectDesignTokenContext.Provider
|
||||
value={{
|
||||
staticColors,
|
||||
dynamicColors,
|
||||
textStyles
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</ProjectDesignTokenContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useProjectDesignTokenContext() {
|
||||
const context = useContext(ProjectDesignTokenContext);
|
||||
|
||||
if (context === undefined) {
|
||||
throw new Error('useProjectDesignTokenContext must be a child of ProjectDesignTokenContextProvider');
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import { ProjectModel } from '@noodl-models/projectmodel';
|
||||
|
||||
export type DesignTokenColor = {
|
||||
name: string;
|
||||
color: string;
|
||||
refs: string[];
|
||||
};
|
||||
|
||||
export function extractProjectColors(
|
||||
project: ProjectModel,
|
||||
colorStyles: {
|
||||
name: string;
|
||||
style: string;
|
||||
}[]
|
||||
): {
|
||||
staticColors: DesignTokenColor[];
|
||||
dynamicColors: DesignTokenColor[];
|
||||
} {
|
||||
const colors: { [key: string]: DesignTokenColor } = {};
|
||||
|
||||
const components = project.getComponents();
|
||||
components.forEach((c) => {
|
||||
c.graph.forEachNode((node) => {
|
||||
const colorPorts = node.getPorts('input').filter((p) => p.type === 'color');
|
||||
const values = colorPorts.map((port) => node.getParameter(port.name));
|
||||
values
|
||||
.filter((name) => name)
|
||||
.forEach((name) => {
|
||||
if (!colors[name]) {
|
||||
colors[name] = {
|
||||
name,
|
||||
color: null,
|
||||
refs: []
|
||||
};
|
||||
}
|
||||
colors[name].refs.push(node.type.name);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const staticColors = Object.values(colors)
|
||||
.filter((c) => colorStyles.find((s) => s.name === c.name))
|
||||
.sort((a, b) => a.name.localeCompare(b.name))
|
||||
.map((x) => ({
|
||||
name: x.name,
|
||||
refs: x.refs,
|
||||
color: ProjectModel.instance.resolveColor(x.name)
|
||||
}));
|
||||
|
||||
const dynamicColors = Object.values(colors)
|
||||
// remove all color styles from the list, so we only get the #HEX colors
|
||||
.filter((c) => !colorStyles.find((s) => s.name === c.name))
|
||||
.sort((a, b) => a.name.localeCompare(b.name))
|
||||
.map((x) => ({
|
||||
name: x.name,
|
||||
refs: x.refs,
|
||||
color: ProjectModel.instance.resolveColor(x.name)
|
||||
}));
|
||||
|
||||
return {
|
||||
staticColors,
|
||||
dynamicColors
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './ProjectDesignTokenContext';
|
||||
@@ -0,0 +1,24 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { CloudService, Environment } from '@noodl-models/CloudServices';
|
||||
import { ProjectModel } from '@noodl-models/projectmodel';
|
||||
|
||||
export function useActiveEnvironment(project: ProjectModel) {
|
||||
const [environment, setEnvironment] = useState<Environment>(null);
|
||||
const [group] = useState({});
|
||||
|
||||
useEffect(() => {
|
||||
function update() {
|
||||
CloudService.instance.getActiveEnvironment(project).then((env) => setEnvironment(env));
|
||||
}
|
||||
|
||||
update();
|
||||
|
||||
project.on('cloudServicesChanged', update, group);
|
||||
return function () {
|
||||
project.off(group);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return environment;
|
||||
}
|
||||
126
packages/noodl-editor/src/editor/src/hooks/useActivityQueue.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { guid } from '@noodl-utils/utils';
|
||||
|
||||
import { ToastType } from '../views/ToastLayer/components/ToastCard';
|
||||
import { ToastLayer } from '../views/ToastLayer/ToastLayer';
|
||||
|
||||
export interface ActivityQueue {
|
||||
activities: string[];
|
||||
hasActivity: boolean;
|
||||
runActivity: (message: string, callback: () => Promise<ActivityResult>) => Promise<void>;
|
||||
runQuietActivity(callback: (id: string) => Promise<void>): Promise<void>;
|
||||
}
|
||||
|
||||
export type ActivityResult = {
|
||||
type: ToastType;
|
||||
message: string;
|
||||
};
|
||||
|
||||
export interface UseActivityQueueOptions {
|
||||
errorMessage?: string;
|
||||
onSuccess?: () => Promise<void>;
|
||||
onError?: () => Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns
|
||||
*/
|
||||
export function useActivityQueue({
|
||||
errorMessage = 'Something went wrong',
|
||||
onSuccess,
|
||||
onError
|
||||
}: UseActivityQueueOptions): ActivityQueue {
|
||||
const [activities, setActivities] = useState<string[]>([]);
|
||||
|
||||
function addActivity(activityId: string) {
|
||||
setActivities((value) => {
|
||||
if (!value.includes(activityId)) {
|
||||
value.push(activityId);
|
||||
}
|
||||
return value;
|
||||
});
|
||||
}
|
||||
|
||||
function removeActivity(activityId: string) {
|
||||
setActivities((value) => {
|
||||
if (value.includes(activityId)) {
|
||||
return value.filter((x) => x !== activityId);
|
||||
}
|
||||
return value;
|
||||
});
|
||||
}
|
||||
|
||||
async function runActivity(message: string, callback: () => Promise<ActivityResult>): Promise<void> {
|
||||
const activityId = guid();
|
||||
addActivity(activityId);
|
||||
ToastLayer.showActivity(message, activityId);
|
||||
|
||||
try {
|
||||
const response = await callback();
|
||||
ToastLayer.hideActivity(activityId);
|
||||
|
||||
switch (response.type) {
|
||||
default:
|
||||
case ToastType.Neutral:
|
||||
ToastLayer.showInteraction(response.message);
|
||||
break;
|
||||
|
||||
case ToastType.Danger:
|
||||
ToastLayer.showError(response.message);
|
||||
break;
|
||||
|
||||
case ToastType.Success:
|
||||
ToastLayer.showSuccess(response.message);
|
||||
break;
|
||||
}
|
||||
|
||||
if (onSuccess) {
|
||||
await onSuccess();
|
||||
}
|
||||
} catch (error) {
|
||||
ToastLayer.hideActivity(activityId);
|
||||
ToastLayer.showError(errorMessage);
|
||||
|
||||
if (onError) {
|
||||
await onError();
|
||||
}
|
||||
|
||||
throw error;
|
||||
} finally {
|
||||
removeActivity(activityId);
|
||||
}
|
||||
}
|
||||
|
||||
async function runQuietActivity(callback: (id: string) => Promise<void>): Promise<void> {
|
||||
const activityId = guid();
|
||||
addActivity(activityId);
|
||||
|
||||
try {
|
||||
await callback(activityId);
|
||||
|
||||
if (onSuccess) {
|
||||
await onSuccess();
|
||||
}
|
||||
} catch (error) {
|
||||
ToastLayer.hideActivity(activityId);
|
||||
ToastLayer.showError(errorMessage);
|
||||
|
||||
if (onError) {
|
||||
await onError();
|
||||
}
|
||||
|
||||
throw error;
|
||||
} finally {
|
||||
removeActivity(activityId);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
activities,
|
||||
hasActivity: activities.length > 0,
|
||||
runActivity,
|
||||
runQuietActivity
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { useCallback, useRef } from 'react';
|
||||
|
||||
import useOnUnmount from './useOnUnmount';
|
||||
|
||||
export default function useCallAfterNextRender() {
|
||||
const rafIdRef = useRef<number>(-1);
|
||||
|
||||
useOnUnmount(() => {
|
||||
cancelAnimationFrame(rafIdRef.current);
|
||||
});
|
||||
|
||||
const callAfterNextRender = useCallback((callback: () => void) => {
|
||||
cancelAnimationFrame(rafIdRef.current);
|
||||
|
||||
rafIdRef.current = requestAnimationFrame(() => {
|
||||
rafIdRef.current = requestAnimationFrame(() => {
|
||||
callback();
|
||||
});
|
||||
});
|
||||
}, []);
|
||||
|
||||
return callAfterNextRender;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
export enum CustomPropertyAnimation {
|
||||
EasingBase = '--easing-base',
|
||||
EasingEqual = '--easing-equal',
|
||||
SpeedQuick = '--speed-quick',
|
||||
SpeedBase = '--speed-base',
|
||||
SpeedSlow = '--speed-slow'
|
||||
}
|
||||
|
||||
export function useCustomPropertyValue(propertyName: CustomPropertyAnimation): any {
|
||||
const styles = useMemo(() => getComputedStyle(document.documentElement), []);
|
||||
|
||||
const style = useMemo(() => {
|
||||
const temp = styles.getPropertyValue(propertyName);
|
||||
|
||||
switch (propertyName) {
|
||||
case CustomPropertyAnimation.SpeedQuick:
|
||||
case CustomPropertyAnimation.SpeedBase:
|
||||
case CustomPropertyAnimation.SpeedSlow:
|
||||
return parseInt(temp);
|
||||
|
||||
default:
|
||||
return temp;
|
||||
}
|
||||
}, [styles]);
|
||||
|
||||
return style;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
export function useDebounceState<T>(value: T, delay: number) {
|
||||
const [debouncedValue, setDebouncedValue] = useState(value);
|
||||
useEffect(() => {
|
||||
const handler = setTimeout(() => {
|
||||
setDebouncedValue(value);
|
||||
}, delay);
|
||||
return () => {
|
||||
clearTimeout(handler);
|
||||
};
|
||||
}, [value, delay]);
|
||||
return debouncedValue;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
export default function useDocumentScrollTimestamp(isEnabled = true, debounceMs = 0) {
|
||||
const [timestamp, setTimestamp] = useState(Date.now());
|
||||
|
||||
useEffect(() => {
|
||||
let debounceTimeoutId: ReturnType<typeof setTimeout>;
|
||||
|
||||
function onResize() {
|
||||
clearTimeout(debounceTimeoutId);
|
||||
debounceTimeoutId = setTimeout(() => setTimestamp(Date.now()), debounceMs);
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
document.removeEventListener('scroll', onResize, true);
|
||||
clearTimeout(debounceTimeoutId);
|
||||
}
|
||||
|
||||
if (isEnabled) {
|
||||
document.addEventListener('scroll', onResize, true);
|
||||
} else {
|
||||
cleanup();
|
||||
}
|
||||
|
||||
return () => {
|
||||
cleanup();
|
||||
};
|
||||
}, [isEnabled, debounceMs]);
|
||||
|
||||
return timestamp;
|
||||
}
|
||||
81
packages/noodl-editor/src/editor/src/hooks/useDragHandler.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { useEffect, useState, useCallback } from 'react';
|
||||
|
||||
export interface DragHandlerOptions {
|
||||
root: React.MutableRefObject<HTMLElement>;
|
||||
minWidth: number;
|
||||
minHeight: number;
|
||||
|
||||
onStartDrag?: () => void;
|
||||
onDrag?: (contentWidth: number, contentHeight: number) => void;
|
||||
onEndDrag?: () => void;
|
||||
}
|
||||
|
||||
export function useDragHandler(options: DragHandlerOptions) {
|
||||
const [dragging, setDragging] = useState(false);
|
||||
const [dragState, setDragState] = useState({
|
||||
dragStartWidth: 0,
|
||||
dragStartHeight: 0,
|
||||
dragStartX: 0,
|
||||
dragStartY: 0
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
function drag(event: MouseEvent) {
|
||||
const deltaX = event.pageX - dragState.dragStartX;
|
||||
const deltaY = event.pageY - dragState.dragStartY;
|
||||
const newWidth = Math.max(options.minWidth, dragState.dragStartWidth + deltaX);
|
||||
const newHeight = Math.max(options.minHeight, dragState.dragStartHeight + deltaY);
|
||||
|
||||
setDragState({
|
||||
dragStartX: event.pageX,
|
||||
dragStartY: event.pageY,
|
||||
dragStartWidth: newWidth,
|
||||
dragStartHeight: newHeight
|
||||
});
|
||||
|
||||
options.onDrag && options.onDrag(newWidth, newHeight);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function endDrag(_event: MouseEvent) {
|
||||
setDragging(false);
|
||||
|
||||
document.removeEventListener('mousemove', drag, true);
|
||||
document.removeEventListener('mouseup', endDrag, true);
|
||||
|
||||
options.onEndDrag && options.onEndDrag();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dragging) {
|
||||
document.addEventListener('mousemove', drag, true);
|
||||
document.addEventListener('mouseup', endDrag, true);
|
||||
}
|
||||
|
||||
return function () {
|
||||
document.removeEventListener('mousemove', drag, true);
|
||||
document.removeEventListener('mouseup', endDrag, true);
|
||||
};
|
||||
}, [dragging]);
|
||||
|
||||
const startDrag = useCallback((event: { pageX: number; pageY: number }) => {
|
||||
setDragging(true);
|
||||
|
||||
setDragState({
|
||||
dragStartX: event.pageX,
|
||||
dragStartY: event.pageY,
|
||||
dragStartWidth: options.root.current.offsetWidth,
|
||||
dragStartHeight: options.root.current.offsetHeight
|
||||
});
|
||||
|
||||
options.onStartDrag && options.onStartDrag();
|
||||
|
||||
return false;
|
||||
}, []);
|
||||
|
||||
return {
|
||||
startDrag
|
||||
};
|
||||
}
|
||||
23
packages/noodl-editor/src/editor/src/hooks/useFetch.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
export default function useFetch(url: string, options: Object = {}) {
|
||||
const [response, setResponse] = useState(null);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const res = await fetch(url, options);
|
||||
const json = await res.json();
|
||||
|
||||
setResponse(json);
|
||||
} catch (error) {
|
||||
setError(error);
|
||||
}
|
||||
};
|
||||
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
return { response, error };
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { SidebarModel } from '@noodl-models/sidebar';
|
||||
import { SidebarModelEvent } from '@noodl-models/sidebar/sidebarmodel';
|
||||
|
||||
export function useFocusRefOnPanelActive(inputRef: React.MutableRefObject<HTMLElement>, panelId: string) {
|
||||
useEffect(() => {
|
||||
if (!inputRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
const eventRef = {};
|
||||
|
||||
SidebarModel.instance.on(
|
||||
SidebarModelEvent.activeChanged,
|
||||
(activeId, previousActiveId) => {
|
||||
if (activeId === panelId && panelId !== previousActiveId) {
|
||||
//not sure why this timeout is required, but it doesn't work without it
|
||||
setTimeout(() => {
|
||||
inputRef.current?.focus();
|
||||
}, 10);
|
||||
}
|
||||
},
|
||||
eventRef
|
||||
);
|
||||
|
||||
return () => {
|
||||
SidebarModel.instance.off(eventRef);
|
||||
};
|
||||
}, [inputRef.current, panelId]);
|
||||
}
|
||||
|
||||
export function useOnPanelActive(func: () => void, panelId: string) {
|
||||
useEffect(() => {
|
||||
const eventRef = {};
|
||||
SidebarModel.instance.on(
|
||||
SidebarModelEvent.activeChanged,
|
||||
(activeId, previousActiveId) => {
|
||||
if (activeId === panelId && panelId !== previousActiveId) {
|
||||
//not sure why this timeout is required, but it doesn't work without it
|
||||
setTimeout(() => {
|
||||
func();
|
||||
}, 10);
|
||||
}
|
||||
},
|
||||
eventRef
|
||||
);
|
||||
|
||||
return () => {
|
||||
SidebarModel.instance.off(eventRef);
|
||||
};
|
||||
}, [func, panelId]);
|
||||
}
|
||||
38
packages/noodl-editor/src/editor/src/hooks/useInterval.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
type IntervalCallback = () => Promise<void>;
|
||||
|
||||
export function useInterval(callback: IntervalCallback, delay: number) {
|
||||
const savedCallback = useRef<IntervalCallback>();
|
||||
|
||||
// Remember the latest callback.
|
||||
useEffect(() => {
|
||||
savedCallback.current = callback;
|
||||
}, [callback]);
|
||||
|
||||
useEffect(() => {
|
||||
let timeoutId: NodeJS.Timeout;
|
||||
let cancelTimeout = false;
|
||||
|
||||
async function tick() {
|
||||
try {
|
||||
await savedCallback.current();
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
if (!cancelTimeout && delay !== null) {
|
||||
timeoutId = setTimeout(tick, delay);
|
||||
}
|
||||
}
|
||||
|
||||
if (delay !== null) {
|
||||
tick();
|
||||
}
|
||||
|
||||
return () => {
|
||||
cancelTimeout = true;
|
||||
clearTimeout(timeoutId);
|
||||
};
|
||||
}, [delay]);
|
||||
}
|
||||
21
packages/noodl-editor/src/editor/src/hooks/useIsOnline.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
export function useIsOnline() {
|
||||
const [isOnline, setIsOnline] = useState(navigator.onLine);
|
||||
|
||||
useEffect(() => {
|
||||
function onStatusChanged() {
|
||||
setIsOnline(navigator.onLine);
|
||||
}
|
||||
|
||||
window.addEventListener('online', onStatusChanged);
|
||||
window.addEventListener('offline', onStatusChanged);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('online', onStatusChanged);
|
||||
window.removeEventListener('offline', onStatusChanged);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return isOnline;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { useModernModel } from '@noodl-hooks/useModel';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { SidebarModel } from '@noodl-models/sidebar';
|
||||
import { SidebarModelEvent } from '@noodl-models/sidebar/sidebarmodel';
|
||||
import KeyboardHandler, { KeyboardCommand } from '@noodl-utils/keyboardhandler';
|
||||
|
||||
export function useKeyboardCommands(createCommandsFn: () => KeyboardCommand[], deps?: React.DependencyList) {
|
||||
useEffect(() => {
|
||||
const commands = createCommandsFn();
|
||||
|
||||
KeyboardHandler.instance.registerCommands(commands);
|
||||
|
||||
return () => {
|
||||
KeyboardHandler.instance.deregisterCommands(commands);
|
||||
};
|
||||
}, deps || []);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Side Panel is always active,
|
||||
* so lets unregister the commands while it is not visible.
|
||||
*
|
||||
* @param createCommandsFn
|
||||
* @param panelId
|
||||
* @param deps
|
||||
*/
|
||||
export function useSidePanelKeyboardCommands(
|
||||
createCommandsFn: () => KeyboardCommand[],
|
||||
panelId: string,
|
||||
deps?: React.DependencyList
|
||||
) {
|
||||
const sidebarModel = useModernModel(SidebarModel.instance, [SidebarModelEvent.activeChanged]);
|
||||
const activeId = sidebarModel.ActiveId;
|
||||
|
||||
useEffect(() => {
|
||||
if (activeId !== panelId) return;
|
||||
|
||||
const commands = createCommandsFn();
|
||||
|
||||
KeyboardHandler.instance.registerCommands(commands);
|
||||
|
||||
return () => {
|
||||
KeyboardHandler.instance.deregisterCommands(commands);
|
||||
};
|
||||
}, [...(deps || []), panelId, activeId]);
|
||||
}
|
||||
13
packages/noodl-editor/src/editor/src/hooks/useLogEffect.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export default function useLogEffect(data: unknown, key?: string) {
|
||||
console.warn('USING useLogEffect, REMOVE BEFORE DEPLOY');
|
||||
|
||||
useEffect(() => {
|
||||
if (Boolean(key)) {
|
||||
console.debug(key, data);
|
||||
} else {
|
||||
console.debug(data);
|
||||
}
|
||||
}, [data]);
|
||||
}
|
||||
77
packages/noodl-editor/src/editor/src/hooks/useModel.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { Model as ModernModel, ModelEventEnum } from '@noodl-utils/model';
|
||||
|
||||
import Model from '../../../shared/model';
|
||||
|
||||
/**
|
||||
* const hosting = useModel(HostingModel.instance) // rerenders on all value updates
|
||||
* const hosting = useModel(HostingModel.instance, ['FrontendsFetched']) // rerender on FrontendsFetched event only
|
||||
*
|
||||
* useModelCallback(HostingModel.instance, (data) => console.log(data)) // runs on all value updates
|
||||
* useModelCallback(HostingModel.instance, (data) => console.log(data), ['FrontendsFetched']) // runs on FrontendsFetched event only
|
||||
*/
|
||||
export function useModel<TModel extends Model>(model: TModel, subscribedEvents?: string[]) {
|
||||
// set and update timestamp to force a component reload
|
||||
const [, setTimestamp] = useState(`${Date.now()}`);
|
||||
const [group] = useState({});
|
||||
|
||||
useEffect(() => {
|
||||
function attachListener(event) {
|
||||
model.on(
|
||||
'Model.' + event,
|
||||
() => {
|
||||
setTimestamp(`Model.${event}: ${Date.now()}`);
|
||||
},
|
||||
group
|
||||
);
|
||||
}
|
||||
|
||||
if (!subscribedEvents) {
|
||||
for (const event in model.events) {
|
||||
attachListener(event);
|
||||
}
|
||||
} else if (Array.isArray(subscribedEvents)) {
|
||||
subscribedEvents.forEach((event) => attachListener(event));
|
||||
}
|
||||
|
||||
return () => {
|
||||
model.off(group);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
export function useModernModel<TModel extends ModernModel<TEnum>, TEnum extends ModelEventEnum = any>(
|
||||
model: TModel,
|
||||
subscribedEvents?: TEnum[]
|
||||
) {
|
||||
// set and update timestamp to force a component reload
|
||||
const [, setTimestamp] = useState(`${Date.now()}`);
|
||||
const [group] = useState({});
|
||||
|
||||
useEffect(() => {
|
||||
function attachListener(event: TSFixme) {
|
||||
model.on(
|
||||
event,
|
||||
() => {
|
||||
setTimestamp(`${event}: ${Date.now()}`);
|
||||
},
|
||||
group
|
||||
);
|
||||
}
|
||||
|
||||
if (!subscribedEvents) {
|
||||
attachListener('*');
|
||||
} else if (Array.isArray(subscribedEvents)) {
|
||||
subscribedEvents.forEach((event) => attachListener(event));
|
||||
}
|
||||
|
||||
return () => {
|
||||
model.off(group);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return model;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { NodeLibrary } from '@noodl-models/nodelibrary';
|
||||
|
||||
export function useNodeLibraryLoaded() {
|
||||
const [group] = useState({});
|
||||
const [loaded, setLoaded] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
function update() {
|
||||
setLoaded(NodeLibrary.instance.isLoaded());
|
||||
}
|
||||
|
||||
update();
|
||||
|
||||
NodeLibrary.instance.on('libraryUpdated', update, group);
|
||||
|
||||
return function () {
|
||||
NodeLibrary.instance.off(group);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return loaded;
|
||||
}
|
||||
5
packages/noodl-editor/src/editor/src/hooks/useOnMount.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { useEffect, EffectCallback } from 'react';
|
||||
|
||||
export default function useOnMount(onMount: EffectCallback) {
|
||||
useEffect(onMount, []);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import useOnMount from './useOnMount';
|
||||
|
||||
export default function useOnUnmount(onUnmount: () => void) {
|
||||
useOnMount(() => onUnmount);
|
||||
}
|
||||
20
packages/noodl-editor/src/editor/src/hooks/useParsedHref.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
export default function useParsedHref(href?: string) {
|
||||
if (!href) return null;
|
||||
|
||||
const parsedHref = useMemo(() => {
|
||||
let newHref = href.trim().replace(/\s/g, '');
|
||||
|
||||
if (/^(:\/\/)/.test(newHref)) {
|
||||
return `http${newHref}`;
|
||||
}
|
||||
if (!/^(f|ht)tps?:\/\//i.test(newHref)) {
|
||||
return `http://${newHref}`;
|
||||
}
|
||||
|
||||
return newHref;
|
||||
}, [href]);
|
||||
|
||||
return parsedHref;
|
||||
}
|
||||
11
packages/noodl-editor/src/editor/src/hooks/usePrevious.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { useRef, useEffect } from 'react';
|
||||
|
||||
export default function usePrevious<T = unknown>(value: T): T | undefined {
|
||||
const ref = useRef<T | undefined>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
ref.current = value;
|
||||
});
|
||||
|
||||
return ref.current;
|
||||
}
|
||||
17
packages/noodl-editor/src/editor/src/hooks/useRunOnExpire.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import useTimeout from '@noodl-hooks/useTimeout';
|
||||
import { useState } from 'react';
|
||||
|
||||
export function useRunOnExpire(onExpire: () => void, ms: number) {
|
||||
const [isTimeoutRunning, setIsTimeoutRunning] = useState(false);
|
||||
useTimeout(onExpire, isTimeoutRunning ? ms : null);
|
||||
|
||||
function startExpiration() {
|
||||
setIsTimeoutRunning(true);
|
||||
}
|
||||
|
||||
function cancelExpiration() {
|
||||
setIsTimeoutRunning(false);
|
||||
}
|
||||
|
||||
return [startExpiration, cancelExpiration];
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
||||
|
||||
interface ThrottleOptions<T = any> {
|
||||
onChange?: (value: T) => void;
|
||||
leading?: boolean;
|
||||
trailing?: boolean;
|
||||
}
|
||||
|
||||
const defaultOptions: ThrottleOptions = {
|
||||
onChange: () => {},
|
||||
leading: false,
|
||||
trailing: true
|
||||
};
|
||||
|
||||
function useThrottle<T>(value: T, delay: number, options: ThrottleOptions<T> = {}): T {
|
||||
const [throttledValue, setThrottledValue] = useState(value);
|
||||
const lastExecuted = useRef(Date.now());
|
||||
const handler = useRef<NodeJS.Timeout | null>(null);
|
||||
const componentMounted = useRef<boolean | null>(null);
|
||||
const leadingCall = useRef(false);
|
||||
const optionsRef = useRef<ThrottleOptions<T> | null>(null);
|
||||
|
||||
options = Object.assign({}, defaultOptions, options);
|
||||
|
||||
// Updating the options on every render, room for improvement
|
||||
useEffect(() => {
|
||||
optionsRef.current = options;
|
||||
});
|
||||
|
||||
const clearHandler = useCallback(() => {
|
||||
if (handler.current) clearTimeout(handler.current);
|
||||
handler.current = null;
|
||||
leadingCall.current = false;
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
componentMounted.current = true;
|
||||
return () => {
|
||||
// on component unmount
|
||||
componentMounted.current = false;
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (optionsRef.current?.onChange) optionsRef.current.onChange(throttledValue);
|
||||
}, [throttledValue]);
|
||||
|
||||
useEffect(() => {
|
||||
if (handler.current) clearTimeout(handler.current);
|
||||
if (leadingCall.current) {
|
||||
leadingCall.current = false;
|
||||
}
|
||||
|
||||
if (!handler.current && options.leading && !leadingCall.current) {
|
||||
setThrottledValue(value);
|
||||
leadingCall.current = true;
|
||||
lastExecuted.current = Date.now();
|
||||
}
|
||||
|
||||
handler.current = setTimeout(() => {
|
||||
let shouldCallFunction = true;
|
||||
if (options.leading && leadingCall.current) {
|
||||
shouldCallFunction = false;
|
||||
}
|
||||
|
||||
clearHandler();
|
||||
|
||||
if (componentMounted.current && options.trailing && shouldCallFunction) {
|
||||
if (Date.now() - lastExecuted.current >= delay) {
|
||||
setThrottledValue(value);
|
||||
lastExecuted.current = Date.now();
|
||||
}
|
||||
}
|
||||
}, delay - (Date.now() - lastExecuted.current));
|
||||
|
||||
return () => {
|
||||
if (handler.current) clearTimeout(handler.current);
|
||||
};
|
||||
}, [value, delay, clearHandler, options.leading, options.trailing]);
|
||||
|
||||
return throttledValue;
|
||||
}
|
||||
|
||||
function useThrottleState<T>(
|
||||
initialValue: T,
|
||||
delay: number,
|
||||
options: ThrottleOptions<T> = {}
|
||||
): [T, React.Dispatch<React.SetStateAction<T>>, T] {
|
||||
const [value, setValue] = useState(initialValue);
|
||||
const throttledValue = useThrottle(value, delay, options);
|
||||
|
||||
return [value, setValue, throttledValue];
|
||||
}
|
||||
|
||||
export { useThrottle, useThrottleState };
|
||||
20
packages/noodl-editor/src/editor/src/hooks/useTimeout.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
export default function useTimeout(callback: () => void, delay: number) {
|
||||
const timeoutRef = useRef(null);
|
||||
const savedCallback = useRef(callback);
|
||||
|
||||
useEffect(() => {
|
||||
savedCallback.current = callback;
|
||||
}, [callback]);
|
||||
|
||||
useEffect(() => {
|
||||
const tick = () => savedCallback.current();
|
||||
|
||||
if (typeof delay === 'number') {
|
||||
timeoutRef.current = window.setTimeout(tick, delay);
|
||||
return () => window.clearTimeout(timeoutRef.current);
|
||||
}
|
||||
}, [delay]);
|
||||
return timeoutRef;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
export function useTogglableArray<T = string | number>(): [
|
||||
items: T[],
|
||||
toggleItem: (value: T) => void,
|
||||
isItemInArray: (value: T) => boolean,
|
||||
clearArray: () => void
|
||||
] {
|
||||
const [items, setItems] = useState<T[]>([]);
|
||||
|
||||
function toggleItem(item: T) {
|
||||
setItems((prev) => {
|
||||
if (prev.includes(item)) {
|
||||
return [...prev].filter((arrValue) => arrValue !== item);
|
||||
} else {
|
||||
return [...prev, item];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function isItemInArray(item: T) {
|
||||
return items.includes(item);
|
||||
}
|
||||
|
||||
function clearArray() {
|
||||
setItems([]);
|
||||
}
|
||||
|
||||
return [items, toggleItem, isItemInArray, clearArray];
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
export function useTriggerRerender() {
|
||||
const [_, setTimestamp] = useState(0);
|
||||
|
||||
function triggerRender() {
|
||||
const timestamp = Date.now();
|
||||
setTimestamp(timestamp);
|
||||
}
|
||||
|
||||
return triggerRender;
|
||||
}
|
||||
|
||||
export function useTriggerRerenderState(): [number, () => void] {
|
||||
const [time, setTimestamp] = useState(0);
|
||||
|
||||
function triggerRender() {
|
||||
const timestamp = Date.now();
|
||||
setTimestamp(timestamp);
|
||||
}
|
||||
|
||||
return [time, triggerRender];
|
||||
}
|
||||
30
packages/noodl-editor/src/editor/src/hooks/useWindowSize.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
function getWindowSize() {
|
||||
return {
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight
|
||||
};
|
||||
}
|
||||
|
||||
export default function useWindowSize(debounceMs = 250) {
|
||||
const [size, setSize] = useState(getWindowSize());
|
||||
|
||||
useEffect(() => {
|
||||
let debounceTimeoutId: ReturnType<typeof setTimeout>;
|
||||
|
||||
function onResize() {
|
||||
clearTimeout(debounceTimeoutId);
|
||||
debounceTimeoutId = setTimeout(() => setSize(getWindowSize()), debounceMs);
|
||||
}
|
||||
|
||||
window.addEventListener('resize', onResize);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', onResize);
|
||||
clearTimeout(debounceTimeoutId);
|
||||
};
|
||||
}, [debounceMs]);
|
||||
|
||||
return size;
|
||||
}
|
||||
@@ -0,0 +1,307 @@
|
||||
import { NodeGraphContextTmp } from '@noodl-contexts/NodeGraphContext/NodeGraphContext';
|
||||
import { filesystem } from '@noodl/platform';
|
||||
|
||||
import { AiCopilotContext } from '@noodl-models/AiAssistant/AiCopilotContext';
|
||||
import { aiNodeTemplates } from '@noodl-models/AiAssistant/AiTemplates';
|
||||
import { ChatHistory, ChatHistoryEvent } from '@noodl-models/AiAssistant/ChatHistory';
|
||||
import { AiNodeTemplate, AiNodeTemplateType } from '@noodl-models/AiAssistant/interfaces';
|
||||
import { ComponentModel } from '@noodl-models/componentmodel';
|
||||
import { NodeGraphModel, NodeGraphNode, NodeGraphNodeSet } from '@noodl-models/nodegraphmodel';
|
||||
import { ProjectModel } from '@noodl-models/projectmodel';
|
||||
import { Model } from '@noodl-utils/model';
|
||||
import { guid } from '@noodl-utils/utils';
|
||||
|
||||
import { EventDispatcher } from '../../../../shared/utils/EventDispatcher';
|
||||
import { PopupItemType } from '../../views/Clippy/ClippyCommandsMetadata';
|
||||
import { ToastLayer } from '../../views/ToastLayer/ToastLayer';
|
||||
|
||||
const docsTemplates = [
|
||||
{
|
||||
label: 'Read from database',
|
||||
desc: 'Create a node that queries the database, filters and returns the results.',
|
||||
examples: [
|
||||
'Get all users that belong to the "Vendor" group',
|
||||
'Get all products, sort from lowest to highest price',
|
||||
'Get all unread messages for the currently logged in user'
|
||||
],
|
||||
primer: 'query.md',
|
||||
prefix: 'const query = Noodl.Records.query;\n',
|
||||
template: 'function-query-database'
|
||||
},
|
||||
{
|
||||
label: 'Fetch REST API',
|
||||
desc: 'Create a node that connects to an external REST API via a HTTP request and performs an action.',
|
||||
examples: ['Get the main image from a wikipedia page', 'List the songs in a playlist in spotify'],
|
||||
primer: 'rest.md',
|
||||
template: 'rest'
|
||||
},
|
||||
{
|
||||
label: 'Form Validation',
|
||||
desc: 'Specify inputs and how you want them validated, the outputs are any validations and errors.',
|
||||
examples: ['Validate a phone number and email'],
|
||||
primer: 'validation.md',
|
||||
template: 'function-form-validation'
|
||||
},
|
||||
{
|
||||
label: 'AI Function',
|
||||
desc: 'Create a function node that performs a task when you trigger Run, it can have inputs and outputs.',
|
||||
examples: [
|
||||
'Create inputs for Array1 and Array2 and output all items with the same ID',
|
||||
'Get a random number between the min and max inputs',
|
||||
'Get the current location of the device'
|
||||
],
|
||||
primer: 'function.md',
|
||||
template: 'function'
|
||||
},
|
||||
{
|
||||
label: 'Write to database',
|
||||
desc: '',
|
||||
examples: [],
|
||||
template: 'function-crud'
|
||||
},
|
||||
{
|
||||
label: '',
|
||||
desc: '',
|
||||
examples: [],
|
||||
template: 'chart'
|
||||
}
|
||||
];
|
||||
|
||||
export type AiTemplate = {
|
||||
id: string;
|
||||
type: AiNodeTemplateType;
|
||||
nodeName: string;
|
||||
templateId: string;
|
||||
template: AiNodeTemplate;
|
||||
name: string;
|
||||
description: string;
|
||||
examples: string[];
|
||||
promptUrl: string;
|
||||
};
|
||||
|
||||
export enum AiAssistantEvent {
|
||||
ProcessingUpdated,
|
||||
ActivityUpdated
|
||||
}
|
||||
|
||||
export type AiAssistantEvents = {
|
||||
[AiAssistantEvent.ProcessingUpdated]: (nodeId: string) => void;
|
||||
[AiAssistantEvent.ActivityUpdated]: () => void;
|
||||
};
|
||||
|
||||
export interface AiActivityItem {
|
||||
id: string;
|
||||
type: PopupItemType;
|
||||
title: string;
|
||||
prompt: string;
|
||||
node?: any;
|
||||
graph?: ComponentModel;
|
||||
}
|
||||
|
||||
async function deprecated_getAiDirPath() {
|
||||
const relative = '.noodl/ai';
|
||||
const path = filesystem.resolve(ProjectModel.instance._retainedProjectDirectory, relative);
|
||||
if (!filesystem.exists(path)) {
|
||||
await filesystem.makeDirectory(path);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
export class AiAssistantModel extends Model<AiAssistantEvent, AiAssistantEvents> {
|
||||
public static instance = new AiAssistantModel();
|
||||
|
||||
public templates: AiTemplate[] = docsTemplates.map(
|
||||
(x): AiTemplate => ({
|
||||
id: x.label,
|
||||
type: aiNodeTemplates[x.template]?.type,
|
||||
nodeName: aiNodeTemplates[x.template]?.name,
|
||||
templateId: x.template,
|
||||
template: aiNodeTemplates[x.template],
|
||||
name: x.label,
|
||||
description: x.desc,
|
||||
examples: x.examples || [],
|
||||
promptUrl: null
|
||||
})
|
||||
);
|
||||
|
||||
private _contexts: Record<string, AiCopilotContext> = {};
|
||||
|
||||
public activities: AiActivityItem[] = [];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
const _self = this;
|
||||
EventDispatcher.instance.on(
|
||||
['Model.nodeRemoved'],
|
||||
function ({ args }: { args: { model: NodeGraphNode }; model: NodeGraphModel }) {
|
||||
const nodeId = args.model.id;
|
||||
if (_self._contexts[nodeId]) {
|
||||
_self._contexts[nodeId].abortController.abort('node deleted');
|
||||
delete _self._contexts[nodeId];
|
||||
}
|
||||
},
|
||||
this
|
||||
);
|
||||
}
|
||||
|
||||
public addActivity({ id, type, title, prompt, node, graph }: AiActivityItem) {
|
||||
this.activities.push({ id, type, title, prompt, node, graph });
|
||||
this.notifyListeners(AiAssistantEvent.ActivityUpdated);
|
||||
}
|
||||
|
||||
public removeActivity(id: AiActivityItem['id']) {
|
||||
const activityTitle = this.activities.find((item) => item.id === id).title;
|
||||
this.activities = this.activities.filter((item) => item.id !== id);
|
||||
this.notifyListeners(AiAssistantEvent.ActivityUpdated);
|
||||
ToastLayer.showInteraction(`AI finished the ${activityTitle} activity`);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns A list of all Node ids that are currently getting processed.
|
||||
*/
|
||||
public getProcessingNodeIds(): string[] {
|
||||
return Object.keys(this._contexts).filter((x) => this._contexts[x].chatHistory.activities.length > 0);
|
||||
}
|
||||
|
||||
public async createContext(node: NodeGraphNode) {
|
||||
const aiAssistant = node.metadata.AiAssistant;
|
||||
if (!aiAssistant) {
|
||||
throw 'This node is not an AI node.';
|
||||
}
|
||||
|
||||
if (this._contexts[node.id]) {
|
||||
return this._contexts[node.id];
|
||||
}
|
||||
|
||||
const chatHistory = ChatHistory.fromJSON(node.metadata.prompt);
|
||||
|
||||
// Backwards compatibility, load the AI file and fetch the template.
|
||||
if (node.metadata.AiAssistant && !node.metadata.prompt) {
|
||||
try {
|
||||
const path = await deprecated_getAiDirPath();
|
||||
const filePath = filesystem.resolve(path, node.metadata.AiAssistant);
|
||||
if (filesystem.exists(filePath)) {
|
||||
const json = await filesystem.readJson(filePath);
|
||||
const messages = json.history || [];
|
||||
messages.forEach((message) => {
|
||||
chatHistory.add(message);
|
||||
});
|
||||
chatHistory.metadata.templateId = json.metadata.templateId;
|
||||
// NOTE: Keeping this since it is used in other places to define it as AI node
|
||||
node.metadata.AiAssistant = 'moved';
|
||||
node.metadata.prompt = chatHistory.toJSON();
|
||||
node.notifyListeners('metadataChanged', { key: 'prompt', data: node.metadata.prompt });
|
||||
await filesystem.removeFile(filePath);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load old AI file.', error);
|
||||
}
|
||||
}
|
||||
|
||||
// HACK: It is loading it twice...
|
||||
if (this._contexts[node.id]) {
|
||||
return this._contexts[node.id];
|
||||
}
|
||||
|
||||
chatHistory.on(ChatHistoryEvent.ActivitiesChanged, () => {
|
||||
this.notifyListeners(AiAssistantEvent.ProcessingUpdated, node.id);
|
||||
});
|
||||
|
||||
const template = this.templates.find((x) => x.templateId === chatHistory.metadata.templateId);
|
||||
if (!template) {
|
||||
throw 'Template not found';
|
||||
}
|
||||
|
||||
const context = new AiCopilotContext(template, chatHistory, node);
|
||||
this._contexts[node.id] = context;
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
public async send(context: AiCopilotContext) {
|
||||
await context.template.template.onMessage(
|
||||
// Send it as an object so the methods are bound to this class.
|
||||
context.toObject(),
|
||||
context.chatHistory.messages.length > 0
|
||||
? context.chatHistory.messages[context.chatHistory.messages.length - 1]
|
||||
: null
|
||||
);
|
||||
|
||||
// Save the chat history
|
||||
context.node.metadata.prompt = context.chatHistory.toJSON();
|
||||
}
|
||||
|
||||
public async createNode(templateId: string, parentModel: NodeGraphNode, pos: TSFixme) {
|
||||
function createNodes(nodeset: NodeGraphNodeSet, pos?: TSFixme, parentModel?: NodeGraphNode, toastMessage?: string) {
|
||||
const nodes: NodeGraphNode[] = [];
|
||||
if (parentModel) {
|
||||
for (const node of nodeset.nodes) {
|
||||
parentModel.addChild(node);
|
||||
nodes.push(node);
|
||||
}
|
||||
} else {
|
||||
const nodeGraph = NodeGraphContextTmp.nodeGraph;
|
||||
const insertedNodeset = nodeGraph.insertNodeSet({
|
||||
nodeset: nodeset,
|
||||
x: pos.x,
|
||||
y: pos.y,
|
||||
toastMessage
|
||||
});
|
||||
for (const node of insertedNodeset.nodes) {
|
||||
nodes.push(node);
|
||||
}
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
|
||||
const template = this.templates.find((x) => x.templateId === templateId);
|
||||
if (!template) {
|
||||
throw 'Template not found';
|
||||
}
|
||||
|
||||
const metadata: Record<string, unknown> = {
|
||||
prompt: new ChatHistory([], { templateId }).toJSON(),
|
||||
AiAssistant: 'old'
|
||||
};
|
||||
|
||||
//hack: nodes that are of type 'green' will get a color override to the node color theme 'data'
|
||||
let color: NodeColor;
|
||||
switch (template.type) {
|
||||
case 'green':
|
||||
color = 'data';
|
||||
break;
|
||||
}
|
||||
if (color) {
|
||||
metadata.colorOverride = color;
|
||||
}
|
||||
|
||||
if (template.template.nodeDisplayName) {
|
||||
metadata.typeLabelOverride = template.template.nodeDisplayName;
|
||||
}
|
||||
|
||||
const nodeset = new NodeGraphNodeSet({
|
||||
nodes: [
|
||||
NodeGraphNode.fromJSON({
|
||||
id: guid(),
|
||||
label: template.name,
|
||||
type: template.nodeName,
|
||||
x: 0,
|
||||
y: 0,
|
||||
metadata
|
||||
})
|
||||
],
|
||||
connections: []
|
||||
});
|
||||
|
||||
// NOTE: This will create a clone of the nodeset, so the ids will be changed
|
||||
const insertedNodes = createNodes(nodeset, pos, parentModel, `Created AI Node`);
|
||||
// HACK: Expect only one node back
|
||||
return this.createContext(insertedNodes[0]);
|
||||
}
|
||||
|
||||
//clears all the context, important when closing a project
|
||||
resetContexts() {
|
||||
this._contexts = {};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import { AiTemplate } from '@noodl-models/AiAssistant/AiAssistantModel';
|
||||
import { ChatHistory } from '@noodl-models/AiAssistant/ChatHistory';
|
||||
import { Ai } from '@noodl-models/AiAssistant/context/ai-api';
|
||||
import { AiQuery } from '@noodl-models/AiAssistant/context/ai-query';
|
||||
import {
|
||||
AiCopilotChatStreamArgs,
|
||||
AiCopilotChatStreamXmlArgs,
|
||||
IAiCopilotContext
|
||||
} from '@noodl-models/AiAssistant/interfaces';
|
||||
import { NodeGraphNode } from '@noodl-models/nodegraphmodel';
|
||||
|
||||
export class AiCopilotContext implements IAiCopilotContext {
|
||||
public readonly abortController = new AbortController();
|
||||
|
||||
constructor(
|
||||
public readonly template: AiTemplate,
|
||||
public readonly chatHistory: ChatHistory,
|
||||
public readonly node: NodeGraphNode
|
||||
) {}
|
||||
|
||||
chatStream(args: AiCopilotChatStreamArgs): Promise<string> {
|
||||
if (!args.abortController) {
|
||||
args.abortController = this.abortController;
|
||||
}
|
||||
return Ai.chatStream(args);
|
||||
}
|
||||
|
||||
chatStreamXml(args: AiCopilotChatStreamXmlArgs): Promise<string> {
|
||||
if (!args.abortController) {
|
||||
args.abortController = this.abortController;
|
||||
}
|
||||
return AiQuery.chatStreamXml(args);
|
||||
}
|
||||
|
||||
toObject(): IAiCopilotContext {
|
||||
return {
|
||||
template: this.template,
|
||||
chatHistory: this.chatHistory,
|
||||
node: this.node,
|
||||
chatStream: this.chatStream.bind(this),
|
||||
chatStreamXml: this.chatStreamXml.bind(this)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { AiNodeTemplate } from '@noodl-models/AiAssistant/interfaces';
|
||||
|
||||
import * as ChartTemplate from './templates/chart';
|
||||
import * as FunctionTemplate from './templates/function';
|
||||
import * as FunctionCrud from './templates/function-crud';
|
||||
import * as FunctionQueryDatabase from './templates/function-query-database';
|
||||
|
||||
export const aiNodeTemplates: Record<string, AiNodeTemplate> = {
|
||||
['function-crud']: FunctionCrud.template,
|
||||
['function-query-database']: FunctionQueryDatabase.template,
|
||||
function: FunctionTemplate.template,
|
||||
chart: ChartTemplate.template
|
||||
};
|
||||
@@ -0,0 +1,161 @@
|
||||
import { AiAssistantModel } from '@noodl-models/AiAssistant/AiAssistantModel';
|
||||
import { AiUtils } from '@noodl-models/AiAssistant/context/ai-utils';
|
||||
import { Model } from '@noodl-utils/model';
|
||||
|
||||
export enum ChatMessageType {
|
||||
User = 'user',
|
||||
Assistant = 'assistant'
|
||||
}
|
||||
|
||||
export enum ChatHistoryState {
|
||||
Idle,
|
||||
Processing
|
||||
}
|
||||
|
||||
export type ChatHistoryActivityId = 'processing' | 'code-generation';
|
||||
|
||||
export type ChatHistoryActivity = {
|
||||
id: ChatHistoryActivityId | string;
|
||||
name: string;
|
||||
status?: string;
|
||||
};
|
||||
|
||||
export type ChatMessage = {
|
||||
snowflakeId: string;
|
||||
type: ChatMessageType;
|
||||
content: string;
|
||||
metadata: Record<string, unknown>;
|
||||
};
|
||||
|
||||
export type ChatSuggestion = {
|
||||
id: string;
|
||||
text: string;
|
||||
};
|
||||
|
||||
export enum ChatHistoryEvent {
|
||||
MessagesChanged,
|
||||
ActivitiesChanged,
|
||||
MetadataChanged
|
||||
}
|
||||
|
||||
type ChatHistoryEvents = {
|
||||
[ChatHistoryEvent.MessagesChanged]: () => void;
|
||||
[ChatHistoryEvent.ActivitiesChanged]: (activities: readonly ChatHistoryActivity[]) => void;
|
||||
[ChatHistoryEvent.MetadataChanged]: () => void;
|
||||
};
|
||||
|
||||
export class ChatHistory extends Model<ChatHistoryEvent, ChatHistoryEvents> {
|
||||
private _messages: ChatMessage[] = [];
|
||||
private _activities: ChatHistoryActivity[] = [];
|
||||
private _metadata: Record<string, unknown>;
|
||||
|
||||
get messages() {
|
||||
return this._messages;
|
||||
}
|
||||
|
||||
get metadata() {
|
||||
return this._metadata;
|
||||
}
|
||||
|
||||
get activities() {
|
||||
return this._activities;
|
||||
}
|
||||
|
||||
get suggestions(): readonly ChatSuggestion[] {
|
||||
if (this._messages.length > 0) {
|
||||
const metadata = this._messages[this._messages.length - 1].metadata;
|
||||
return (metadata.suggestions as ChatSuggestion[]) || [];
|
||||
}
|
||||
|
||||
// When there are no messages,
|
||||
// show a list of example suggestions
|
||||
const template = AiAssistantModel.instance.templates.find((x) => x.templateId === this.metadata.templateId);
|
||||
if (template) {
|
||||
return template.examples.map((x) => ({
|
||||
id: x,
|
||||
text: x
|
||||
}));
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
constructor(items: ChatMessage[], metadata: Record<string, unknown> = {}) {
|
||||
super();
|
||||
this._messages = items || [];
|
||||
this._metadata = metadata || {};
|
||||
}
|
||||
|
||||
addActivity(activity: ChatHistoryActivity) {
|
||||
this._activities.push(activity);
|
||||
this.notifyListeners(ChatHistoryEvent.ActivitiesChanged, this._activities);
|
||||
}
|
||||
|
||||
removeActivity(activityId: string) {
|
||||
const length = this._activities.length;
|
||||
this._activities = this._activities.filter((x) => x.id !== activityId);
|
||||
if (this._activities.length !== length) {
|
||||
this.notifyListeners(ChatHistoryEvent.ActivitiesChanged, this._activities);
|
||||
}
|
||||
}
|
||||
|
||||
clearActivities() {
|
||||
if (this._activities.length === 0) return;
|
||||
this._activities.length = 0;
|
||||
this.notifyListeners(ChatHistoryEvent.ActivitiesChanged, this._activities);
|
||||
}
|
||||
|
||||
add(message: PartialWithRequired<ChatMessage, 'content'>) {
|
||||
if (!message) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
message.snowflakeId = AiUtils.generateSnowflakeId();
|
||||
if (!message.type) message.type = ChatMessageType.User;
|
||||
if (!message.metadata) message.metadata = {};
|
||||
|
||||
this.messages.push(message as ChatMessage);
|
||||
this.notifyListeners(ChatHistoryEvent.MessagesChanged);
|
||||
|
||||
return message.snowflakeId;
|
||||
}
|
||||
|
||||
updateLast(data?: Partial<Pick<ChatMessage, 'content' | 'metadata'>>) {
|
||||
if (data.content) {
|
||||
this.messages[this.messages.length - 1].content = data.content;
|
||||
}
|
||||
if (data.metadata) {
|
||||
this.messages[this.messages.length - 1].metadata = {
|
||||
...this.messages[this.messages.length - 1].metadata,
|
||||
...data.metadata
|
||||
};
|
||||
}
|
||||
|
||||
this.notifyListeners(ChatHistoryEvent.MessagesChanged);
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this._messages.length = 0;
|
||||
// this.copilot.notifyListeners(CopilotEvent.MessagesChanged);
|
||||
}
|
||||
|
||||
removeLast(): void {
|
||||
this._messages.pop();
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
history: this._messages,
|
||||
metadata: this._metadata
|
||||
};
|
||||
}
|
||||
|
||||
static fromJSON(json: any) {
|
||||
return new ChatHistory(json?.history, json?.metadata);
|
||||
}
|
||||
}
|
||||
|
||||
export interface CreateNodeFileOptions {
|
||||
nodeId: string;
|
||||
templateId: string;
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
export enum CopilotMessageType {
|
||||
User,
|
||||
System,
|
||||
Assistant,
|
||||
AssistantData,
|
||||
Debug
|
||||
}
|
||||
|
||||
export enum CopilotRole {
|
||||
User = 'user',
|
||||
Assistant = 'assistant',
|
||||
System = 'system'
|
||||
}
|
||||
|
||||
type CopilotMessageUser = {
|
||||
type: CopilotMessageType.User;
|
||||
role: CopilotRole.User;
|
||||
index: number;
|
||||
prompt: string;
|
||||
};
|
||||
|
||||
export type CopilotMessageAssistant = {
|
||||
type: CopilotMessageType.Assistant | CopilotMessageType.AssistantData;
|
||||
role: CopilotRole.Assistant;
|
||||
subject: string;
|
||||
shortend: string;
|
||||
conversationStage: string;
|
||||
action: string;
|
||||
response: string;
|
||||
};
|
||||
|
||||
type CopilotMessageSystem = {
|
||||
type: CopilotMessageType.Debug | CopilotMessageType.System;
|
||||
role: CopilotRole.System;
|
||||
response: string;
|
||||
};
|
||||
|
||||
export type CopilotMessage = CopilotMessageUser | CopilotMessageAssistant | CopilotMessageSystem;
|
||||
|
||||
export namespace CopilotMessagePrompt {
|
||||
export function system(content: string): CopilotMessage {
|
||||
return {
|
||||
type: CopilotMessageType.System,
|
||||
role: CopilotRole.System,
|
||||
response: content
|
||||
};
|
||||
}
|
||||
|
||||
export function debug(content: string): CopilotMessage {
|
||||
return {
|
||||
type: CopilotMessageType.Debug,
|
||||
role: CopilotRole.System,
|
||||
response: content
|
||||
};
|
||||
}
|
||||
|
||||
export function user(prompt: string, index: number): CopilotMessage {
|
||||
return {
|
||||
type: CopilotMessageType.User,
|
||||
role: CopilotRole.User,
|
||||
index: index || -1,
|
||||
prompt: prompt.trim()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export namespace CopilotMessageUtils {
|
||||
export function isVisibleToUser(message: CopilotMessage) {
|
||||
return [CopilotMessageType.User, CopilotMessageType.Assistant].includes(message.type);
|
||||
}
|
||||
|
||||
export function toText(message: CopilotMessage) {
|
||||
switch (message.role) {
|
||||
case CopilotRole.Assistant:
|
||||
return message.response;
|
||||
case CopilotRole.System:
|
||||
return message.response;
|
||||
case CopilotRole.User:
|
||||
return message.prompt;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import SchemaHandler from '@noodl-utils/schemahandler';
|
||||
|
||||
export async function extractDatabaseSchema() {
|
||||
const schema = SchemaHandler.instance;
|
||||
if (typeof schema.haveCloudServices === 'undefined') {
|
||||
console.error('Missing database schema');
|
||||
|
||||
try {
|
||||
await schema._fetch();
|
||||
} catch (error) {
|
||||
// lets ignore it then...
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
const dbCollectionsSource =
|
||||
schema.dbCollections
|
||||
.map((collection) => {
|
||||
let str = `${collection.name}\n`;
|
||||
Object.keys(collection.schema.properties).forEach((name) => {
|
||||
const property = collection.schema.properties[name];
|
||||
switch (property.type) {
|
||||
case 'Pointer': {
|
||||
str += `- ${name}:Pointer ${property.targetClass}\n`;
|
||||
break;
|
||||
}
|
||||
|
||||
case 'Relation': {
|
||||
str += `- ${name}:Relation ${property.targetClass}\n`;
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
str += `- ${name}:${property.type}\n`;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return str;
|
||||
})
|
||||
.join('\n') + '\n';
|
||||
|
||||
return dbCollectionsSource;
|
||||
}
|
||||
|
||||
export async function extractDatabaseSchemaJSON(): Promise<{ name: string; schema: TSFixme }[]> {
|
||||
const schema = SchemaHandler.instance;
|
||||
if (typeof schema.haveCloudServices === 'undefined') {
|
||||
console.error('Missing database schema');
|
||||
|
||||
try {
|
||||
await schema._fetch();
|
||||
} catch (error) {
|
||||
// lets ignore it then...
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
return Object.keys(schema.dbCollections).map((key) => schema.dbCollections[key]);
|
||||
}
|
||||
|
||||
export function databaseSchemaCompact(schema: TSFixme): { name: string; compact: string }[] {
|
||||
return schema.map((x) => ({
|
||||
name: x.name,
|
||||
compact: Object.keys(x.schema.properties).join(',')
|
||||
}));
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import { ReActCommand, ReActCommandLexer } from '@noodl-models/AiAssistant/_backend/commandLexer';
|
||||
import { ChatHistory, ChatMessageType } from '@noodl-models/AiAssistant/ChatHistory';
|
||||
import { Ai } from '@noodl-models/AiAssistant/context/ai-api';
|
||||
import { AiCopilotChatMessage } from '@noodl-models/AiAssistant/interfaces';
|
||||
import { NodeGraphModel } from '@noodl-models/nodegraphmodel';
|
||||
|
||||
export class ReActContext {
|
||||
snowflakeId: string;
|
||||
chatHistory: ChatHistory;
|
||||
nodeGraphModel?: NodeGraphModel;
|
||||
}
|
||||
|
||||
export abstract class ReActBaseTool {
|
||||
public abstract execute(command: ReActCommand, context: ReActContext): void;
|
||||
}
|
||||
|
||||
export abstract class ReActAgent<TParams = unknown> {
|
||||
protected abstract get commands(): Record<string, ReActBaseTool>;
|
||||
protected abstract createSystemContext(context: ReActContext, params: TParams): string;
|
||||
protected abstract createHistory(context: ReActContext, params: TParams): AiCopilotChatMessage[];
|
||||
|
||||
public async act(context: ReActContext, params: TParams) {
|
||||
const parser = new ReActCommandLexer();
|
||||
|
||||
const systemContext = this.createSystemContext(context, params);
|
||||
const history = this.createHistory(context, params);
|
||||
|
||||
context.chatHistory.add({
|
||||
type: ChatMessageType.Assistant,
|
||||
content: ''
|
||||
});
|
||||
|
||||
await Ai.chatStream({
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
content: systemContext
|
||||
},
|
||||
...history
|
||||
],
|
||||
provider: {
|
||||
model: 'gpt-4',
|
||||
temperature: 0
|
||||
},
|
||||
onStream: (_, text) => {
|
||||
const newCommands = parser.append(text);
|
||||
console.log('[stream]', _);
|
||||
newCommands.forEach((command) => {
|
||||
if (this.commands[command.type]) {
|
||||
this.commands[command.type].execute(command, context);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
export class ReActCommand {
|
||||
constructor(public type: string, public args: string[]) {}
|
||||
}
|
||||
|
||||
export class ReActCommandLexer {
|
||||
private _position = 0;
|
||||
private _commands: ReActCommand[] = [];
|
||||
private _buffer = '';
|
||||
|
||||
public get commands(): readonly ReActCommand[] {
|
||||
return this._commands;
|
||||
}
|
||||
|
||||
private peek(input: string): string {
|
||||
if (this._position < input.length) {
|
||||
return input[this._position];
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
private next(input: string): string {
|
||||
const ch = input[this._position];
|
||||
this._position++;
|
||||
return ch;
|
||||
}
|
||||
|
||||
private readUntil(input: string, stopChars: string[]): string {
|
||||
let value = '';
|
||||
let escaped = false;
|
||||
while (this._position < input.length) {
|
||||
const ch = this.peek(input);
|
||||
if (ch === '\n') {
|
||||
value += ch;
|
||||
this.next(input);
|
||||
} else if (stopChars.includes(ch) && !escaped) {
|
||||
break;
|
||||
} else if (ch === '\\' && !escaped) {
|
||||
escaped = true;
|
||||
this.next(input);
|
||||
} else {
|
||||
escaped = false;
|
||||
value += this.next(input);
|
||||
}
|
||||
}
|
||||
return value.trim();
|
||||
}
|
||||
|
||||
public append(ch: string): ReActCommand[] {
|
||||
this._buffer += ch;
|
||||
const commandCount = this.commands.length;
|
||||
this.tokenize(this._buffer);
|
||||
// TODO: Check if any of the command content changed
|
||||
if (this.commands.length > commandCount) {
|
||||
return this.commands.slice(commandCount - 1);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
public tokenize(input: string): ReActCommand[] {
|
||||
this._position = 0;
|
||||
this._commands = [];
|
||||
|
||||
while (this._position < input.length) {
|
||||
const uppercaseRegex = /[A-Z]/;
|
||||
while (this._position < input.length && !uppercaseRegex.test(this.peek(input))) {
|
||||
this.next(input);
|
||||
}
|
||||
|
||||
const value = this.readUntil(input, ['[']);
|
||||
const commandType = value.trim();
|
||||
|
||||
const args: string[] = [];
|
||||
if (this.peek(input) === '[') {
|
||||
this.next(input);
|
||||
}
|
||||
|
||||
while (this.peek(input) !== ']') {
|
||||
if (this._position >= input.length) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (this.peek(input) === '"') {
|
||||
this.next(input);
|
||||
const arg = this.readUntil(input, ['"']);
|
||||
args.push(arg);
|
||||
this.next(input);
|
||||
} else {
|
||||
this.next(input);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.peek(input) === ']') {
|
||||
this.next(input);
|
||||
if (commandType !== '' && /^[A-Z]/.test(commandType)) {
|
||||
this._commands.push(new ReActCommand(commandType, args));
|
||||
}
|
||||
} else {
|
||||
if (this._commands.length > 0) {
|
||||
const lastCommand = this._commands[this._commands.length - 1];
|
||||
lastCommand.type = commandType;
|
||||
lastCommand.args = args;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this._commands;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
export enum TokenTypes {
|
||||
OPEN_TAG = 'OPEN_TAG',
|
||||
CLOSE_TAG = 'CLOSE_TAG',
|
||||
SELF_CLOSING_TAG = 'SELF_CLOSING_TAG',
|
||||
TEXT = 'TEXT'
|
||||
}
|
||||
|
||||
export class Token {
|
||||
constructor(public type: TokenTypes, public value: string, public attributes: Record<string, string> = {}) {}
|
||||
}
|
||||
|
||||
export class Lexer {
|
||||
private _position = 0;
|
||||
private _tokens: Token[] = [];
|
||||
private _buffer = '';
|
||||
|
||||
public get tokens(): readonly Token[] {
|
||||
return this._tokens;
|
||||
}
|
||||
|
||||
private peek(input: string): string {
|
||||
if (this._position < input.length) {
|
||||
return input[this._position];
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
private next(input: string): string {
|
||||
const ch = input[this._position];
|
||||
this._position++;
|
||||
return ch;
|
||||
}
|
||||
|
||||
private readUntil(input: string, stopChar: string): string {
|
||||
let value = '';
|
||||
while (this._position < input.length) {
|
||||
const ch = this.peek(input);
|
||||
if (ch === stopChar) {
|
||||
break;
|
||||
}
|
||||
value += this.next(input);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private tokenizeTag(input: string): { tagName: string; attributes: Record<string, string> } {
|
||||
const parts = input.trim().split(/(?=\s\w+=)/);
|
||||
const tagName = parts.shift()?.trim() || '';
|
||||
const attributes: Record<string, string> = {};
|
||||
|
||||
for (const part of parts) {
|
||||
const [key, value] = part.trim().split('=');
|
||||
if (value) {
|
||||
attributes[key] = value.replace(/^"(.*)"$/, '$1');
|
||||
}
|
||||
}
|
||||
|
||||
return { tagName, attributes };
|
||||
}
|
||||
|
||||
public tokenize(input: string): Token[] {
|
||||
const _tokens: Token[] = [];
|
||||
this._position = 0;
|
||||
|
||||
while (this._position < input.length) {
|
||||
const ch = this.peek(input);
|
||||
|
||||
if (ch === '<') {
|
||||
this.next(input);
|
||||
const value = this.readUntil(input, '>');
|
||||
|
||||
// If there is no closing '>' for an OPEN_TAG, return an empty array and store it in the _buffer
|
||||
if (this._position >= input.length) {
|
||||
this._buffer = '<' + value;
|
||||
return [];
|
||||
}
|
||||
|
||||
this.next(input);
|
||||
|
||||
if (value.startsWith('/')) {
|
||||
_tokens.push(new Token(TokenTypes.CLOSE_TAG, value.slice(1)));
|
||||
} else if (value.endsWith('/')) {
|
||||
const { tagName, attributes } = this.tokenizeTag(value.slice(0, -1));
|
||||
_tokens.push(new Token(TokenTypes.SELF_CLOSING_TAG, tagName, attributes));
|
||||
} else {
|
||||
const { tagName, attributes } = this.tokenizeTag(value);
|
||||
_tokens.push(new Token(TokenTypes.OPEN_TAG, tagName, attributes));
|
||||
}
|
||||
} else {
|
||||
// handle tag values
|
||||
let value = '';
|
||||
while (this._position < input.length) {
|
||||
const nextChar = this.peek(input);
|
||||
if (nextChar === '<') {
|
||||
break;
|
||||
}
|
||||
value += this.next(input);
|
||||
}
|
||||
if (value) {
|
||||
_tokens.push(new Token(TokenTypes.TEXT, value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return _tokens;
|
||||
}
|
||||
|
||||
public append(input: string): Token[] {
|
||||
// If there is a _buffer, prepend it to the input and clear the _buffer
|
||||
if (this._buffer) {
|
||||
input = this._buffer + input;
|
||||
this._buffer = '';
|
||||
}
|
||||
|
||||
// Tokenize the new input
|
||||
const new_tokens = this.tokenize(input);
|
||||
const updated_tokens = [];
|
||||
|
||||
// If the last token is TEXT and the first new token is not an OPEN_TAG, merge them
|
||||
if (this._tokens.length > 0 && new_tokens.length > 0) {
|
||||
const lastToken = this._tokens[this._tokens.length - 1];
|
||||
const firstNewToken = new_tokens[0];
|
||||
|
||||
if (
|
||||
lastToken.type === TokenTypes.TEXT &&
|
||||
![TokenTypes.OPEN_TAG, TokenTypes.CLOSE_TAG, TokenTypes.SELF_CLOSING_TAG].includes(firstNewToken.type)
|
||||
) {
|
||||
lastToken.value += firstNewToken.value;
|
||||
new_tokens.shift();
|
||||
updated_tokens.push(lastToken);
|
||||
}
|
||||
}
|
||||
|
||||
this._tokens = this._tokens.concat(new_tokens);
|
||||
return [...new_tokens, ...updated_tokens];
|
||||
}
|
||||
|
||||
public getTokenIndex() {
|
||||
return this._tokens.length - 1;
|
||||
}
|
||||
|
||||
public getTextBetween(start: number, end: number) {
|
||||
return _tokensToText(this._tokens.slice(start, end));
|
||||
}
|
||||
|
||||
public getLastToken(): Token | undefined {
|
||||
if (this._tokens.length > 0) {
|
||||
return this._tokens[this._tokens.length - 1];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public getFullText(): string {
|
||||
return _tokensToText(this._tokens);
|
||||
}
|
||||
}
|
||||
|
||||
export function _tokensToText(_tokens: Token[]): string {
|
||||
return _tokens.reduce((text, token) => {
|
||||
if (token.type === TokenTypes.TEXT) {
|
||||
return text + token.value;
|
||||
} else if (token.type === TokenTypes.OPEN_TAG) {
|
||||
const attributes = Object.keys(token.attributes).map((x) => `${x}="${token.attributes[x]}"`);
|
||||
return attributes.length > 0
|
||||
? text + '<' + token.value + ' ' + attributes.join(' ') + '>'
|
||||
: text + '<' + token.value + '>';
|
||||
} else if (token.type === TokenTypes.CLOSE_TAG) {
|
||||
return text + '</' + token.value + '>';
|
||||
} else if (token.type === TokenTypes.SELF_CLOSING_TAG) {
|
||||
const attributes = Object.keys(token.attributes).map((x) => `${x}="${token.attributes[x]}"`);
|
||||
return attributes.length > 0
|
||||
? text + '<' + token.value + ' ' + attributes.join(' ') + ' />'
|
||||
: text + '<' + token.value + '/>';
|
||||
}
|
||||
return text;
|
||||
}, '');
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import { CopilotMessage, CopilotMessageType, CopilotRole } from '@noodl-models/AiAssistant/ChatMessage';
|
||||
|
||||
export interface ChatGPTMessage {
|
||||
role: 'system' | 'assistant' | 'user';
|
||||
name?: string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
export function isVisibleToOpenAI(message: CopilotMessage) {
|
||||
return [
|
||||
CopilotMessageType.Assistant,
|
||||
CopilotMessageType.AssistantData,
|
||||
CopilotMessageType.System,
|
||||
CopilotMessageType.User
|
||||
].includes(message.type);
|
||||
}
|
||||
|
||||
export function toOpenAI(message: CopilotMessage): ChatGPTMessage {
|
||||
// if (message.name) {
|
||||
// return {
|
||||
// role: this.role,
|
||||
// name: this.name,
|
||||
// content: this.content
|
||||
// };
|
||||
// }
|
||||
|
||||
switch (message.role) {
|
||||
case CopilotRole.Assistant:
|
||||
return {
|
||||
role: message.role,
|
||||
content: message.response
|
||||
};
|
||||
|
||||
case CopilotRole.System:
|
||||
return {
|
||||
role: message.role,
|
||||
content: message.response
|
||||
};
|
||||
|
||||
case CopilotRole.User:
|
||||
return {
|
||||
role: message.role,
|
||||
content: `Message ${message.index}: ${message.prompt}`
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import { Lexer, Token, TokenTypes } from './lexer';
|
||||
|
||||
export type CallbackFunction = (tagName: string, text: string) => void;
|
||||
export type TagCallbackFunction = (tagName: string, fullText: string) => void;
|
||||
export type TagOpenCallbackFunction = (tagName: string, attributes: Record<string, string>) => void;
|
||||
|
||||
export class Parser extends Lexer {
|
||||
private openTagStack: Token[] = [];
|
||||
private lastTextToken: Token | null = null;
|
||||
private callback: CallbackFunction;
|
||||
private openTagCallback: TagOpenCallbackFunction;
|
||||
private closeTagCallback: TagCallbackFunction;
|
||||
|
||||
constructor(
|
||||
callback: CallbackFunction,
|
||||
openTagCallback: TagOpenCallbackFunction,
|
||||
closeTagCallback: TagCallbackFunction
|
||||
) {
|
||||
super();
|
||||
this.callback = callback;
|
||||
this.openTagCallback = openTagCallback;
|
||||
this.closeTagCallback = closeTagCallback;
|
||||
}
|
||||
|
||||
public append(input: string): Token[] {
|
||||
const newTokens = super.append(input);
|
||||
|
||||
for (const token of newTokens) {
|
||||
if (token.type === TokenTypes.OPEN_TAG) {
|
||||
this.openTagStack.push(token);
|
||||
this.openTagCallback(token.value, token.attributes);
|
||||
} else if (token.type === TokenTypes.CLOSE_TAG) {
|
||||
const openTagIndex = this.openTagStack.findIndex((t) => t.value === token.value);
|
||||
if (openTagIndex !== -1) {
|
||||
this.openTagStack.splice(openTagIndex, 1);
|
||||
const lastToken = this.getLastToken();
|
||||
if (lastToken && lastToken.type === TokenTypes.TEXT && this.openTagStack.length === 0) {
|
||||
this.callback(token.value, lastToken.value);
|
||||
}
|
||||
this.closeTagCallback(token.value, this.lastTextToken?.value || '');
|
||||
}
|
||||
} else if (token.type === TokenTypes.SELF_CLOSING_TAG) {
|
||||
this.openTagCallback(token.value, token.attributes);
|
||||
this.closeTagCallback(token.value, '');
|
||||
} else if (token.type === TokenTypes.TEXT) {
|
||||
this.lastTextToken = token;
|
||||
if (this.openTagStack.length > 0) {
|
||||
const currentOpenTag = this.openTagStack[this.openTagStack.length - 1];
|
||||
if (currentOpenTag.type === TokenTypes.OPEN_TAG) {
|
||||
this.callback(currentOpenTag.value, token.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return newTokens;
|
||||
}
|
||||
|
||||
public getCurrentOpenTag(): Token | null {
|
||||
if (this.openTagStack.length > 0) {
|
||||
return this.openTagStack[this.openTagStack.length - 1];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public getLastTextToken(): Token | null {
|
||||
return this.lastTextToken;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
export type AiAssistantModel = {
|
||||
name: string;
|
||||
displayName: string;
|
||||
promptTokenCost: number;
|
||||
completionTokenCost: number;
|
||||
};
|
||||
|
||||
export interface AiAssistantConfig {
|
||||
version: string;
|
||||
models: AiAssistantModel[];
|
||||
}
|
||||
|
||||
export async function verifyOpenAiApiKey(apiKey: string): Promise<Record<string, { id: string }> | null> {
|
||||
const response = await fetch(`https://api.openai.com/v1/models`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: 'Bearer ' + apiKey
|
||||
}
|
||||
});
|
||||
|
||||
if (response.status !== 200) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const json = await response.json();
|
||||
|
||||
const models = json.data.reduce((acc, item) => {
|
||||
acc[item.id] = item;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
return models;
|
||||
}
|
||||
|
||||
export namespace AiAssistantApi {
|
||||
export async function getConfig(): Promise<AiAssistantConfig> {
|
||||
return {
|
||||
version: '0.0.0',
|
||||
models: [
|
||||
{
|
||||
name: 'gpt-4',
|
||||
displayName: 'gpt-4 (8k context)',
|
||||
promptTokenCost: 0.03,
|
||||
completionTokenCost: 0.06
|
||||
},
|
||||
{
|
||||
name: 'gpt-3.5-turbo',
|
||||
displayName: 'gpt-3.5-turbo',
|
||||
promptTokenCost: 0.002,
|
||||
completionTokenCost: 0.03
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
import { EventStreamContentType, fetchEventSource } from '@microsoft/fetch-event-source';
|
||||
import { OpenAiStore } from '@noodl-store/AiAssistantStore';
|
||||
|
||||
import { AiCopilotChatProviders, AiCopilotChatStreamArgs } from '@noodl-models/AiAssistant/interfaces';
|
||||
|
||||
function toChatProvider(provider: AiCopilotChatProviders | undefined) {
|
||||
return {
|
||||
model: provider?.model || 'gpt-3.5-turbo',
|
||||
temperature: provider?.temperature,
|
||||
max_tokens: provider?.max_tokens
|
||||
};
|
||||
}
|
||||
|
||||
async function directChatOpenAi({ messages, provider, abortController, onEnd, onStream }: AiCopilotChatStreamArgs) {
|
||||
const OPENAI_API_KEY = OpenAiStore.getApiKey();
|
||||
const controller = abortController || new AbortController();
|
||||
let endpoint = `https://api.openai.com/v1/chat/completions`;
|
||||
|
||||
if (OpenAiStore.getVersion() === 'enterprise') {
|
||||
endpoint = OpenAiStore.getEndpoint();
|
||||
}
|
||||
|
||||
let fullText = '';
|
||||
let completionTokenCount = 0;
|
||||
|
||||
let tries = 2;
|
||||
await fetchEventSource(endpoint, {
|
||||
method: 'POST',
|
||||
openWhenHidden: true,
|
||||
headers: {
|
||||
Authorization: 'Bearer ' + OPENAI_API_KEY,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
signal: controller.signal,
|
||||
body: JSON.stringify({
|
||||
...toChatProvider(provider),
|
||||
messages,
|
||||
stream: true
|
||||
}),
|
||||
async onopen(response) {
|
||||
if (response.ok && response.headers.get('content-type') === EventStreamContentType) {
|
||||
return; // everything's good
|
||||
} else if (response.status >= 400 && response.status < 500 && response.status !== 429) {
|
||||
// client-side errors are usually non-retriable:
|
||||
throw 'FatalError';
|
||||
} else {
|
||||
throw 'RetriableError';
|
||||
}
|
||||
},
|
||||
onmessage(ev) {
|
||||
if (ev.data === '[DONE]') {
|
||||
controller.abort();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const json = JSON.parse(ev.data);
|
||||
const delta = json.choices[0].delta.content;
|
||||
if (delta) {
|
||||
completionTokenCount++;
|
||||
fullText += delta;
|
||||
console.debug('[stream]', fullText);
|
||||
onStream && onStream(fullText, delta);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
},
|
||||
onclose() {
|
||||
onEnd && onEnd();
|
||||
},
|
||||
onerror(err) {
|
||||
const errText = err.toString();
|
||||
if (['FatalError'].includes(errText)) {
|
||||
throw err; // rethrow to stop the operation
|
||||
} else if (['RetriableError'].includes(errText)) {
|
||||
if (tries <= 0) {
|
||||
throw `Apologies, OpenAI is currently facing heavy traffic, causing delays in processing requests. Please be patient and try again later.`;
|
||||
}
|
||||
tries--;
|
||||
} else {
|
||||
// do nothing to automatically retry. You can also
|
||||
// return a specific retry interval here.
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
fullText,
|
||||
completionTokenCount
|
||||
};
|
||||
}
|
||||
|
||||
export namespace Ai {
|
||||
export async function chatStream(args: AiCopilotChatStreamArgs): Promise<string> {
|
||||
let fullText = '';
|
||||
|
||||
const version = OpenAiStore.getVersion();
|
||||
if (['full-beta', 'enterprise'].includes(version)) {
|
||||
const result = await directChatOpenAi(args);
|
||||
fullText = result.fullText;
|
||||
} else {
|
||||
throw 'Invalid AI version.';
|
||||
}
|
||||
|
||||
return fullText;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import { ReActCommand, ReActCommandLexer } from '@noodl-models/AiAssistant/_backend/commandLexer';
|
||||
import { Parser } from '@noodl-models/AiAssistant/_backend/parser';
|
||||
import { Ai } from '@noodl-models/AiAssistant/context/ai-api';
|
||||
import { AiCopilotChatArgs, AiCopilotChatStreamXmlArgs } from '@noodl-models/AiAssistant/interfaces';
|
||||
|
||||
export namespace AiQuery {
|
||||
export async function chatReAct({
|
||||
messages,
|
||||
provider,
|
||||
abortController
|
||||
}: AiCopilotChatArgs): Promise<{ commands: readonly ReActCommand[]; fullText: string }> {
|
||||
const parser = new ReActCommandLexer();
|
||||
let fullText = '';
|
||||
|
||||
await Ai.chatStream({
|
||||
provider,
|
||||
messages,
|
||||
abortController,
|
||||
onStream: (_, text) => {
|
||||
if (text) {
|
||||
fullText += text;
|
||||
parser.append(text);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
commands: parser.commands,
|
||||
fullText
|
||||
};
|
||||
}
|
||||
|
||||
export async function chatStreamXml({
|
||||
messages,
|
||||
provider,
|
||||
abortController,
|
||||
onEnd,
|
||||
onStream,
|
||||
onTagOpen,
|
||||
onTagEnd
|
||||
}: AiCopilotChatStreamXmlArgs): Promise<string> {
|
||||
const parser = new Parser(
|
||||
(tagName, text) => {
|
||||
console.debug(tagName, text);
|
||||
onStream && onStream(tagName, text);
|
||||
},
|
||||
(tagName, attributes) => {
|
||||
onTagOpen && onTagOpen(tagName, attributes);
|
||||
},
|
||||
(tagName, fullText) => {
|
||||
onTagEnd && onTagEnd(tagName, fullText);
|
||||
}
|
||||
);
|
||||
|
||||
await Ai.chatStream({
|
||||
provider,
|
||||
messages,
|
||||
abortController,
|
||||
onEnd,
|
||||
onStream: (_, text) => {
|
||||
if (text) {
|
||||
parser.append(text);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return parser.getFullText();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import { PromiseUtils, RandomUtils } from '@noodl/platform';
|
||||
|
||||
// NOTE: Would be nice to have a text buffer class where we can write text blocks,
|
||||
// perhaps with streaming and then fake stream out the text as it becomes ready.
|
||||
// This will create a really nice UX flow while the LLM is processing.
|
||||
// Maybe call it something like StreamingTextBuffer?
|
||||
|
||||
export namespace AiUtils {
|
||||
export function generateSnowflakeId() {
|
||||
const timestamp = Date.now().toString(16).padStart(12, '0');
|
||||
const randomString = Math.random().toString(36).substring(2, 8);
|
||||
return `${timestamp}-${randomString}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias for fakeTokenStream, just with a smaller delay.
|
||||
*
|
||||
* @param inputString
|
||||
* @param callback
|
||||
* @param options
|
||||
* @returns
|
||||
*/
|
||||
export function fakeTokenStreamFast(
|
||||
inputString: string,
|
||||
callback: (delta: string, fullText: string) => void,
|
||||
options?: {
|
||||
delay?: [number, number];
|
||||
signal?: AbortSignal;
|
||||
}
|
||||
) {
|
||||
return fakeTokenStream(inputString, callback, { delay: [10, 25], ...(options || {}) });
|
||||
}
|
||||
|
||||
export async function fakeTokenStream(
|
||||
inputString: string,
|
||||
callback: (delta: string, fullText: string) => void,
|
||||
options?: {
|
||||
delay?: [number, number];
|
||||
signal?: AbortSignal;
|
||||
}
|
||||
) {
|
||||
// Split the input string into smaller chunks (tokens) of 2 to 3 characters
|
||||
const tokens = [];
|
||||
let tokenLength: number;
|
||||
for (let i = 0; i < inputString.length; i += tokenLength) {
|
||||
tokenLength = Math.floor(Math.random() * 2 + 2);
|
||||
tokens.push(inputString.slice(i, i + tokenLength));
|
||||
}
|
||||
|
||||
const delayMin = options?.delay?.length === 2 ? Number(options.delay[0]) : 50;
|
||||
const delayMax = options?.delay?.length === 2 ? Number(options.delay[1]) : 100;
|
||||
|
||||
let fullText = '';
|
||||
|
||||
// Iterate through the tokens and call the callback function with each token
|
||||
for (let i = 0; i < tokens.length; i++) {
|
||||
if (options?.signal?.aborted) {
|
||||
return;
|
||||
}
|
||||
|
||||
fullText += tokens[i];
|
||||
callback(tokens[i], fullText);
|
||||
await PromiseUtils.sleep(RandomUtils.range(delayMin, delayMax));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './AiAssistantModel';
|
||||
@@ -0,0 +1,124 @@
|
||||
import { AiTemplate } from '@noodl-models/AiAssistant/AiAssistantModel';
|
||||
import { ChatHistory, ChatMessage } from '@noodl-models/AiAssistant/ChatHistory';
|
||||
import { CopilotMessage, CopilotMessageAssistant } from '@noodl-models/AiAssistant/ChatMessage';
|
||||
import { ChatGPTMessage } from '@noodl-models/AiAssistant/_backend/mapper';
|
||||
import { NodeGraphNode } from '@noodl-models/nodegraphmodel';
|
||||
import { IModel } from '@noodl-utils/model';
|
||||
|
||||
export type AiNodeTemplateType = 'pink' | 'purple' | 'green' | 'grey' | 'blue';
|
||||
|
||||
export type AiCopilotTextProviders = {
|
||||
model: 'text-davinci-003',
|
||||
temperature?: number;
|
||||
max_tokens?: number;
|
||||
}
|
||||
|
||||
export type ModelName = 'gpt-3.5-turbo' | 'gpt-4';
|
||||
|
||||
export type AiCopilotChatProviders = {
|
||||
model: 'gpt-3.5-turbo',
|
||||
temperature?: number;
|
||||
max_tokens?: number;
|
||||
} | {
|
||||
model: 'gpt-4',
|
||||
temperature?: number;
|
||||
max_tokens?: number;
|
||||
}
|
||||
|
||||
export type AiCopilotTextArgs = {
|
||||
content: string;
|
||||
provider?: AiCopilotTextProviders;
|
||||
}
|
||||
|
||||
export type AiCopilotChatMessage = {
|
||||
role: 'system' | 'user' | 'assistant' | string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
export type AiCopilotChatArgs = {
|
||||
messages: AiCopilotChatMessage[];
|
||||
provider?: AiCopilotChatProviders;
|
||||
abortController?: AbortController;
|
||||
}
|
||||
|
||||
export type AiCopilotChatStreamArgs = Prettify<AiCopilotChatArgs & {
|
||||
onStream?: (fullText: string, text: string) => void;
|
||||
onEnd?: () => void;
|
||||
}>;
|
||||
|
||||
export type AiCopilotChatStreamXmlArgs = Prettify<AiCopilotChatArgs & {
|
||||
onStream?: (tagName: string, text: string) => void;
|
||||
onTagOpen?: (tagName: string, attributes: Record<string, string>) => void;
|
||||
onTagEnd?: (tagName: string, fullText: string) => void;
|
||||
onEnd?: () => void;
|
||||
}>;
|
||||
|
||||
export interface IAiCopilotContext {
|
||||
template: AiTemplate;
|
||||
chatHistory: ChatHistory;
|
||||
node: NodeGraphNode;
|
||||
|
||||
chatStream(args: AiCopilotChatStreamArgs): Promise<string>;
|
||||
chatStreamXml(args: AiCopilotChatStreamXmlArgs): Promise<string>;
|
||||
}
|
||||
|
||||
export type AiNodeTemplate = {
|
||||
type: AiNodeTemplateType;
|
||||
name: string;
|
||||
nodeDisplayName?: string;
|
||||
onMessage: (context: IAiCopilotContext, message: ChatMessage) => Promise<void>;
|
||||
}
|
||||
|
||||
// Memory fragments?
|
||||
export interface ICopilotMemory {
|
||||
get messages(): ReadonlyArray<CopilotMessage>;
|
||||
|
||||
add(message: CopilotMessage): void;
|
||||
|
||||
clear(): void;
|
||||
|
||||
forget(): void;
|
||||
|
||||
fetch(tokenLimit: number): ChatGPTMessage[];
|
||||
}
|
||||
|
||||
export interface ICopilotHistory {
|
||||
/** Send more information to AI. */
|
||||
respond(text: string): void;
|
||||
|
||||
assistant(text: string): void;
|
||||
|
||||
user(text: string): void;
|
||||
|
||||
/** Notify the user. */
|
||||
notify(text: string): void;
|
||||
}
|
||||
|
||||
export enum CopilotState {
|
||||
Idle,
|
||||
Processing
|
||||
}
|
||||
|
||||
export interface ICopilotAgentExecutor {
|
||||
state: CopilotState;
|
||||
currentResponse: CopilotMessageAssistant | null;
|
||||
|
||||
stop(): void;
|
||||
|
||||
execute(): void;
|
||||
}
|
||||
|
||||
export enum CopilotEvent {
|
||||
MessagesChanged,
|
||||
StateChanged
|
||||
}
|
||||
|
||||
export type CopilotEvents = {
|
||||
[CopilotEvent.MessagesChanged]: () => void;
|
||||
[CopilotEvent.StateChanged]: () => void;
|
||||
};
|
||||
|
||||
export interface ICopilot extends IModel<CopilotEvent, CopilotEvents>, ICopilotHistory {
|
||||
get memory(): ICopilotMemory;
|
||||
get executor(): ICopilotAgentExecutor;
|
||||
}
|
||||