├── .gitignore ├── 3rd_party ├── diff.min.js ├── lottie-player.js ├── marked.min.js ├── peerjs.min.js ├── plotly-2.26.0.min.js ├── pyodide │ └── pyodide.js ├── shpwrite.js └── trustedtypes.api_only.build.js ├── LICENSE ├── README.md ├── background.js ├── css ├── OEEex.css ├── ai.css └── darkMode.css ├── fonts └── MaterialIcons-Regular.woff2 ├── images ├── logo.json ├── logo_white_OEEex_close_128.png ├── logo_white_OEEex_close_16.png ├── logo_white_OEEex_close_32.png ├── logo_white_OEEex_close_48.png ├── logo_white_OEEex_close_64.png ├── logo_white_OEEex_open_128.png ├── logo_white_OEEex_open_16.png ├── logo_white_OEEex_open_32.png ├── logo_white_OEEex_open_48.png ├── logo_white_OEEex_open_64.png └── pyLogo.svg ├── main.js ├── mainThread.js ├── manifest.json ├── modules ├── OEEMenu.js ├── aiCodeGeneration.js ├── consoleError.js ├── copyAsJson.js ├── darkMode.js ├── docLink.js ├── editorSettings.js ├── externalLoad.js ├── insertFucntionSignature.js ├── llm_ai │ ├── AIModelInterface.js │ ├── GeminiModel.js │ ├── LLMSFactory.js │ ├── OllamaModel.js │ └── OpenAIModel.js ├── openInNewTab.js ├── plotly.js ├── pythonCE.js ├── runAll.js ├── sharedCodeSession.js ├── surveyMessage.js ├── terminal.js └── uploadWithManifest.js ├── modules_bg ├── actionButtonModuleManager.js ├── aiModuleManager.js ├── darkModeManager.js ├── initConfig.js └── oeelCache.js ├── option ├── ai-settings.css ├── ai-settings.js ├── ai.html ├── options.css ├── options.html └── options.js └── otherAssets ├── darkPlotly.json └── python ├── OEE_Interface.py └── earthengine-api.tar.gz /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | _metadata/* 3 | *.DS_Store 4 | -------------------------------------------------------------------------------- /3rd_party/trustedtypes.api_only.build.js: -------------------------------------------------------------------------------- 1 | (function(){/* 2 | 3 | Copyright 2017 Google Inc. All Rights Reserved. 4 | 5 | Licensed under the W3C SOFTWARE AND DOCUMENT NOTICE AND LICENSE. 6 | 7 | https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document 8 | */ 9 | const e="onabort onactivate onactivateinvisible onafterprint onafterupdate onanimationcancel onanimationend onanimationiteration onanimationstart onariarequest onauxclick onbeforeactivate onbeforecopy onbeforecut onbeforedeactivate onbeforeeditfocus onbeforepaste onbeforeprint onbeforeunload onbegin onblur onbounce oncancel oncanplay oncanplaythrough oncellchange onchange onclick onclose oncommand oncontextmenu oncontrolselect oncopy oncuechange oncut ondataavailable ondatasetchanged ondatasetcomplete ondblclick ondeactivate ondrag ondragdrop ondragend ondragenter ondragexit ondragleave ondragover ondragstart ondrop ondurationchange onemptied onend onended onerror onerrorupdate onexit onfilterchange onfinish onfocus onfocusin onfocusout onformdata onfullscreenchange onfullscreenerror ongotpointercapture onhelp oninput oninvalid onkeydown onkeypress onkeyup onlayoutcomplete onload onloadeddata onloadedmetadata onloadend onloadstart onlosecapture onlostpointercapture onmediacomplete onmediaerror onmessage onmousedown onmouseenter onmouseleave onmousemove onmouseout onmouseover onmouseup onmousewheel onmove onmoveend onmovestart onmozfullscreenchange onmozfullscreenerror onmscontentzoom onmsgesturechange onmsgesturedoubletap onmsgestureend onmsgesturehold onmsgesturestart onmsgesturetap onmsgotpointercapture onmsinertiastart onmslostpointercapture onmsmanipulationstatechanged onmspointercancel onmspointerdown onmspointerenter onmspointerleave onmspointermove onmspointerout onmspointerover onmspointerup onoffline ononline onoutofsync onoverscroll onpaste onpause onplay onplaying onpointercancel onpointerdown onpointerenter onpointerleave onpointermove onpointerout onpointerover onpointerrawupdate onpointerup onprogress onpropertychange onratechange onreadystatechange onrepeat onreset onresize onresizeend onresizestart onresume onreverse onrowdelete onrowenter onrowexit onrowinserted onscroll onscrollend onsearch onseek onseeked onseeking onselect onselectionchange onselectstart onshow onstalled onstart onstop onstorage onsubmit onsuspend onsynchrestored ontimeerror ontimeupdate ontoggle ontrackchange ontransitioncancel ontransitionend ontransitionrun ontransitionstart onunload onurlflip onvolumechange onwaiting onwebkitanimationend onwebkitanimationiteration onwebkitanimationstart onwebkitfullscreenchange onwebkitfullscreenerror onwebkittransitionend onwheel".split(" "); 10 | function E(){if("undefined"!==typeof window){const h=[];for(const r in HTMLElement.prototype)"on"===r.slice(0,2)&&h.push(r);return h}return e};const K="undefined"!==typeof window,L=()=>{throw new TypeError("undefined conversion");},U=()=>null,V=String.prototype.toLowerCase,aa=String.prototype.toUpperCase;function W(){throw new TypeError("Illegal constructor");}function X(){throw new TypeError("Illegal constructor");} 11 | const {trustedTypes:Y}=function(){function h(a){let b=F.get(a);void 0===b&&(b=q(null),F.set(a,b));return b}function r(a){const b=M(a);if(null==b||M(b)!==N)throw Error();for(const d of G(b))t(a,d,{value:a[d]});return a}function ba(a){if(!O(a)||!O(a.raw)||1!==a.length)throw new TypeError("Invalid input");a=ca(a);if(this===k){var b=da.call(null,"template");b.innerHTML=a;a=b.innerHTML}b=g(new this(v,"fromLiteral"));h(b).v=a;return b}function H(a,b){g(a.prototype);delete a.name;t(a,"fromLiteral",{value:ba.bind(a)}); 12 | t(a,"name",{value:b})}function I(a){return b=>b instanceof a&&F.has(b)}function ea(a,b){function d(n,J){const fa=b[J]||("default"==a?U:L),ha=g(new n(v,a));return g({[J](l,...z){l=fa(""+l,...z);if(void 0===l||null===l){if("default"==a)return l;l=""}l=""+l;z=g(q(ha));h(z).v=l;return z}}[J])}const c=q(W.prototype);for(const n of G(A))c[n]=d(A[n],n);t(c,"name",{value:a,writable:!1,configurable:!1,enumerable:!0});return g(c)}function P(a,b,d,c="",n=""){a=aa.apply(String(a));(c=n?n:c)||(c="http://www.w3.org/1999/xhtml"); 13 | if(c=w.apply(f,[c])?f[c]:null){if(w.apply(c,[a])&&c[a]&&w.apply(c[a][b],[d])&&c[a][b][d])return c[a][b][d];if(w.apply(c,["*"])&&w.apply(c["*"][b],[d])&&c["*"][b][d])return c["*"][b][d]}}function Q(){return x}const ia=Object.assign,q=Object.create,t=Object.defineProperty,g=Object.freeze,G=Object.getOwnPropertyNames,M=Object.getPrototypeOf,N=Object.prototype,O=Object.isFrozen,ca=String.raw,w=N.hasOwnProperty,da=document?document.createElement.bind(document):null,ja=Array.prototype.forEach,ka=Array.prototype.push, 14 | v=Symbol(),F=r(new WeakMap),y=r([]),B=r([]);let R=!0,x=null,C=!1;class D{constructor(a,b){if(a!==v)throw Error("cannot call the constructor");t(this,"policyName",{value:b,enumerable:!0})}toString(){return h(this).v}toJSON(){return h(this).v}valueOf(){return h(this).v}}class p extends D{}H(p,"TrustedScriptURL");class k extends D{}H(k,"TrustedHTML");class m extends D{}H(m,"TrustedScript");g(D.prototype);const S=g(q(new k(v,"")));h(S).v="";const T=g(q(new m(v,"")));h(T).v="";const f={["http://www.w3.org/1999/xhtml"]:{EMBED:{attributes:{src:p.name}}, 15 | IFRAME:{attributes:{srcdoc:k.name}},OBJECT:{attributes:{data:p.name,codebase:p.name}},SCRIPT:{attributes:{src:p.name,text:m.name},properties:{innerText:m.name,textContent:m.name,text:m.name}},"*":{attributes:{},properties:{innerHTML:k.name,outerHTML:k.name}}},["http://www.w3.org/2000/svg"]:{"*":{attributes:{},properties:{}}}};var u={codebase:"codeBase",formaction:"formAction"};!K||"srcdoc"in HTMLIFrameElement.prototype||delete f["http://www.w3.org/1999/xhtml"].IFRAME.attributes.srcdoc;for(const a of Object.keys(f["http://www.w3.org/1999/xhtml"])){f["http://www.w3.org/1999/xhtml"][a].properties|| 16 | (f["http://www.w3.org/1999/xhtml"][a].properties={});for(const b of Object.keys(f["http://www.w3.org/1999/xhtml"][a].attributes))f["http://www.w3.org/1999/xhtml"][a].properties[u[b]?u[b]:b]=f["http://www.w3.org/1999/xhtml"][a].attributes[b]}for(const a of E())f["http://www.w3.org/1999/xhtml"]["*"].attributes[a]="TrustedScript",f["http://www.w3.org/2000/svg"]["*"].attributes[a]="TrustedScript";const A={createHTML:k,createScriptURL:p,createScript:m},la=A.hasOwnProperty;u=q(X.prototype);ia(u,{createPolicy:function(a, 17 | b){if(!a.match(/^[-#a-zA-Z0-9=_/@.%]+$/g))throw new TypeError("Policy "+a+" contains invalid characters.");if(C&&-1===B.indexOf(a)&&-1===B.indexOf("*"))throw new TypeError("Policy "+a+" disallowed.");if("default"===a&&x)throw new TypeError("Policy "+a+" already exists.");if(C&&!R&&-1!==y.indexOf(a))throw new TypeError("Policy "+a+" exists.");y.push(a);const d=q(null);if(b&&"object"===typeof b)for(const c of G(b))la.call(A,c)&&(d[c]=b[c]);else console.warn("trustedTypes.createPolicy "+a+" was given an empty policy"); 18 | g(d);b=ea(a,d);"default"===a&&(x=b);return b},isHTML:I(k),isScriptURL:I(p),isScript:I(m),getAttributeType:function(a,b,d="",c=""){return P(a,"attributes",V.apply(String(b)),d,c)||null},getPropertyType:function(a,b,d=""){return P(a,"properties",String(b),d)||null},g:function(a=""){if(!a)try{a=document.documentElement.namespaceURI}catch(b){a="http://www.w3.org/1999/xhtml"}return(a=f[a])?JSON.parse(JSON.stringify(a)):{}},emptyHTML:S,emptyScript:T,defaultPolicy:x,TrustedHTML:k,TrustedScriptURL:p,TrustedScript:m}); 19 | t(u,"defaultPolicy",{get:Q,set:()=>{}});return{trustedTypes:g(u),l:function(a,b){C=!0;B.length=0;ja.call(a,d=>{ka.call(B,""+d)});R=b;y.length=0},h:function(){C=!1},i:Q,j:function(){x=null;y.splice(y.indexOf("default"),1)}}}();if("undefined"!==typeof window&&(window.TrustedTypes&&"undefined"===typeof window.trustedTypes&&(window.trustedTypes=Object.freeze(window.TrustedTypes)),"undefined"===typeof window.trustedTypes)){var Z=Object.create(X.prototype);Object.assign(Z,{isHTML:Y.isHTML,isScriptURL:Y.isScriptURL,isScript:Y.isScript,createPolicy:Y.createPolicy,getAttributeType:Y.getAttributeType,getPropertyType:Y.getPropertyType,getTypeMapping:Y.g,emptyHTML:Y.emptyHTML,emptyScript:Y.emptyScript,_isPolyfill_:!0});Object.defineProperty(Z, 20 | "defaultPolicy",Object.getOwnPropertyDescriptor(Y,"defaultPolicy")||{});window.trustedTypes=Object.freeze(Z);window.TrustedHTML=Y.TrustedHTML;window.TrustedScriptURL=Y.TrustedScriptURL;window.TrustedScript=Y.TrustedScript;window.TrustedTypePolicy=W;window.TrustedTypePolicyFactory=X};}).call(this); 21 | 22 | //# sourceMappingURL=trustedtypes.api_only.build.js.map 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Open Earth Engine Extension (OEEex) 2 | 3 | The Open Earth Engine Extension (OEEex) is an unofficial browser extension designed to enhance the Google Earth Engine (GEE) Code Editor with additional features and improved usability. 4 | 5 | ## Installation 6 | 7 | OEEex is available for both Google Chrome and Mozilla Firefox browsers. 8 | 9 | - **Google Chrome**: Install the extension from the [Chrome Web Store](https://chrome.google.com/webstore/detail/open-earth-engine-extensio/dhkobehdekjgdahfldleahkekjffibhg). After installation, refresh any open GEE Code Editor pages to activate the extension. 10 | - **Mozilla Firefox**: Install the extension from the [Firefox Add-ons Store](https://addons.mozilla.org/en-US/firefox/addon/oeeex/). Refresh any open GEE Code Editor pages to activate the extension. 11 | 12 | ## Features 13 | 14 | OEEex offers a variety of features to enhance your GEE experience: 15 | 16 | - **Run All!**: A single button that starts all tasks present in the task manager. To use it, run a set of tasks, and the button will appear. Simply press it, and the tasks will start running. 17 | - **Night Mode**: Introduces a dark theme to the GEE Code Editor. Select your preferred mode by clicking the logo in the upper right corner. For automatic mode based on your browser or system settings, double-click the logo or select the mode in the [options page](chrome-extension://dhkobehdekjgdahfldleahkekjffibhg/options.html). All Earth Engine tabs open in the same browser are linked; changing the mode in one tab will change it in the others. 18 | - **Code Editor Shortcuts**: Allows customization of keyboard shortcuts within the code editor. Mac users have a pre-configured selection (e.g., ⌘+S, ⌘+Enter). Modify these in the extension's [options page](chrome-extension://dhkobehdekjgdahfldleahkekjffibhg/options.html). 19 | - **OEEL Caching**: Provides a 1-hour cache for files from the [Open Earth Engine Library](https://www.open-geocomputing.org/OpenEarthEngineLibrary/), reducing execution time when using the library. This feature is enabled by default and cannot be disabled. 20 | - **Manifest Upload**: Allows drag-and-drop ingestion of GeoTIFFs with their manifest files directly into GEE. Create a manifest following the guidelines [here](https://developers.google.com/earth-engine/guides/asset_manifest), using local relative Unix-style file paths instead of `gs://` addresses. Store the `manifest.json` file in a folder with your GeoTIFFs (or zipped tables), then drag and drop this folder onto the upload icon in the asset tab to initiate the upload. 21 | 22 | For detailed information on each feature, please refer to the [feature documentation](https://www.open-geocomputing.org/OEEex/). 23 | 24 | ## Recent Changes 25 | 26 | - **Removal of Planet Imagery Transfer**: The feature related to Planet imagery transfer has been removed. 27 | - **Removal of Asset Availability**: The asset availability check feature has been discontinued. 28 | - **Discontinuation of Automatic Export**: The automatic export feature has been replaced with the "Run All!" functionality. 29 | 30 | ## Contributing 31 | 32 | Contributions are welcome! 33 | 34 | ## License 35 | 36 | This project is licensed under the GPLv3 License. 37 | -------------------------------------------------------------------------------- /background.js: -------------------------------------------------------------------------------- 1 | import { initialize as darkModeInitialize } from './modules_bg/darkModeManager.js'; 2 | import { initialize as oeelCacheInitialize } from './modules_bg/oeelCache.js'; 3 | import { initialize as actionButtonInitialize } from './modules_bg/actionButtonModuleManager.js'; 4 | import { initialize as aiOllamaFetchInit } from './modules_bg/aiModuleManager.js'; 5 | import { initialize as initConfig } from './modules_bg/initConfig.js'; 6 | // import { initialize as messageChatGPTInitialize } from './modules_bg/messageChatGPT.js'; 7 | 8 | // // Add other module imports here 9 | 10 | // // Initialize all modules 11 | initConfig(); 12 | actionButtonInitialize(); 13 | darkModeInitialize(); 14 | oeelCacheInitialize(); 15 | aiOllamaFetchInit(); 16 | // messageChatGPTInitialize(); 17 | // // Call initialize for all other modules 18 | -------------------------------------------------------------------------------- /css/OEEex.css: -------------------------------------------------------------------------------- 1 | .OEEexHide, 2 | .OEEexHide+div, 3 | .OEEexHide+div+div, 4 | .OEEexHide+div+div+div, 5 | .OEEexHide+div+div+div+div{ 6 | display: none; 7 | } 8 | .OEEexHide+hr.divider, 9 | .OEEexHide+div+hr.divider, 10 | .OEEexHide+div+div+hr.divider, 11 | .OEEexHide+div+div+div+hr.divider, 12 | .OEEexHide+div+div+div+div+hr.divider{ 13 | display: none; 14 | } 15 | .OEEexVerbose{ 16 | background-color: #b5fffe; 17 | } 18 | 19 | .dark .OEEexVerbose{ 20 | background-color: #005e5d; 21 | } 22 | 23 | .OEEexUploadButton iron-icon{ 24 | min-width: 30px; 25 | box-shadow: rgb(0 0 0 / 12%) 0px 1px 3px, rgb(0 0 0 / 24%) 0px 1px 2px; 26 | color: var(--ee-legacy-blue); 27 | font-size: var(--ee-button-font-size); 28 | min-height: 30px; 29 | } 30 | 31 | .OEEexUploadButton.dragover iron-icon{ 32 | color: blueviolet; 33 | } 34 | 35 | ee-console-log.OEEexTerminal{ 36 | position: absolute; 37 | bottom: -30px; 38 | width: 98%; 39 | } 40 | 41 | ee-console-log.OEEexTerminal div{ 42 | display: inline-grid; 43 | width: calc( 99% - 18px); 44 | margin: 0!important; 45 | background-color: transparent!important; 46 | } 47 | 48 | ee-console-log.OEEexTerminal input.jfk-textinput{ 49 | border: none; 50 | background-color: transparent; 51 | } 52 | 53 | ee-console-log.OEEexTerminal input.jfk-textinput:focus{ 54 | box-shadow: none; 55 | } 56 | 57 | ee-console-log.OEEexTerminal input::placeholder{ 58 | color: transparent; 59 | } 60 | 61 | ee-tab-panel ee-tab:nth-child(2){ 62 | height: calc(100% - 45px); 63 | } 64 | 65 | .oeeMenuContextContainer { 66 | z-index: 10; 67 | display: flex; 68 | flex-direction: column; 69 | gap: 5px; 70 | background: #f0f0f0; 71 | border: 1px solid #d4d4d4; 72 | padding: 5px; 73 | position: absolute; 74 | border-radius: 5px; 75 | } 76 | 77 | .oeeMenuContextButton { 78 | display: block; 79 | width: 100%; 80 | box-sizing: border-box; 81 | background-color: #fff; /* Default background */ 82 | border: none; 83 | padding: 5px 10px; 84 | text-align: left; 85 | } 86 | 87 | .oeeMenuContextButton:hover { 88 | background-color: #e0e0e0; /* Darker background on hover */ 89 | } 90 | 91 | .oeeSCSContainer{ 92 | z-index: 10; 93 | position: fixed; 94 | left: 50%; 95 | top: 50%; 96 | transform: translate(-50%, -50%); 97 | display: none; 98 | flex-direction: column; 99 | gap: 5px; 100 | background: #f0f0f0; 101 | border: 1px solid #d4d4d4; 102 | border-radius: 5px; 103 | padding: 5px; 104 | } 105 | 106 | .oeeSCSContainer.visible{ 107 | display: flex; 108 | } 109 | 110 | .session-toggle { 111 | display: flex; 112 | border: 1px solid #d4d4d4; 113 | border-radius: 5px; 114 | overflow: hidden; /* Ensures the buttons fit nicely inside the container */ 115 | } 116 | 117 | .toggle-btn { 118 | flex: 1; 119 | padding: 5px 10px; 120 | border: none; 121 | background: #e0e0e0; 122 | color: #000; 123 | cursor: pointer; 124 | transition: background-color 0.3s; 125 | } 126 | 127 | .toggle-btn.left { 128 | border-right: 1px solid #d4d4d4; 129 | border-radius: 5px 0 0 5px; 130 | } 131 | 132 | .toggle-btn.right { 133 | border-radius: 0 5px 5px 0; 134 | } 135 | 136 | .toggle-btn.active { 137 | background: #f0f0f0; 138 | } 139 | 140 | .toggle-btn { 141 | flex: 1; 142 | padding: 5px 10px; 143 | border: none; 144 | background: #e0e0e0; 145 | color: #000; 146 | cursor: pointer; 147 | transition: background-color 0.3s, box-shadow 0.3s; 148 | position: relative; /* For z-index */ 149 | z-index: 1; /* Ensure active button appears above the non-active */ 150 | } 151 | 152 | .toggle-btn.active, #validateSession:active { 153 | background: #d0d0d0; /* Slightly darker to simulate being pressed */ 154 | box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.2); /* Inner shadow for "in" effect */ 155 | z-index: 2; /* Ensure active button is above others */ 156 | } 157 | 158 | .oeeSCSContainer input.wrong{ 159 | color:red; 160 | } 161 | 162 | .remoteSelection{ 163 | background-color: rgba(0,255,0,0.5); 164 | } -------------------------------------------------------------------------------- /css/ai.css: -------------------------------------------------------------------------------- 1 | /* Container sizing (wrap around rootDiv, or use .container class if you prefer) */ 2 | #aiFirstPanelDiv { 3 | height: calc( var(--height) - 6px ); 4 | display: flex; 5 | gap: 2px; /* Space between items */ 6 | flex-direction: column; 7 | } 8 | 9 | /* Collapsible styling */ 10 | .collapsible { 11 | margin-bottom: 3px; 12 | display: block; 13 | } 14 | 15 | #aiFirstPanelDiv button { 16 | width: 100%; 17 | background-color: #eee; 18 | border: none; 19 | padding: 3px; 20 | text-align: left; 21 | font-size: 16px; 22 | cursor: pointer; 23 | outline: none; 24 | max-height: 25px; 25 | flex: 1; 26 | box-shadow: 0px 0px 5px gray; 27 | } 28 | 29 | #aiFirstPanelDiv button:hover{ 30 | background-color: #ccc; 31 | } 32 | 33 | /* Content hidden by default */ 34 | .content { 35 | overflow: hidden; 36 | transition: flex 0.3s ease-out; 37 | padding: 0 10px; /* No vertical padding so it doesn't expand space. */ 38 | flex: 0; 39 | } 40 | 41 | /* On toggle, expand to some max-height. 42 | Adjust 200px or use calc(...) if you want dynamic sizing. */ 43 | .content.show { 44 | flex-grow: 10; 45 | } 46 | 47 | #aiFirstPanelDiv .content.generate-code-panel textarea,#aiFirstPanelDiv .content.alter-code-panel textarea{ 48 | width: calc(100% - 4px); 49 | height: calc( var(--height) - 6px - ( 46px * 5) ); 50 | } 51 | 52 | #aiFirstPanelDiv .content.explain-code-panel button{ 53 | margin: 5px 0 0 0; 54 | max-height: none; 55 | } 56 | 57 | #aiFirstPanelDiv .content.explain-code-panel button:first-child{ 58 | margin-top: 15px; 59 | border-radius: 10px 10px 0px 0px; 60 | } 61 | 62 | #aiFirstPanelDiv .content.explain-code-panel button:last-child{ 63 | margin-botton: 15px; 64 | border-radius: 0px 0px 10px 10px; 65 | } 66 | 67 | .aiResult{ 68 | margin-bottom: 3px; 69 | display: inline-block; 70 | padding: 10px 15px; 71 | border: 2px solid #333; 72 | border-radius: 8px; 73 | background-color: #f4f4f4; 74 | box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.2); 75 | font-family: Arial, sans-serif; 76 | text-align: justify; 77 | } 78 | 79 | .dark .aiResult{ 80 | background-color:#2d2d2d; 81 | box-shadow: 2px 2px 5px rgba(170, 170, 170, 0.2); 82 | } 83 | 84 | .ace_dark .ace_gutter-cell.oeeex-ai-comment.ace_info, .ace_gutter-cell.oeeex-ai-comment.ace_info{ 85 | background-image: url('data:image/svg+xml;utf8,🧠'); 86 | filter: grayscale(0.8); 87 | } 88 | 89 | .ace_dark .ace_gutter-cell.oeeex-ai-comment.ace_info:hover,.ace_gutter-cell.oeeex-ai-comment.ace_info:hover{ 90 | filter: grayscale(0); 91 | } -------------------------------------------------------------------------------- /css/darkMode.css: -------------------------------------------------------------------------------- 1 | .dark #head{ 2 | background-color:#242629; 3 | background-image:-webkit-gradient(radial,100 36,0,100 -40,120,from(#16181a),to(#242629)); 4 | border-bottom:1px solid #1a1a1a; 5 | } 6 | 7 | .dark #logo>img{ 8 | filter: invert(1) hue-rotate(180deg) brightness(1.5); 9 | } 10 | 11 | .dark #main{ 12 | background-color:var(--oeel-background-color) ; 13 | color: var(--oeel-color); 14 | } 15 | 16 | 17 | div.ui-root { 18 | color: initial; 19 | } 20 | 21 | 22 | html.dark{ 23 | --oeel-background-color: rgb(29, 31, 33); 24 | --oeel-color: #C5C8C6; 25 | --ee-button-action-color:#000; 26 | --ee-button-menu-color:#000; 27 | --ee-legacy-blue:#1050b7; 28 | --ee-legacy-darkblue:#125ace; 29 | --ee-legacy-darkestblue:#619eff; 30 | --ee-legacy-alert:#bd6b00; 31 | --ee-legacy-red:#c83f2d; 32 | --ee-legacy-color-inactive:#878787; 33 | --ee-legacy-color-error:#ff4242; 34 | --ee-legacy-button-border:#242424; 35 | --ee-legacy-button-border-shadow:#404040; 36 | --color-error:#ff4242; 37 | --color-hover-bg:#0f0f0f; 38 | --icon-loading:url('/images/loading.gif'); 39 | --icon_dms_black_18:url(//fonts.gstatic.com/s/i/googlematerialicons/mapsmode/v16/black-18dp/1x/gm_mapsmode_black_18dp.png); 40 | --icon_dms_white_18:url(//fonts.gstatic.com/s/i/googlematerialicons/mapsmode/v16/white-18dp/1x/gm_mapsmode_white_18dp.png); 41 | --icon_classifier_black_18:url(//fonts.gstatic.com/s/i/googlematerialicons/scatter_plot/v10/black-18dp/1x/gm_scatter_plot_black_18dp.png); 42 | --icon_classifier_white_18:url(//fonts.gstatic.com/s/i/googlematerialicons/scatter_plot/v10/white-18dp/1x/gm_scatter_plot_white_18dp.png); 43 | --icon_image_collection_black_18:url(//www.gstatic.com/images/icons/material/system/1x/auto_awesome_motion_black_18dp.png); 44 | --icon_image_collection_white_18:url(//www.gstatic.com/images/icons/material/system/1x/auto_awesome_motion_white_18dp.png); 45 | --icon_image_black_18:url(//www.gstatic.com/images/icons/material/system/1x/image_black_18dp.png); 46 | --icon_image_white_18:url(//www.gstatic.com/images/icons/material/system/1x/image_white_18dp.png); 47 | --icon_table_black_18:url(//www.gstatic.com/images/icons/material/system/1x/table_chart_black_18dp.png); 48 | --icon_table_white_18:url(//www.gstatic.com/images/icons/material/system/1x/table_chart_white_18dp.png); 49 | --icon_folder_black_18:url(//www.gstatic.com/images/icons/material/system/1x/folder_black_18dp.png); 50 | --icon_folder_white_18:url(//www.gstatic.com/images/icons/material/system/1x/folder_white_18dp.png) 51 | } 52 | 53 | .dark .docs-list .docs-method-header:hover, .dark .docs-list-popup .docs-method-header { 54 | background: #0f0f0f; 55 | } 56 | 57 | .dark .docs-list-popup { 58 | background : var(--oeel-background-color); 59 | color: var(--oeel-color); 60 | } 61 | 62 | .dark .tree-manager .tree-file:not(.drag-and-drop):before, .dark .tree-drag-clone.tree-file:not(.drag-and-drop):before, .dark .zippy>.header:before, .dark ee-zippy>.header:before{ 63 | filter: invert(1) hue-rotate(180deg); 64 | } 65 | 66 | .dark .tree-manager .tree-file:hover, .dark .tree-manager .tree-file.tree-item-selected{ 67 | background: var(--color-hover-bg); 68 | } 69 | 70 | .dark ::selection { 71 | background: #445871; 72 | } 73 | 74 | .dark .tree-manager .tree-drag-target{ 75 | background:#413506; 76 | } 77 | .dark .tree-drag-clone{ 78 | background:var(--oeel-background-color); 79 | } 80 | 81 | .dark .env-list>.header, .dark .env-list>.body{ 82 | background:var(--oeel-background-color); 83 | } 84 | 85 | .dark .env-entry-prefix{ 86 | color:white 87 | } 88 | 89 | .dark .env-list>.env-gutter,.dark .ace_editor .ace_gutter{ 90 | background:#393939 !important; 91 | 92 | } 93 | 94 | .dark .env-list>.env-gutter:hover, .dark .env-list>.header:hover{ 95 | background-color: black; 96 | } 97 | 98 | .dark .ace_ee_instance, .dark .ace_ee_static{ 99 | color: #c43aff!important 100 | } 101 | 102 | .dark .goog-splitpane-handle { 103 | background-color: #535151; 104 | filter: invert(1) brightness(1.7); 105 | } 106 | 107 | .dark .editor-panel>.header, .dark .tab-panel>.header .goog-tab-bar{ 108 | border-color: #333333; 109 | } 110 | 111 | 112 | .dark .ace_numeric { 113 | color: #4685ea !important; 114 | } 115 | 116 | .dark .ace_storage { 117 | color: #E1E1E1 !important; 118 | } 119 | 120 | .dark .ace_ee_sync_xhr { 121 | color: #f50057 !important; 122 | } 123 | 124 | .dark input{ 125 | background-color:var(--oeel-background-color) ; 126 | } 127 | .dark input.ace_search_field{ 128 | background-color: revert; 129 | } 130 | 131 | .dark .ee-search .search-textbox, .dark .search, .dark .repo-manager .search{ 132 | color: var(--oeel-color); 133 | border-color: #242424; 134 | border-top-color: #404040; 135 | } 136 | 137 | .dark .repo-manager .search{ 138 | border-color: #2f2f2f; 139 | border-top-color: #5b5a5a; 140 | } 141 | 142 | /*:host(.dark) .user-box { 143 | background-color: #2f2f2f; 144 | }*/ 145 | 146 | .dark .tab-panel>.header .goog-tab-selected { 147 | background: var(--oeel-background-color); 148 | color: white; 149 | border-bottom-color: var(--oeel-background-color); 150 | } 151 | 152 | .dark .task.legacy .info{ 153 | color: var(--oeel-color); 154 | } 155 | 156 | .dark .panel .explorer.loading:before { 157 | filter: invert(1) hue-rotate(180deg) brightness(1.5); 158 | transform-origin: 8px 8px; 159 | transform: rotate(180deg); 160 | } 161 | 162 | .dark .env-entry .env-entry-delete{ 163 | filter: invert(1); 164 | } 165 | 166 | .dark .ee-renamer .jfk-textinput{ 167 | color: white; 168 | background-color: black; 169 | } 170 | 171 | .dark input.jfk-textinput, .dark .modal-dialog-content input 172 | {background-color: revert} 173 | 174 | 175 | .dark .tree-manager .tree-folder>ee-zippy>.header:hover, 176 | .dark .repo-manager .repo-list>ee-zippy>.header:hover { 177 | background: black; 178 | } 179 | 180 | .dark .tree-manager .button { 181 | background-color: #1d1d1d; 182 | } 183 | 184 | .dark ee-task-manager-app{ 185 | background-color:rgb(29, 31, 33) ; 186 | color: #C5C8C6; 187 | } 188 | 189 | .dark .oeeMenuContextContainer { 190 | background: #0f0f0f; 191 | border: 1px solid #2b2b2b; 192 | } 193 | .dark .oeeMenuContextButton{ 194 | color:var(--oeel-color); 195 | background-color: var(--oeel-background-color); /* Default background */ 196 | } 197 | 198 | .dark .oeeMenuContextButton:hover { 199 | background-color: #404040; /* Darker background on hover */ 200 | } 201 | 202 | 203 | .dark .oeeSCSContainer{ 204 | color:var(--oeel-color); 205 | background: #0f0f0f; 206 | border: 1px solid #2b2b2b; 207 | } 208 | 209 | .dark .oeeSCSContainer input{ 210 | color:var(--oeel-color); 211 | } 212 | 213 | .dark .session-toggle { 214 | border: 1px solid #2b2b2b; 215 | } 216 | 217 | .dark .toggle-btn { 218 | flex: 1; 219 | padding: 5px 10px; 220 | border: none; 221 | background: #404040; 222 | color: var(--oeel-color);; 223 | cursor: pointer; 224 | transition: background-color 0.3s; 225 | } 226 | 227 | .dark .toggle-btn.active { 228 | background: #0f0f0f; 229 | } 230 | 231 | .dark .toggle-btn { 232 | background: #404040; 233 | color: var(--oeel-color);; 234 | } 235 | 236 | .dark .toggle-btn.active, .dark #validateSession:active { 237 | background: #2e2e2e; /* Slightly lighter to simulate being pressed */ 238 | box-shadow: inset 0 3px 5px rgba(1, 1, 1, 0.2); /* Inner shadow for "in" effect */ 239 | } 240 | -------------------------------------------------------------------------------- /fonts/MaterialIcons-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-geocomputing/OEEex/2f420a0fa9edfc125fb16803d9a4bb942680658d/fonts/MaterialIcons-Regular.woff2 -------------------------------------------------------------------------------- /images/logo_white_OEEex_close_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-geocomputing/OEEex/2f420a0fa9edfc125fb16803d9a4bb942680658d/images/logo_white_OEEex_close_128.png -------------------------------------------------------------------------------- /images/logo_white_OEEex_close_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-geocomputing/OEEex/2f420a0fa9edfc125fb16803d9a4bb942680658d/images/logo_white_OEEex_close_16.png -------------------------------------------------------------------------------- /images/logo_white_OEEex_close_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-geocomputing/OEEex/2f420a0fa9edfc125fb16803d9a4bb942680658d/images/logo_white_OEEex_close_32.png -------------------------------------------------------------------------------- /images/logo_white_OEEex_close_48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-geocomputing/OEEex/2f420a0fa9edfc125fb16803d9a4bb942680658d/images/logo_white_OEEex_close_48.png -------------------------------------------------------------------------------- /images/logo_white_OEEex_close_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-geocomputing/OEEex/2f420a0fa9edfc125fb16803d9a4bb942680658d/images/logo_white_OEEex_close_64.png -------------------------------------------------------------------------------- /images/logo_white_OEEex_open_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-geocomputing/OEEex/2f420a0fa9edfc125fb16803d9a4bb942680658d/images/logo_white_OEEex_open_128.png -------------------------------------------------------------------------------- /images/logo_white_OEEex_open_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-geocomputing/OEEex/2f420a0fa9edfc125fb16803d9a4bb942680658d/images/logo_white_OEEex_open_16.png -------------------------------------------------------------------------------- /images/logo_white_OEEex_open_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-geocomputing/OEEex/2f420a0fa9edfc125fb16803d9a4bb942680658d/images/logo_white_OEEex_open_32.png -------------------------------------------------------------------------------- /images/logo_white_OEEex_open_48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-geocomputing/OEEex/2f420a0fa9edfc125fb16803d9a4bb942680658d/images/logo_white_OEEex_open_48.png -------------------------------------------------------------------------------- /images/logo_white_OEEex_open_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-geocomputing/OEEex/2f420a0fa9edfc125fb16803d9a4bb942680658d/images/logo_white_OEEex_open_64.png -------------------------------------------------------------------------------- /images/pyLogo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | 11 | 12 | 13 | 18 | 19 | 20 | 21 | 22 | 27 | 28 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | // main.js 2 | 3 | let permanentModule2Load=[ 4 | "externalLoad", 5 | "OEEMenu", // add the OEE Menu in the top right corner 6 | "surveyMessage" 7 | ]; 8 | 9 | let moules2Load = [ 10 | "aiCodeGeneration", 11 | "copyAsJson", 12 | "darkMode", 13 | "editorSettings", 14 | "openInNewTab", 15 | "plotly", 16 | "sharedCodeSession", 17 | "uploadWithManifest" 18 | ]; 19 | 20 | // Load all permanent modules 21 | for (let i = 0; i < permanentModule2Load.length; i++) { 22 | const moduleName = permanentModule2Load[i]; 23 | import('./modules/' + moduleName + '.js') 24 | .then(module => { 25 | if (module.initialize) { 26 | module.initialize(); 27 | //console.log(moduleName) 28 | } 29 | }) 30 | .catch(err => { 31 | console.error('Error loading ' + moduleName + ':', err); 32 | }); 33 | } 34 | 35 | 36 | // Load all relevant settings from storage 37 | chrome.storage.local.get(moules2Load, function(items) { 38 | for (let i = 0; i < moules2Load.length; i++) { 39 | const moduleName = moules2Load[i]; 40 | 41 | // Check if the module is enabled (i.e., true) before loading 42 | if (items[moduleName]) { // temporarly force all 43 | import('./modules/' + moduleName + '.js') 44 | .then(module => { 45 | if (module.initialize) { 46 | module.initialize(); 47 | //console.log(moduleName) 48 | } 49 | }) 50 | .catch(err => { 51 | console.error('Error loading ' + moduleName + ':', err); 52 | }); 53 | } 54 | } 55 | }); 56 | 57 | /*************** MT part **************************/ 58 | 59 | let moulesMT2Load = [ 60 | "aiCodeGeneration", 61 | "consoleError", 62 | "darkMode", 63 | "docLink", 64 | "editorSettings", 65 | "insertFucntionSignature", 66 | "plotly", 67 | "pythonCE", 68 | "runAll", 69 | "sharedCodeSession", 70 | "terminal", 71 | "uploadWithManifest" 72 | ]; 73 | 74 | chrome.storage.local.get(moulesMT2Load, function(items) { 75 | window.dispatchEvent(new CustomEvent("moduleMT2Load",{detail:{modules:items, extensionId:chrome.runtime.id}})) 76 | }); 77 | 78 | -------------------------------------------------------------------------------- /mainThread.js: -------------------------------------------------------------------------------- 1 | // mainThread.js 2 | 3 | let permanentModule2Load=[ 4 | ]; 5 | 6 | 7 | // Load all relevant settings from storage 8 | 9 | for (let i = 0; i < permanentModule2Load.length; i++) { 10 | const moduleName = permanentModule2Load[i]; 11 | import('./modules/' + moduleName + '.js') 12 | .then(module => { 13 | if (module.initializeMT) { 14 | module.initializeMT(); 15 | console.log(moduleName) 16 | } 17 | }) 18 | .catch(err => { 19 | console.error('Error loading ' + moduleName + ':', err); 20 | }); 21 | } 22 | 23 | 24 | window.addEventListener("moduleMT2Load", function(items) { 25 | let extensionId=items.detail.extensionId; 26 | items=items.detail.modules; 27 | moules2Load=Object.keys(items) 28 | for (let i = 0; i < moules2Load.length; i++) { 29 | const moduleName = moules2Load[i]; 30 | 31 | // Check if the module is enabled (i.e., true) before loading 32 | if (items[moduleName]) { 33 | import('./modules/' + moduleName + '.js') 34 | .then(module => { 35 | if (module.initializeMT) { 36 | module.initializeMT(extensionId); 37 | //console.log(moduleName) 38 | } 39 | }) 40 | .catch(err => { 41 | console.error('Error loading ' + moduleName + ':', err); 42 | }); 43 | } 44 | } 45 | }); 46 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Open Earth Engine extension", 3 | "description": "An unoffical extension to enhance Google Earth Engine, and Open Earth Engine experience.", 4 | "version": "2.0.3", 5 | "manifest_version": 3, 6 | "background": 7 | { 8 | "service_worker": "background.js", 9 | "type": "module" 10 | }, 11 | "web_accessible_resources": 12 | [ 13 | { 14 | "resources": 15 | [ 16 | "3rd_party/*", 17 | "images/*", 18 | "materials/*", 19 | "modules/*", 20 | "option/*", 21 | "otherAssets/*" 22 | ], 23 | "matches": 24 | [ 25 | "https://code.earthengine.google.com/*", 26 | "https://code.earthengine.google.co.in/*" 27 | ] 28 | }, 29 | { 30 | "resources": ["modules/addPlotly.js"], 31 | "matches": ["https://*.earthengine.app/*"] 32 | }, 33 | { 34 | "resources":["options.html"], 35 | "matches":["https://www.open-geocomputing.org/*"] 36 | } 37 | ], 38 | "host_permissions": [ 39 | "http://127.0.0.1/", 40 | "http://localhost/", 41 | "https://code.earthengine.google.com/", 42 | "https://code.earthengine.google.com/repo/file/load?*", 43 | "https://code.earthengine.google.co.in/", 44 | "https://code.earthengine.google.co.in/repo/file/load?*", 45 | "https://generativelanguage.googleapis.com/" 46 | ], 47 | "permissions": [ 48 | "activeTab", 49 | "declarativeNetRequest", 50 | "storage" 51 | ], 52 | "content_scripts": 53 | [ 54 | { 55 | "matches": 56 | [ 57 | "https://code.earthengine.google.com/*", 58 | "https://code.earthengine.google.co.in/*", 59 | "https://*.earthengine.app/*" 60 | ], 61 | "css": 62 | [ 63 | "css/ai.css", 64 | "css/darkMode.css", 65 | "css/OEEex.css" 66 | ], 67 | "js": 68 | [ 69 | "main.js" 70 | ], 71 | "run_at": "document_end" 72 | }, 73 | { 74 | "matches": 75 | [ 76 | "https://code.earthengine.google.com/*", 77 | "https://code.earthengine.google.co.in/*", 78 | "https://*.earthengine.app/*" 79 | ], 80 | "js": 81 | [ 82 | "mainThread.js" 83 | ], 84 | "run_at": "document_end", 85 | "world": "MAIN" 86 | } 87 | ], 88 | "action": { 89 | "default_icon": { 90 | "16": "/images/logo_white_OEEex_open_16.png", 91 | "32": "/images/logo_white_OEEex_open_32.png", 92 | "48": "/images/logo_white_OEEex_open_48.png", 93 | "128": "/images/logo_white_OEEex_open_128.png" 94 | } 95 | }, 96 | "icons": { 97 | "16": "/images/logo_white_OEEex_open_16.png", 98 | "32": "/images/logo_white_OEEex_open_32.png", 99 | "48": "/images/logo_white_OEEex_open_48.png", 100 | "128": "/images/logo_white_OEEex_open_128.png" 101 | }, 102 | "options_page": "option/options.html", 103 | "externally_connectable": { 104 | "matches": [ 105 | "https://code.earthengine.google.com/*", 106 | "https://code.earthengine.google.co.in/*", 107 | "https://*.googleusercontent.com/*" 108 | ] 109 | } 110 | } -------------------------------------------------------------------------------- /modules/OEEMenu.js: -------------------------------------------------------------------------------- 1 | function addOEEMenu(){ 2 | 3 | let button=document.createElement('ee-menu-button'); 4 | button.innerHTML=''; 5 | button.setAttribute('align',"right"); 6 | 7 | let userBoxElement=document.getElementsByTagName('user-box') 8 | if(userBoxElement && userBoxElement.length>0) 9 | { 10 | var localRoot=userBoxElement[0].shadowRoot; 11 | localRoot.children[0].insertBefore(button,localRoot.children[0].firstChild) 12 | } 13 | 14 | addContextMneu(button); 15 | } 16 | 17 | let contextOEEMenuRegistationMenuItem=null; 18 | 19 | function addContextMneu(button){ 20 | const contextMenu = document.createElement('div'); 21 | contextMenu.classList.add('oeeMenuContextContainer'); 22 | document.body.appendChild(contextMenu); 23 | 24 | const menuItems = []; 25 | function registerMenuItem(label, action) { 26 | menuItems.push({ label, action }); 27 | } 28 | contextOEEMenuRegistationMenuItem=registerMenuItem; 29 | 30 | // Step 3: Function to populate and display the context menu 31 | function showContextMenu(x, y) { 32 | contextMenu.innerHTML = ''; // Clear existing items 33 | menuItems.forEach(item => { 34 | const button = document.createElement('button'); 35 | button.classList.add('oeeMenuContextButton'); 36 | button.textContent = item.label; 37 | button.onclick = () => { 38 | item.action(); 39 | contextMenu.style.display = 'none'; // Hide menu after action 40 | }; 41 | contextMenu.appendChild(button); 42 | }); 43 | 44 | contextMenu.style.left = `${x}px`; 45 | contextMenu.style.top = `${y}px`; 46 | contextMenu.style.display = 'block'; 47 | } 48 | 49 | 50 | button.addEventListener('click', (e) => { 51 | e.stopPropagation(); // Prevent click from propagating to the document 52 | // Use the button's position as the anchor point for the context menu 53 | const rect = e.target.getBoundingClientRect(); 54 | const x = rect.left; 55 | const y = rect.bottom; // Position the menu at the bottom of the button 56 | showContextMenu(x, y); 57 | }); 58 | 59 | // Add an event listener to the entire document 60 | document.addEventListener('click', (event) => { 61 | // Check if the click was outside the contextMenu 62 | if (!contextMenu.contains(event.target)) { 63 | contextMenu.style.display = 'none'; 64 | } 65 | }); 66 | 67 | document.addEventListener('keydown', (event) => { 68 | if ((event.keyCode == 27)) { 69 | contextMenu.style.display = 'none'; 70 | } 71 | }); 72 | 73 | // Ensure the menu doesn't close when clicking inside it 74 | contextMenu.addEventListener('click', (event) => { 75 | event.stopPropagation(); 76 | }); 77 | } 78 | 79 | function addOEEMenuListner(){ 80 | window.addEventListener('add2OEEMenu', (event) => { 81 | const { name, callback } = event.detail; 82 | contextOEEMenuRegistationMenuItem(name, callback) 83 | }); 84 | } 85 | 86 | export function initialize(){ 87 | addOEEMenu(); 88 | addOEEMenuListner() 89 | const customEvent = new CustomEvent('add2OEEMenu', { 90 | detail: { 91 | name: 'Options', 92 | callback: () => window.open(chrome.runtime.getURL('/option/options.html'), '_blank') 93 | } 94 | }); 95 | window.dispatchEvent(customEvent); 96 | } -------------------------------------------------------------------------------- /modules/consoleError.js: -------------------------------------------------------------------------------- 1 | function loadConsoleErrorWatcher(){ 2 | 3 | 4 | 5 | let rootElement=document.querySelector('.goog-splitpane-second-container ee-tab-panel').shadowRoot 6 | var sheet = new CSSStyleSheet 7 | sheet.replaceSync( '.header button.highlight.error,.header button.error { background-color:#ff0000d9 }') 8 | // Append your style to the existing style sheet. 9 | rootElement.adoptedStyleSheets=[...rootElement.adoptedStyleSheets,sheet]; 10 | 11 | let consoleButton = Array.from(rootElement.querySelectorAll('.header button')).find(button => button.innerText === 'Console'); 12 | 13 | let consoleElement=document.querySelector('ee-console'); 14 | if(!consoleElement)return; 15 | 16 | let MutationObserver = window.MutationObserver || window.WebKitMutationObserver; 17 | 18 | let mut2Error = new MutationObserver(function singleElementObserver(mutationsList){ 19 | for (const mutation of mutationsList) { 20 | const target = mutation.target; 21 | if(target.classList.contains('error')){ 22 | document.dispatchEvent(new CustomEvent("errorInCode",{detail:{type:"remote", messgae: target.innerText}})) 23 | } 24 | } 25 | }); 26 | 27 | let obsForDynamicErrors = { childList: false, attributes:true, subtree: false, attributeFilter: ['class']}; 28 | 29 | let observerEmptyList = new MutationObserver(function(mutList){ 30 | 31 | [...mutList].map(function(mut){ 32 | if(document.querySelectorAll('ee-console-log').length==0){ 33 | consoleButton.classList.remove("error"); 34 | } 35 | [...mut.addedNodes].map(function(e){ 36 | if([...Object.getOwnPropertySymbols(e)].some(s=>e[s]=='error')|| (e.parentNode?.classList.contains("error"))){ 37 | document.dispatchEvent(new CustomEvent("errorInCode",{detail:{type:"local", messgae: e.innerText}})) 38 | }else{ 39 | e.querySelectorAll(".explorer").forEach(item => mut2Error.observe(item, obsForDynamicErrors)) 40 | } 41 | }); 42 | }); 43 | }); 44 | let obslistChildConfig = { childList: true}; 45 | observerEmptyList.observe(consoleElement, obslistChildConfig); 46 | 47 | document.addEventListener("errorInCode", x => consoleButton.classList.add("error")) 48 | } 49 | 50 | 51 | export function initializeMT(){ 52 | loadConsoleErrorWatcher(); 53 | } -------------------------------------------------------------------------------- /modules/copyAsJson.js: -------------------------------------------------------------------------------- 1 | function loadConsoleJSONWatcher(){ 2 | let MutationObserver = window.MutationObserver || window.WebKitMutationObserver; 3 | let myObserver = new MutationObserver(function(mutList){ 4 | [...mutList].map(function(mut){ 5 | [...mut.addedNodes].map(function(e){ 6 | if(e.classList.contains('OEEexJSONAnalysis')) 7 | return; 8 | e.classList.add('OEEexJSONAnalysis') 9 | let myObserverLocal = new MutationObserver(function(val){ 10 | val=[...val.map(e => Array.from(e.addedNodes))].flat(); 11 | let button=val.filter(o=>o.classList.contains('json-switch'))[0]; 12 | let pre=val.filter(o=>o.tagName=='PRE')[0]; 13 | 14 | if (pre && button) { 15 | button.addEventListener('dblclick',function(el){ 16 | navigator.clipboard.writeText(pre.innerText); 17 | }) 18 | } 19 | myObserverLocal.disconnect(); 20 | }); 21 | let obsConfig = { childList: true}; 22 | let obj=e.querySelector('.explorer.loading'); 23 | if(obj) 24 | myObserverLocal.observe(obj, obsConfig); 25 | }); 26 | }); 27 | }); 28 | let obsConfig = { childList: true}; 29 | 30 | if(document.querySelector('ee-console')) 31 | myObserver.observe(document.querySelector('ee-console'), obsConfig); 32 | } 33 | 34 | export function initialize(){ 35 | loadConsoleJSONWatcher(); 36 | } 37 | -------------------------------------------------------------------------------- /modules/darkMode.js: -------------------------------------------------------------------------------- 1 | // let OEEexidString=document.currentScript.src.match("([a-z]{32})|([0-9a-f-]{36})")[0]; 2 | let lightIsAutomatic=true; 3 | let portWithBackground=null; 4 | 5 | let OEEexEscape = trustedTypes.createPolicy("OEEexEscape", { 6 | createHTML: (string, sink) => string 7 | }); 8 | 9 | 10 | function setPortWithBackground(){ 11 | portWithBackground= chrome.runtime.connect(chrome.runtime.id,{name: "oeel.extension.lightMode"}); 12 | 13 | 14 | portWithBackground.onMessage.addListener((request, sender, sendResponse) => { 15 | if(request.type=='changeLightMode'){ 16 | if(request.message=='automatic'){ 17 | // if((typeof buttonLight!= 'undefined') && buttonLight) 18 | // buttonLight.innerHTML=OEEexEscape.createHTML('brightness_medium'); 19 | switch2DarkMode((window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches),true) 20 | }else{ 21 | switch2DarkMode(request.message,false); 22 | } 23 | }; 24 | }) 25 | portWithBackground.onDisconnect.addListener(function(port){ 26 | portWithBackground=null; 27 | setPortWithBackground(); 28 | }) 29 | 30 | document.addEventListener("requestDarkModeInfoEvent",function(event){ 31 | portWithBackground.postMessage({type:"getLightMode"}); 32 | }) 33 | window.addEventListener("load", () => { 34 | getLastModeValue(); 35 | }); 36 | } 37 | 38 | 39 | function switch2DarkMode(toDark,isAuto=false){ 40 | const event = new CustomEvent("darkModeEvent", { detail: { toDark: toDark, isAuto:isAuto } }); 41 | document.dispatchEvent(event); 42 | } 43 | 44 | function sendNewlightMode(mode){ 45 | portWithBackground.postMessage({type:"setLightMode", message:mode}); 46 | } 47 | 48 | function switchMode(){ 49 | sendNewlightMode(!document.getElementsByTagName('html')[0].classList.contains('dark')); 50 | } 51 | 52 | function switchModeToAutomatic(){ 53 | sendNewlightMode('automatic') 54 | } 55 | 56 | window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => { 57 | getLastModeValue(); 58 | }); 59 | 60 | function setLightModeButton(){ 61 | let button=document.createElement('ee-menu-button'); 62 | button.innerHTML=OEEexEscape.createHTML('brightness_medium'); 65 | button.setAttribute('align',"right"); 66 | let userBoxElement=document.getElementsByTagName('user-box') 67 | if(userBoxElement && userBoxElement.length>0){ 68 | let localRoot=userBoxElement[0].shadowRoot; 69 | localRoot.children[0].insertBefore(button,localRoot.children[0].firstChild) 70 | 71 | let buttonLight=localRoot.getElementById('toogleModeButton'); 72 | buttonLight.addEventListener("click", switchMode); 73 | buttonLight.addEventListener("dblclick", switchModeToAutomatic); 74 | 75 | setTimeout(function(){ 76 | buttonLight.style["font-size"] = "24px"; 77 | },100); 78 | 79 | document.addEventListener("darkModeEvent",function(event){ 80 | let buttonMode='brightness_medium'; 81 | if (!event.detail.isAuto) { 82 | buttonMode=(event.detail.toDark?'brightness_high':'brightness_4') 83 | } 84 | buttonLight.innerHTML=OEEexEscape.createHTML(buttonMode) 85 | }) 86 | 87 | let sheet = new CSSStyleSheet 88 | sheet.replaceSync( '.user-box.dark { background-color:black } .dark .project-label {background: #1e1d1d; box-shadow: #1e1d1d 0px 0px 4px 1px inset;}') 89 | // Append your style to the existing style sheet. 90 | localRoot.adoptedStyleSheets=[...localRoot.adoptedStyleSheets,sheet]; 91 | 92 | document.addEventListener("darkModeEvent",function(event){ 93 | localRoot.firstElementChild.classList.toggle('dark', event.detail.toDark) 94 | }) 95 | } 96 | } 97 | 98 | function htmlRoot(event){ 99 | let html=document.getElementsByTagName('html')[0]; 100 | document.addEventListener("darkModeEvent",function(event){ 101 | html.classList.toggle('dark', event.detail.toDark) 102 | }) 103 | } 104 | 105 | function taskPanel(event){ 106 | let eeTaskPaneList=document.getElementsByTagName('ee-task-pane'); 107 | 108 | if(!(eeTaskPaneList && eeTaskPaneList.length>0)) return 109 | 110 | { 111 | let localRoot=eeTaskPaneList[0].shadowRoot; 112 | let sheet = new CSSStyleSheet 113 | sheet.replaceSync( '.dark .header,.dark .section-title{ color:var(--oeel-color); }'); 114 | localRoot.adoptedStyleSheets=[...localRoot.adoptedStyleSheets,sheet]; 115 | document.addEventListener("darkModeEvent",function(event){ 116 | [...localRoot.children].map(e=>e.classList.toggle('dark', event.detail.toDark)) 117 | }) 118 | } 119 | 120 | { 121 | let localRoot=eeTaskPaneList[0].shadowRoot.querySelector('ee-remote-task-list').shadowRoot; 122 | let sheet = new CSSStyleSheet 123 | sheet.replaceSync( '.dark .task.legacy .info,.dark.section-title{ color:var(--oeel-color); } .dark .task.legacy .info .error-message {color: #e34a4a;}'+ 124 | '.dark .task.legacy .content {background-color: #545454;}'+ 125 | '.dark .task.legacy.failed .content {background-color: rgb(187, 0, 0);}'+ 126 | '.dark .task.legacy.completed .content {background-color: var(--ee-legacy-blue);}'+ 127 | '.dark .task.legacy .indicator{filter: invert(1)} .dark .task.task.submitted-to-backend .indicator, .dark .task.task.running-on-backend .indicator{filter: invert(1) hue-rotate(180deg) brightness(1.5);transform: rotate(180deg);}'+ 128 | '.dark .task.legacy.failed .indicator{filter: brightness(1.5);} .dark .task.legacy:not(.completed):not(.failed) .content{background-color: rgb(86 86 86);}'+ 129 | '.dark .task.legacy.type-INGEST_TABLE .content::before{background-image: url(//www.gstatic.com/images/icons/material/system/1x/file_upload_white_24dp.png);}'); 130 | localRoot.adoptedStyleSheets=[...localRoot.adoptedStyleSheets,sheet]; 131 | document.addEventListener("darkModeEvent",function(event){ 132 | [...localRoot.children].map(e=>e.classList.toggle('dark', event.detail.toDark)) 133 | }) 134 | 135 | let sheetTaskPan = new CSSStyleSheet 136 | sheetTaskPan.replaceSync( '.dark .task.legacy .content {background-color: #545454;}'); 137 | eeTaskPaneList[0].shadowRoot.adoptedStyleSheets=[...eeTaskPaneList[0].shadowRoot.adoptedStyleSheets,sheetTaskPan]; 138 | } 139 | } 140 | 141 | function docsListPanel(event){ 142 | let docList=document.querySelector('ee-docs-list') 143 | if(!docList) return 144 | document.addEventListener("darkModeEvent",function(event){ 145 | docList.classList.toggle('dark', event.detail.toDark) 146 | }) 147 | let localRoot=docList.shadowRoot; 148 | let sheet = new CSSStyleSheet; 149 | sheet.replaceSync(':host(.dark) ee-zippy > .header:hover {background: var(--color-hover-bg);}'+ 150 | ':host(.dark) ee-zippy > .header::before {filter:invert()}'); 151 | localRoot.adoptedStyleSheets=[...localRoot.adoptedStyleSheets,sheet]; 152 | } 153 | 154 | 155 | function consolPanel(event){ 156 | let eeConsole=document.getElementsByTagName('ee-console'); 157 | if(!(eeConsole && eeConsole.length>0)) return; 158 | 159 | let localRoot=eeConsole[0].shadowRoot; 160 | let sheet = new CSSStyleSheet 161 | sheet.replaceSync( '.dark.intro-message.console-message{ color: hsl(0deg 0% 0% / 78%);'); 162 | localRoot.adoptedStyleSheets=[...localRoot.adoptedStyleSheets,sheet]; 163 | 164 | document.addEventListener("darkModeEvent",function(event){ 165 | [...localRoot.children].map(e=>e.classList.toggle('dark', event.detail.toDark)) 166 | }) 167 | } 168 | 169 | 170 | function registerElements(){ 171 | setLightModeButton(); 172 | htmlRoot(); 173 | consolPanel(); 174 | docsListPanel(); 175 | taskPanel(); 176 | } 177 | 178 | export function initialize(){ 179 | setPortWithBackground(); 180 | registerElements() 181 | } 182 | 183 | function getLastModeValue(){ 184 | document.dispatchEvent(new CustomEvent("requestDarkModeInfoEvent")); 185 | } 186 | 187 | /*************** MT part **************************/ 188 | 189 | function switchCodeEditor(event){ 190 | let editorElement=document.getElementsByClassName('ace_editor') 191 | if(editorElement && editorElement.length>0){ 192 | editorElement[0].id='editor' 193 | let editor = ace.edit("editor"); 194 | let theme='xcode'; 195 | if (event.detail.toDark){ 196 | theme='tomorrow_night'; 197 | } 198 | else{ 199 | theme='xcode'; 200 | } 201 | editor.setTheme('ace/theme/'+theme) 202 | } 203 | } 204 | 205 | function registerElementsMT(){ 206 | document.addEventListener("darkModeEvent",switchCodeEditor); 207 | } 208 | 209 | export function initializeMT(){ 210 | registerElementsMT(); 211 | getLastModeValue(); 212 | } 213 | 214 | -------------------------------------------------------------------------------- /modules/docLink.js: -------------------------------------------------------------------------------- 1 | let OEEexEscapeURL = trustedTypes.createPolicy("OEEexEscapeURL", { 2 | createScriptURL: (string, sink) => string 3 | }); 4 | 5 | let OEEexEscape = trustedTypes.createPolicy("OEEexEscape", { 6 | createHTML: (string, sink) => string 7 | }); 8 | 9 | function loadDocWatcher(){ 10 | let MutationObserver = window.MutationObserver || window.WebKitMutationObserver; 11 | let myObserver = new MutationObserver(function(mutList){ 12 | [...mutList].map(function(mut){ 13 | [...mut.addedNodes].map(function(e){ 14 | if(e.nodeName=="EE-DOCS-POPUP") 15 | { 16 | setTimeout(function(){ 17 | 18 | let urlname=e[Object.getOwnPropertySymbols(e)[2]].name.replace(/\./g, '-').toLowerCase(); 19 | let docFooterDiv=e.shadowRoot.querySelector("paper-dialog .buttons"); 20 | let externalDocButton=docFooterDiv.querySelector(".external-doc-button") 21 | if(!externalDocButton){ 22 | externalDocButton = document.createElement("a"); 23 | externalDocButton.className="external-doc-button" 24 | externalDocButton.style.fontFamily= "Material Icons" 25 | externalDocButton.target= '_blank', 26 | externalDocButton.style.margin="auto 0"; 27 | // Set the text content of the button 28 | externalDocButton.textContent = 'open_in_new'; 29 | docFooterDiv.insertBefore(externalDocButton, docFooterDiv.firstChild); 30 | docFooterDiv.style.justifyContent='space-between'; 31 | } 32 | externalDocButton.href="https://developers.google.com/earth-engine/apidocs/"+urlname; 33 | },0) 34 | 35 | } 36 | }); 37 | }); 38 | }); 39 | let obsConfig = { childList: true}; 40 | 41 | if(document.querySelector('body')) 42 | myObserver.observe(document.querySelector('body'), obsConfig); 43 | } 44 | 45 | 46 | 47 | function addfunctionName(){ 48 | 49 | [...new Set([...document.querySelector('ee-docs-list').shadowRoot.querySelectorAll('ee-zippy'),document.querySelector('ee-docs-list').shadowRoot] 50 | .map((e)=>[...e.querySelectorAll('ee-node-summary')]).flat())].forEach(e=>{ 51 | let span=document.createElement('span'); 52 | span.classList.add('insertInCEButton','material-icons') 53 | span.innerHTML=OEEexEscape.createHTML("keyboard_tab"); 54 | e.shadowRoot.lastChild.appendChild(span); 55 | }) 56 | } 57 | 58 | function loadFunctionSignaturesObserver(){ 59 | 60 | const observer = new MutationObserver(function(){ 61 | this.disconnect(); 62 | setTimeout(addfunctionName,0); 63 | }); 64 | 65 | if(document.querySelector('ee-docs-list')) 66 | observer.observe(document.querySelector('ee-docs-list').shadowRoot, {subtree: true, childList: true}); 67 | } 68 | 69 | export function initializeMT(){ 70 | loadDocWatcher(); 71 | loadFunctionSignaturesObserver(); 72 | } -------------------------------------------------------------------------------- /modules/editorSettings.js: -------------------------------------------------------------------------------- 1 | // var OEEexidString=document.currentScript.src.match("([a-z]{32})|([0-9a-f-]{36})")[0]; 2 | 3 | function initEditorSettings(){ 4 | 5 | var sheet = new CSSStyleSheet 6 | sheet.replaceSync( ":root{--editorFontSize:13px; --editorFontFamily:'Menlo','Monaco','DejaVu Sans Mono','Bitstream Vera Sans Mono','Consolas','source-code-pro',monospace }\n \ 7 | #editor.ace_editor.enable-suggestions .stan-underline{ height: calc( var(--editorFontSize, 13px) - 1px) !important; }\ 8 | .env-list .zippy .header{font-size:calc( var(--editorFontSize, 13px) + 1px)}") 9 | // Append your style to the existing style sheet. 10 | document.adoptedStyleSheets=[...document.adoptedStyleSheets,sheet]; 11 | 12 | function removeOriginalFontInCE(){ 13 | try{ 14 | [...document.querySelector("div.ace_editor").parentElement.childNodes]. 15 | map(e=>e.setAttribute('style',(e.getAttribute('style')?e.getAttribute('style'):"")+ 16 | "font-family: var(--editorFontFamily, 'Menlo','Monaco','DejaVu Sans Mono','Bitstream Vera Sans Mono','Consolas','source-code-pro',monospace )!important;font-size: var(--editorFontSize, 13px)!important")); 17 | }catch(e){ 18 | setTimeout(removeOriginalFontInCE,10) 19 | } 20 | } 21 | 22 | removeOriginalFontInCE(); 23 | 24 | function applyConfig(data){ 25 | if (Object.keys(data).length === 0) return; 26 | document.dispatchEvent(new CustomEvent("EC_seetings",{detail:data})) 27 | } 28 | 29 | let EC_configList=['ESfontSize','ESfontFamily','EStabSize','ES_SC'] 30 | 31 | chrome.storage.local.get(EC_configList, function(data) { 32 | applyConfig(data) 33 | }); 34 | 35 | chrome.storage.onChanged.addListener((changes, area) => { 36 | if(area!=="local") return; 37 | applyConfig(Object.fromEntries( 38 | EC_configList 39 | .filter(key => changes[key]) // Keep only existing keys 40 | .map(key => [key, changes[key].newValue]) // Replace object with newValue 41 | )) 42 | 43 | }); 44 | } 45 | 46 | 47 | export function initialize(){ 48 | initEditorSettings(); 49 | } 50 | 51 | /*************** MT part **************************/ 52 | 53 | // // var OEEexidString=document.currentScript.src.match("([a-z]{32})|([0-9a-f-]{36})")[0]; 54 | 55 | function setECSettings(request){ 56 | let editor=null; 57 | let editorElement=document.getElementsByClassName('ace_editor') 58 | if( editorElement && editorElement.length>0){ 59 | editorElement[0].id='editor' 60 | editor = ace.edit("editor"); 61 | }else{ 62 | setTimeout(setECSettings,10,request); 63 | return; 64 | } 65 | 66 | 67 | if(!(editor.commands && editor.setOption)) return; 68 | 69 | let root = document.documentElement; 70 | if (request.ESfontSize) root.style.setProperty('--editorFontSize', request.ESfontSize + "px"); 71 | if (request.ESfontFamily){ 72 | root.style.setProperty('--editorFontFamily', request.ESfontFamily); 73 | if (request.ESfontFamily=='default') 74 | root.style.setProperty('--editorFontFamily', "'Menlo','Monaco','DejaVu Sans Mono','Bitstream Vera Sans Mono','Consolas','source-code-pro',monospace"); 75 | } 76 | if (request.EStabSize) setTimeout(function(){editor.setOption('tabSize',request.EStabSize)},1); 77 | if (request.ES_SC){ 78 | for (const [key, value] of Object.entries(request.ES_SC)) { 79 | editor.commands.commands[key].bindKey.mac=value; 80 | editor.commands.commands[key].bindKey.win=value; 81 | } 82 | editor.commands.addCommands(editor.commands.commands) 83 | } 84 | } 85 | 86 | 87 | export function initializeMT(){ 88 | //initEditorSettings(); 89 | 90 | document.addEventListener("EC_seetings",function(event){ 91 | setECSettings(event.detail) 92 | }) 93 | } -------------------------------------------------------------------------------- /modules/externalLoad.js: -------------------------------------------------------------------------------- 1 | //this is a special module that load external script 2 | 3 | function loadFont(){ 4 | let fontLink = document.createElement('link'); 5 | fontLink.type = 'text/css'; 6 | fontLink.rel = 'stylesheet'; 7 | (document.head || document.documentElement).appendChild(fontLink); 8 | fontLink.href = "https://fonts.googleapis.com/icon?family=Material+Icons" 9 | } 10 | 11 | 12 | export function initialize(){ 13 | loadFont() 14 | } -------------------------------------------------------------------------------- /modules/insertFucntionSignature.js: -------------------------------------------------------------------------------- 1 | let OEEexEscape = trustedTypes.createPolicy("OEEexEscape", { 2 | createHTML: (string, sink) => string 3 | }); 4 | 5 | function injectFunctionSignature(event){ 6 | event.stopPropagation(); 7 | 8 | let ho=this.getRootNode().host; 9 | let functionInfo=ho[Object.getOwnPropertySymbols(ho)[1]]; 10 | event.stopPropagation(); 11 | let text=''; 12 | for (var i = (functionInfo.isStatic ? 0 : 1); i < functionInfo.signature.args.length; i++) { 13 | let name=('optional'in functionInfo.signature.args[i] && functionInfo.signature.args[i].optional? '//':' ') 14 | +' '+functionInfo.signature.args[i].name+':' 15 | if('default'in functionInfo.signature.args[i]){ 16 | if(functionInfo.signature.args[i].default==null) 17 | name+="null"; 18 | else if(functionInfo.signature.args[i].default==true) 19 | name+="true"; 20 | else if(functionInfo.signature.args[i].default==false) 21 | name+="false"; 22 | else if(typeof functionInfo.signature.args[i].default ==='string'){ 23 | name+='"'+functionInfo.signature.args[i].default+'"'; 24 | }else{ 25 | name+=functionInfo.signature.args[i].default; 26 | } 27 | } 28 | name+=',\n'; 29 | text+=name; 30 | } 31 | if(functionInfo.signature.args.length<(2+!functionInfo.isStatic)) 32 | text=functionInfo.name.slice((functionInfo.isStatic?0:functionInfo.name.lastIndexOf('.')))+'('+text.slice(0,-3)+')'; 33 | else 34 | text=functionInfo.name.slice((functionInfo.isStatic?0:functionInfo.name.lastIndexOf('.')))+'({\n'+text+'})'; 35 | if(functionInfo.signature.args.length<(1+!functionInfo.isStatic)){ 36 | text=functionInfo.name.slice((functionInfo.isStatic?0:functionInfo.name.lastIndexOf('.')))+'()'; 37 | } 38 | editor.insert(text); 39 | } 40 | 41 | let editor; 42 | 43 | function addFunctionSignaturesButtons(){ 44 | let editorElement=document.getElementsByClassName('ace_editor') 45 | if(editorElement && editorElement.length>0){ 46 | editorElement[0].id='editor' 47 | editor = ace.edit("editor"); 48 | } 49 | 50 | [...new Set([...document.querySelector('ee-docs-list').shadowRoot.querySelectorAll('ee-zippy'),document.querySelector('ee-docs-list').shadowRoot] 51 | .map((e)=>[...e.querySelectorAll('ee-node-summary')]).flat())].forEach(e=>{ 52 | let span=document.createElement('span'); 53 | span.classList.add('insertInCEButton','material-icons') 54 | span.innerHTML=OEEexEscape.createHTML("keyboard_tab"); 55 | e.shadowRoot.lastChild.appendChild(span); 56 | 57 | span.addEventListener('click',injectFunctionSignature); 58 | 59 | var style = document.createElement('style'); 60 | style.innerText = '.docs-method-header{position:relative;padding-right: 25px;} .docs-method-header span { content:"keyboard_tab"; font-family: "Material Icons"; position: absolute; right: 0px; font-size: 1.5em;} .docs-method-header span:hover{color:#0062ff;}'; 61 | e.shadowRoot.appendChild(style) 62 | }) 63 | } 64 | 65 | function loadFunctionSignaturesObserver(){ 66 | 67 | const observer = new MutationObserver(function(){ 68 | this.disconnect(); 69 | setTimeout(addFunctionSignaturesButtons,0); 70 | }); 71 | 72 | if(document.querySelector('ee-docs-list')) 73 | observer.observe(document.querySelector('ee-docs-list').shadowRoot, {subtree: true, childList: true}); 74 | } 75 | 76 | 77 | 78 | export function initializeMT(){ 79 | loadFunctionSignaturesObserver(); 80 | } -------------------------------------------------------------------------------- /modules/llm_ai/AIModelInterface.js: -------------------------------------------------------------------------------- 1 | if (window.trustedTypes && window.trustedTypes.createPolicy) { // Feature testing 2 | window.trustedTypes.createPolicy('default', { 3 | createHTML: (string) => string, 4 | createScriptURL: string => string, // warning: this is unsafe! 5 | createScript: string => string, // warning: this is unsafe! 6 | }); 7 | } 8 | 9 | export class AIModelInterface { 10 | 11 | stringToFunction(s){ 12 | let f= (input) => new Function( 13 | ...Object.keys(input), // Extract input keys as function parameters 14 | `return \`${s}\`;` // Template literal processing 15 | )(...Object.values(input)); // Pass values dynamically 16 | return f; 17 | } 18 | 19 | constructor(llmsSetting) { 20 | this.host = llmsSetting.interfaceParam.host || ""; 21 | this.apiKey = llmsSetting.interfaceParam.apiKey || ""; 22 | this.modelVersion = llmsSetting.interfaceParam.modelVersion || ""; 23 | this.language = llmsSetting.language || "English"; 24 | this.customPrompt = Object.fromEntries( 25 | Object.entries(llmsSetting.interfaceParam.customPrompt || {}) 26 | .filter(([_, value]) => value) 27 | .map(([key, value]) => [key, this.stringToFunction(value)]) 28 | ); 29 | } 30 | 31 | setModelVersion(modelVersion) { 32 | this.modelVersion = modelVersion; 33 | } 34 | 35 | async request(taskType, input) { 36 | throw new Error("Method 'request' must be implemented in subclasses."); 37 | } 38 | 39 | async getAvailableModels() { 40 | throw new Error("Method 'getAvailableModels' must be implemented in subclasses."); 41 | } 42 | 43 | getPrompt(taskType, input, defaultPrompts) { 44 | return (this?.customPrompt[taskType] ? this.customPrompt[taskType](input) : defaultPrompts[taskType](input)); 45 | } 46 | 47 | async generateCode(input) { 48 | return this.request("generate_code",input); 49 | } 50 | 51 | async explainCode(input) { 52 | return this.request("explain_code", input); 53 | } 54 | 55 | async highLevelExplainCode(input) { 56 | return this.request("high_level_explain_code", input); 57 | } 58 | 59 | async alterCode(input) { 60 | return this.request("alter_code", input); 61 | } 62 | 63 | async fixCode(input) { 64 | return this.request("fix_code", input); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /modules/llm_ai/GeminiModel.js: -------------------------------------------------------------------------------- 1 | import { AIModelInterface } from "./AIModelInterface.js"; 2 | 3 | export class GeminiModel extends AIModelInterface { 4 | constructor(llmsSetting, extensionId=null) { 5 | super(llmsSetting); 6 | this.extensionId=extensionId; 7 | this.endpoint = `${this.host}models/${this.modelVersion}:generateContent`; 8 | this.modelsEndpoint = `${this.host}/models`; 9 | this.superPrompt =`You are an expert assistant in Google Earth Engine (GEE) coding. Any code request must be written exclusively in JavaScript for the browser-based Google Earth Engine Code Editor. Additionally comment should be written exlusively in ${this.language}. For explanatory text that needs to be structured, use Markdown syntax.\n`; 10 | this.defaultPrompts = { 11 | generate_code: this.stringToFunction("Generate efficient code for:\n${prompt}"), 12 | explain_code: this.stringToFunction("Explain this code line by line:\n${code}"), 13 | high_level_explain_code: this.stringToFunction("Summarize the purpose of this code:\n${code}"), 14 | alter_code: this.stringToFunction("Modify the following code:\n${code}\nChanges: ${prompt}\n Prefer to provide a code pach if possible, alternatively you can provide a complete code."), 15 | fix_code: this.stringToFunction("Fix the errors in this code:\n${code}\nError: ${errors}\n Provide only the code patch (diff file) to correct the code. Diff content should be dircetly in the patch parameter or the answer.") 16 | }; 17 | } 18 | 19 | async fetchGemini() { 20 | return new Promise((resolve, reject) => { 21 | chrome.runtime.sendMessage(this.extensionId, 22 | {action:"geminiRequest",arguments:[...arguments]}, 23 | (serializedResponse) => { 24 | if(!serializedResponse) 25 | reject("Extension connection error") 26 | const { status, statusText, headers, body } = JSON.parse(serializedResponse); 27 | 28 | let response = new Response(body, { 29 | status, 30 | statusText, 31 | headers, 32 | }); 33 | resolve(response) 34 | } 35 | ); 36 | }); 37 | } 38 | 39 | async request(taskType, input) { 40 | const response = await this.fetchGemini(this.endpoint+`?key=${this.apiKey}`, { 41 | method: "POST", 42 | headers: { 43 | "Content-Type": "application/json" 44 | }, 45 | body: JSON.stringify({ 46 | prompt: { text: this.getPrompt(taskType, input, this.defaultPrompts) }, 47 | temperature: 0.7, 48 | system_instruction:this.superPrompt 49 | }) 50 | }); 51 | 52 | const data = await response.json(); 53 | return JSON.parse(data.candidates?.[0]?.content?.parts[0]?.text) || "No response"; 54 | } 55 | 56 | async getAvailableModels() { 57 | return ["gemini-pro", "gemini-1.5"]; // Placeholder: Google API does not list models 58 | } 59 | 60 | async generateCode(input) { 61 | const payload = { 62 | contents: [ 63 | { 64 | role: "user", 65 | parts: [ 66 | { 67 | text: this.getPrompt("generate_code", input, this.defaultPrompts) 68 | } 69 | ] 70 | } 71 | ], 72 | systemInstruction: { 73 | role: "user", 74 | parts: [ 75 | { 76 | text: this.superPrompt 77 | } 78 | ] 79 | }, 80 | generationConfig: { 81 | temperature: 0.7, 82 | responseMimeType: "application/json", 83 | responseSchema: { 84 | type: "object", 85 | properties: { 86 | code: { type: "string" }, 87 | explanation: { type: "string" } 88 | }, 89 | required: ["code", "explanation"] 90 | } 91 | } 92 | }; 93 | 94 | const response = await this.fetchGemini(this.endpoint+`?key=${this.apiKey}`, { 95 | method: "POST", 96 | headers: { 97 | "Content-Type": "application/json" 98 | }, 99 | body: JSON.stringify(payload) 100 | }); 101 | const data = await response.json(); 102 | return JSON.parse(data.candidates?.[0]?.content?.parts[0]?.text) || "No response"; 103 | } 104 | 105 | async explainCode(input) { 106 | const payload = { 107 | contents: [ 108 | { 109 | role: "user", 110 | parts: [ 111 | { 112 | text: this.getPrompt("explain_code", input, this.defaultPrompts) 113 | } 114 | ] 115 | } 116 | ], 117 | systemInstruction: { 118 | role: "user", 119 | parts: [ 120 | { 121 | text: this.superPrompt 122 | } 123 | ] 124 | }, 125 | generationConfig: { 126 | temperature: 0.7, 127 | responseMimeType: "application/json", 128 | responseSchema: { 129 | type: "object", 130 | properties: { 131 | explanations: { 132 | type: "array", 133 | items: { 134 | type: "object", 135 | properties: { 136 | code_line: { 137 | type: "string" 138 | }, 139 | comment: { 140 | type: "string" 141 | } 142 | }, 143 | required: [ 144 | "code_line", 145 | "comment" 146 | ] 147 | } 148 | } 149 | }, 150 | required: [ 151 | "explanations" 152 | ] 153 | } 154 | } 155 | }; 156 | 157 | const response = await this.fetchGemini(this.endpoint+`?key=${this.apiKey}`, { 158 | method: "POST", 159 | headers: { 160 | "Content-Type": "application/json" 161 | }, 162 | body: JSON.stringify(payload) 163 | }); 164 | const data = await response.json(); 165 | return JSON.parse(data.candidates?.[0]?.content?.parts[0]?.text) || "No response"; 166 | } 167 | 168 | async highLevelExplainCode(input) { 169 | const payload = { 170 | contents: [ 171 | { 172 | role: "user", 173 | parts: [ 174 | { 175 | text: this.getPrompt("high_level_explain_code", input, this.defaultPrompts) 176 | } 177 | ] 178 | } 179 | ], 180 | systemInstruction: { 181 | role: "user", 182 | parts: [ 183 | { 184 | text: this.superPrompt 185 | } 186 | ] 187 | }, 188 | generationConfig: { 189 | temperature: 0.7, 190 | responseMimeType: "application/json", 191 | responseSchema: { 192 | type: "object", 193 | properties: { 194 | explanation: { type: "string" } 195 | }, 196 | required: ["explanation"] 197 | } 198 | } 199 | }; 200 | 201 | const response = await this.fetchGemini(this.endpoint+`?key=${this.apiKey}`, { 202 | method: "POST", 203 | headers: { 204 | "Content-Type": "application/json" 205 | }, 206 | body: JSON.stringify(payload) 207 | }); 208 | const data = await response.json(); 209 | return JSON.parse(data.candidates?.[0]?.content?.parts[0]?.text) || "No response"; 210 | } 211 | 212 | async alterCode(input) { 213 | 214 | const payload = { 215 | contents: [ 216 | { 217 | role: "user", 218 | parts: [ 219 | { 220 | text: this.getPrompt("alter_code", input, this.defaultPrompts) 221 | } 222 | ] 223 | } 224 | ], 225 | systemInstruction: { 226 | role: "user", 227 | parts: [ 228 | { 229 | text: this.superPrompt 230 | } 231 | ] 232 | }, 233 | generationConfig: { 234 | temperature: 0.7, 235 | responseMimeType: "application/json", 236 | responseSchema: { 237 | type: "object", 238 | properties: { 239 | explanation: { type: "string" }, 240 | code: { type: "string" }, 241 | patch: { type: "string" } 242 | }, 243 | required: ["explanation"], 244 | oneOf: [ 245 | { required: ["code"] }, 246 | { required: ["patch"] } 247 | ] 248 | } 249 | } 250 | }; 251 | 252 | 253 | const response = await this.fetchGemini(this.endpoint+`?key=${this.apiKey}`, { 254 | method: "POST", 255 | headers: { 256 | "Content-Type": "application/json" 257 | }, 258 | body: JSON.stringify(payload) 259 | }); 260 | const data = await response.json(); 261 | return JSON.parse(data.candidates?.[0]?.content?.parts[0]?.text) || "No response"; 262 | } 263 | 264 | async fixCode(input) { 265 | 266 | const payload = { 267 | contents: [ 268 | { 269 | role: "user", 270 | parts: [ 271 | { 272 | text: this.getPrompt("fix_code", input, this.defaultPrompts) 273 | } 274 | ] 275 | } 276 | ], 277 | systemInstruction: { 278 | role: "user", 279 | parts: [ 280 | { 281 | text: this.superPrompt 282 | } 283 | ] 284 | }, 285 | generationConfig: { 286 | temperature: 0.7, 287 | responseMimeType: "application/json", 288 | responseSchema: { 289 | type: "object", 290 | properties: { 291 | patch: { type: "string" }, 292 | explanation: { type: "string" } 293 | }, 294 | required: ["patch", "explanation"] 295 | } 296 | } 297 | }; 298 | 299 | 300 | const response = await this.fetchGemini(this.endpoint+`?key=${this.apiKey}`, { 301 | method: "POST", 302 | headers: { 303 | "Content-Type": "application/json" 304 | }, 305 | body: JSON.stringify(payload) 306 | }); 307 | const data = await response.json(); 308 | return JSON.parse(data.candidates?.[0]?.content?.parts[0]?.text) || "No response"; 309 | } 310 | 311 | 312 | } 313 | -------------------------------------------------------------------------------- /modules/llm_ai/LLMSFactory.js: -------------------------------------------------------------------------------- 1 | import { OpenAIModel } from "./OpenAIModel.js"; 2 | import { GeminiModel } from "./GeminiModel.js"; 3 | import { OllamaModel } from "./OllamaModel.js"; 4 | 5 | export function createAIModel(llmsSetting, extensionId=null) { 6 | switch (llmsSetting.interface) { 7 | case "openai": 8 | return new OpenAIModel(llmsSetting); 9 | case "gemini": 10 | return new GeminiModel(llmsSetting, extensionId); 11 | case "ollama": 12 | return new OllamaModel(llmsSetting, extensionId); 13 | default: 14 | throw new Error("Unsupported AI model interface"); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /modules/llm_ai/OllamaModel.js: -------------------------------------------------------------------------------- 1 | import { AIModelInterface } from "./AIModelInterface.js"; 2 | 3 | export class OllamaModel extends AIModelInterface { 4 | constructor(llmsSetting, extensionId=null) { 5 | super(llmsSetting); 6 | this.extensionId=extensionId; 7 | this.isLocalhost = this.host.includes("localhost") || this.host.includes("127.0.0.1"); 8 | this.endpoint = `${this.host}/api/generate`; 9 | this.modelsEndpoint = `${this.host}/api/tags`; 10 | this.superPrompt =`You are an expert assistant in Google Earth Engine (GEE) coding. Any code request must be written exclusively in JavaScript for the browser-based Google Earth Engine Code Editor. Additionally comment should be written exlusively in ${this.language}. For explanatory text that needs to be structured, use Markdown syntax.\n`; 11 | this.defaultPrompts = { 12 | generate_code: this.stringToFunction("Generate efficient code for:\n${prompt}"), 13 | explain_code: this.stringToFunction("Explain this code line by line:\n${code}"), 14 | high_level_explain_code: this.stringToFunction("Summarize the purpose of this code:\n${code}"), 15 | alter_code: this.stringToFunction("Modify the following code:\n${code}\nChanges: ${prompt}\n Provide a complete code."), 16 | fix_code: this.stringToFunction("Fix the errors in this code:\n${code}\nError: ${errors}\n Provide only the code patch (diff file) to correct the code. Diff content should be dircetly in the patch parameter or the answer.") 17 | }; 18 | } 19 | 20 | async fetchOllama() { 21 | if (this.isLocalhost) { 22 | return new Promise((resolve, reject) => { 23 | chrome.runtime.sendMessage(this.extensionId, 24 | {action:"ollamaRequest",arguments:[...arguments]}, 25 | (serializedResponse) => { 26 | if(!serializedResponse) 27 | reject("Extension connection error") 28 | const { status, statusText, headers, body } = JSON.parse(serializedResponse); 29 | 30 | let response = new Response(body, { 31 | status, 32 | statusText, 33 | headers, 34 | }); 35 | resolve(response) 36 | } 37 | ); 38 | }); 39 | } else { 40 | return fetch.apply(null, arguments); 41 | } 42 | } 43 | 44 | async request(taskType, input) { 45 | const payload = { 46 | model: this.modelVersion, 47 | prompt: this.superPrompt+this.getPrompt(taskType, input, this.defaultPrompts), 48 | stream: false 49 | }; 50 | 51 | const response = await this.fetchOllama(this.endpoint, { 52 | method: "POST", 53 | headers: { "Content-Type": "application/json" }, 54 | body: JSON.stringify(payload) 55 | }); 56 | 57 | const data = await response.json(); 58 | return data.response || "No response"; 59 | } 60 | 61 | async generateCode(input) { 62 | const payload = { 63 | model: this.modelVersion, 64 | system: this.superPrompt, 65 | prompt: this.getPrompt("generate_code", input, this.defaultPrompts), 66 | stream: false, 67 | format: { 68 | type: "object", 69 | properties: { 70 | code: { 71 | type: "string" 72 | }, 73 | explanation: { 74 | "type": "string" 75 | } 76 | }, 77 | required: [ 78 | "code", 79 | "explanation" 80 | ] 81 | } 82 | }; 83 | 84 | const response = await this.fetchOllama(this.endpoint, { 85 | method: "POST", 86 | headers: { "Content-Type": "application/json" }, 87 | body: JSON.stringify(payload) 88 | }); 89 | 90 | const data = await response.json(); 91 | return JSON.parse(data.response); 92 | } 93 | 94 | async explainCode(input) { 95 | const payload = { 96 | model: this.modelVersion, 97 | system: this.superPrompt, 98 | prompt: this.getPrompt("explain_code", input, this.defaultPrompts), 99 | stream: false, 100 | format: { 101 | type: "object", 102 | properties: { 103 | explanations: { 104 | type: "array", 105 | description: "An array of code lines along with their associated comments.", 106 | items: { 107 | type: "object", 108 | properties: { 109 | code_line: { 110 | type: "string", 111 | description: "A line of code to be explained." 112 | }, 113 | comment: { 114 | type: "string", 115 | description: "The associated comment explaining the line of code." 116 | } 117 | }, 118 | required: ["code_line", "comment"], 119 | additionalProperties: false 120 | } 121 | } 122 | }, 123 | required: ["explanations"], 124 | additionalProperties: false 125 | } 126 | }; 127 | 128 | const response = await this.fetchOllama(this.endpoint, { 129 | method: "POST", 130 | headers: { "Content-Type": "application/json" }, 131 | body: JSON.stringify(payload) 132 | }); 133 | const data = await response.json(); 134 | return JSON.parse(data.response); 135 | } 136 | 137 | async highLevelExplainCode(input) { 138 | const payload = { 139 | model: this.modelVersion, 140 | system: this.superPrompt, 141 | prompt: this.getPrompt("high_level_explain_code", input, this.defaultPrompts), 142 | stream: false, 143 | format: { 144 | type: "object", 145 | properties: { 146 | explanation: { type: "string" } 147 | }, 148 | required: ["explanation"], 149 | additionalProperties: false 150 | } 151 | }; 152 | 153 | const response = await this.fetchOllama(this.endpoint, { 154 | method: "POST", 155 | headers: { "Content-Type": "application/json" }, 156 | body: JSON.stringify(payload) 157 | }); 158 | const data = await response.json(); 159 | return JSON.parse(data.response); 160 | } 161 | 162 | async alterCode(input) { 163 | const payload = { 164 | model: this.modelVersion, 165 | system: this.superPrompt, 166 | prompt: this.getPrompt("alter_code", input, this.defaultPrompts), 167 | stream: false, 168 | format: { 169 | type: "object", 170 | properties: { 171 | explanation: { type: "string" }, 172 | code: { type: "string" }, 173 | //patch: { type: "string" } 174 | }, 175 | required: ["explanation","code"], 176 | additionalProperties: false 177 | } 178 | }; 179 | 180 | const response = await this.fetchOllama(this.endpoint, { 181 | method: "POST", 182 | headers: { "Content-Type": "application/json" }, 183 | body: JSON.stringify(payload) 184 | }); 185 | const data = await response.json(); 186 | return JSON.parse(data.response); 187 | } 188 | 189 | async fixCode(input) { 190 | const payload = { 191 | model: this.modelVersion, 192 | system: this.superPrompt, 193 | prompt: this.getPrompt("fix_code", input, this.defaultPrompts), 194 | stream: false, 195 | format: { 196 | type: "object", 197 | properties: { 198 | explanation: { type: "string" }, 199 | patch: { type: "string" } 200 | }, 201 | required: ["explanation", "patch"], 202 | additionalProperties: false 203 | } 204 | }; 205 | 206 | const response = await this.fetchOllama(this.endpoint, { 207 | method: "POST", 208 | headers: { "Content-Type": "application/json" }, 209 | body: JSON.stringify(payload) 210 | }); 211 | const data = await response.json(); 212 | return JSON.parse(data.response); 213 | } 214 | 215 | 216 | 217 | 218 | 219 | async getAvailableModels() { 220 | const response = await this.fetchOllama(this.modelsEndpoint); 221 | const data = await response.json(); 222 | return data.models?.map(model => model.name) || []; 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /modules/llm_ai/OpenAIModel.js: -------------------------------------------------------------------------------- 1 | import { AIModelInterface } from "./AIModelInterface.js"; 2 | 3 | export class OpenAIModel extends AIModelInterface { 4 | constructor(llmsSetting) { 5 | super(llmsSetting); 6 | this.endpoint = `${this.host}/chat/completions`; 7 | this.modelsEndpoint = `${this.host}/models`; 8 | this.superPrompt =`You are an expert assistant in Google Earth Engine (GEE) coding. Any code request must be written exclusively in JavaScript for the browser-based Google Earth Engine Code Editor. Additionally comment should be written exlusively in ${this.language}. For explanatory text that needs to be structured, use Markdown syntax.\n`; 9 | this.defaultPrompts = { 10 | generate_code: this.stringToFunction("Generate efficient code for:\n${prompt}"), 11 | explain_code: this.stringToFunction("Explain this code line by line:\n${code}"), 12 | high_level_explain_code: this.stringToFunction("Summarize the purpose of this code:\n${code}"), 13 | alter_code: this.stringToFunction("Modify the following code:\n${code}\nChanges: ${prompt}\n Prefer to provide a code pach if possible, alternatively you can provide a complete code."), 14 | fix_code: this.stringToFunction("Fix the errors in this code:\n${code}\nError: ${errors}\n Provide only the code patch (diff file) to correct the code. Diff content should be dircetly in the patch parameter or the answer.") 15 | }; 16 | } 17 | 18 | async request(taskType, input) { 19 | const response = await fetch(this.endpoint, { 20 | method: "POST", 21 | headers: { 22 | "Authorization": `Bearer ${this.apiKey}`, 23 | "Content-Type": "application/json" 24 | }, 25 | body: JSON.stringify({ 26 | model: this.modelVersion, 27 | messages: [ 28 | { role: "user", content: this.superPrompt}, 29 | { role: "user", content: this.getPrompt(taskType, input, this.defaultPrompts) } 30 | ] 31 | }) 32 | }); 33 | 34 | const data = await response.json(); 35 | return data.choices?.[0]?.message?.content || "No response"; 36 | } 37 | 38 | async generateCode(input) { 39 | //console.log("input",input, this.getPrompt("generate_code", input, this.defaultPrompts)) 40 | const response = await fetch(this.endpoint, { 41 | method: "POST", 42 | headers: { 43 | "Authorization": `Bearer ${this.apiKey}`, 44 | "Content-Type": "application/json" 45 | }, 46 | body: JSON.stringify({ 47 | model: this.modelVersion, 48 | messages: [ 49 | { role: "developer", content: this.superPrompt}, 50 | { role: "user", content: this.getPrompt("generate_code", input, this.defaultPrompts) } 51 | ], 52 | response_format: { 53 | type: "json_schema", 54 | json_schema:{ 55 | name: "codeAnswer", 56 | strict: true, 57 | schema: { 58 | type: "object", 59 | properties: { 60 | explanation: { 61 | type: "string" 62 | }, 63 | code: { 64 | type: "string" 65 | } 66 | }, 67 | required: [ 68 | "explanation", 69 | "code" 70 | ], 71 | additionalProperties: false 72 | } 73 | } 74 | }, 75 | }) 76 | }); 77 | 78 | const data = await response.json(); 79 | return JSON.parse(data.choices?.[0]?.message?.content) || "No response"; 80 | } 81 | 82 | async explainCode(input) { 83 | //console.log("input",input, this.getPrompt("explain_code", input, this.defaultPrompts)) 84 | const response = await fetch(this.endpoint, { 85 | method: "POST", 86 | headers: { 87 | "Authorization": `Bearer ${this.apiKey}`, 88 | "Content-Type": "application/json" 89 | }, 90 | body: JSON.stringify({ 91 | model: this.modelVersion, 92 | messages: [ 93 | { role: "developer", content: this.superPrompt}, 94 | { role: "user", content: this.getPrompt("explain_code", input, this.defaultPrompts) } 95 | ], 96 | response_format: { 97 | type: "json_schema", 98 | json_schema:{ 99 | name: "code_explanation", 100 | schema: { 101 | type: "object", 102 | properties: { 103 | explanations: { 104 | type: "array", 105 | description: "An array of code lines along with their associated comments.", 106 | items: { 107 | type: "object", 108 | properties: { 109 | code_line: { 110 | type: "string", 111 | description: "A line of code to be explained." 112 | }, 113 | comment: { 114 | type: "string", 115 | description: "The associated comment explaining the line of code." 116 | } 117 | }, 118 | required: [ 119 | "code_line", 120 | "comment" 121 | ], 122 | additionalProperties: false 123 | } 124 | } 125 | }, 126 | required: [ 127 | "explanations" 128 | ], 129 | additionalProperties: false 130 | }, 131 | strict: true 132 | } 133 | } 134 | }) 135 | }); 136 | 137 | const data = await response.json(); 138 | return JSON.parse(data.choices?.[0]?.message?.content) || "No response"; 139 | } 140 | 141 | async highLevelExplainCode(input) { 142 | //console.log("input",input, this.getPrompt("high_level_explain_code", input, this.defaultPrompts)) 143 | const response = await fetch(this.endpoint, { 144 | method: "POST", 145 | headers: { 146 | "Authorization": `Bearer ${this.apiKey}`, 147 | "Content-Type": "application/json" 148 | }, 149 | body: JSON.stringify({ 150 | model: this.modelVersion, 151 | messages: [ 152 | { role: "developer", content: this.superPrompt}, 153 | { role: "user", content: this.getPrompt("high_level_explain_code", input, this.defaultPrompts) } 154 | ], 155 | response_format: { 156 | type: "json_schema", 157 | json_schema:{ 158 | name: "codeAnswer", 159 | strict: true, 160 | schema: { 161 | type: "object", 162 | properties: { 163 | explanation: { 164 | type: "string" 165 | } 166 | }, 167 | required: [ 168 | "explanation" 169 | ], 170 | additionalProperties: false 171 | } 172 | } 173 | }, 174 | }) 175 | }); 176 | 177 | const data = await response.json(); 178 | return JSON.parse(data.choices?.[0]?.message?.content) || "No response"; 179 | } 180 | 181 | async alterCode(input) { 182 | //console.log("input",input, this.getPrompt("alter_code", input, this.defaultPrompts)) 183 | const response = await fetch(this.endpoint, { 184 | method: "POST", 185 | headers: { 186 | "Authorization": `Bearer ${this.apiKey}`, 187 | "Content-Type": "application/json" 188 | }, 189 | body: JSON.stringify({ 190 | model: this.modelVersion, 191 | messages: [ 192 | { role: "developer", content: this.superPrompt}, 193 | { role: "user", content: this.getPrompt("alter_code", input, this.defaultPrompts) } 194 | ], 195 | response_format: { 196 | type: "json_schema", 197 | json_schema:{ 198 | name: "codeAnswer", 199 | strict: true, 200 | schema: { 201 | type: "object", 202 | properties: { 203 | explanation: { 204 | type: "string" 205 | }, 206 | code: { 207 | type: "string" 208 | }, 209 | patch: { 210 | type: "string" 211 | } 212 | }, 213 | required: [ 214 | "explanation" 215 | ], 216 | oneOf: [ 217 | { "required": ["code"] }, 218 | { "required": ["patch"] } 219 | ], 220 | additionalProperties: false 221 | } 222 | } 223 | }, 224 | }) 225 | }); 226 | 227 | const data = await response.json(); 228 | return JSON.parse(data.choices?.[0]?.message?.content) || "No response"; 229 | } 230 | 231 | async fixCode(input) { 232 | //console.log("input",input, this.getPrompt("fix_code", input, this.defaultPrompts)) 233 | const response = await fetch(this.endpoint, { 234 | method: "POST", 235 | headers: { 236 | "Authorization": `Bearer ${this.apiKey}`, 237 | "Content-Type": "application/json" 238 | }, 239 | body: JSON.stringify({ 240 | model: this.modelVersion, 241 | messages: [ 242 | { role: "developer", content: this.superPrompt}, 243 | { role: "user", content: this.getPrompt("fix_code", input, this.defaultPrompts) } 244 | ], 245 | response_format: { 246 | type: "json_schema", 247 | json_schema:{ 248 | name: "codeAnswer", 249 | strict: true, 250 | schema: { 251 | type: "object", 252 | properties: { 253 | explanation: { 254 | type: "string" 255 | }, 256 | patch: { 257 | type: "string" 258 | } 259 | }, 260 | required: [ 261 | "explanation", 262 | "patch" 263 | ], 264 | additionalProperties: false 265 | } 266 | } 267 | }, 268 | }) 269 | }); 270 | 271 | const data = await response.json(); 272 | return JSON.parse(data.choices?.[0]?.message?.content) || "No response"; 273 | } 274 | 275 | async getAvailableModels() { 276 | // OpenAI requires authentication to list models 277 | const response = await fetch(this.modelsEndpoint, { 278 | method: "GET", 279 | headers: { "Authorization": `Bearer ${this.apiKey}` } 280 | }); 281 | const data = await response.json(); 282 | return data.data?.map(model => model.id) || []; 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /modules/openInNewTab.js: -------------------------------------------------------------------------------- 1 | let oee_dbclickScriptTimeout=null; 2 | 3 | 4 | function addScriptPath(targetNode){ 5 | function constructPath(e,start){ 6 | let parent=e.parentElement.closest('ee-zippy, .zippy') 7 | if(parent.classList.contains('zippy')){ 8 | // the end of the recursion 9 | let name=parent.querySelector('.zippy > .header .tree-item-name').innerText; 10 | return name+':'+start; 11 | }else{ 12 | // get header 13 | let name=parent.querySelector('ee-zippy > .header .tree-item-name').innerText; 14 | //and ast the other part 15 | return constructPath(parent, name+'/'+start) 16 | } 17 | } 18 | 19 | let nodesToExplore=targetNode.map(e=>e.addedNodes[0]).filter(e=> e.classList?.contains('file-type-file')); 20 | 21 | nodesToExplore.map(e=>{ 22 | let path=constructPath(e,e.querySelector('.tree-item-name').innerText) 23 | e.addEventListener('click',event=>{ 24 | if(!event.detail?.timeoutClick && event.target.classList.contains("tree-item-name")){ 25 | event.preventDefault(); 26 | event.stopPropagation(); 27 | 28 | if(oee_dbclickScriptTimeout){ 29 | clearTimeout(oee_dbclickScriptTimeout); 30 | oee_dbclickScriptTimeout=null; 31 | window.open("https://"+location.host+"/?scriptPath="+encodeURIComponent(path), '_blank') 32 | }else{ 33 | var event = new CustomEvent("click", {'detail':{'timeoutClick': true}}); 34 | oee_dbclickScriptTimeout=setTimeout(function(){e.dispatchEvent(event)},300) 35 | } 36 | }else{ 37 | oee_dbclickScriptTimeout=null; 38 | } 39 | 40 | 41 | },true); 42 | e.addEventListener('contextmenu',event=>{ 43 | 44 | if(event.target.classList.contains("tree-item-name")){ 45 | event.preventDefault(); 46 | event.stopPropagation(); 47 | 48 | navigator.clipboard.writeText(path); 49 | }else{ 50 | oee_dbclickScriptTimeout=null; 51 | } 52 | 53 | 54 | },true); 55 | }) 56 | } 57 | 58 | function addObeserver(){ 59 | let targetNode=document.querySelector(".tree-manager.repo-manager") 60 | const config = { childList: true, subtree: true }; 61 | 62 | // Callback function to execute when mutations are observed 63 | const callback = (mutationList, observer) => { 64 | addScriptPath(mutationList) 65 | }; 66 | 67 | // Create an observer instance linked to the callback function 68 | const observer = new MutationObserver(callback); 69 | 70 | // Start observing the target node for configured mutations 71 | observer.observe(targetNode, config); 72 | } 73 | 74 | export function initialize(){ 75 | addObeserver(); 76 | } -------------------------------------------------------------------------------- /modules/plotly.js: -------------------------------------------------------------------------------- 1 | const consolePlotlyExtensionPrefix='OEEex_AddonPlotly'; 2 | let plotPosition=0; 3 | let EECache={}; 4 | 5 | let listPlot=[]; 6 | 7 | const listPlotlyEvent=[ 8 | "plotly_click", 9 | "plotly_hover", 10 | "plotly_unhover", 11 | "plotly_selecting", 12 | "plotly_selected", 13 | "plotly_legendclick", 14 | "plotly_legenddoubleclick", 15 | "plotly_restyle", 16 | "plotly_relayout", 17 | "plotly_deselect", 18 | "plotly_doubleclick", 19 | "plotly_redraw", 20 | "plotly_animated" 21 | ]; 22 | 23 | let plotlyDarkTemplate={}; 24 | 25 | 26 | let OEEexEscapeURL = trustedTypes.createPolicy("OEEexEscapeURL", { 27 | createScriptURL: (string, sink) => string 28 | }); 29 | 30 | let OEEexEscape = trustedTypes.createPolicy("OEEexEscape", { 31 | createHTML: (string, sink) => string 32 | }); 33 | 34 | function isPromise(p) { 35 | if (typeof p === 'object' && typeof p.then === 'function') { 36 | return true; 37 | } 38 | 39 | return false; 40 | } 41 | 42 | function hashCode(str) { 43 | let hash = 0; 44 | for (let i = 0, len = str.length; i < len; i++) { 45 | let chr = str.charCodeAt(i); 46 | hash = (hash << 5) - hash + chr; 47 | hash |= 0; // Convert to 32bit integer 48 | } 49 | return hash; 50 | } 51 | 52 | function htmlDecode(input) { 53 | var doc = new DOMParser().parseFromString(input, "text/html"); 54 | return doc.documentElement.textContent; 55 | } 56 | 57 | function injectPlotly(extensionId){ 58 | var s = document.createElement('script'); 59 | s.src = OEEexEscapeURL.createScriptURL("chrome-extension://"+extensionId+"/3rd_party/plotly-2.26.0.min.js"); 60 | s.onload = function() { 61 | this.remove(); 62 | }; 63 | (document.head || document.documentElement).appendChild(s); 64 | } 65 | 66 | 67 | 68 | function loadConsolePlotlyWatcher(){ 69 | let MutationObserver = window.MutationObserver || window.WebKitMutationObserver; 70 | let myObserver = new MutationObserver(function(mutList){ 71 | 72 | [...mutList].map(function(mut){ 73 | [...mut.addedNodes].map(function(e){ 74 | if(e.classList.contains('OEEexPlotlyAnalysis')) 75 | return; 76 | e.classList.add('OEEexPlotlyAnalysis') 77 | analysisPlotlyAddon(e) 78 | }); 79 | }); 80 | }); 81 | let obsConfig = { childList: true}; 82 | 83 | if(document.querySelector('ee-console')) 84 | myObserver.observe(document.querySelector('ee-console'), obsConfig); 85 | 86 | let myObserver2= new MutationObserver(function(mutList){ 87 | 88 | let cleaned=[...mutList].map(e=> [...e.addedNodes]).flat().filter(f=> (f.classList && 89 | f.classList.contains('ui-label') && 90 | ! f.classList.contains('OEEexPlotlyAnalysis') && 91 | f.textContent && 92 | f.textContent.startsWith(consolePlotlyExtensionPrefix+':')&& 93 | document.body.contains(f)) 94 | || 95 | (f.classList && 96 | f.classList.contains('ui-textbox') && 97 | ! f.classList.contains('OEEexPlotlyAnalysis') && 98 | f.querySelector('input').placeholder=="OEEex_Active_AddonPlotly" && 99 | document.body.contains(f))) 100 | 101 | cleaned=cleaned.filter(function(value, index, self) { 102 | return self.indexOf(value) === index; 103 | }); 104 | 105 | cleaned.map(function(e){ 106 | e.style.padding=0; 107 | if(e.classList.contains('ui-textbox')) 108 | { 109 | e.classList.add('OEEexPlotlyAnalysis') 110 | let input=e.querySelector('input'); 111 | input.style.display='none'; 112 | let plotDiv=document.createElement('div'); 113 | plotDiv.style.margin=0; 114 | e.appendChild(plotDiv); 115 | if(input.placeholder=="OEEex_Active_AddonPlotly"){ 116 | addPlotlyPlot(input.value,plotDiv,true,input); 117 | } 118 | } 119 | else{ 120 | e.classList.add('OEEexPlotlyAnalysis') 121 | addPlotlyPlot(e.textContent.slice((consolePlotlyExtensionPrefix+':').length),e,true); 122 | } 123 | }); 124 | 125 | }); 126 | let obsConfig2 = { childList: true, subtree:true}; 127 | if(document.querySelector('.ui-root')) 128 | myObserver2.observe(document.querySelector('.ui-root'), obsConfig2); 129 | 130 | 131 | let resizeObserver= new ResizeObserver(function(newSize,element){ 132 | document.querySelectorAll('.js-plotly-plot:not(.inApp)').forEach(function(e){ 133 | Plotly.relayout(e,{width: newSize[0].contentRect.width-5}) 134 | }) 135 | }); 136 | 137 | if(document.querySelectorAll('.goog-splitpane-second-container').length>1) 138 | resizeObserver.observe(document.querySelectorAll('.goog-splitpane-second-container')[1]); 139 | 140 | if(document.querySelector('.goog-button.run-button')) 141 | document.querySelector('.goog-button.run-button').addEventListener('click',function(){plotPosition=0;}) 142 | if(document.querySelector('.goog-button.reset-button')) 143 | document.querySelector('.goog-button.reset-button').addEventListener('click',function(){plotPosition=0;}) 144 | } 145 | 146 | function analysisPlotlyAddon(val){ 147 | val.querySelectorAll('.trivial').forEach(function(obj){ 148 | let consoleCode=obj.innerHTML; 149 | if(consoleCode.startsWith(consolePlotlyExtensionPrefix+':')){ 150 | addPlotlyPlot(consoleCode.slice((consolePlotlyExtensionPrefix+':').length),obj,false); 151 | return; 152 | } 153 | }); 154 | 155 | val.querySelectorAll('.ui-widget.ui-textbox').forEach(function(obj){ 156 | let input=obj.querySelector('input'); 157 | let plotDiv=document.createElement('div'); 158 | obj.appendChild(plotDiv); 159 | if(input.placeholder=="OEEex_Active_AddonPlotly"){ 160 | input.style.display='none'; 161 | addPlotlyPlot(input.value,plotDiv,false,input); 162 | } 163 | }); 164 | 165 | } 166 | 167 | function explorAllJSON(input){ 168 | let promisesArray=[]; 169 | 170 | if(input && !(typeof input === 'string' || input instanceof String)){ 171 | let keys=Object.keys(input); 172 | for (let idx=0; idx< keys.length; idx++) { 173 | let key=keys[idx]; 174 | if(input[key] && input[key].toString && input[key].toString().slice(0,3)=="ee."){ 175 | let keyCache=hashCode(ee.Serializer.toJSON(input[key])) 176 | if(keyCache in EECache){ 177 | if(isPromise(EECache[keyCache])){ 178 | EECache[keyCache].then(function(eeCompute){ 179 | input[key]=eeCompute; 180 | }) 181 | }else{ 182 | input[key]=EECache[keyCache]; 183 | } 184 | }else{ 185 | 186 | let prom=new Promise((resolve, reject) => { 187 | input[key].evaluate(function(eeCompute){ 188 | resolve(eeCompute); 189 | input[key]=eeCompute; 190 | EECache[keyCache]=eeCompute 191 | }) 192 | }) 193 | promisesArray.push(prom) 194 | } 195 | continue; 196 | } 197 | 198 | let out=explorAllJSON(input[key]); 199 | input[key]=out.ud; 200 | promisesArray=promisesArray.concat(out.promises); 201 | } 202 | } 203 | return {ud:input,promises:promisesArray} 204 | } 205 | 206 | function updatePlot(plotDiv,plot){ 207 | plotDiv.classList.add('loading'); 208 | var plotEval=explorAllJSON(plot) 209 | Promise.all(plotEval.promises).then(function(){ 210 | let plot=configPlot(plotEval.ud,plotDiv,plotDiv.classList.contains('inApp')); 211 | Plotly.react(plotDiv,plot.data,plot.layout); 212 | plotDiv.classList.remove('loading'); 213 | plotDiv.addEventListener('refreshDraw', switch2DarkMode.bind(null, plot), false); 214 | }) 215 | } 216 | 217 | 218 | 219 | function configPlot(plot,val,inApp){ 220 | 221 | 222 | if(!plot.layout){ 223 | plot.layout={} 224 | } 225 | if(!plot.layout.width){ 226 | if(!inApp && document.querySelectorAll('.goog-splitpane-second-container').length>1) 227 | plot.layout.width=getComputedStyle(document.querySelectorAll('.goog-splitpane-second-container')[1]).width.slice(0,-2)-12; 228 | if(inApp && val.style.width){ 229 | plot.layout.width=val.style.width.slice(0,-2); 230 | } 231 | } 232 | if(!plot.layout.height){ 233 | if(!inApp && document.querySelectorAll('.goog-splitpane-second-container').length>1) 234 | plot.layout.height=Math.max(Math.min(getComputedStyle(document.querySelectorAll('.goog-splitpane-second-container')[1]).height.slice(0,-2)-12,500),250); 235 | if(inApp && val.style.height) 236 | { 237 | plot.layout.height=val.style.height.slice(0,-2); 238 | } 239 | } 240 | if(!plot.layout.margin){ 241 | plot.layout.margin={ 242 | l: 50, 243 | r: 50, 244 | b: 50, 245 | t: 50, 246 | pad: 4 247 | } 248 | } 249 | 250 | if(plot.transparent) 251 | { 252 | plot.layout.paper_bgcolor="#0000"; 253 | plot.layout.plot_bgcolor="#0000"; 254 | } 255 | 256 | if(plot.annotations){ 257 | if(!plot.layout)plot.layout={}; 258 | plot.layout.annotations=plot.annotations; 259 | } 260 | 261 | if(document.getElementsByTagName('html')[0].classList.contains('dark')){ 262 | plot.layout.template=plotlyDarkTemplate; 263 | } 264 | 265 | return plot 266 | } 267 | 268 | function switch2DarkMode(plot,e) { 269 | console.log(event,e,plot) 270 | if(e.detail.darkMode){ 271 | plot.layout.template=plotlyDarkTemplate; 272 | }else{ 273 | delete plot.layout.template; 274 | } 275 | Plotly.relayout(e.currentTarget,plot.layout) 276 | } 277 | 278 | function addPlotlyPlot(consoleCode,val,inApp,input){ 279 | val.classList.add('loading'); 280 | val.classList.add('explorer'); 281 | val.innerHTML=OEEexEscape.createHTML('Plotly: Computing'); 282 | let plot=ee.Deserializer.fromJSON(consoleCode) 283 | let locPlotPosition=plotPosition++; 284 | var plotEval=explorAllJSON(plot) 285 | Promise.all(plotEval.promises).then(function(){ 286 | plot=configPlot(plotEval.ud,val, inApp); 287 | 288 | //for config 289 | let imageExportFormat='png';// one of png, svg, jpeg, webp 290 | let imageExportName="EE_plotly_chart"; 291 | if(document.querySelectorAll('.goog-splitpane-second-container .panel.editor-panel .header > span').length>0) 292 | imageExportName="EE_plotly_chart_"+document.querySelectorAll('.goog-splitpane-second-container .panel.editor-panel .header > span')[0].textContent.replace('/', '_')+'_'+locPlotPosition; 293 | let imageExportScale=4; 294 | if(plot.exportFormat){ 295 | imageExportFormat=plot.exportFormat; 296 | } 297 | if(plot.exportName){ 298 | imageExportName=plot.exportName; 299 | } 300 | if(plot.exportScale){ 301 | imageExportScale=plot.exportScale; 302 | } 303 | 304 | let config={ 305 | displaylogo: false, 306 | toImageButtonOptions: { 307 | format: imageExportFormat, 308 | filename: imageExportName, 309 | scale: imageExportScale // Multiply title/legend/axis/canvas sizes by this factor 310 | } 311 | } 312 | 313 | 314 | val.innerHTML=OEEexEscape.createHTML(''); 315 | 316 | Plotly.newPlot( val, plot.data, plot.layout,config ) 317 | 318 | if(inApp) 319 | { 320 | val.classList.add('inApp') 321 | } 322 | 323 | if(input){ 324 | const desc = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value"); 325 | Object.defineProperty(input, "value", { 326 | get: desc.get, 327 | set: function(v) { 328 | try{ 329 | let payload=ee.Deserializer.fromJSON(v) 330 | if(payload.toEE===false){ 331 | updatePlot(val,payload) 332 | } 333 | }catch(e){ 334 | 335 | } 336 | desc.set.call(this, v); 337 | } 338 | }); 339 | 340 | function allEvent(type, input, data){ 341 | let payload={toEE:true, type:type, plotlyData:data,time:Date.now()} 342 | try{ 343 | input.value=JSON.stringify(payload,function(key,val){return (key.startsWith('_')|| ['targetLinks','sourceLinks','links','data','fullData'].includes(key) ?undefined:val)}) 344 | input.dispatchEvent(new Event('change')) 345 | }catch(e){ 346 | console.error(e) 347 | } 348 | } 349 | 350 | for (var i = listPlotlyEvent.length - 1; i >= 0; i--) { 351 | let eventType=listPlotlyEvent[i]; 352 | val.on(eventType, function(data){allEvent(eventType,input,data)}); 353 | } 354 | 355 | } 356 | 357 | val.addEventListener('refreshDraw', switch2DarkMode.bind(null, plot), false); 358 | 359 | val.classList.remove('loading'); 360 | //[...document.querySelectorAll('.gm-style')].forEach(e=> e.dispatchEvent(new Event('resize'))) 361 | listPlot.push(val) 362 | }) 363 | } 364 | 365 | function loadDarkModeManager(){ 366 | 367 | document.addEventListener("darkModeEvent",function(event){ 368 | listPlot=listPlot.filter((e)=>document.body.contains(e)) 369 | listPlot.forEach(function(elem){ 370 | elem.dispatchEvent(new CustomEvent('refreshDraw',{detail:{darkMode:event.detail.toDark}})); 371 | }) 372 | }) 373 | } 374 | 375 | 376 | async function loadJSON(extensionId) { 377 | const response = await fetch("chrome-extension://"+extensionId+"/otherAssets/darkPlotly.json"); 378 | const data = await response.json(); 379 | return data; 380 | } 381 | 382 | export function initializeMT(extensionId){ 383 | loadJSON(extensionId).then(data => plotlyDarkTemplate=data); 384 | injectPlotly(extensionId); 385 | loadConsolePlotlyWatcher(); 386 | loadDarkModeManager(); 387 | 388 | } -------------------------------------------------------------------------------- /modules/runAll.js: -------------------------------------------------------------------------------- 1 | let oeex_submitTaskInterval=null; 2 | 3 | function addRunAllTaskButton(){ 4 | let taskPanel=document.querySelector('#task-pane').shadowRoot.querySelector(".container"); 5 | const observer = new MutationObserver(function(mutationsList){ 6 | if(mutationsList.length>0 && mutationsList[0].addedNodes.length>0){ 7 | for (var i = mutationsList[0].addedNodes.length - 1; i >= 0; i--) { 8 | if(mutationsList[0].addedNodes[i].className=="client-task-pane"){ 9 | let taskPanel=mutationsList[0].addedNodes[i]; 10 | let runAllButton=document.createElement("ee-button") 11 | runAllButton.classList.add('run-all-button') 12 | runAllButton.classList.add('run-button') 13 | runAllButton.setAttribute('type', 'action'); 14 | runAllButton.setAttribute('style', 'height: 0px; right: -4px; float: right; bottom: 30px; position: relative; line-height: 6px; font-weight: 700;'); 15 | runAllButton.innerHTML="Run all!"; 16 | taskPanel.insertBefore(runAllButton,taskPanel.firstChild); 17 | runAllButton.addEventListener("click",function(event){ 18 | if(oeex_submitTaskInterval){ 19 | clearInterval(oeex_submitTaskInterval); 20 | oeex_submitTaskInterval=null; 21 | } 22 | oeex_submitTaskInterval=setInterval(function () { 23 | let disbaledListEvent=[...taskPanel.querySelectorAll('.client-task-list .task ee-button.run-button:not(.run-all-button)[disabled]')]; 24 | let runableEvent=taskPanel.querySelector('.client-task-list .task ee-button.run-button:not(.run-all-button):not([disabled])'); 25 | if(!runableEvent){ 26 | clearInterval(oeex_submitTaskInterval); 27 | oeex_submitTaskInterval=null; 28 | } 29 | if(disbaledListEvent.length<5){ 30 | var event = new Event('click'); 31 | event.metaKey=true; 32 | if(runableEvent) 33 | runableEvent.dispatchEvent(event); 34 | } 35 | }, 1); 36 | }) 37 | } 38 | } 39 | } 40 | }); 41 | 42 | observer.observe(taskPanel, {subtree: false, childList: true}); 43 | } 44 | 45 | export function initializeMT(){ 46 | addRunAllTaskButton(); 47 | } -------------------------------------------------------------------------------- /modules/sharedCodeSession.js: -------------------------------------------------------------------------------- 1 | let showVersionSwitchModal=null; 2 | let sessionPassword=null; 3 | let listConnection=new Set(); 4 | 5 | 6 | let OEEexEscapeURL = trustedTypes.createPolicy("OEEexEscapeURL", { 7 | createScriptURL: (string, sink) => string 8 | }); 9 | 10 | 11 | let OEEexEscape = trustedTypes.createPolicy("OEEexEscape", { 12 | createHTML: (string, sink) => string 13 | }); 14 | 15 | 16 | 17 | function anwserToRequest(data){ 18 | if(data.type && data.type=="editorFullContent"){ 19 | editor.getSession().setValue(data.content); 20 | lastSendVersion=data.content; 21 | } 22 | if(data.type && data.type=="editorPatch"){ 23 | let previousLastVersion=lastSendVersion 24 | lastSendVersion=Diff.applyPatch(lastSendVersion, data.patch); 25 | let newCode=Diff.applyPatch(editor.getSession().getValue(), data.patch); 26 | if(newCode) 27 | { 28 | editor.getSession().setValue(newCode); 29 | } 30 | else{ 31 | //try to fix 32 | let patch = Diff.createPatch("filename", previousLastVersion, editor.getSession().getValue(), "", ""); 33 | let newCode=Diff.applyPatch(previousLastVersion, patch); 34 | if(newCode) 35 | { 36 | console.log("fix worked") 37 | editor.getSession().setValue(newCode); 38 | //showVersionSwitchModal(); 39 | }else{ 40 | showVersionSwitchModal(); 41 | } 42 | } 43 | } 44 | if(data.type && data.type=="selections"){ 45 | displayRemoteSelection(data); 46 | } 47 | } 48 | 49 | function displayRemoteSelection(data){ 50 | // Get all markers 51 | var markers = editor.session.getMarkers(); 52 | 53 | // Iterate through the markers object 54 | for (var markerId in markers) { 55 | // Check if the marker class matches "myCustomHighlightClass" 56 | if (markers[markerId].clazz === "remoteSelection") { 57 | // Remove the marker 58 | editor.session.removeMarker(markerId); 59 | } 60 | } 61 | 62 | if(data.isEmpty) 63 | return 64 | 65 | 66 | let convertedRanges=data.ranges.map(convertRange) 67 | 68 | convertedRanges.map(function(range){ 69 | editor.getSession().addMarker(range, "remoteSelection", "text", false); 70 | }) 71 | 72 | // var myRange = new Range(1, 0, 1, 10); // This highlights from row 1, column 0 to row 1, column 10 73 | 74 | // // Add the marker 75 | // // Parameters for addMarker are: range, className, type, inFront 76 | // var markerId = editor.session.addMarker(myRange, "myCustomHighlightClass", "text", false); 77 | } 78 | 79 | function convertRange(range){ 80 | // Sample multi-line original and new texts 81 | var originalText = lastSendVersion; 82 | var newText =editor.getSession().getValue() ; 83 | 84 | // Example selection in original text: From "original" in line 3, column 5 to column 14 85 | var selectionStart = range.start; 86 | var selectionEnd = range.end; 87 | 88 | // Generate unique placeholders 89 | var startPlaceholder = ""; 90 | var endPlaceholder = ""; 91 | 92 | // Function to insert placeholders into the original text 93 | function insertPlaceholders(text, start, end, startPlaceholder, endPlaceholder) { 94 | // Convert row/column positions to linear indices 95 | var lines = text.split("\n"); 96 | var startIndex = lines.slice(0, start.row).join("\n").length + start.column + 1; // +1 for newline characters 97 | var endIndex = lines.slice(0, end.row).join("\n").length + end.column + 1; // +1 for newline characters 98 | 99 | // Insert placeholders 100 | return text.slice(0, startIndex) + startPlaceholder + text.slice(startIndex, endIndex) + endPlaceholder + text.slice(endIndex); 101 | } 102 | 103 | // Insert placeholders into the original text 104 | var modifiedOriginalText = insertPlaceholders(originalText, selectionStart, selectionEnd, startPlaceholder, endPlaceholder); 105 | 106 | // Generate a patch and apply it 107 | var patch = Diff.createPatch("filename", originalText, newText, "", ""); 108 | var patchedText = Diff.applyPatch(modifiedOriginalText, patch); 109 | 110 | // Find the new positions of the placeholders 111 | var newStartIndex = patchedText.indexOf(startPlaceholder); 112 | var newEndIndex = patchedText.indexOf(endPlaceholder) - startPlaceholder.length; // Adjust for the length of the start placeholder 113 | 114 | // Convert linear indices back to row/column positions 115 | function indexToPosition(text, index) { 116 | var lines = text.substring(0, index).split("\n"); 117 | var row = lines.length - 1; 118 | var column = lines[lines.length - 1].length; 119 | return { row:row, column:column }; 120 | } 121 | 122 | var newStartPosition = indexToPosition(patchedText, newStartIndex); 123 | var newEndPosition = indexToPosition(patchedText, newEndIndex); 124 | 125 | return new aceRange(newStartPosition.row,newStartPosition.column, newEndPosition.row,newEndPosition.column); 126 | } 127 | 128 | function getLastContent(){ 129 | return {type:"editorFullContent", content:lastSendVersion} 130 | } 131 | 132 | function getPatch(oldVersion, newVersion){ 133 | return {type:"editorPatch", patch:Diff.createPatch("EECode", oldVersion, newVersion)} 134 | } 135 | 136 | function onEditorChange(change){ 137 | let newVersion=editor.getSession().getValue(); 138 | broadcastToEveryone(getPatch(lastSendVersion,newVersion)); 139 | lastSendVersion=newVersion; 140 | } 141 | 142 | function onSelectionChange(event,selection){ 143 | broadcastToEveryone({type:"selections",ranges:JSON.parse(JSON.stringify(selection.getAllRanges())), isEmpty:selection.isEmpty()}) 144 | } 145 | 146 | function broadcastToEveryone(content){ 147 | [...listConnection].map(x => x.send(content)); 148 | } 149 | 150 | 151 | let lastSendVersion=""; 152 | 153 | function addToListOFConnectionAndSendLastversion(con){ 154 | listConnection.add(con) 155 | con.send(getLastContent()); 156 | } 157 | 158 | let editor=null; 159 | let aceRange=null; 160 | function handleSessionAction(sessionId, password, sessionType) { 161 | 162 | let editorElement=document.getElementsByClassName('ace_editor'); 163 | if(editorElement && editorElement.length>0){ 164 | editorElement[0].id='editor' 165 | editor = ace.edit("editor"); 166 | aceRange = ace.require('ace/range').Range; 167 | } 168 | 169 | let oeePeerServer={ 170 | host: "ee-peer.open-geocomputing.org", 171 | port: 443, 172 | path: "/ee-peer", 173 | } 174 | // get editor 175 | if("host"==sessionType ){ 176 | sessionPassword=password; 177 | var peer = new Peer(sessionId,oeePeerServer); 178 | peer.on('open', function(id) { 179 | //console.log('My peer ID is: ' + id); 180 | editor.getSession().on('change', onEditorChange); 181 | editor.getSession().selection.on('changeSelection', onSelectionChange); 182 | document.dispatchEvent(new CustomEvent("startCodeShareSession",{detail:{action:"started"}})) 183 | 184 | }); 185 | 186 | peer.on('connection', function(con){ 187 | con.on('data', function(data){ 188 | if(data.type && data.type=="connection"){ 189 | if(data.status && data.status=="active"){ 190 | if(sessionPassword!=""){ 191 | //console.log("request pasword"); 192 | con.send({type:"passwordRequest"}); 193 | }else{ 194 | addToListOFConnectionAndSendLastversion(con); 195 | con.send({type:"connection",status:"registred"}); 196 | } 197 | } 198 | } 199 | 200 | if(data.type && data.type=="password"){ 201 | if(sessionPassword=="" || data.pwd==sessionPassword){ 202 | addToListOFConnectionAndSendLastversion(con); 203 | con.send({type:"connection",status:"registred"}); 204 | }else{ 205 | con.send({type:"wrongPassword"}); 206 | setTimeout(con.close,100); 207 | } 208 | } 209 | }); 210 | }); 211 | } 212 | 213 | if("join"==sessionType ){ 214 | sessionPassword=password; 215 | var peer = new Peer(oeePeerServer); 216 | peer.on('open', function(id) { 217 | //console.log('My peer ID is: ' + id); 218 | var con = peer.connect(sessionId,{reliable:true}); 219 | con.on('data',anwserToRequest); 220 | con.on('data', function(data){ 221 | if(data.type && data.type=="passwordRequest"){ 222 | con.send({type:"password",pwd:sessionPassword}) 223 | } 224 | if(data.type && data.type=="wrongPassword"){ 225 | document.dispatchEvent(new CustomEvent("startCodeShareSession",{detail:{action:"wrongPassword"}})) 226 | } 227 | if(data.type && data.type=="connection" && data.status=="registred"){ 228 | document.dispatchEvent(new CustomEvent("startCodeShareSession",{detail:{action:"started"}})) 229 | } 230 | 231 | }); 232 | con.on('open', function(){ 233 | con.send({type:"connection",status:"active"}); 234 | // request last version, not needed the server send it automatically 235 | }); 236 | }); 237 | 238 | peer.on('error', function(err) { 239 | document.dispatchEvent(new CustomEvent("startCodeShareSession",{detail:{action:"wrong"}})) 240 | peer.destroy() 241 | }); 242 | } 243 | } 244 | 245 | export function initializeMT(extensionId){ 246 | document.addEventListener("startCodeShareSession",function(e){ 247 | let param=e.detail; 248 | if(param.action=="open") 249 | handleSessionAction( param.sessionIdVal, param.assword, param.sessionType); 250 | }); 251 | } 252 | 253 | 254 | 255 | function createSharedCodeSessionPopup(){ 256 | // Create the popup container 257 | const popup = document.createElement('div'); 258 | popup.className = 'oeeSCSContainer'; // Reuse context menu styling 259 | document.body.appendChild(popup); 260 | 261 | // Add content to the popup 262 | popup.innerHTML = OEEexEscape.createHTML(` 263 |
264 | 265 | 266 |
267 | 268 | 269 | 270 | 271 | `); 272 | 273 | const codePatchingErrorPopup = document.createElement('div'); 274 | codePatchingErrorPopup.className = 'oeeSCSContainer'; // Reuse context menu styling 275 | codePatchingErrorPopup.id="versionSwitchModal" 276 | document.body.appendChild(codePatchingErrorPopup); 277 | 278 | codePatchingErrorPopup.innerHTML = OEEexEscape.createHTML(` 279 |

⚠️Unable to apply the updates!⚠️
Do you want to stay on the current version or switch to the new version?

280 | 281 | 282 | `); 283 | 284 | showVersionSwitchModal = function() { 285 | const modal = document.getElementById('versionSwitchModal'); 286 | modal.classList.add('visible'); 287 | } 288 | 289 | // Function to hide the modal 290 | function hideVersionSwitchModal() { 291 | const modal = document.getElementById('versionSwitchModal'); 292 | modal.classList.remove('visible'); 293 | } 294 | 295 | // Event listeners for the buttons 296 | document.getElementById('stayButton').addEventListener('click', function() { 297 | hideVersionSwitchModal(); 298 | //console.log("User chose to stay on the current version."); 299 | // Additional logic to handle staying on the current version 300 | // what should we do ?? 301 | }); 302 | 303 | document.getElementById('switchButton').addEventListener('click', function() { 304 | hideVersionSwitchModal(); 305 | editor.getSession().setValue(lastSendVersion) 306 | }); 307 | 308 | 309 | let sessionId = document.getElementById('sessionId'); 310 | let passwordSession = document.getElementById('sessionPassword'); 311 | let hostSession = document.getElementById('hostSession'); 312 | let joinSession = document.getElementById('joinSession') 313 | 314 | // Function to toggle the popup visibility 315 | function togglePopup() { 316 | popup.classList.toggle("visible") 317 | } 318 | 319 | // Function to update the popup based on the selection 320 | const sessionTypeSelector = document.getElementById('sessionType'); 321 | const validateButton = document.getElementById('validateSession'); 322 | 323 | 324 | const customEvent = new CustomEvent('add2OEEMenu', { 325 | detail: { 326 | name: 'Share Code Session', 327 | callback: () => togglePopup() 328 | } 329 | }); 330 | window.dispatchEvent(customEvent); 331 | 332 | // Prevent clicks within the popup from closing it 333 | popup.addEventListener('mousedown', (event) => { 334 | event.stopPropagation(); 335 | }); 336 | 337 | document.addEventListener('mousedown', (event) => { 338 | if (!popup.contains(event.target) && popup.classList.contains("visible")) { 339 | togglePopup(); 340 | } 341 | }); 342 | 343 | document.addEventListener('keydown', (event) => { 344 | if ((event.keyCode == 27) && !popup.contains(event.target) && popup.classList.contains("visible")) { 345 | togglePopup(); 346 | } 347 | }); 348 | 349 | joinSession.addEventListener('click', function() { 350 | this.classList.add('active'); 351 | hostSession.classList.remove('active'); 352 | sessionTypeSelector.value = 'join'; 353 | validateButton.textContent = 'Connect'; 354 | }); 355 | 356 | hostSession.addEventListener('click', function() { 357 | this.classList.add('active'); 358 | joinSession.classList.remove('active'); 359 | sessionTypeSelector.value = 'host'; 360 | validateButton.textContent = 'Host'; 361 | if(sessionId.value==""){ 362 | sessionId.value = "ee-"+Math.random().toString(36).substring(2, 8); // Generate session ID 363 | } 364 | }); 365 | 366 | validateButton.addEventListener('click', function() { 367 | const sessionIdVal = sessionId.value; 368 | const password = passwordSession.value; 369 | const sessionType = sessionTypeSelector.value; // 'host' or 'join' 370 | sessionId.classList.remove("wrong"); 371 | passwordSession.classList.remove("wrong"); 372 | document.dispatchEvent(new CustomEvent("startCodeShareSession",{detail:{ 373 | action:"open", 374 | sessionIdVal:sessionIdVal, 375 | password:password, 376 | sessionType:sessionType 377 | }})) 378 | 379 | document.addEventListener("startCodeShareSession",function(e){ 380 | let param=e.detail; 381 | if(param.action=="wrongPassword") 382 | popup.querySelector("#sessionPassword").classList.add("wrong"); 383 | if(param.action=="started") 384 | popup.classList.remove("visible"); 385 | if(param.action=="wrong") 386 | popup.querySelector("#sessionId").classList.add("wrong") 387 | }); 388 | 389 | }); 390 | 391 | } 392 | 393 | function injectPeer(OEEexidString){ 394 | var s = document.createElement('script'); 395 | s.src = OEEexEscapeURL.createScriptURL("chrome-extension://"+OEEexidString+"/3rd_party/peerjs.min.js"); 396 | s.onload = function() { 397 | this.remove(); 398 | }; 399 | (document.head || document.documentElement).appendChild(s); 400 | } 401 | 402 | function injectDiff(OEEexidString){ 403 | var s = document.createElement('script'); 404 | s.src = OEEexEscapeURL.createScriptURL("chrome-extension://"+OEEexidString+"/3rd_party/diff.min.js"); 405 | s.onload = function() { 406 | this.remove(); 407 | }; 408 | (document.head || document.documentElement).appendChild(s); 409 | } 410 | 411 | 412 | export function initialize(){ 413 | injectPeer(chrome.runtime.id); 414 | injectDiff(chrome.runtime.id); 415 | createSharedCodeSessionPopup(); 416 | } 417 | 418 | -------------------------------------------------------------------------------- /modules/surveyMessage.js: -------------------------------------------------------------------------------- 1 | 2 | var OEEexidString=chrome.runtime.id 3 | 4 | function pythonMessage(){ 5 | var s = document.createElement('script'); 6 | s.src = 'chrome-extension://'+OEEexidString+'/3rd_party/lottie-player.js'; 7 | s.onload = function() { 8 | this.remove(); 9 | }; 10 | (document.head || document.documentElement).appendChild(s); 11 | //document.querySelector("button.goog-button.reset-button").click() 12 | let message=document.querySelector("ee-console").shadowRoot.querySelector(".intro-message"); 13 | if(message){ 14 | message.innerHTML=('You\'ve successfully activated Python for your code editor\ 15 | \ 16 |
To begin, we invite you to navigate to the feature page where you\'ll find multiple examples to review. It would also be beneficial for you to familiarize yourself with any known limitations.\ 17 |
Keep in mind, this feature is still in its experimental🧪 phase! Your understanding and participation in its development are much appreciated. If you stumble upon any bugs, please report them on the extension\'s Github issues page.') 18 | message.style.background='linear-gradient(to top right, hsl(244deg 59% 55% / 50%) 10%, hsl(274deg 91% 79% / 50%))'; 19 | message.style.borderRadius= '7px'; 20 | message.style.padding= '7px'; 21 | message.style.textAlign= 'justify'; 22 | } 23 | } 24 | 25 | function surveyMessage(){ 26 | let start=new Date('2024-01-23'); 27 | let end=new Date('2024-03-23'); 28 | let now=new Date() 29 | if(!((start< now) && (now< end)))return; 30 | var s = document.createElement('script'); 31 | s.src = 'chrome-extension://'+OEEexidString+'/3rd_party/lottie-player.js'; 32 | s.onload = function() { 33 | this.remove(); 34 | }; 35 | (document.head || document.documentElement).appendChild(s); 36 | //document.querySelector("button.goog-button.reset-button").click() 37 | let message=document.querySelector("ee-console").shadowRoot.querySelector(".intro-message"); 38 | if(message){ 39 | message.innerHTML=('Open Earth Engine Toolbox Annual Survey\ 40 | \ 41 |
The end of the year is close, and it\'s time for a small survey. Please let us know about your experience with the Open Earth Engine Library and extension. ') 42 | message.style.background='linear-gradient(to top right, hsl(244deg 59% 55% / 50%) 10%, hsl(274deg 91% 79% / 50%))'; 43 | message.style.borderRadius= '5px'; 44 | message.style.paddingLeft= '5px'; 45 | } 46 | } 47 | 48 | function v2Message(){ 49 | let start=new Date('2025-01-23'); 50 | let end=new Date('2025-03-23'); 51 | let now=new Date() 52 | if(!((start< now) && (now< end)))return; 53 | var s = document.createElement('script'); 54 | s.src = 'chrome-extension://'+OEEexidString+'/3rd_party/lottie-player.js'; 55 | s.onload = function() { 56 | this.remove(); 57 | }; 58 | (document.head || document.documentElement).appendChild(s); 59 | //document.querySelector("button.goog-button.reset-button").click() 60 | let message=document.querySelector("ee-console").shadowRoot.querySelector(".intro-message"); 61 | if(message){ 62 | message.innerHTML=('🚀 Open Earth Engine Toolbox V2 is Here! 🎉\ 63 |
\ 64 | \ 65 |
We’re thrilled to introduce Open Earth Engine Toolbox V2, packed with new improvements, an integrated AI feature 🤖, and a complete background redesign to boost performance and reduce crashes! ⚡🚀\ 66 |
If you come across any issues or bugs, please report them on our GitHub issues page 🛠️🔍.\ 67 | ') 68 | message.style.background='linear-gradient(to top right, hsl(244deg 59% 55% / 50%) 10%, hsl(274deg 91% 79% / 50%))'; 69 | message.style.borderRadius= '5px'; 70 | message.style.paddingLeft= '5px'; 71 | } 72 | } 73 | 74 | 75 | export function initialize(){ 76 | chrome.storage.local.get(["pythonCE"],function(r){ 77 | if(r["pythonCE"]) window.addEventListener("load",pythonMessage) 78 | }) 79 | window.addEventListener("load",surveyMessage); 80 | window.addEventListener("load",v2Message); 81 | } -------------------------------------------------------------------------------- /modules/terminal.js: -------------------------------------------------------------------------------- 1 | 2 | let OEEexEscapeURL = trustedTypes.createPolicy("OEEexEscapeURL", { 3 | createScriptURL: (string, sink) => string 4 | }); 5 | 6 | 7 | let OEEexEscape = trustedTypes.createPolicy("OEEexEscape", { 8 | createHTML: (string, sink) => string 9 | }); 10 | 11 | 12 | function sendCodeFromEditor2Terminal(){}; 13 | 14 | function loadConsoleTerminalWatcher(){ 15 | let MutationObserver = window.MutationObserver || window.WebKitMutationObserver; 16 | let myObserver = new MutationObserver(function(mutList){ 17 | 18 | [...mutList].map(function(mut){ 19 | [...mut.addedNodes].map(function(e){ 20 | if(e.classList.contains('OEEexTerminalAnalysis')) 21 | return; 22 | e.classList.add('OEEexTerminalAnalysis') 23 | analysisTerminalAddon(e) 24 | }); 25 | }); 26 | }); 27 | let obsConfig = { childList: true}; 28 | 29 | document.addEventListener('keydown',function(event) { 30 | if(/*(event.metaKey) &&*/ event.which == 116) { 31 | sendCodeFromEditor2Terminal(); 32 | } 33 | }); 34 | 35 | if(document.querySelector('ee-console')) 36 | myObserver.observe(document.querySelector('ee-console'), obsConfig); 37 | } 38 | 39 | function analysisTerminalAddon(val){ 40 | 41 | val.querySelectorAll('.ui-widget.ui-textbox').forEach(function(obj){ 42 | let input=obj.querySelector('input'); 43 | if(input.placeholder==">>"){ 44 | input.placeholder=""; 45 | obj.parentNode.classList.add("OEEexTerminal") 46 | let s=document.createElement("span"); 47 | s.textContent=">>"; 48 | obj.parentNode.insertBefore(s,obj); 49 | } 50 | 51 | // add F5 shortcut 52 | let editorElement=document.getElementsByClassName('ace_editor'); 53 | if(editorElement && editorElement.length>0){ 54 | editorElement[0].id='editor' 55 | let editor = ace.edit("editor"); 56 | } 57 | let current=""; 58 | let historyCmd=[""]; 59 | let postionHitory=0; 60 | 61 | input.addEventListener('change', (event) => { 62 | historyCmd[0]=event.target.value; 63 | if(historyCmd[0]!="") historyCmd.unshift(""); 64 | },true); 65 | 66 | sendCodeFromEditor2Terminal=function(){ 67 | input.value=editor.getSelectedText().replaceAll("\n","\\n"); 68 | input.dispatchEvent(new Event('change')); 69 | } 70 | 71 | input.addEventListener('keydown',function(event) { 72 | let skip=false; 73 | if(event.which == 13){ 74 | input.dispatchEvent(new Event('change')); 75 | } 76 | if( event.which == 38 && historyCmd.length>0) {//up 77 | postionHitory=(++postionHitory>=historyCmd.length?historyCmd.length-1:postionHitory); 78 | skip=true; 79 | } 80 | if( event.which == 40 && historyCmd.length>0) {// down 81 | postionHitory=(--postionHitory<0?0:postionHitory); 82 | skip=true; 83 | } 84 | if(skip){ 85 | input.value=historyCmd[postionHitory]; 86 | input.selectionEnd = historyCmd[postionHitory].length; 87 | input.selectionStart = historyCmd[postionHitory].length; 88 | }else{ 89 | historyCmd[0]=input.value; 90 | } 91 | }); 92 | }); 93 | } 94 | 95 | export function initializeMT(extensionId){ 96 | loadConsoleTerminalWatcher(); 97 | } 98 | 99 | -------------------------------------------------------------------------------- /modules_bg/actionButtonModuleManager.js: -------------------------------------------------------------------------------- 1 | export function initialize(){ 2 | chrome.action.onClicked.addListener(tab => { 3 | if(tab.url.startsWith('https://code.earthengine.google.com') || tab.url.startsWith('https://code.earthengine.google.co.in')){ 4 | chrome.tabs.create({ url: "https://www.open-geocomputing.org/OpenEarthEngineLibrary/" }); 5 | } 6 | else{ 7 | chrome.tabs.create({ url: "https://code.earthengine.google.com/" }); 8 | } 9 | }); 10 | } -------------------------------------------------------------------------------- /modules_bg/aiModuleManager.js: -------------------------------------------------------------------------------- 1 | function removeOrigin(requestDetails) { 2 | details.requestHeaders = details.requestHeaders.filter(header => 3 | header.name.toLowerCase() !== "origin" 4 | ); 5 | 6 | return { requestHeaders: details.requestHeaders }; 7 | } 8 | 9 | function firefoxAddWebRequest(url){ 10 | browser.webRequest.onBeforeSendHeaders.addListener( 11 | removeOrigin, 12 | { urls: [url+"/*"] }, 13 | ["blocking", "requestHeaders"] 14 | ); 15 | } 16 | 17 | function chromeAddNetRequest(url){ 18 | chrome.declarativeNetRequest.updateDynamicRules({ 19 | addRules: [ 20 | { 21 | "id": 2, // Rule ID 2 22 | "priority": 1, 23 | "action": { 24 | "type": "modifyHeaders", 25 | "requestHeaders": [ 26 | { 27 | "header": "Origin", 28 | "operation": "remove" 29 | } 30 | ] 31 | }, 32 | "condition": { 33 | "urlFilter": (url+"/*").replace(/([^:])\/\//, '$1/'), 34 | "resourceTypes": ["xmlhttprequest"] 35 | } 36 | } 37 | ], 38 | removeRuleIds: [2] 39 | }); 40 | } 41 | 42 | function firefoxRemoveWebRequest(){ 43 | browser.webRequest.onBeforeRequest.removeListener(removeOrigin); 44 | } 45 | 46 | function chromeRemoveNetRequest(){ 47 | chrome.declarativeNetRequest.updateDynamicRules( 48 | { 49 | removeRuleIds: [2] 50 | }) 51 | } 52 | 53 | function toggleCache(activate,url){ 54 | if(activate){ 55 | if(chrome.declarativeNetRequest) 56 | chromeAddNetRequest(url); 57 | else 58 | firefoxAddWebRequest(url); 59 | }else{ 60 | if(chrome.declarativeNetRequest) 61 | chromeRemoveNetRequest(); 62 | else 63 | firefoxRemoveWebRequest(); 64 | } 65 | 66 | } 67 | 68 | export function initialize(){ 69 | let status=false; 70 | let ollamaURL=""; 71 | chrome.storage.local.get(['aiCodeGeneration','ollamaURL'], function(dict){ 72 | status=dict["aiCodeGeneration"]; 73 | ollamaURL=dict["ollamaURL"]; 74 | toggleCache(status,ollamaURL); 75 | }); 76 | chrome.storage.onChanged.addListener(function (changes, namespace) { 77 | if (changes.aiCodeGeneration || changes.ollamaURL) { 78 | if(changes.aiCodeGeneration && changes.aiCodeGeneration.oldValue !== changes.aiCodeGeneration.newValue) 79 | status=changes.aiCodeGeneration.newValue 80 | if(changes.ollamaUR && changes.ollamaURL.oldValue !== changes.ollamaURL.newValue) 81 | ollamaURL=changes.ollamaURL.newValue 82 | toggleCache(status,ollamaURL); 83 | } 84 | }); 85 | // } 86 | 87 | // export function initialize(){ 88 | chrome.runtime.onMessageExternal.addListener((request, sender, sendResponse) => { 89 | if (request.action === "ollamaRequest" || request.action==="geminiRequest") { 90 | fetch(...request.arguments).then(async (res) => { 91 | const headers = Object.fromEntries(res.headers.entries()); 92 | const body = await res.text(); // Capture the body as text 93 | 94 | // Serialize response 95 | const serializedResponse = JSON.stringify({ 96 | status: res.status, 97 | statusText: res.statusText, 98 | headers, 99 | body, 100 | }) 101 | sendResponse(serializedResponse); 102 | }) 103 | } 104 | }); 105 | } -------------------------------------------------------------------------------- /modules_bg/darkModeManager.js: -------------------------------------------------------------------------------- 1 | 2 | function addPortListner(){ 3 | chrome.runtime.onConnect.addListener(portConnection); 4 | } 5 | 6 | function addListenerOnNewPort(port){ 7 | port.onMessage.addListener((request, sender, sendResponse) => { 8 | if(request.type=='getLightMode'){ 9 | chrome.storage.local.get(['lightMode'], function(mode) { 10 | if('lightMode' in mode) 11 | sender.postMessage({ type:'changeLightMode', message: mode['lightMode'] }); 12 | else 13 | sender.postMessage({ type:'changeLightMode', message: 'automatic' }); 14 | }); 15 | } 16 | 17 | if(request.type=='setLightMode'){ 18 | chrome.storage.local.set({lightMode: request.message}); 19 | for (var i = listPort.length - 1; i >= 0; i--) { 20 | listPort[i].postMessage({ type:'changeLightMode', message: request.message }); 21 | } 22 | } 23 | }); 24 | } 25 | 26 | function portConnection(port) { 27 | if(port.name === "oeel.extension.lightMode"){ 28 | listPort.push(port); 29 | addListenerOnNewPort(port); 30 | port.onDisconnect.addListener(function() { 31 | listPort= listPort.filter(function(el) { return el !== port}); 32 | }); 33 | } 34 | } 35 | 36 | let listPort=[]; 37 | 38 | export function initialize(){ 39 | addPortListner(); 40 | } -------------------------------------------------------------------------------- /modules_bg/initConfig.js: -------------------------------------------------------------------------------- 1 | const aiDefaultConfig= 2 | { 3 | "customPromptsEnabled": false, 4 | "language":"English", 5 | "gemini": 6 | { 7 | "apiKey": "", 8 | "customPrompt": 9 | { 10 | "alter_code": "", 11 | "explain_code": "", 12 | "fix_code": "", 13 | "generate_code": "", 14 | "high_level_explain_code": "" 15 | }, 16 | "host": "https://generativelanguage.googleapis.com/v1beta/", 17 | "modelVersion": "gemini-2.0-flash" 18 | }, 19 | "interface": "openai", 20 | "ollama": 21 | { 22 | "customPrompt": 23 | { 24 | "alter_code": "", 25 | "explain_code": "", 26 | "fix_code": "", 27 | "generate_code": "", 28 | "high_level_explain_code": "" 29 | }, 30 | "host": "http://localhost:11434", 31 | "modelVersion": "" 32 | }, 33 | "openai": 34 | { 35 | "apiKey": "", 36 | "customPrompt": 37 | { 38 | "alter_code": "", 39 | "explain_code": "", 40 | "fix_code": "", 41 | "generate_code": "", 42 | "high_level_explain_code": "" 43 | }, 44 | "host": "https://api.openai.com/v1/", 45 | "modelVersion": "gpt-4o" 46 | } 47 | } 48 | 49 | let defaultParam={ 50 | //isShareable:false, 51 | aiCodeGeneration:true, 52 | aiConfig:aiDefaultConfig, 53 | consoleError:true, 54 | copyAsJson:true, 55 | darkMode:true, 56 | docLink:true, 57 | editorSettings:true, 58 | ES_SC:(navigator.platform.toLowerCase().includes('mac')?{Execute: 'Command+Enter', 'Execute With Profiler': 'Alt+Command+Enter', Save: 'Command+S', Search: 'Alt+Command+F', Suggestion: 'Ctrl+Alt+Command+Space', alignCursors: 'Alt+Command+A'}:{}), 59 | ESfontFamily:"default", 60 | ESfontSize:13, 61 | EStabSize:2, 62 | insertFucntionSignature:true, 63 | lightMode: 'automatic', 64 | oeelCache:true, 65 | ollamaURL:"http://localhost:11434/", 66 | openInNewTab:true, 67 | plotly:true, 68 | pythonCE:true, 69 | runAll:true, 70 | sharedCodeSession:true, 71 | surveyMessage:true, 72 | terminal:true, 73 | uploadWithManifest:true 74 | } 75 | 76 | export function initialize(){ 77 | chrome.runtime.onInstalled.addListener(function(details) { 78 | chrome.storage.local.get(Object.keys(defaultParam),function(currentParam){ 79 | chrome.storage.local.set({ ...defaultParam, ...currentParam }); 80 | }); 81 | }); 82 | } -------------------------------------------------------------------------------- /modules_bg/oeelCache.js: -------------------------------------------------------------------------------- 1 | function redirect(requestDetails) { 2 | let newUrl="https://proxy-oeel-code.open-geocomputing.org/OpenEarthEngineLibrary/"+ 3 | requestDetails.url.match("^https://code.earthengine.google.(com|co\\.in)/repo/file/load\\?repo=users%2FOEEL%2Flib\\&path=(.*)")[2]; 4 | return { 5 | redirectUrl: newUrl 6 | }; 7 | } 8 | 9 | function firefoxAddWebRequest(future){ 10 | future.then(function(){ 11 | 12 | 13 | browser.webRequest.onBeforeRequest.addListener( 14 | redirect, 15 | {urls:[ 16 | "https://code.earthengine.google.com/repo/file/load?repo=users%2FOEEL%2Flib&path=*", 17 | "https://code.earthengine.google.co.in/repo/file/load?repo=users%2FOEEL%2Flib&path=*" 18 | ], types:["xmlhttprequest"]}, 19 | ["blocking"] 20 | ); 21 | }).catch(function(){ 22 | //nothing 23 | }) 24 | } 25 | 26 | function chromeAddNetRequest(future){ 27 | future.then(function(){ 28 | //sucess 29 | chrome.declarativeNetRequest.updateDynamicRules( 30 | {addRules:[{ 31 | "id": 1, 32 | "priority": 1, 33 | "action": { 34 | "type": "redirect", 35 | "redirect": { 36 | "regexSubstitution": "https://proxy-oeel-code.open-geocomputing.org/OpenEarthEngineLibrary/\\2" 37 | } 38 | }, 39 | "condition": { 40 | "regexFilter": "^https://code.earthengine.google.(com|co\\.in)/repo/file/load\\?repo=users%2FOEEL%2Flib\\&path=(.*)" 41 | }}], 42 | removeRuleIds: [1] 43 | }) 44 | }).catch(function(){ 45 | chrome.declarativeNetRequest.updateDynamicRules( 46 | { 47 | removeRuleIds: [1] 48 | }) 49 | }) 50 | } 51 | 52 | function firefoxRemoveWebRequest(){ 53 | browser.webRequest.onBeforeRequest.removeListener(redirect); 54 | } 55 | 56 | function chromeRemoveNetRequest(){ 57 | chrome.declarativeNetRequest.updateDynamicRules( 58 | { 59 | removeRuleIds: [1] 60 | }) 61 | } 62 | 63 | function checkIfCacheServerWork(){ 64 | return fetch('https://proxy-oeel-code.open-geocomputing.org/OpenEarthEngineLibrary/loadAll'); 65 | } 66 | 67 | function toggleCache(activate){ 68 | if(activate){ 69 | let future=checkIfCacheServerWork(); 70 | if(chrome.declarativeNetRequest) 71 | chromeAddNetRequest(future); 72 | else 73 | firefoxAddWebRequest(future); 74 | }else{ 75 | if(chrome.declarativeNetRequest) 76 | chromeRemoveNetRequest(); 77 | else 78 | firefoxRemoveWebRequest(); 79 | } 80 | 81 | } 82 | 83 | export function initialize(){ 84 | chrome.storage.local.get(['oeelCache'], function(dict){ 85 | toggleCache(dict["oeelCache"]) 86 | }); 87 | chrome.storage.onChanged.addListener(function (changes, namespace) { 88 | if (changes.oeelCache && changes.oeelCache.oldValue !== changes.oeelCache.newValue) { 89 | toggleCache(changes.oeelCache.newValue); 90 | } 91 | }); 92 | } -------------------------------------------------------------------------------- /option/ai-settings.css: -------------------------------------------------------------------------------- 1 | html { 2 | --ai-background-color: rgb(240, 240, 240); 3 | --ai-color: black; 4 | background-color: var(--ai-background-color); 5 | } 6 | 7 | html.dark { 8 | --ai-background-color: rgb(29, 31, 33); 9 | --ai-color: #C5C8C6; 10 | } 11 | 12 | body { 13 | 14 | color: var(--ai-color); 15 | font-family: Arial, sans-serif; 16 | padding: 20px; 17 | } 18 | 19 | .subtitle, .title, .label { 20 | color: var(--ai-color); 21 | } 22 | 23 | .container { 24 | max-width: 800px; 25 | margin: 0 auto; 26 | } 27 | 28 | h1, h2 { 29 | color: var(--ai-color); 30 | } 31 | 32 | .field { 33 | margin-bottom: 1rem; 34 | } 35 | 36 | .select select { 37 | width: 100%; 38 | } 39 | 40 | .input, .textarea { 41 | width: 100%; 42 | padding: 10px; 43 | border: 1px solid #ccc; 44 | border-radius: 5px; 45 | background-color: var(--ai-background-color); 46 | color: var(--ai-color); 47 | } 48 | 49 | .dark .input::placeholder, .dark .textarea::placeholder{ 50 | color: #5c5b5b ; 51 | } 52 | 53 | .textarea { 54 | height: 120px; 55 | resize: vertical; 56 | } 57 | 58 | .ai-settings { 59 | display: none; 60 | padding: 15px; 61 | border: 1px solid #ccc; 62 | border-radius: 5px; 63 | background-color: rgba(62, 142, 208, 0.1); 64 | margin-bottom: 20px; 65 | } 66 | 67 | .openai-settings, .gemini-settings, .ollama-settings { 68 | display: none; 69 | } 70 | 71 | .button.is-primary { 72 | background-color: #3e8ed0; 73 | color: white; 74 | border: none; 75 | padding: 10px 15px; 76 | border-radius: 5px; 77 | cursor: pointer; 78 | } 79 | 80 | .button.is-primary:hover { 81 | background-color: #3273a8; 82 | } 83 | 84 | .custom-prompt-section { 85 | display: none; 86 | } 87 | 88 | .custom-prompt-enabled .custom-prompt-section { 89 | display: block; 90 | } 91 | -------------------------------------------------------------------------------- /option/ai-settings.js: -------------------------------------------------------------------------------- 1 | 2 | function lightSetup(){ 3 | var lightIsAutomatic=true; 4 | 5 | var portWithBackground=null; 6 | function setPortWithBackground(){ 7 | portWithBackground= chrome.runtime.connect({name: "oeel.extension.lightMode"}); 8 | portWithBackground.onDisconnect.addListener(function(port){ 9 | portWithBackground=null; 10 | setPortWithBackground(); 11 | }) 12 | 13 | portWithBackground.onMessage.addListener((request, 14 | sender, 15 | sendResponse) => { 16 | if(request.type=='changeLightMode'){ 17 | if(request.message=='automatic'){ 18 | switch2DarkMode((window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches), 19 | true) 20 | }else{ 21 | switch2DarkMode(request.message, 22 | false); 23 | } 24 | }; 25 | }) 26 | } 27 | 28 | setPortWithBackground(); 29 | 30 | 31 | function switch2DarkMode(toDark, 32 | isAuto=false){ 33 | lightIsAutomatic=isAuto; 34 | document.getElementsByTagName('html')[0].classList.toggle('dark',toDark) 35 | }; 36 | 37 | window.addEventListener("load", 38 | function(){ 39 | portWithBackground.postMessage({type:"getLightMode"}); 40 | }); 41 | 42 | 43 | window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', 44 | e => { 45 | if(lightIsAutomatic) 46 | switch2DarkMode(e.matches, 47 | true); 48 | }); 49 | } 50 | lightSetup(); 51 | 52 | 53 | function aiParamSetup(){ 54 | 55 | let isLoading=false; 56 | const aiInterface = document.getElementById("aiInterface"); 57 | const settingsSections = document.querySelectorAll(".ai-settings"); 58 | const customPromptToggle = document.getElementById("customPromptToggle"); 59 | const customPromptSections = document.querySelectorAll(".custom-prompt-section"); 60 | const languageSelector = document.getElementById("languageSelector"); 61 | 62 | function updateSettingsDisplay() { 63 | settingsSections.forEach(section => section.style.display = "none"); 64 | document.querySelector(`.${aiInterface.value}-settings`).style.display = "block"; 65 | saveAIConfig(); // Save when interface changes 66 | } 67 | 68 | function toggleCustomPrompts() { 69 | const isChecked = customPromptToggle.checked; 70 | customPromptSections.forEach(section => { 71 | section.style.display = isChecked ? "block" : "none"; 72 | }); 73 | saveAIConfig(); // Save when toggling 74 | } 75 | 76 | aiInterface.addEventListener("change", updateSettingsDisplay); 77 | customPromptToggle.addEventListener("change", toggleCustomPrompts); 78 | 79 | function collectSettings(interfaceName) { 80 | const settings = { customPrompt: {} }; 81 | document.querySelectorAll(`.${interfaceName}-settings .input, .${interfaceName}-settings .textarea`).forEach(input => { 82 | if (input.classList.contains("custom-prompt")) { 83 | settings.customPrompt[input.dataset.type] = input.value || ""; 84 | } else { 85 | settings[input.dataset.type] = input.value || ""; 86 | } 87 | }); 88 | return settings; 89 | } 90 | 91 | function saveAIConfig() { 92 | if (isLoading) return; 93 | const aiConfig = { 94 | interface: aiInterface.value, 95 | customPromptsEnabled: customPromptToggle.checked, 96 | language: languageSelector.value, 97 | openai: collectSettings("openai"), 98 | gemini: collectSettings("gemini"), 99 | ollama: collectSettings("ollama") 100 | }; 101 | 102 | chrome.storage.local.set({ aiConfig }, () => { 103 | console.log("AI configuration saved:", aiConfig); 104 | }); 105 | } 106 | 107 | function loadAIConfig() { 108 | chrome.storage.local.get("aiConfig", (data) => { 109 | if (data.aiConfig) { 110 | isLoading=true; 111 | aiInterface.value = data.aiConfig.interface || "openai"; 112 | customPromptToggle.checked = data.aiConfig.customPromptsEnabled || false; 113 | languageSelector.value = data.aiConfig.language || "English"; 114 | toggleCustomPrompts(); 115 | ["openai", "gemini", "ollama"].forEach(interfaceName => { 116 | if (data.aiConfig[interfaceName]) { 117 | document.querySelectorAll(`.${interfaceName}-settings .input, .${interfaceName}-settings .textarea`).forEach(input => { 118 | if (input.classList.contains("custom-prompt")) { 119 | input.value = data.aiConfig[interfaceName].customPrompt[input.dataset.type] || ""; 120 | } else { 121 | input.value = data.aiConfig[interfaceName][input.dataset.type] || ""; 122 | } 123 | }); 124 | } 125 | }); 126 | updateSettingsDisplay(); 127 | } 128 | isLoading=false; 129 | }); 130 | } 131 | 132 | loadAIConfig(); 133 | 134 | document.querySelector(".button.is-primary").addEventListener("click", saveAIConfig); 135 | document.querySelectorAll(".input, .textarea").forEach(input => { 136 | input.addEventListener("blur", saveAIConfig); // Auto-save on field blur 137 | }); 138 | languageSelector.addEventListener("change", saveAIConfig); 139 | 140 | 141 | } 142 | 143 | 144 | document.addEventListener("DOMContentLoaded", function () { 145 | aiParamSetup(); 146 | }); 147 | -------------------------------------------------------------------------------- /option/ai.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | AI Assistance Settings 7 | 8 | 9 | 10 | 11 | 12 |
13 |

AI Assistance Settings

14 | 15 |
16 | 17 |
18 |
19 | 24 |
25 |
26 |
27 | 28 |
29 | 32 |
33 | 34 |
35 | 36 |
37 |
38 | 69 |
70 |
71 |
72 | 73 |
74 |

OpenAI Settings

75 | 76 | 77 | 78 | 79 | 80 | 81 |
82 |
83 |

Custom Prompts

84 |

Custom prompts can use the following variables to retrieve elements from the interface: ${prompt}, ${code}, ${selectedCode}, and ${errors}.

85 |
86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 |
97 |
98 | 99 | 124 | 125 | 148 | 149 | 150 |
151 | 152 | -------------------------------------------------------------------------------- /option/options.css: -------------------------------------------------------------------------------- 1 | html{ 2 | --oeel-background-color: rgb(240,240,240); 3 | --oeel-color: black; 4 | } 5 | 6 | html.dark{ 7 | --oeel-background-color: rgb(29, 31, 33); 8 | --oeel-color: #C5C8C6; 9 | } 10 | 11 | header{ 12 | width: auto; 13 | height: 300px; 14 | margin-bottom: 10px; 15 | padding-bottom: 10px; 16 | background: linear-gradient(to top right, #524ad0 10%, #D099FA); 17 | } 18 | 19 | #logo{ 20 | margin: 0 auto; 21 | } 22 | 23 | .param > div{ 24 | display: none; 25 | } 26 | 27 | .param > div.available{ 28 | display: block; 29 | } 30 | 31 | .dark{ 32 | background-color:var(--oeel-background-color) ; 33 | color: var(--oeel-color); 34 | } 35 | 36 | .dark body, .dark h2{ 37 | color: var(--oeel-color); 38 | } 39 | 40 | main div.block{ 41 | max-width: 700px; 42 | margin: 0 auto; 43 | } 44 | 45 | div.lock{ 46 | margin: -1em 0; 47 | } 48 | 49 | .message-body{ 50 | position: relative; 51 | } 52 | 53 | .lightMode, .dynamicPlotlyMode{ 54 | border: solid 2px #3e8ed0; 55 | border-radius: calc(1em + 3px); 56 | right: 1em; 57 | position: absolute; 58 | top: 0.75em; 59 | padding: 6px 5px 0px 5px; 60 | margin: 0; 61 | background-color: #3e8ed033; 62 | } 63 | 64 | .lightMode span, .dynamicPlotlyMode span{ 65 | color: gray; 66 | } 67 | .lightMode span.active,.dynamicPlotlyMode span.active{ 68 | text-shadow: #3e8ed0 0px 0px 7px; 69 | color: #3e8ed0; 70 | } 71 | 72 | .off .message-body{ 73 | /*pointer-events: none;*/ 74 | filter: opacity(0.8) grayscale(0.6); 75 | } 76 | 77 | .off .message-body span.lightMode, .off .message-body div.subsection.box{ 78 | /*pointer-events: none;*/ 79 | filter: blur(1.5px); 80 | pointer-events:none 81 | } 82 | 83 | .off .message-header{ 84 | filter: opacity(0.5) 85 | } 86 | 87 | .message-header .icon{ 88 | margin: -2px auto -2px 10px; 89 | font-size: 35px; 90 | } 91 | 92 | .message-header .icon{ 93 | display: none; 94 | } 95 | 96 | .somethingMissing:not(.off) .message-header .icon{ 97 | display: flex; 98 | } 99 | 100 | 101 | 102 | .hr-text { 103 | position: relative; 104 | text-align: center; 105 | height: 1.5em; 106 | background-color: transparent; 107 | } 108 | 109 | .hr-text:before { 110 | content: ''; 111 | background: linear-gradient(to right, transparent, #818078, transparent); 112 | position: absolute; 113 | left: 0; 114 | top: 50%; 115 | width: 100%; 116 | height: 1px; 117 | } 118 | .hr-text:after { 119 | color: black; 120 | content: attr(data-content); 121 | position: relative; 122 | padding: 0 0.5em; 123 | line-height: 1.5em; 124 | background-color: white; 125 | } 126 | 127 | .hr-big{ 128 | background: linear-gradient(90deg, #0000 0%, #818078 15%, #111111 50%, #818078 85%, #0000 100%); 129 | left: 0; 130 | top: 50%; 131 | width: 100%; 132 | height: 2px; 133 | } 134 | 135 | .radio-list ol,.radio-list input{ 136 | display: inline-grid; 137 | vertical-align: middle; 138 | } 139 | 140 | input[type=range] { 141 | -webkit-appearance: none; 142 | margin: 20px 0; 143 | width: 100%; 144 | } 145 | @-moz-document url-prefix() { 146 | input[type=range] { 147 | margin: 10px 0; 148 | } 149 | } 150 | input[type=range]:focus { 151 | outline: none; 152 | } 153 | input[type=range]::-webkit-slider-runnable-track{ 154 | width: 100%; 155 | height: 4px; 156 | cursor: pointer; 157 | animate: 0.2s; 158 | background: #3e8ed0; 159 | border-radius: 25px; 160 | } 161 | input[type=range]::-webkit-slider-thumb{ 162 | height: 20px; 163 | width: 20px; 164 | border-radius: 50%; 165 | background: #fff; 166 | box-shadow: 0 0 4px 0 rgba(0,0,0, 1); 167 | cursor: pointer; 168 | -webkit-appearance: none; 169 | margin-top: -8px; 170 | } 171 | input[type=range]:focus::-webkit-slider-runnable-track{ 172 | background: #3e8ed0; 173 | } 174 | 175 | input[type="range"]::-moz-range-track { 176 | width: 100%; 177 | height: 4px; 178 | cursor: pointer; 179 | animate: 0.2s; 180 | background: #3e8ed0; 181 | border-radius: 25px; 182 | } 183 | input[type=range]::-moz-slider-thumb { 184 | height: 20px; 185 | width: 20px; 186 | border-radius: 50%; 187 | background: #fff; 188 | box-shadow: 0 0 4px 0 rgba(0,0,0, 1); 189 | cursor: pointer; 190 | -webkit-appearance: none; 191 | margin-top: -8px; 192 | } 193 | input[type=range]:focus::-moz-slider-runnable-track { 194 | background: #3e8ed0; 195 | } 196 | 197 | .range-wrap{ 198 | width: 100%; 199 | position: relative; 200 | } 201 | .range-value{ 202 | position: absolute; 203 | top: -50%; 204 | } 205 | .range-value span{ 206 | width: 30px; 207 | height: 24px; 208 | line-height: 24px; 209 | text-align: center; 210 | background: #3e8ed0; 211 | color: #fff; 212 | font-size: 12px; 213 | display: block; 214 | position: absolute; 215 | left: 50%; 216 | transform: translate(-50%, 0); 217 | border-radius: 6px; 218 | } 219 | .range-value span:before{ 220 | content: ""; 221 | position: absolute; 222 | width: 0; 223 | height: 0; 224 | border-top: 10px solid #3e8ed0; 225 | border-left: 5px solid transparent; 226 | border-right: 5px solid transparent; 227 | top: 100%; 228 | left: 50%; 229 | margin-left: -5px; 230 | margin-top: -1px; 231 | } 232 | 233 | .control .wrong, .control .wrong + span.icon{ 234 | color: red 235 | } 236 | 237 | #planetApiOptions .planetApi{ 238 | display: none; 239 | } 240 | 241 | #planetApiOptions.v1 .planetApi.v1,#planetApiOptions.v2 .planetApi.v2{ 242 | display: block; 243 | } 244 | 245 | img.PlanetThumbnail { 246 | max-height: 150px; 247 | } 248 | 249 | #orderBlock .table td{ 250 | vertical-align: middle; 251 | font-size: 1.3em; 252 | } 253 | 254 | #orderBlock .table{ 255 | background-color: transparent; 256 | } 257 | 258 | #orderBlock article .message-body 259 | { 260 | max-height: 0px; 261 | padding: 0; 262 | overflow: hidden; 263 | transition: all 1s cubic-bezier(0, 0, 0, 1);; 264 | } 265 | 266 | #orderBlock article.expended .message-body 267 | { 268 | max-height: 100000px; 269 | transition: all 1s cubic-bezier(1, 0, 1, 1); 270 | } 271 | 272 | #orderBlock article .expandButton 273 | { 274 | transition: all 1s cubic-bezier(0.52, 0, 0.89, 1.33); 275 | } 276 | 277 | #orderBlock article.expended .expandButton 278 | { 279 | transform: rotate(180deg); 280 | transition: all 1s cubic-bezier(0.52, 0, 0.89, 1.33); 281 | } 282 | 283 | #orderBlock article table.planetImages tr 284 | { 285 | height: 180px; 286 | } 287 | 288 | .editorSettings input, .editorSettings select{ 289 | position: absolute; 290 | right: 3em; 291 | } 292 | 293 | .editorSettings select{ 294 | margin-top: 4px; 295 | } 296 | 297 | .editorSettings .EC_SC_Setting{ 298 | width:250px; 299 | text-align: right; 300 | } 301 | 302 | .experimental{ 303 | font-size: 20px; 304 | filter: drop-shadow(0px 0px 1px white); 305 | margin: -12px 0px -10px 0px; 306 | display: inline-block; 307 | } 308 | 309 | .headerLogo{ 310 | width: 20px; 311 | filter: drop-shadow(0px 0px 3px white); 312 | margin: 0px 8px -4px 0px 313 | } 314 | -------------------------------------------------------------------------------- /option/options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | OEEex options 8 | 9 | 10 | 11 | 13 | 20 | 21 | 22 |
23 | 24 |
25 |
26 |
27 |
28 |
29 |
30 | Night mode 31 |
32 | 33 |
34 |
35 |
36 | Select the light mode 37 | 38 | 39 | brightness_4 40 | 41 | 42 | linear_scale 43 | 44 | 45 | brightness_6 46 | 47 | 48 | linear_scale 49 | 50 | 51 | brightness_7 52 | 53 | 54 |
55 |
56 |
57 |
58 |
59 |
60 | Editor settings 61 |
62 | 63 |
64 |
65 |
66 | Tab size 67 | 68 |
69 | Font size 70 | 71 |
72 | Font family 73 | 76 |
77 |

Shortcut

78 |
79 | Execute 80 |
81 | Execute With Profiler 82 |
83 | Save 84 |
85 | Search 86 |
87 | Suggestion 88 |
89 | alignCursors 90 |
91 |
92 | 95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 | Open scripts in new tab 103 |
104 | 105 |
106 |
107 |
108 | Allow to open a script in a new tab by double clicking on it 109 |
110 |
111 |
112 | 113 |
114 |
115 |
116 | Copy JSON 117 |
118 | 119 |
120 |
121 |
122 | Allow to copy JSON data by double clicking on "json" in the console 123 |
124 |
125 |
126 | 127 |
128 |
129 |
130 | OEEL Cache 131 |
132 | 133 |
134 |
135 |
136 | This add cache for faster OEEL loading 137 |
138 |
139 |
140 |
141 |
142 |
143 | Plotly 144 |
145 | 146 |
147 |
148 |
149 | This enable plotly for nice and intercative plots in the console, panels and maps. Work on EE App too. 150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 | 158 | Python (Pyodide) 🧪 159 |
160 |
161 | 162 |
163 |
164 |
165 | This enables Python support through Pyodide. Use the OEEL function to load a Python file. The environment is preimported with ee, oeel, and Map. Each new run will have its own context; therefore, changes made in a previous run will normally have no effect. Some standard packages, such as numpy and matplotlib are preinstalled. 166 |
167 |
168 |
169 |
170 |
171 |
172 | Insert function in code editor 173 |
174 | 175 |
176 |
177 |
178 | This allow to automatically insert a function signature from documentation to the code editor. 179 |
180 |
181 |
182 | 195 |
196 |
197 |
198 | Error in console header 199 |
200 | 201 |
202 |
203 |
204 | Adjust the color of the console header in red to display errors. This is incredibly useful when using the inspector tool, as it provides immediate notification of errors within your script. 205 |
206 |
207 |
208 | 222 |
223 |
224 |
225 | Upload images with manifest 226 |
227 | 228 |
229 |
230 |
231 | This add support for image upload using manifests 232 |
233 |
Parallel upload
234 |
235 |
236 | 237 |
238 |
Parallel downlaod (for transfer)
239 |
240 |
241 | 242 |
243 |
244 |
245 |
246 |
247 | 248 |
249 |
250 |
251 |
252 | AI Assistance 🧪 253 |
254 |
255 | 256 |
257 |
258 |
259 | AI assistance settings have been moved. Click here to configure OpenAI, Gemini, and Ollama settings. 260 |
261 |
262 |
263 |

This extension was originally developed by Mathieu Gravey as part of the Open Geocumputing to enhance the usage of the Open Earth Engine Library. Animations on this page were done by Pauline Ahumada.

264 | 265 |
266 |
267 | 268 | 269 | -------------------------------------------------------------------------------- /option/options.js: -------------------------------------------------------------------------------- 1 | listOfScript=[ 2 | 'aiCodeGeneration', 3 | 'copyAsJson', 4 | 'docLink', 5 | 'plotly', 6 | 'consoleError', 7 | 'editorSettings', 8 | 'darkMode', 9 | 'insertFucntionSignature', 10 | 'oeelCache', 11 | 'openInNewTab', 12 | 'pythonCE', 13 | 'uploadWithManifest', 14 | ]; 15 | 16 | var lightIsAutomatic=true; 17 | 18 | var portWithBackground=null; 19 | function setPortWithBackground(){ 20 | portWithBackground= chrome.runtime.connect({name: "oeel.extension.lightMode"}); 21 | portWithBackground.onDisconnect.addListener(function(port){ 22 | portWithBackground=null; 23 | setPortWithBackground(); 24 | }) 25 | 26 | portWithBackground.onMessage.addListener((request, 27 | sender, 28 | sendResponse) => { 29 | if(request.type=='changeLightMode'){ 30 | if(request.message=='automatic'){ 31 | if((typeof buttonLight!= 'undefined') && buttonLight) 32 | buttonLight.innerHTML='brightness_medium'; 33 | switch2DarkMode((window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches), 34 | true) 35 | }else{ 36 | switch2DarkMode(request.message, 37 | false); 38 | } 39 | }; 40 | }) 41 | } 42 | 43 | setPortWithBackground(); 44 | 45 | 46 | function runOnceLoaded(){ 47 | // const logoPlayer = document.querySelector("#logo"); 48 | // //logoPlayer.setDirection(1); 49 | // logoPlayer.play(); 50 | 51 | for (var i = listOfScript.length - 1; i >= 0; i--) { 52 | document.querySelector('.'+listOfScript[i])?.classList.add('available'); 53 | } 54 | } 55 | 56 | function updateStatus(){ 57 | chrome.storage.local.get(listOfScript, 58 | function(result){ 59 | for(let key in result) 60 | setSatus(key, 61 | result[key], 62 | true) 63 | }); 64 | } 65 | 66 | function setModuleStatus(){ 67 | document.querySelectorAll('.lock').forEach((e)=> 68 | e.addEventListener('click', 69 | function(){ 70 | let superParentNode=e.parentNode.parentNode.parentNode; 71 | let className=([...superParentNode.classList].filter(value => listOfScript.includes(value)))[0]; 72 | let newKey={}; 73 | newKey[className]=superParentNode.classList.contains('off'); 74 | chrome.storage.local.set(newKey); 75 | //updateStatus(); 76 | setSatus(className, 77 | newKey[className], 78 | false) 79 | } 80 | )); 81 | } 82 | 83 | function setSatus(key, 84 | value, 85 | isInit){ 86 | if(!(listOfScript.includes(key))) 87 | return; 88 | let element=document.querySelector('.'+key); 89 | if(!element)return; 90 | let player=element.querySelector('.lock lottie-player'); 91 | player.autoplay=false; 92 | 93 | if(value){ 94 | element.classList.remove('off'); 95 | player.setDirection(1); 96 | player.play(); 97 | }else{ 98 | element.classList.add('off'); 99 | if (!isInit) { 100 | player.setDirection(-1); 101 | player.play(); 102 | } 103 | } 104 | } 105 | 106 | chrome.storage.onChanged.addListener(function(dic){ 107 | for( let idx in listOfScript) 108 | { 109 | let key=listOfScript[idx]; 110 | if(key in dic){ 111 | setSatus(key, 112 | dic[key]['newValue']); 113 | } 114 | } 115 | }); 116 | 117 | 118 | 119 | function setLight(){ 120 | var lightModeElement=document.querySelector('.lightMode'); 121 | lightModeElement.querySelector('span.isAutomatic').addEventListener('click', 122 | function(){ 123 | sendNewlightMode('automatic'); 124 | }); 125 | lightModeElement.querySelector('span.isDark').addEventListener('click', 126 | function(){ 127 | sendNewlightMode(true); 128 | }); 129 | lightModeElement.querySelector('span.isLight').addEventListener('click', 130 | function(){ 131 | sendNewlightMode(false); 132 | }); 133 | } 134 | 135 | function switch2DarkMode(toDark, 136 | isAuto=false){ 137 | lightIsAutomatic=isAuto; 138 | document.getElementsByTagName('html')[0].classList.toggle('dark',toDark) 139 | 140 | var lightModeElement=document.querySelector('.lightMode'); 141 | lightModeElement.querySelectorAll('span').forEach((e)=>e.classList.remove('active')) 142 | 143 | if(lightIsAutomatic){ 144 | lightModeElement.querySelector('span.isAutomatic').classList.add('active'); 145 | }else{ 146 | if(toDark) 147 | lightModeElement.querySelector('span.isDark').classList.add('active'); 148 | else 149 | lightModeElement.querySelector('span.isLight').classList.add('active'); 150 | } 151 | }; 152 | 153 | window.addEventListener("load", 154 | function(){ 155 | portWithBackground.postMessage({type:"getLightMode"}); 156 | }); 157 | 158 | function sendNewlightMode(mode){ 159 | portWithBackground.postMessage({type:"setLightMode", 160 | message:mode}); 161 | } 162 | 163 | window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', 164 | e => { 165 | if(lightIsAutomatic) 166 | switch2DarkMode(e.matches, 167 | true); 168 | }); 169 | 170 | // manifestUpload 171 | 172 | 173 | function setManifestUploadParam(dic){ 174 | if('parallelUpload' in dic){ 175 | let value=dic['parallelUpload']; 176 | if(typeof value === 'object' && 'newValue' in value)value=value['newValue']; 177 | document.getElementById('parallelUpload').value=value; 178 | document.getElementById('parallelUpload').dispatchEvent(new Event('input')); 179 | } 180 | if('parallelDownload' in dic){ 181 | let value=dic['parallelDownload']; 182 | if(typeof value === 'object' && 'newValue' in value) value=value['newValue']; 183 | document.getElementById('parallelDownload').value=value; 184 | document.getElementById('parallelDownload').dispatchEvent(new Event('input')); 185 | } 186 | } 187 | 188 | chrome.storage.onChanged.addListener(setManifestUploadParam); 189 | 190 | function saveNewManifestParam(event=false){ 191 | chrome.storage.local.set({parallelUpload:parseInt(document.getElementById('parallelUpload').value), 192 | 193 | parallelDownload:parseInt(document.getElementById('parallelDownload').value)}); 194 | } 195 | 196 | document.getElementById('parallelUpload').addEventListener('change', 197 | saveNewManifestParam); 198 | document.getElementById('parallelDownload').addEventListener('change', 199 | saveNewManifestParam); 200 | 201 | chrome.storage.local.get(['parallelUpload', 202 | 'parallelDownload'], 203 | setManifestUploadParam); 204 | 205 | /** 206 | * adapted from Lalit Patel 207 | * Website: http://www.lalit.org/lab/javascript-css-font-detect/ 208 | */ 209 | 210 | function listMonospaceFonts() { 211 | 212 | let array=['Bitstream Vera Sans Mono','Courier','Consolas','DejaVu Sans Mono','Lucida Console','Menlo','Monaco','PT Mono','Roboto Mono','source-code-pro','Ubuntu Mono'] 213 | 214 | // a font will be compared against all the three default fonts. 215 | // and if it doesn't match all 3 then that font is not available. 216 | var baseFonts = ['monospace', 'sans-serif', 'serif']; 217 | 218 | //we use m or w because these two characters take up the maximum width. 219 | // And we use a LLi so that the same matching fonts can get separated 220 | var testString = "mmmmmmmmmmlli"; 221 | 222 | //we test using 72px font size, we may use any size. I guess larger the better. 223 | var testSize = '72px'; 224 | 225 | var h = document.getElementsByTagName("body")[0]; 226 | 227 | // create a SPAN in the document to get the width of the text we use to test 228 | var s = document.createElement("span"); 229 | s.style.fontSize = testSize; 230 | s.innerHTML = testString; 231 | var defaultWidth = {}; 232 | var defaultHeight = {}; 233 | for (var index in baseFonts) { 234 | //get the default width for the three base fonts 235 | s.style.fontFamily = baseFonts[index]; 236 | h.appendChild(s); 237 | defaultWidth[baseFonts[index]] = s.offsetWidth; //width for the default font 238 | defaultHeight[baseFonts[index]] = s.offsetHeight; //height for the defualt font 239 | h.removeChild(s); 240 | } 241 | 242 | function detect(font) { 243 | var detected = false; 244 | for (var index in baseFonts) { 245 | s.style.fontFamily = font + ',' + baseFonts[index]; // name of the font along with the base font for fallback. 246 | h.appendChild(s); 247 | var matched = (s.offsetWidth != defaultWidth[baseFonts[index]] || s.offsetHeight != defaultHeight[baseFonts[index]]); 248 | h.removeChild(s); 249 | detected = detected || matched; 250 | } 251 | return detected; 252 | } 253 | 254 | return array.filter(f=> detect(f)) 255 | }; 256 | 257 | function initES(){ 258 | 259 | (function(){ 260 | let selector=document.querySelector('#fontFamily'); 261 | let listFont=listMonospaceFonts().map(fontName=>selector.appendChild(new Option(fontName, fontName))) 262 | })() 263 | 264 | chrome.storage.local.get(['ESfontSize','ESfontFamily','EStabSize','ES_SC'], 265 | function(data){ 266 | if (data.ESfontSize) {document.querySelector('#fontSize').setAttribute('value',data.ESfontSize);} 267 | if (data.ESfontFamily) {document.querySelector('#fontFamily').value=data.ESfontFamily;} 268 | if (data.EStabSize) {document.querySelector('#tabSize').value=data.EStabSize;} 269 | if (data.ES_SC) { 270 | for (const [key, value] of Object.entries(data.ES_SC)) { 271 | console.log(key, value); 272 | let obj=document.querySelector('.EC_SC_Setting[name="'+key+'"]'); 273 | if(obj) obj.value=value; 274 | } 275 | } 276 | }); 277 | 278 | document.querySelector('#fontSize').addEventListener('change',function(event){chrome.storage.local.set({ESfontSize:event.target.value});}); 279 | document.querySelector('#fontFamily').addEventListener('change',function(event){chrome.storage.local.set({ESfontFamily:event.target.value})}); 280 | document.querySelector('#tabSize').addEventListener('change',function(event){chrome.storage.local.set({EStabSize:event.target.value})}); 281 | document.querySelectorAll('.EC_SC_Setting').forEach(function(el){el.addEventListener('change',function(event){ 282 | let newSc={} 283 | document.querySelectorAll('.EC_SC_Setting').forEach(function(e){ 284 | if(e.value) 285 | newSc[e.name]=e.value; 286 | }) 287 | chrome.storage.local.set({ES_SC:newSc}) 288 | }); 289 | }); 290 | 291 | } 292 | 293 | 294 | //init 295 | initES(); 296 | runOnceLoaded(); 297 | setLight(); 298 | setModuleStatus(); 299 | updateStatus(); 300 | 301 | 302 | function makeRangeDisplay(idObject){ 303 | let rangeV = document.getElementById(idObject); 304 | let range=rangeV.parentNode.querySelector('input'); 305 | 306 | let setValue = ()=>{ 307 | newValue = Number( (range.value - range.min) * 100 / (range.max - range.min) ), 308 | 309 | newPosition = 10 - (newValue * 0.2); 310 | rangeV.innerHTML = `${range.value}`; 311 | rangeV.style.left = `calc(${newValue}% + (${newPosition}px))`; 312 | }; 313 | setValue() 314 | range.addEventListener('input', 315 | setValue); 316 | } 317 | 318 | 319 | makeRangeDisplay('rangeParallelUpload'); 320 | makeRangeDisplay('rangeParallelDownload'); 321 | 322 | -------------------------------------------------------------------------------- /otherAssets/darkPlotly.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "barpolar": [ 4 | { 5 | "marker": { 6 | "line": { 7 | "color": "#1d1f21", 8 | "width": 0.5 9 | }, 10 | "pattern": { 11 | "fillmode": "overlay", 12 | "size": 10, 13 | "solidity": 0.2 14 | } 15 | }, 16 | "type": "barpolar" 17 | } 18 | ], 19 | "bar": [ 20 | { 21 | "error_x": { 22 | "color": "#f2f5fa" 23 | }, 24 | "error_y": { 25 | "color": "#f2f5fa" 26 | }, 27 | "marker": { 28 | "line": { 29 | "color": "#1d1f21", 30 | "width": 0.5 31 | }, 32 | "pattern": { 33 | "fillmode": "overlay", 34 | "size": 10, 35 | "solidity": 0.2 36 | } 37 | }, 38 | "type": "bar" 39 | } 40 | ], 41 | "carpet": [ 42 | { 43 | "aaxis": { 44 | "endlinecolor": "#A2B1C6", 45 | "gridcolor": "#506784", 46 | "linecolor": "#506784", 47 | "minorgridcolor": "#506784", 48 | "startlinecolor": "#A2B1C6" 49 | }, 50 | "baxis": { 51 | "endlinecolor": "#A2B1C6", 52 | "gridcolor": "#506784", 53 | "linecolor": "#506784", 54 | "minorgridcolor": "#506784", 55 | "startlinecolor": "#A2B1C6" 56 | }, 57 | "type": "carpet" 58 | } 59 | ], 60 | "choropleth": [ 61 | { 62 | "colorbar": { 63 | "outlinewidth": 0, 64 | "ticks": "" 65 | }, 66 | "type": "choropleth" 67 | } 68 | ], 69 | "contourcarpet": [ 70 | { 71 | "colorbar": { 72 | "outlinewidth": 0, 73 | "ticks": "" 74 | }, 75 | "type": "contourcarpet" 76 | } 77 | ], 78 | "contour": [ 79 | { 80 | "colorbar": { 81 | "outlinewidth": 0, 82 | "ticks": "" 83 | }, 84 | "colorscale": [ 85 | [ 86 | 0.0, 87 | "#0d0887" 88 | ], 89 | [ 90 | 0.1111111111111111, 91 | "#46039f" 92 | ], 93 | [ 94 | 0.2222222222222222, 95 | "#7201a8" 96 | ], 97 | [ 98 | 0.3333333333333333, 99 | "#9c179e" 100 | ], 101 | [ 102 | 0.4444444444444444, 103 | "#bd3786" 104 | ], 105 | [ 106 | 0.5555555555555556, 107 | "#d8576b" 108 | ], 109 | [ 110 | 0.6666666666666666, 111 | "#ed7953" 112 | ], 113 | [ 114 | 0.7777777777777778, 115 | "#fb9f3a" 116 | ], 117 | [ 118 | 0.8888888888888888, 119 | "#fdca26" 120 | ], 121 | [ 122 | 1.0, 123 | "#f0f921" 124 | ] 125 | ], 126 | "type": "contour" 127 | } 128 | ], 129 | "heatmapgl": [ 130 | { 131 | "colorbar": { 132 | "outlinewidth": 0, 133 | "ticks": "" 134 | }, 135 | "colorscale": [ 136 | [ 137 | 0.0, 138 | "#0d0887" 139 | ], 140 | [ 141 | 0.1111111111111111, 142 | "#46039f" 143 | ], 144 | [ 145 | 0.2222222222222222, 146 | "#7201a8" 147 | ], 148 | [ 149 | 0.3333333333333333, 150 | "#9c179e" 151 | ], 152 | [ 153 | 0.4444444444444444, 154 | "#bd3786" 155 | ], 156 | [ 157 | 0.5555555555555556, 158 | "#d8576b" 159 | ], 160 | [ 161 | 0.6666666666666666, 162 | "#ed7953" 163 | ], 164 | [ 165 | 0.7777777777777778, 166 | "#fb9f3a" 167 | ], 168 | [ 169 | 0.8888888888888888, 170 | "#fdca26" 171 | ], 172 | [ 173 | 1.0, 174 | "#f0f921" 175 | ] 176 | ], 177 | "type": "heatmapgl" 178 | } 179 | ], 180 | "heatmap": [ 181 | { 182 | "colorbar": { 183 | "outlinewidth": 0, 184 | "ticks": "" 185 | }, 186 | "colorscale": [ 187 | [ 188 | 0.0, 189 | "#0d0887" 190 | ], 191 | [ 192 | 0.1111111111111111, 193 | "#46039f" 194 | ], 195 | [ 196 | 0.2222222222222222, 197 | "#7201a8" 198 | ], 199 | [ 200 | 0.3333333333333333, 201 | "#9c179e" 202 | ], 203 | [ 204 | 0.4444444444444444, 205 | "#bd3786" 206 | ], 207 | [ 208 | 0.5555555555555556, 209 | "#d8576b" 210 | ], 211 | [ 212 | 0.6666666666666666, 213 | "#ed7953" 214 | ], 215 | [ 216 | 0.7777777777777778, 217 | "#fb9f3a" 218 | ], 219 | [ 220 | 0.8888888888888888, 221 | "#fdca26" 222 | ], 223 | [ 224 | 1.0, 225 | "#f0f921" 226 | ] 227 | ], 228 | "type": "heatmap" 229 | } 230 | ], 231 | "histogram2dcontour": [ 232 | { 233 | "colorbar": { 234 | "outlinewidth": 0, 235 | "ticks": "" 236 | }, 237 | "colorscale": [ 238 | [ 239 | 0.0, 240 | "#0d0887" 241 | ], 242 | [ 243 | 0.1111111111111111, 244 | "#46039f" 245 | ], 246 | [ 247 | 0.2222222222222222, 248 | "#7201a8" 249 | ], 250 | [ 251 | 0.3333333333333333, 252 | "#9c179e" 253 | ], 254 | [ 255 | 0.4444444444444444, 256 | "#bd3786" 257 | ], 258 | [ 259 | 0.5555555555555556, 260 | "#d8576b" 261 | ], 262 | [ 263 | 0.6666666666666666, 264 | "#ed7953" 265 | ], 266 | [ 267 | 0.7777777777777778, 268 | "#fb9f3a" 269 | ], 270 | [ 271 | 0.8888888888888888, 272 | "#fdca26" 273 | ], 274 | [ 275 | 1.0, 276 | "#f0f921" 277 | ] 278 | ], 279 | "type": "histogram2dcontour" 280 | } 281 | ], 282 | "histogram2d": [ 283 | { 284 | "colorbar": { 285 | "outlinewidth": 0, 286 | "ticks": "" 287 | }, 288 | "colorscale": [ 289 | [ 290 | 0.0, 291 | "#0d0887" 292 | ], 293 | [ 294 | 0.1111111111111111, 295 | "#46039f" 296 | ], 297 | [ 298 | 0.2222222222222222, 299 | "#7201a8" 300 | ], 301 | [ 302 | 0.3333333333333333, 303 | "#9c179e" 304 | ], 305 | [ 306 | 0.4444444444444444, 307 | "#bd3786" 308 | ], 309 | [ 310 | 0.5555555555555556, 311 | "#d8576b" 312 | ], 313 | [ 314 | 0.6666666666666666, 315 | "#ed7953" 316 | ], 317 | [ 318 | 0.7777777777777778, 319 | "#fb9f3a" 320 | ], 321 | [ 322 | 0.8888888888888888, 323 | "#fdca26" 324 | ], 325 | [ 326 | 1.0, 327 | "#f0f921" 328 | ] 329 | ], 330 | "type": "histogram2d" 331 | } 332 | ], 333 | "histogram": [ 334 | { 335 | "marker": { 336 | "pattern": { 337 | "fillmode": "overlay", 338 | "size": 10, 339 | "solidity": 0.2 340 | } 341 | }, 342 | "type": "histogram" 343 | } 344 | ], 345 | "mesh3d": [ 346 | { 347 | "colorbar": { 348 | "outlinewidth": 0, 349 | "ticks": "" 350 | }, 351 | "type": "mesh3d" 352 | } 353 | ], 354 | "parcoords": [ 355 | { 356 | "line": { 357 | "colorbar": { 358 | "outlinewidth": 0, 359 | "ticks": "" 360 | } 361 | }, 362 | "type": "parcoords" 363 | } 364 | ], 365 | "pie": [ 366 | { 367 | "automargin": true, 368 | "type": "pie" 369 | } 370 | ], 371 | "scatter3d": [ 372 | { 373 | "line": { 374 | "colorbar": { 375 | "outlinewidth": 0, 376 | "ticks": "" 377 | } 378 | }, 379 | "marker": { 380 | "colorbar": { 381 | "outlinewidth": 0, 382 | "ticks": "" 383 | } 384 | }, 385 | "type": "scatter3d" 386 | } 387 | ], 388 | "scattercarpet": [ 389 | { 390 | "marker": { 391 | "colorbar": { 392 | "outlinewidth": 0, 393 | "ticks": "" 394 | } 395 | }, 396 | "type": "scattercarpet" 397 | } 398 | ], 399 | "scattergeo": [ 400 | { 401 | "marker": { 402 | "colorbar": { 403 | "outlinewidth": 0, 404 | "ticks": "" 405 | } 406 | }, 407 | "type": "scattergeo" 408 | } 409 | ], 410 | "scattergl": [ 411 | { 412 | "marker": { 413 | "line": { 414 | "color": "#283442" 415 | } 416 | }, 417 | "type": "scattergl" 418 | } 419 | ], 420 | "scattermapbox": [ 421 | { 422 | "marker": { 423 | "colorbar": { 424 | "outlinewidth": 0, 425 | "ticks": "" 426 | } 427 | }, 428 | "type": "scattermapbox" 429 | } 430 | ], 431 | "scatterpolargl": [ 432 | { 433 | "marker": { 434 | "colorbar": { 435 | "outlinewidth": 0, 436 | "ticks": "" 437 | } 438 | }, 439 | "type": "scatterpolargl" 440 | } 441 | ], 442 | "scatterpolar": [ 443 | { 444 | "marker": { 445 | "colorbar": { 446 | "outlinewidth": 0, 447 | "ticks": "" 448 | } 449 | }, 450 | "type": "scatterpolar" 451 | } 452 | ], 453 | "scatter": [ 454 | { 455 | "marker": { 456 | "line": { 457 | "color": "#283442" 458 | } 459 | }, 460 | "type": "scatter" 461 | } 462 | ], 463 | "scatterternary": [ 464 | { 465 | "marker": { 466 | "colorbar": { 467 | "outlinewidth": 0, 468 | "ticks": "" 469 | } 470 | }, 471 | "type": "scatterternary" 472 | } 473 | ], 474 | "surface": [ 475 | { 476 | "colorbar": { 477 | "outlinewidth": 0, 478 | "ticks": "" 479 | }, 480 | "colorscale": [ 481 | [ 482 | 0.0, 483 | "#0d0887" 484 | ], 485 | [ 486 | 0.1111111111111111, 487 | "#46039f" 488 | ], 489 | [ 490 | 0.2222222222222222, 491 | "#7201a8" 492 | ], 493 | [ 494 | 0.3333333333333333, 495 | "#9c179e" 496 | ], 497 | [ 498 | 0.4444444444444444, 499 | "#bd3786" 500 | ], 501 | [ 502 | 0.5555555555555556, 503 | "#d8576b" 504 | ], 505 | [ 506 | 0.6666666666666666, 507 | "#ed7953" 508 | ], 509 | [ 510 | 0.7777777777777778, 511 | "#fb9f3a" 512 | ], 513 | [ 514 | 0.8888888888888888, 515 | "#fdca26" 516 | ], 517 | [ 518 | 1.0, 519 | "#f0f921" 520 | ] 521 | ], 522 | "type": "surface" 523 | } 524 | ], 525 | "table": [ 526 | { 527 | "cells": { 528 | "fill": { 529 | "color": "#506784" 530 | }, 531 | "line": { 532 | "color": "#1d1f21" 533 | } 534 | }, 535 | "header": { 536 | "fill": { 537 | "color": "#2a3f5f" 538 | }, 539 | "line": { 540 | "color": "#1d1f21" 541 | } 542 | }, 543 | "type": "table" 544 | } 545 | ] 546 | }, 547 | "layout": { 548 | "annotationdefaults": { 549 | "arrowcolor": "#f2f5fa", 550 | "arrowhead": 0, 551 | "arrowwidth": 1 552 | }, 553 | "autotypenumbers": "strict", 554 | "coloraxis": { 555 | "colorbar": { 556 | "outlinewidth": 0, 557 | "ticks": "" 558 | } 559 | }, 560 | "colorscale": { 561 | "diverging": [ 562 | [ 563 | 0, 564 | "#8e0152" 565 | ], 566 | [ 567 | 0.1, 568 | "#c51b7d" 569 | ], 570 | [ 571 | 0.2, 572 | "#de77ae" 573 | ], 574 | [ 575 | 0.3, 576 | "#f1b6da" 577 | ], 578 | [ 579 | 0.4, 580 | "#fde0ef" 581 | ], 582 | [ 583 | 0.5, 584 | "#f7f7f7" 585 | ], 586 | [ 587 | 0.6, 588 | "#e6f5d0" 589 | ], 590 | [ 591 | 0.7, 592 | "#b8e186" 593 | ], 594 | [ 595 | 0.8, 596 | "#7fbc41" 597 | ], 598 | [ 599 | 0.9, 600 | "#4d9221" 601 | ], 602 | [ 603 | 1, 604 | "#276419" 605 | ] 606 | ], 607 | "sequential": [ 608 | [ 609 | 0.0, 610 | "#0d0887" 611 | ], 612 | [ 613 | 0.1111111111111111, 614 | "#46039f" 615 | ], 616 | [ 617 | 0.2222222222222222, 618 | "#7201a8" 619 | ], 620 | [ 621 | 0.3333333333333333, 622 | "#9c179e" 623 | ], 624 | [ 625 | 0.4444444444444444, 626 | "#bd3786" 627 | ], 628 | [ 629 | 0.5555555555555556, 630 | "#d8576b" 631 | ], 632 | [ 633 | 0.6666666666666666, 634 | "#ed7953" 635 | ], 636 | [ 637 | 0.7777777777777778, 638 | "#fb9f3a" 639 | ], 640 | [ 641 | 0.8888888888888888, 642 | "#fdca26" 643 | ], 644 | [ 645 | 1.0, 646 | "#f0f921" 647 | ] 648 | ], 649 | "sequentialminus": [ 650 | [ 651 | 0.0, 652 | "#0d0887" 653 | ], 654 | [ 655 | 0.1111111111111111, 656 | "#46039f" 657 | ], 658 | [ 659 | 0.2222222222222222, 660 | "#7201a8" 661 | ], 662 | [ 663 | 0.3333333333333333, 664 | "#9c179e" 665 | ], 666 | [ 667 | 0.4444444444444444, 668 | "#bd3786" 669 | ], 670 | [ 671 | 0.5555555555555556, 672 | "#d8576b" 673 | ], 674 | [ 675 | 0.6666666666666666, 676 | "#ed7953" 677 | ], 678 | [ 679 | 0.7777777777777778, 680 | "#fb9f3a" 681 | ], 682 | [ 683 | 0.8888888888888888, 684 | "#fdca26" 685 | ], 686 | [ 687 | 1.0, 688 | "#f0f921" 689 | ] 690 | ] 691 | }, 692 | "colorway": [ 693 | "#636efa", 694 | "#EF553B", 695 | "#00cc96", 696 | "#ab63fa", 697 | "#FFA15A", 698 | "#19d3f3", 699 | "#FF6692", 700 | "#B6E880", 701 | "#FF97FF", 702 | "#FECB52" 703 | ], 704 | "font": { 705 | "color": "#f2f5fa" 706 | }, 707 | "geo": { 708 | "bgcolor": "#1d1f21", 709 | "lakecolor": "#1d1f21", 710 | "landcolor": "#1d1f21", 711 | "showlakes": true, 712 | "showland": true, 713 | "subunitcolor": "#506784" 714 | }, 715 | "hoverlabel": { 716 | "align": "left" 717 | }, 718 | "hovermode": "closest", 719 | "mapbox": { 720 | "style": "dark" 721 | }, 722 | "paper_bgcolor": "#1d1f21", 723 | "plot_bgcolor": "#1d1f21", 724 | "polar": { 725 | "angularaxis": { 726 | "gridcolor": "#506784", 727 | "linecolor": "#506784", 728 | "ticks": "" 729 | }, 730 | "bgcolor": "#1d1f21", 731 | "radialaxis": { 732 | "gridcolor": "#506784", 733 | "linecolor": "#506784", 734 | "ticks": "" 735 | } 736 | }, 737 | "scene": { 738 | "xaxis": { 739 | "backgroundcolor": "#1d1f21", 740 | "gridcolor": "#506784", 741 | "gridwidth": 2, 742 | "linecolor": "#506784", 743 | "showbackground": true, 744 | "ticks": "", 745 | "zerolinecolor": "#C8D4E3" 746 | }, 747 | "yaxis": { 748 | "backgroundcolor": "#1d1f21", 749 | "gridcolor": "#506784", 750 | "gridwidth": 2, 751 | "linecolor": "#506784", 752 | "showbackground": true, 753 | "ticks": "", 754 | "zerolinecolor": "#C8D4E3" 755 | }, 756 | "zaxis": { 757 | "backgroundcolor": "#1d1f21", 758 | "gridcolor": "#506784", 759 | "gridwidth": 2, 760 | "linecolor": "#506784", 761 | "showbackground": true, 762 | "ticks": "", 763 | "zerolinecolor": "#C8D4E3" 764 | } 765 | }, 766 | "shapedefaults": { 767 | "line": { 768 | "color": "#f2f5fa" 769 | } 770 | }, 771 | "sliderdefaults": { 772 | "bgcolor": "#C8D4E3", 773 | "bordercolor": "#1d1f21", 774 | "borderwidth": 1, 775 | "tickwidth": 0 776 | }, 777 | "ternary": { 778 | "aaxis": { 779 | "gridcolor": "#506784", 780 | "linecolor": "#506784", 781 | "ticks": "" 782 | }, 783 | "baxis": { 784 | "gridcolor": "#506784", 785 | "linecolor": "#506784", 786 | "ticks": "" 787 | }, 788 | "bgcolor": "#1d1f21", 789 | "caxis": { 790 | "gridcolor": "#506784", 791 | "linecolor": "#506784", 792 | "ticks": "" 793 | } 794 | }, 795 | "updatemenudefaults": { 796 | "bgcolor": "#506784", 797 | "borderwidth": 0 798 | }, 799 | "xaxis": { 800 | "automargin": true, 801 | "gridcolor": "#283442", 802 | "linecolor": "#506784", 803 | "ticks": "", 804 | "title": { 805 | "standoff": 15 806 | }, 807 | "zerolinecolor": "#283442", 808 | "zerolinewidth": 2 809 | }, 810 | "yaxis": { 811 | "automargin": true, 812 | "gridcolor": "#283442", 813 | "linecolor": "#506784", 814 | "ticks": "", 815 | "title": { 816 | "standoff": 15 817 | }, 818 | "zerolinecolor": "#283442", 819 | "zerolinewidth": 2 820 | } 821 | } 822 | } -------------------------------------------------------------------------------- /otherAssets/python/OEE_Interface.py: -------------------------------------------------------------------------------- 1 | import oeeJS 2 | import json 3 | import ee 4 | import types 5 | import pyodide 6 | try: 7 | import matplotlib.pyplot 8 | withPlotly=True 9 | except Exception as e: 10 | withPlotly=False 11 | 12 | import io 13 | import base64 14 | oeel_namespaceArray = [] 15 | 16 | consoleLog=print 17 | 18 | mappingFunction={}; 19 | 20 | class oeeRequire: 21 | __dict__=dict({}); 22 | def __init__(self, path, d): 23 | self.__dict__.update(d) 24 | self.path=path; 25 | 26 | def __getattr__(self, name): 27 | if name in self.__dict__: 28 | return self.__dict__[name] 29 | else: 30 | raise AttributeError("No such attribute: " + name) 31 | 32 | def __setattr__(self, name, value): 33 | self.__dict__[name] = value 34 | 35 | def __delattr__(self, name): 36 | if name in self.__dict__: 37 | del self.__dict__[name] 38 | else: 39 | raise AttributeError("No such attribute: " + name) 40 | def __str__(self): 41 | return "requireJS: "+self.path; 42 | 43 | class oeelChain: 44 | def __init__(self, attr_chain=[]): 45 | self.attr_chain = attr_chain 46 | 47 | def __getattr__(self, attr): 48 | return oeelChain(self.attr_chain + [attr]) 49 | 50 | def __call__(self, *args, **kwargs): 51 | # Here you could call the function you want with the collected attributes and parameters 52 | if not kwargs: 53 | return ee_Js2Py(oeeJS.oeelCall(ee_Py2Js(self.attr_chain), ee_Py2Js([ee_Py2Js(value) for value in args]))) 54 | else: 55 | return ee_Js2Py(oeeJS.oeelCall(ee_Py2Js(self.attr_chain), ee_Py2Js([{ee_Py2Js(value) for key, value in kwargs}]))) 56 | 57 | def requireJS(self, path): 58 | sourceCode=oeeJS.oeeRequireJS( ee_Py2Js(path)) 59 | js_output=pyodide.code.run_js("(function(){var exports={};" +sourceCode+"\n return exports})()"); 60 | return oeeRequire(path,{key:ee_Js2Py(value) for key, value in js_output.to_py(depth=1).items() }); 61 | 62 | class eeExportModule: 63 | def __init__(self, attr_chain=[]): 64 | self.attr_chain = attr_chain 65 | 66 | def __getattr__(self, attr): 67 | return eeExportModule(self.attr_chain + [attr]) 68 | 69 | def __call__(self, *args, **kwargs): 70 | # Here you could call the function you want with the collected attributes and parameters 71 | if not kwargs: 72 | val=oeeJS.oeelExport(ee_Py2Js(self.attr_chain), ee_Py2Js([ee_Py2Js(value) for value in args])) 73 | else: 74 | val=oeeJS.oeelExport(ee_Py2Js(self.attr_chain), ee_Py2Js([{ee_Py2Js(value) for key, value in kwargs}])) 75 | class eeTask: 76 | def status(self): 77 | return {'state':'UNKNOWN'} 78 | def start(self): 79 | print("Nice try!👏") 80 | def __str__(self): 81 | return "Dummy class for expoterd task" 82 | return eeTask(); 83 | 84 | class eeDataModule: 85 | def __init__(self, attr_chain=[]): 86 | self.attr_chain = attr_chain 87 | 88 | def __getattr__(self, attr): 89 | class eeDataFucntion: 90 | def __init__(self, dataFunction): 91 | self.dataFunction=dataFunction; 92 | def __call__(self, *args, **kwargs): 93 | if not kwargs: 94 | return ee_Js2Py(oeeJS.oeelData(ee_Py2Js(self.dataFunction), ee_Py2Js([ee_Py2Js(value) for value in args]))) 95 | else: 96 | return ee_Js2Py(oeeJS.oeelData(ee_Py2Js(self.dataFunction), ee_Py2Js([{ee_Py2Js(value) for key, value in kwargs}]))) 97 | def __str__(self): 98 | return "ee.data."+self.dataFunction 99 | return eeDataFucntion(attr); 100 | 101 | def __str__(self): 102 | return "Dummy class for ee.data" 103 | 104 | ee.batch.Export=eeExportModule(); 105 | ee.data=eeDataModule(); 106 | 107 | import os 108 | 109 | import sys 110 | 111 | class EEimpport(): 112 | def __init__(self): 113 | self.struct={"users":{}} 114 | os.makedirs("users", exist_ok=True); 115 | pass 116 | 117 | def decodePathAdd(self,soruceTree,treeToAdd,path): 118 | for key,value in soruceTree.items(): 119 | if(isinstance(value,dict)): 120 | treeToAdd[key]={}; 121 | self.decodePathAdd(soruceTree[key],treeToAdd[key],path+"/"+key); 122 | else: 123 | treeToAdd[key.rstrip(".py")]=path+"/"+key; 124 | 125 | def getEEPath(self,path,obj=None,currentPath=""): 126 | if obj==None: 127 | obj=self.struct; 128 | splitPath=path.split(".",1) 129 | if(splitPath[0] in obj): 130 | if(len(splitPath)>1): 131 | propagated=self.getEEPath(splitPath[1],obj[splitPath[0]],currentPath+"/"+splitPath[0]); 132 | return propagated;#splitPath[0]+"/"+propagated if isinstance(propagated,str) else propagated; 133 | else: 134 | if('__init__' in obj[splitPath[0]]): 135 | return obj[splitPath[0]]['__init__']; 136 | else: 137 | return obj[splitPath[0]]; 138 | else: 139 | result=ee_Js2Py(oeeJS.importPakageStruct(currentPath.lstrip("/")+"/"+splitPath[0])) 140 | obj[splitPath[0]]={} 141 | self.decodePathAdd(result["tree"],obj[splitPath[0]],currentPath+"/"+splitPath[0]+":"); 142 | return None; 143 | 144 | def find_module(self, fullname, path=None): 145 | fullNameAsPath=fullname.replace('.',"/") 146 | if fullname.startswith("users") and not (os.path.exists(fullNameAsPath) or os.path.exists(fullNameAsPath+'.py')): 147 | downloadPath=self.getEEPath(fullname); 148 | if(isinstance(downloadPath,str)): 149 | downloadPath=downloadPath.lstrip("/") 150 | code=oeeJS.importCode(downloadPath) 151 | pathStore=downloadPath.replace(":",""); 152 | os.makedirs(os.path.dirname(pathStore), exist_ok=True); 153 | with open(pathStore, 'w') as file: 154 | file.write(code); 155 | else: 156 | os.makedirs(fullNameAsPath, exist_ok=True); 157 | self.find_module(fullname, path) 158 | 159 | def load_module(self, fullname): 160 | pass 161 | 162 | 163 | EEimpportModule=EEimpport(); 164 | sys.meta_path.insert(0,EEimpportModule) 165 | 166 | def installDictionaryAtPath(obj,path): 167 | os.makedirs(path, exist_ok=True); 168 | for key, value in obj.items(): 169 | if(isinstance(value,dict)): 170 | installDictionaryAtPath(value,path+key+'/') 171 | else: 172 | with open(path+key, 'w') as file: 173 | file.write(value); 174 | 175 | def installPackageFromObject(obj,path): 176 | shortPath=path.split(":", 1)[1] 177 | path=path.replace(":", "/"); 178 | installDictionaryAtPath(ee_Js2Py(obj),path+'/') 179 | os.symlink(path, shortPath); 180 | 181 | def eePrint(*toPrints): 182 | toPrints=list(toPrints) 183 | for ix in range(len(toPrints)): 184 | if(isinstance(toPrints[ix], ee.computedobject.ComputedObject)): 185 | toPrints[ix]=ee_Py2Js(toPrints[ix]); 186 | elif(hasattr(toPrints[ix], '__str__')): 187 | toPrints[ix]=str(toPrints[ix]); 188 | else: 189 | toPrints[ix]=ee_Py2Js(toPrints[ix]); 190 | oeeJS.oeePrint(ee_Py2Js(toPrints)) 191 | 192 | def eeMapOp(name,args): 193 | oeeJS.oeeMap(name, ee_Py2Js([ee_Py2Js(value) for value in args])) 194 | if withPlotly: 195 | def displayPlt(bbox_inches="tight", dpi=100): 196 | buffer = io.BytesIO() 197 | matplotlib.pyplot.savefig(buffer, format='png',bbox_inches=bbox_inches,dpi=dpi) 198 | buffer.seek(0) 199 | encoded_image = base64.b64encode(buffer.getvalue()).decode('utf-8') 200 | oeeJS.oeePlot("data:image/png;base64,"+encoded_image); 201 | 202 | matplotlib.pyplot.show=displayPlt; 203 | 204 | class Map(): 205 | def __init__(self): 206 | self.functions = ["addLayer","setLocked","setControlVisibility","setCenter","setGestureHandling","setZoom","centerObject","setOptions","clear","style"]; 207 | 208 | def __getattr__(self, name): 209 | def method(*args, **kwargs): 210 | eeMapOp(name,args) 211 | return method 212 | 213 | def loadModule(string,name): 214 | code_block = compile(string, '', 'exec') 215 | idVal=len(oeel_namespaceArray); 216 | oeel_namespaceArray.append({}) 217 | exec(compile("", '', 'exec'), oeel_namespaceArray[idVal]) 218 | oeel_namespaceArray[idVal]["__builtins__"]["consoleLog"]=consoleLog; 219 | oeel_namespaceArray[idVal]["__builtins__"]["print"]=eePrint; 220 | oeel_namespaceArray[idVal]["__builtins__"]["Map"]=Map(); 221 | oeel_namespaceArray[idVal]["__builtins__"]["ee"]=ee; 222 | oeel_namespaceArray[idVal]["__builtins__"]["oeel"]=oeelChain(); 223 | oeel_namespaceArray[idVal]["__name__"]=name; 224 | exec(code_block, oeel_namespaceArray[idVal]) 225 | return {"pyId":idVal,"id":":\"{}\"".format(name), "answerType":"moduleLoaded","type":"Python Module", "functions":list(filter(lambda x: not(x.startswith("__") and x.endswith("__")), oeel_namespaceArray[idVal].keys()))}; 226 | 227 | def run(string,dataset): 228 | dataset=dataset.to_py(depth=1); 229 | dataset={key: ee_Js2Py(value) for key, value in dataset.items()}; 230 | code_block = compile(string, '', 'exec') 231 | exec(compile("", '', 'exec'), dataset) 232 | dataset["__builtins__"]["consoleLog"]=consoleLog; 233 | dataset["__builtins__"]["print"]=eePrint; 234 | dataset["__builtins__"]["Map"]=Map(); 235 | dataset["__builtins__"]["ee"]=ee; 236 | dataset["__builtins__"]["oeel"]=oeelChain(); 237 | dataset["__name__"]="__main__"; 238 | exec(code_block, dataset) 239 | return {"answerType":"run","type":"Python single run"}; 240 | 241 | 242 | def isEEObject(val): 243 | return oeeJS.oeeIsEE(val); 244 | 245 | def EEtype(val): 246 | return oeeJS.oeeEEtype(val); 247 | 248 | def isJSFunction(val): 249 | return oeeJS.oeeIsFunction(val); 250 | 251 | def ee_Js2Py(val): 252 | if isEEObject(val): 253 | return getattr(ee,EEtype(val))(ee.deserializer.decode(oeeJS.oeeEncodeEE(val).to_py())); 254 | if isJSFunction(val): 255 | def wrapper(*args,**kwargs): 256 | if not kwargs: 257 | return oeeJS.callJSFucntion(val,ee_Py2Js([ee_Py2Js(value) for value in args])); 258 | else: 259 | return oeeJS.callJSFucntion(val,ee_Py2Js([{key:ee_Py2Js(value) for key, value in kwargs.items()}])); 260 | return wrapper; 261 | return val.to_py(); 262 | 263 | 264 | def ee_Py2Js(val): 265 | if isinstance(val, ee.computedobject.ComputedObject): 266 | #EE object 267 | return oeeJS.oeeAsEEJS(ee.serializer.encode(val),val.name()); 268 | if isinstance(val, (types.FunctionType, types.BuiltinFunctionType, types.MethodType, types.LambdaType, types.GeneratorType)): 269 | raise NotImplementedError("Unsupported Feature: Python function from JS not supported!") 270 | #return getJsCallable(val) 271 | return oeeJS.oeeAsJS(val) 272 | 273 | def getJsCallable(fun): 274 | idVal=id(fun) 275 | mappingFunction[idVal]=fun; 276 | return oeeJS.oeelGetJsCallable(idVal) 277 | 278 | def listPkgsInstalled(): 279 | import micropip 280 | return ee_Py2Js(list(micropip.list())); 281 | 282 | async def installPackage(pkgs): 283 | import micropip 284 | await micropip.install(ee_Js2Py(pkgs)) 285 | 286 | def callFunction(idVal,name,args): 287 | func = oeel_namespaceArray[idVal][name]; 288 | args=args.to_py(depth=1); 289 | 290 | if type(args) is list: # Python's built-in array type is called list 291 | r=func(*[ee_Js2Py(value) for value in args]) 292 | elif type(args) is dict: # Python's built-in dictionary type is called dict 293 | r=func(**{key: ee_Js2Py(value) for key, value in args.items()}) 294 | else: 295 | r=func(args) 296 | 297 | return {"answerType":"functionResult", "value":ee_Py2Js(r)}; 298 | 299 | def callFunc(pyFucntionID,args):#inlined 300 | args=args.to_py(depth=1); 301 | func=mappingFunction[pyFucntionID]; 302 | 303 | if type(args) is list: # Python's built-in array type is called list 304 | return func(*[ee_Js2Py(value) for value in args]) 305 | elif type(args) is dict: # Python's built-in dictionary type is called dict 306 | return func(**{key: ee_Js2Py(value) for key, value in args.items()}) 307 | else: 308 | return func(args) 309 | -------------------------------------------------------------------------------- /otherAssets/python/earthengine-api.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-geocomputing/OEEex/2f420a0fa9edfc125fb16803d9a4bb942680658d/otherAssets/python/earthengine-api.tar.gz --------------------------------------------------------------------------------