├── .env.development ├── .eslintignore ├── .eslintrc.json ├── .github └── workflows │ ├── build.yml │ ├── lint.yml │ └── python_tests.yml ├── .gitignore ├── .prettierrc.json ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── SplootCode-screenshot-readme.png ├── babel.config.json ├── index.html ├── jest.config.ts ├── package.json ├── packages ├── components │ ├── .gitignore │ ├── package.json │ ├── rollup.config.js │ ├── src │ │ ├── export_text_modal.tsx │ │ ├── index.ts │ │ ├── menu_bar.css │ │ ├── menu_bar.tsx │ │ ├── run_type_icon.tsx │ │ └── save_project_modal.tsx │ └── tsconfig.json ├── core │ ├── .gitignore │ ├── package.json │ ├── rollup.config.js │ ├── src │ │ ├── code_io │ │ │ ├── filesystem.ts │ │ │ ├── filesystem_file_loader.ts │ │ │ ├── local_storage_file_loader.ts │ │ │ ├── local_storage_project_loader.ts │ │ │ ├── starting_files.ts │ │ │ └── static_file_loader.ts │ │ ├── colors.ts │ │ ├── feature_flags │ │ │ └── feature_flags.ts │ │ ├── http_types.ts │ │ ├── index.ts │ │ └── language │ │ │ ├── annotations │ │ │ └── annotations.ts │ │ │ ├── autocomplete │ │ │ ├── autocompleter.ts │ │ │ ├── registry.ts │ │ │ └── suggested_node.ts │ │ │ ├── capture │ │ │ └── runtime_capture.ts │ │ │ ├── childset.ts │ │ │ ├── fragment.ts │ │ │ ├── fragment_adapter.ts │ │ │ ├── invariants.ts │ │ │ ├── mutations │ │ │ ├── child_set_mutations.ts │ │ │ ├── mutation_dispatcher.ts │ │ │ ├── node_mutations.ts │ │ │ ├── project_mutations.ts │ │ │ └── scope_mutations.ts │ │ │ ├── node.ts │ │ │ ├── node_category_registry.ts │ │ │ ├── observers.ts │ │ │ ├── projects │ │ │ ├── file.ts │ │ │ ├── file_loader.ts │ │ │ ├── package.ts │ │ │ ├── project.ts │ │ │ └── run_settings.ts │ │ │ ├── tray │ │ │ └── tray.ts │ │ │ ├── type_registry.ts │ │ │ └── validation │ │ │ └── validation_watcher.ts │ └── tsconfig.json ├── editor │ ├── .gitignore │ ├── jest.config.ts │ ├── package.json │ ├── rollup.config.js │ ├── src │ │ ├── components │ │ │ ├── attached_child.css │ │ │ ├── attached_child.tsx │ │ │ ├── autosave_info.tsx │ │ │ ├── config_panel.tsx │ │ │ ├── cursor.css │ │ │ ├── cursor.tsx │ │ │ ├── drag_overlay.css │ │ │ ├── drag_overlay.tsx │ │ │ ├── edit_box.css │ │ │ ├── edit_box.tsx │ │ │ ├── editor.css │ │ │ ├── editor.tsx │ │ │ ├── editor_banner.tsx │ │ │ ├── editor_side_menu.css │ │ │ ├── editor_side_menu.tsx │ │ │ ├── fragment.tsx │ │ │ ├── insert_box.css │ │ │ ├── insert_box.tsx │ │ │ ├── list_block.css │ │ │ ├── list_block.tsx │ │ │ ├── node_block.css │ │ │ ├── node_block.test.tsx │ │ │ ├── node_block.tsx │ │ │ ├── placeholder_label.css │ │ │ ├── placeholder_label.tsx │ │ │ ├── property.css │ │ │ ├── property.tsx │ │ │ ├── runtime_annotations.css │ │ │ ├── runtime_annotations.tsx │ │ │ ├── separator.tsx │ │ │ ├── string_literal.css │ │ │ ├── string_literal.tsx │ │ │ ├── string_node.css │ │ │ ├── string_node.tsx │ │ │ ├── test_request_panel.tsx │ │ │ ├── token_list_block.tsx │ │ │ ├── tray │ │ │ │ ├── add_import_modal.tsx │ │ │ │ ├── category.tsx │ │ │ │ ├── entry.tsx │ │ │ │ ├── imports_tray.tsx │ │ │ │ ├── scope_tray.css │ │ │ │ ├── scope_tray.tsx │ │ │ │ ├── tray.css │ │ │ │ └── tray.tsx │ │ │ ├── tree_list_block.css │ │ │ └── tree_list_block.tsx │ │ ├── context │ │ │ ├── autosave_watcher.ts │ │ │ ├── cursor_map.ts │ │ │ ├── edit_box.ts │ │ │ ├── editor_context.ts │ │ │ ├── insert_box.ts │ │ │ ├── multiselect_deleter.ts │ │ │ ├── multiselect_fragment_creator.ts │ │ │ ├── multiselect_tree_walker.ts │ │ │ ├── rendered_tree_iterator.ts │ │ │ ├── runtime_context_manager.ts │ │ │ ├── selection.ts │ │ │ └── undoWatcher.ts │ │ ├── editor_hosting_config.ts │ │ ├── index.ts │ │ ├── layout │ │ │ ├── attach_right_layout_handler.ts │ │ │ ├── before_stack_layout_handler.ts │ │ │ ├── block_layout_handler.ts │ │ │ ├── breadcrumbs_layout_handler.ts │ │ │ ├── childset_layout_handler.ts │ │ │ ├── layout_constants.ts │ │ │ ├── rendered_childset_block.ts │ │ │ ├── rendered_fragment.ts │ │ │ ├── rendered_node.ts │ │ │ ├── stack_layout_handler.ts │ │ │ ├── token_layout_handler.ts │ │ │ └── tree_layout_handler.ts │ │ ├── runtime │ │ │ ├── file_change_watcher.ts │ │ │ ├── frame_state_manager.ts │ │ │ ├── project_file_change_watcher.ts │ │ │ ├── python_frame.css │ │ │ ├── python_frame.tsx │ │ │ ├── python_runtime_panel.tsx │ │ │ ├── response_viewer.css │ │ │ ├── response_viewer.tsx │ │ │ ├── terminal.css │ │ │ ├── wasm-tty │ │ │ │ └── wasm-tty.ts │ │ │ ├── web_runtime.css │ │ │ └── web_runtime.tsx │ │ └── tests │ │ │ ├── check_observer_mapping.ts │ │ │ ├── dict_multiselect.test.ts │ │ │ ├── edit_actions.test.ts │ │ │ ├── fragment_insert.test.ts │ │ │ └── multiselect.test.ts │ └── tsconfig.json ├── language-python │ ├── .gitignore │ ├── jest.config.ts │ ├── package.json │ ├── rollup.config.js │ ├── src │ │ ├── analyzer │ │ │ └── python_analyzer.ts │ │ ├── generated │ │ │ └── .gitignore │ │ ├── index.ts │ │ ├── nodes │ │ │ ├── declared_identifier.ts │ │ │ ├── literals.ts │ │ │ ├── python_argument.ts │ │ │ ├── python_assignment.ts │ │ │ ├── python_binary_operator.ts │ │ │ ├── python_brackets.ts │ │ │ ├── python_break.ts │ │ │ ├── python_call_member.ts │ │ │ ├── python_call_node.ts │ │ │ ├── python_call_variable.ts │ │ │ ├── python_comment.ts │ │ │ ├── python_continue.ts │ │ │ ├── python_decorator.ts │ │ │ ├── python_dictionary.ts │ │ │ ├── python_elif.ts │ │ │ ├── python_else.ts │ │ │ ├── python_expression.ts │ │ │ ├── python_file.ts │ │ │ ├── python_for.ts │ │ │ ├── python_from_import.ts │ │ │ ├── python_function.ts │ │ │ ├── python_identifier.ts │ │ │ ├── python_if.ts │ │ │ ├── python_import.ts │ │ │ ├── python_keyvalue.ts │ │ │ ├── python_keyword_argument.ts │ │ │ ├── python_list.ts │ │ │ ├── python_member.ts │ │ │ ├── python_module_identifier.ts │ │ │ ├── python_node.ts │ │ │ ├── python_return.ts │ │ │ ├── python_set.ts │ │ │ ├── python_slice.ts │ │ │ ├── python_slice_range.ts │ │ │ ├── python_statement.ts │ │ │ ├── python_string.ts │ │ │ ├── python_subscript.ts │ │ │ ├── python_tuple.ts │ │ │ ├── python_while.ts │ │ │ ├── scope_argument_autocompleter.ts │ │ │ ├── scope_autocompleter.ts │ │ │ ├── scope_member_autocompleter.ts │ │ │ ├── tests │ │ │ │ ├── parsetree.test.ts │ │ │ │ ├── serialization.test.ts │ │ │ │ ├── single_examples.ts │ │ │ │ ├── test_utils.ts │ │ │ │ └── tree_walker.ts │ │ │ ├── utils.ts │ │ │ └── variable_reference.ts │ │ ├── scope │ │ │ ├── python.ts │ │ │ ├── python_scope.ts │ │ │ └── types.ts │ │ ├── standard_lib_modules.json │ │ ├── tests │ │ │ ├── load_projects.test.ts │ │ │ ├── test_data │ │ │ │ ├── blank_main.json │ │ │ │ ├── helloname_main.json │ │ │ │ ├── missing_expressions_main.json │ │ │ │ ├── missing_values_main.json │ │ │ │ ├── secret_password_main.json │ │ │ │ ├── temperature_conversion_main.json │ │ │ │ └── test_collections_main.json │ │ │ └── test_file_loader.ts │ │ ├── tray │ │ │ ├── language.test.ts │ │ │ └── language.ts │ │ └── type_loader.ts │ ├── tray │ │ ├── abc.json │ │ ├── aifc.json │ │ ├── argparse.json │ │ ├── array.json │ │ ├── ast.json │ │ ├── asyncio.json │ │ ├── atexit.json │ │ ├── base64.json │ │ ├── bdb.json │ │ ├── binascii.json │ │ ├── bisect.json │ │ ├── builtins.json │ │ ├── bz2.json │ │ ├── cProfile.json │ │ ├── calendar.json │ │ ├── cgi.json │ │ ├── cgitb.json │ │ ├── chunk.json │ │ ├── cmath.json │ │ ├── cmd.json │ │ ├── code.json │ │ ├── codecs.json │ │ ├── codeop.json │ │ ├── collections.json │ │ ├── colorsys.json │ │ ├── compileall.json │ │ ├── configparser.json │ │ ├── contextlib.json │ │ ├── contextvars.json │ │ ├── copy.json │ │ ├── copyreg.json │ │ ├── crypt.json │ │ ├── csv.json │ │ ├── ctypes.json │ │ ├── dataclasses.json │ │ ├── datetime.json │ │ ├── decimal.json │ │ ├── difflib.json │ │ ├── dis.json │ │ ├── doctest.json │ │ ├── email.json │ │ ├── encodings.json │ │ ├── enum.json │ │ ├── errno.json │ │ ├── faulthandler.json │ │ ├── filecmp.json │ │ ├── fileinput.json │ │ ├── flask.json │ │ ├── fnmatch.json │ │ ├── fractions.json │ │ ├── ftplib.json │ │ ├── functools.json │ │ ├── gc.json │ │ ├── genericpath.json │ │ ├── getopt.json │ │ ├── getpass.json │ │ ├── gettext.json │ │ ├── glob.json │ │ ├── graphlib.json │ │ ├── gzip.json │ │ ├── hashlib.json │ │ ├── heapq.json │ │ ├── hmac.json │ │ ├── html.json │ │ ├── http.json │ │ ├── imaplib.json │ │ ├── imghdr.json │ │ ├── importlib.json │ │ ├── inspect.json │ │ ├── io.json │ │ ├── ipaddress.json │ │ ├── itertools.json │ │ ├── json.json │ │ ├── keyword.json │ │ ├── linecache.json │ │ ├── locale.json │ │ ├── logging.json │ │ ├── lzma.json │ │ ├── mailbox.json │ │ ├── mailcap.json │ │ ├── marshal.json │ │ ├── math.json │ │ ├── mimetypes.json │ │ ├── mmap.json │ │ ├── modulefinder.json │ │ ├── multiprocessing.json │ │ ├── netrc.json │ │ ├── nntplib.json │ │ ├── ntpath.json │ │ ├── nturl2path.json │ │ ├── numbers.json │ │ ├── numpy.json │ │ ├── opcode.json │ │ ├── operator.json │ │ ├── optparse.json │ │ ├── os.json │ │ ├── pandas.json │ │ ├── pathlib.json │ │ ├── pdb.json │ │ ├── pickle.json │ │ ├── pickletools.json │ │ ├── pipes.json │ │ ├── pkgutil.json │ │ ├── platform.json │ │ ├── plistlib.json │ │ ├── poplib.json │ │ ├── posix.json │ │ ├── posixpath.json │ │ ├── pprint.json │ │ ├── profile.json │ │ ├── pstats.json │ │ ├── py_compile.json │ │ ├── pyclbr.json │ │ ├── pydoc.json │ │ ├── pydoc_data.json │ │ ├── pyexpat.json │ │ ├── queue.json │ │ ├── quopri.json │ │ ├── random.json │ │ ├── re.json │ │ ├── reprlib.json │ │ ├── requests.json │ │ ├── rlcompleter.json │ │ ├── runpy.json │ │ ├── sched.json │ │ ├── secrets.json │ │ ├── select.json │ │ ├── selectors.json │ │ ├── shelve.json │ │ ├── shlex.json │ │ ├── shutil.json │ │ ├── signal.json │ │ ├── site.json │ │ ├── smtplib.json │ │ ├── sndhdr.json │ │ ├── socket.json │ │ ├── socketserver.json │ │ ├── sqlite3.json │ │ ├── sre_compile.json │ │ ├── sre_constants.json │ │ ├── sre_parse.json │ │ ├── ssl.json │ │ ├── stat.json │ │ ├── statistics.json │ │ ├── streamlit.json │ │ ├── string.json │ │ ├── stringprep.json │ │ ├── struct.json │ │ ├── subprocess.json │ │ ├── sunau.json │ │ ├── symtable.json │ │ ├── sys.json │ │ ├── sysconfig.json │ │ ├── tabnanny.json │ │ ├── tarfile.json │ │ ├── telnetlib.json │ │ ├── tempfile.json │ │ ├── textwrap.json │ │ ├── this.json │ │ ├── threading.json │ │ ├── time.json │ │ ├── timeit.json │ │ ├── token.json │ │ ├── tokenize.json │ │ ├── trace.json │ │ ├── traceback.json │ │ ├── tracemalloc.json │ │ ├── types.json │ │ ├── typing.json │ │ ├── unicodedata.json │ │ ├── unittest.json │ │ ├── urllib.json │ │ ├── uu.json │ │ ├── uuid.json │ │ ├── warnings.json │ │ ├── wave.json │ │ ├── weakref.json │ │ ├── webbrowser.json │ │ ├── wsgiref.json │ │ ├── xdrlib.json │ │ ├── xml.json │ │ ├── xmlrpc.json │ │ ├── zipapp.json │ │ ├── zipfile.json │ │ ├── zipimport.json │ │ ├── zlib.json │ │ └── zoneinfo.json │ └── tsconfig.json ├── language-web │ ├── .gitignore │ ├── code_io │ │ ├── import_html.ts │ │ └── import_js.ts │ ├── components │ │ └── datasheet │ │ │ └── datasheet.tsx │ ├── definitions │ │ └── loader.ts │ ├── index.ts │ ├── javascript_node.ts │ ├── package.json │ ├── rollup.config.js │ ├── tsconfig.json │ ├── type_loader.ts │ └── types │ │ ├── component │ │ ├── component_declaration.ts │ │ ├── component_invocation.ts │ │ ├── component_property.ts │ │ ├── declared_property.ts │ │ ├── for_each_expression.ts │ │ ├── property_reference.ts │ │ └── react_element.ts │ │ ├── css │ │ └── css_properties.ts │ │ ├── dataset │ │ ├── datasheet.ts │ │ ├── field_declaration.ts │ │ ├── row.ts │ │ └── string_entry.ts │ │ ├── html │ │ ├── html_attribute.ts │ │ ├── html_document.ts │ │ ├── html_element.ts │ │ ├── html_script_element.ts │ │ ├── html_style_element.ts │ │ └── tags.ts │ │ ├── js │ │ ├── assignment.ts │ │ ├── async_function.ts │ │ ├── await_expression.ts │ │ ├── binary_operator.ts │ │ ├── call_member.ts │ │ ├── call_variable.ts │ │ ├── declared_identifier.ts │ │ ├── expression.ts │ │ ├── functions.ts │ │ ├── if.ts │ │ ├── import.ts │ │ ├── import_default.ts │ │ ├── inline_function.ts │ │ ├── javascript_file.ts │ │ ├── list.ts │ │ ├── literals.ts │ │ ├── logical_expression.ts │ │ ├── lookup_expression.ts │ │ ├── member_expression.ts │ │ ├── object_expression.ts │ │ ├── object_property.ts │ │ ├── return.ts │ │ ├── variable_declaration.ts │ │ └── variable_reference.ts │ │ ├── jss_styles │ │ ├── jss_class_block.ts │ │ ├── jss_class_reference.ts │ │ ├── jss_hover_block.ts │ │ ├── jss_style_block.ts │ │ └── jss_style_property.ts │ │ └── styles │ │ ├── style_property.ts │ │ ├── style_rule.ts │ │ └── style_selector.ts ├── runtime-python │ ├── .gitignore │ ├── package.json │ ├── rollup.config.js │ ├── src │ │ ├── autocomplete.ts │ │ ├── autocomplete_webworker.ts │ │ ├── index.ts │ │ ├── message_types.ts │ │ ├── pyodide.ts │ │ ├── pyright.ts │ │ ├── runtime │ │ │ ├── autocomplete-worker-manager.ts │ │ │ ├── common.ts │ │ │ ├── index.ts │ │ │ └── worker-manager.ts │ │ ├── static_urls.ts │ │ └── webworker.ts │ └── tsconfig.json └── runtime-web │ ├── .gitignore │ ├── index.ts │ ├── serviceworker.ts │ └── tsconfig.json ├── public └── static │ ├── generated │ └── .gitignore │ └── projects │ ├── blank │ ├── main │ │ ├── index.html.sp │ │ └── package.sp │ └── project.sp │ ├── blankpython │ ├── main │ │ ├── main.py.sp │ │ └── package.sp │ └── project.sp │ ├── bouncing │ ├── main │ │ ├── app.js.sp │ │ ├── index.html.sp │ │ └── package.sp │ └── project.sp │ ├── flashcards │ ├── main │ │ ├── app.js.sp │ │ ├── index.html.sp │ │ ├── package.sp │ │ └── words.sheet.sp │ └── project.sp │ ├── gallery │ ├── main │ │ ├── gallery.js.sp │ │ ├── index.html.sp │ │ ├── package.sp │ │ └── setupjss.js.sp │ └── project.sp │ ├── helloname │ ├── main │ │ ├── main.py.sp │ │ └── package.sp │ └── project.sp │ ├── secret_password │ ├── main │ │ ├── main.py.sp │ │ └── package.sp │ └── project.sp │ └── temperature_conversion │ ├── main │ ├── main.py.sp │ └── package.sp │ └── project.sp ├── python ├── .gitignore ├── convert_ast.py ├── executor.py ├── generate.sh ├── generate_builtins.py ├── generate_tray.py ├── library │ ├── overrides.yaml │ ├── sploot-node-docs.yaml │ └── tray_categories.yaml ├── module_loader.py ├── packages │ ├── requests-2.28.1-py3-none-any.whl │ ├── requests-2.28.2-py3-none-any.whl │ ├── stlite_pyarrow-0.1.0-py3-none-any.whl │ └── streamlit-1.19.0-py2.py3-none-any.whl ├── requirements.txt ├── tests │ ├── __init__.py │ ├── test_annotation_limits_executor.py │ ├── test_convert_ast.py │ ├── test_execute.py │ ├── test_loop_annotations.py │ ├── test_module_loader.py │ ├── test_text_generator.py │ └── test_tray_generation.py └── text_generator.py ├── reactpreview.config.js ├── scripts ├── dummy.ts ├── generateLibs.ts └── pyright_tray_generate.ts ├── splootframepythonclient.html ├── splootstreamlitpythonclient.html ├── src-runtime ├── autocomplete_webworker.ts ├── index.ts ├── python.d.ts ├── runtime_webworker.ts └── static_urls.ts ├── src ├── app.css ├── app.tsx ├── code_io │ └── static_projects.ts ├── index.d.ts ├── index.tsx ├── module_loader.ts ├── pages │ ├── project_editor.css │ ├── project_editor.tsx │ ├── python_editor.css │ ├── python_editor_panels.tsx │ └── user_home.tsx ├── providers.tsx └── template.html ├── tsconfig.json ├── vite-runtime.config.ts ├── vite.config.ts └── yarn.lock /.env.development: -------------------------------------------------------------------------------- 1 | SPLOOT_FRAME_VIEW_DOMAIN=localhost:3001 2 | SPLOOT_FRAME_VIEW_SCHEME=http 3 | SPLOOT_EDITOR_DOMAIN=http://localhost:3000 4 | SPLOOT_RUNTIME_PYTHON_STATIC_FOLDER=/runtime-python/static 5 | SPLOOT_TYPESHED_PATH=/static/typeshed/ 6 | SPLOOT_GOOGLE_ANALYTICS_ID 7 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | dist-runtime 4 | frame-dist 5 | lib 6 | out -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", // Specifies the ESLint parser 4 | "parserOptions": { 5 | "ecmaVersion": 2020, 6 | "sourceType": "module", 7 | "ecmaFeatures": { 8 | "jsx": true 9 | } 10 | }, 11 | "settings": { 12 | "react": { 13 | "version": "detect" 14 | } 15 | }, 16 | "extends": [ 17 | "plugin:react/recommended", 18 | "plugin:@typescript-eslint/recommended", 19 | "plugin:prettier/recommended" 20 | ], 21 | "plugins": ["unused-imports", "sort-imports-es6-autofix", "import"], 22 | "rules": { 23 | // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs 24 | "@typescript-eslint/no-unused-vars": ["error", { "vars": "all", "args": "none", "ignoreRestSiblings": false }], 25 | "@typescript-eslint/no-empty-interface": ["off"], 26 | "@typescript-eslint/no-explicit-any": ["off"], 27 | "@typescript-eslint/ban-ts-comment": ["off"], 28 | "@typescript-eslint/no-empty-function": ["off"], 29 | "import/no-duplicates": ["error"], 30 | "unused-imports/no-unused-imports": "error", 31 | "sort-imports-es6-autofix/sort-imports-es6": ["error", { 32 | "ignoreCase": false, 33 | "ignoreMemberSort": false, 34 | "memberSyntaxSortOrder": ["none", "all", "single", "multiple"] 35 | }] 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build and Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | build-and-test: 13 | name: Build prod 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Check out Git repository 18 | uses: actions/checkout@v2 19 | - name: Setup python 20 | uses: actions/setup-python@v2 21 | with: 22 | python-version: '3.10' 23 | - name: Yarn install 24 | run: yarn 25 | - name: Run Python generation 26 | run: cd python && ./generate.sh && cd .. 27 | - name: Build packages 28 | run: yarn build:packages 29 | - name: Check packages 30 | run: yarn check:packages 31 | - name: Check types 32 | run: yarn tsc 33 | - name: Test 34 | run: yarn test 35 | - name: Build app 36 | run: yarn build:app 37 | - name: Build runtime 38 | run: yarn build:runtime 39 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | run-linters: 13 | name: Run linters 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Check out Git repository 18 | uses: actions/checkout@v2 19 | - name: Yarn install 20 | run: yarn 21 | - name: Lint 22 | run: yarn lint 23 | -------------------------------------------------------------------------------- /.github/workflows/python_tests.yml: -------------------------------------------------------------------------------- 1 | name: Python tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | run-python-tests: 13 | name: Run tests 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Check out Git repository 18 | uses: actions/checkout@v2 19 | - name: Set up Python 20 | uses: actions/setup-python@v2 21 | with: 22 | python-version: '3.10' 23 | - name: Run tests 24 | run: cd python && pip install -r requirements.txt && python -m unittest 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Python 2 | __pycache__ 3 | 4 | # non-dev env files 5 | production.env 6 | 7 | # Firebase files 8 | .firebase 9 | 10 | # Terraform 11 | .terraform 12 | 13 | lib 14 | 15 | # Mac files 16 | .DS_Store 17 | 18 | # Logs 19 | logs 20 | *.log 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | lerna-debug.log* 25 | 26 | # Diagnostic reports (https://nodejs.org/api/report.html) 27 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 28 | 29 | # Runtime data 30 | pids 31 | *.pid 32 | *.seed 33 | *.pid.lock 34 | 35 | # Directory for instrumented libs generated by jscoverage/JSCover 36 | lib-cov 37 | 38 | # Coverage directory used by tools like istanbul 39 | coverage 40 | *.lcov 41 | 42 | # nyc test coverage 43 | .nyc_output 44 | 45 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 46 | .grunt 47 | 48 | # Bower dependency directory (https://bower.io/) 49 | bower_components 50 | 51 | # node-waf configuration 52 | .lock-wscript 53 | 54 | # Compiled binary addons (https://nodejs.org/api/addons.html) 55 | build/Release 56 | 57 | # Dependency directories 58 | node_modules/ 59 | jspm_packages/ 60 | 61 | # TypeScript v1 declaration files 62 | typings/ 63 | 64 | # TypeScript cache 65 | *.tsbuildinfo 66 | 67 | # Optional npm cache directory 68 | .npm 69 | 70 | # Optional eslint cache 71 | .eslintcache 72 | 73 | # Microbundle cache 74 | .rpt2_cache/ 75 | .rts2_cache_cjs/ 76 | .rts2_cache_es/ 77 | .rts2_cache_umd/ 78 | 79 | # Optional REPL history 80 | .node_repl_history 81 | 82 | # Output of 'npm pack' 83 | *.tgz 84 | 85 | # Yarn Integrity file 86 | .yarn-integrity 87 | 88 | # parcel-bundler cache (https://parceljs.org/) 89 | .cache 90 | 91 | # Next.js build output 92 | .next 93 | 94 | # Nuxt.js build / generate output 95 | .nuxt 96 | dist 97 | dist-runtime 98 | frame-dist 99 | 100 | # Gatsby files 101 | .cache/ 102 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 103 | # https://nextjs.org/blog/next-9-1#public-directory-support 104 | # public 105 | 106 | # vuepress build output 107 | .vuepress/dist 108 | 109 | # Serverless directories 110 | .serverless/ 111 | 112 | # FuseBox cache 113 | .fusebox/ 114 | 115 | # DynamoDB Local files 116 | .dynamodb/ 117 | 118 | # TernJS port file 119 | .tern-port 120 | 121 | .rollup.cache -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "trailingComma": "es5", 4 | "singleQuote": true, 5 | "printWidth": 120, 6 | "tabWidth": 2 7 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.formatting.provider": "black", 3 | "editor.codeActionsOnSave": { 4 | "source.fixAll.eslint": "explicit" 5 | }, 6 | } -------------------------------------------------------------------------------- /SplootCode-screenshot-readme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SplootCode/splootcode/d621bcd5f30df97150d3e1388f60307636568780/SplootCode-screenshot-readme.png -------------------------------------------------------------------------------- /babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["@babel/plugin-proposal-decorators", { "legacy": true }], 4 | ["@babel/plugin-proposal-class-properties", { "loose": false }] 5 | ] 6 | } -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | SplootCode 11 | 12 | 13 |
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from '@jest/types' 2 | 3 | const config: Config.InitialOptions = { 4 | preset: 'ts-jest', 5 | verbose: true, 6 | testPathIgnorePatterns: ['node_modules', 'lib', 'dist', 'out'], 7 | moduleNameMapper: { 8 | '\\.(css|less)$': 'identity-obj-proxy', 9 | }, 10 | } 11 | export default config 12 | -------------------------------------------------------------------------------- /packages/components/.gitignore: -------------------------------------------------------------------------------- 1 | # non-dev env files 2 | production.env 3 | 4 | # Firebase files 5 | .firebase 6 | 7 | lib 8 | 9 | # Terraform 10 | .terraform 11 | 12 | # Lambda zips 13 | 14 | # Mac files 15 | .DS_Store 16 | 17 | # Logs 18 | logs 19 | *.log 20 | npm-debug.log* 21 | yarn-debug.log* 22 | yarn-error.log* 23 | lerna-debug.log* 24 | 25 | # Diagnostic reports (https://nodejs.org/api/report.html) 26 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 27 | 28 | # Runtime data 29 | pids 30 | *.pid 31 | *.seed 32 | *.pid.lock 33 | 34 | # Directory for instrumented libs generated by jscoverage/JSCover 35 | lib-cov 36 | 37 | # Coverage directory used by tools like istanbul 38 | coverage 39 | *.lcov 40 | 41 | # nyc test coverage 42 | .nyc_output 43 | 44 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 45 | .grunt 46 | 47 | # Bower dependency directory (https://bower.io/) 48 | bower_components 49 | 50 | # node-waf configuration 51 | .lock-wscript 52 | 53 | # Compiled binary addons (https://nodejs.org/api/addons.html) 54 | build/Release 55 | 56 | # Dependency directories 57 | node_modules/ 58 | jspm_packages/ 59 | 60 | # TypeScript v1 declaration files 61 | typings/ 62 | 63 | # TypeScript cache 64 | *.tsbuildinfo 65 | 66 | # Optional npm cache directory 67 | .npm 68 | 69 | # Optional eslint cache 70 | .eslintcache 71 | 72 | # Microbundle cache 73 | .rpt2_cache/ 74 | .rts2_cache_cjs/ 75 | .rts2_cache_es/ 76 | .rts2_cache_umd/ 77 | 78 | # Optional REPL history 79 | .node_repl_history 80 | 81 | # Output of 'npm pack' 82 | *.tgz 83 | 84 | # Yarn Integrity file 85 | .yarn-integrity 86 | 87 | # parcel-bundler cache (https://parceljs.org/) 88 | .cache 89 | 90 | # Next.js build output 91 | .next 92 | 93 | # Nuxt.js build / generate output 94 | .nuxt 95 | dist 96 | out 97 | 98 | # Gatsby files 99 | .cache/ 100 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 101 | # https://nextjs.org/blog/next-9-1#public-directory-support 102 | # public 103 | 104 | # vuepress build output 105 | .vuepress/dist 106 | 107 | # Serverless directories 108 | .serverless/ 109 | 110 | # FuseBox cache 111 | .fusebox/ 112 | 113 | # DynamoDB Local files 114 | .dynamodb/ 115 | 116 | # TernJS port file 117 | .tern-port 118 | -------------------------------------------------------------------------------- /packages/components/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@splootcode/components", 3 | "version": "1.0.0", 4 | "type": "module", 5 | "main": "./dist/index.js", 6 | "module": "./dist/index.mjs", 7 | "styles": "./dist/index.css", 8 | "types": "./dist/index.d.ts", 9 | "exports": { 10 | ".": { 11 | "import": "./dist/index.mjs", 12 | "require": "./dist/index.js" 13 | }, 14 | "./styles.css": { 15 | "import": "./dist/index.css" 16 | } 17 | }, 18 | "files": [ 19 | "dist/index.css" 20 | ], 21 | "scripts": { 22 | "test": "echo 'No tests for components package.'", 23 | "build": "yarn rollup -c" 24 | }, 25 | "private": true, 26 | "peerDependencies": { 27 | "@chakra-ui/icons": "^1.0.4", 28 | "@chakra-ui/react": "^1.6.9", 29 | "@emotion/react": "^11.1.4", 30 | "@emotion/styled": "^11.0.0", 31 | "react": "17.0.2", 32 | "react-router-dom": "^5.1.2" 33 | }, 34 | "devDependencies": { 35 | "@rollup/plugin-node-resolve": "^15.0.2", 36 | "@rollup/plugin-typescript": "^11.1.0", 37 | "@types/react": "^17.0.2", 38 | "@types/react-router-dom": "^5.3.3", 39 | "react-icons": "^4.4.0", 40 | "rollup": "3.20.2", 41 | "rollup-plugin-dts": "^5.3.0", 42 | "rollup-plugin-import-css": "^3.2.1", 43 | "tslib": "^1.10.0", 44 | "typescript": "^4.8.4" 45 | }, 46 | "dependencies": {} 47 | } 48 | -------------------------------------------------------------------------------- /packages/components/rollup.config.js: -------------------------------------------------------------------------------- 1 | import css from 'rollup-plugin-import-css' 2 | import dts from 'rollup-plugin-dts' 3 | import typescript from '@rollup/plugin-typescript' 4 | import { nodeResolve } from '@rollup/plugin-node-resolve' 5 | 6 | export default [ 7 | { 8 | external: ['react', '@emotion/core', '@emotion/styled', '@chakra-ui/react', '@chakra-ui/icons'], 9 | plugins: [typescript(), css(), nodeResolve({ resolveOnly: ['react-icons'] })], 10 | input: 'src/index.ts', 11 | output: [ 12 | { 13 | file: `dist/index.mjs`, 14 | format: 'es', 15 | sourcemap: true, 16 | }, 17 | { 18 | file: `dist/index.js`, 19 | format: 'cjs', 20 | sourcemap: true, 21 | }, 22 | ], 23 | }, 24 | { 25 | input: 'src/index.ts', 26 | output: [{ file: `dist/index.d.ts`, format: 'es' }], 27 | plugins: [dts(), css()], 28 | }, 29 | ] 30 | -------------------------------------------------------------------------------- /packages/components/src/index.ts: -------------------------------------------------------------------------------- 1 | export { MenuBar, MenuBarItem } from './menu_bar' 2 | export { SaveProjectModal } from './save_project_modal' 3 | export type { MainMenuItem, MenuBarProps } from './menu_bar' 4 | export { RunTypeIcon } from './run_type_icon' 5 | export { ExportTextModal } from './export_text_modal' 6 | -------------------------------------------------------------------------------- /packages/components/src/menu_bar.css: -------------------------------------------------------------------------------- 1 | #menubar { 2 | display: flex; 3 | justify-content: space-between; 4 | height: 36px; 5 | padding: 0px 4px; 6 | background-color: var(--chakra-colors-gray-800); 7 | box-sizing: border-box; 8 | border-bottom: 1px solid var(--chakra-colors-gray-900); 9 | } 10 | 11 | .menubar-left { 12 | display: inline-block; 13 | } 14 | 15 | .menubar-center { 16 | display: flex; 17 | align-items: center; 18 | gap: 8px; 19 | } 20 | 21 | .menubar-item { 22 | display: inline-block; 23 | min-width: 60px; 24 | } 25 | 26 | .menubar-right { 27 | display: flex; 28 | align-items: center; 29 | } 30 | 31 | .menubar-separator { 32 | background-color: var(--chakra-colors-green-500); 33 | height: 16px; 34 | width: 1px; 35 | display: inline-block; 36 | } -------------------------------------------------------------------------------- /packages/components/src/menu_bar.tsx: -------------------------------------------------------------------------------- 1 | import './menu_bar.css' 2 | import React from 'react' 3 | import { Button, IconButton, Menu, MenuButton, MenuItem, MenuList } from '@chakra-ui/react' 4 | import { HamburgerIcon } from '@chakra-ui/icons' 5 | import { Link } from 'react-router-dom' 6 | 7 | export interface MainMenuItem { 8 | name: string 9 | disabled?: boolean 10 | onClick: () => void 11 | } 12 | 13 | export interface MenuBarProps { 14 | children?: React.ReactNode 15 | menuItems: MainMenuItem[] 16 | } 17 | 18 | export const MenuBarItem = ({ children }: { children: React.ReactNode }): React.ReactElement => { 19 | return
{children}
20 | } 21 | 22 | export const MenuBar = ({ children, menuItems }: MenuBarProps): React.ReactElement => { 23 | return ( 24 | 53 | ) 54 | } 55 | -------------------------------------------------------------------------------- /packages/components/src/run_type_icon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { AiOutlineClockCircle } from 'react-icons/ai' 3 | import { BiGlobe } from 'react-icons/bi' 4 | import { FaStream, FaTerminal } from 'react-icons/fa' 5 | import { Icon } from '@chakra-ui/react' 6 | import { RunType } from '@splootcode/core' 7 | 8 | export function RunTypeIcon(props: { runType: RunType }) { 9 | switch (props.runType) { 10 | case RunType.COMMAND_LINE: 11 | return 12 | case RunType.HTTP_REQUEST: 13 | return 14 | case RunType.SCHEDULE: 15 | return 16 | case RunType.STREAMLIT: 17 | return 18 | default: 19 | return 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/components/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "ES2020", 5 | "lib": ["DOM", "ES2020"], 6 | "moduleResolution": "node", 7 | "jsx": "react", 8 | "esModuleInterop": true, 9 | "skipLibCheck": true, 10 | "allowSyntheticDefaultImports": true, 11 | "experimentalDecorators": true, 12 | "resolveJsonModule": true, 13 | "skipDefaultLibCheck": true, 14 | "downlevelIteration": true, 15 | "noEmit": true, 16 | "baseUrl": ".", 17 | }, 18 | "include": ["src"], 19 | "exclude": [ 20 | "node_modules", 21 | "dist", 22 | "out", 23 | "*.config.*" 24 | ], 25 | } 26 | -------------------------------------------------------------------------------- /packages/core/.gitignore: -------------------------------------------------------------------------------- 1 | # non-dev env files 2 | production.env 3 | 4 | # Firebase files 5 | .firebase 6 | 7 | lib 8 | 9 | # Terraform 10 | .terraform 11 | 12 | # Lambda zips 13 | 14 | # Mac files 15 | .DS_Store 16 | 17 | # Logs 18 | logs 19 | *.log 20 | npm-debug.log* 21 | yarn-debug.log* 22 | yarn-error.log* 23 | lerna-debug.log* 24 | 25 | # Diagnostic reports (https://nodejs.org/api/report.html) 26 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 27 | 28 | # Runtime data 29 | pids 30 | *.pid 31 | *.seed 32 | *.pid.lock 33 | 34 | # Directory for instrumented libs generated by jscoverage/JSCover 35 | lib-cov 36 | 37 | # Coverage directory used by tools like istanbul 38 | coverage 39 | *.lcov 40 | 41 | # nyc test coverage 42 | .nyc_output 43 | 44 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 45 | .grunt 46 | 47 | # Bower dependency directory (https://bower.io/) 48 | bower_components 49 | 50 | # node-waf configuration 51 | .lock-wscript 52 | 53 | # Compiled binary addons (https://nodejs.org/api/addons.html) 54 | build/Release 55 | 56 | # Dependency directories 57 | node_modules/ 58 | jspm_packages/ 59 | 60 | # TypeScript v1 declaration files 61 | typings/ 62 | 63 | # TypeScript cache 64 | *.tsbuildinfo 65 | 66 | # Optional npm cache directory 67 | .npm 68 | 69 | # Optional eslint cache 70 | .eslintcache 71 | 72 | # Microbundle cache 73 | .rpt2_cache/ 74 | .rts2_cache_cjs/ 75 | .rts2_cache_es/ 76 | .rts2_cache_umd/ 77 | 78 | # Optional REPL history 79 | .node_repl_history 80 | 81 | # Output of 'npm pack' 82 | *.tgz 83 | 84 | # Yarn Integrity file 85 | .yarn-integrity 86 | 87 | # parcel-bundler cache (https://parceljs.org/) 88 | .cache 89 | 90 | # Next.js build output 91 | .next 92 | 93 | # Nuxt.js build / generate output 94 | .nuxt 95 | dist 96 | out 97 | 98 | # Gatsby files 99 | .cache/ 100 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 101 | # https://nextjs.org/blog/next-9-1#public-directory-support 102 | # public 103 | 104 | # vuepress build output 105 | .vuepress/dist 106 | 107 | # Serverless directories 108 | .serverless/ 109 | 110 | # FuseBox cache 111 | .fusebox/ 112 | 113 | # DynamoDB Local files 114 | .dynamodb/ 115 | 116 | # TernJS port file 117 | .tern-port 118 | -------------------------------------------------------------------------------- /packages/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@splootcode/core", 3 | "version": "1.0.0", 4 | "type": "module", 5 | "main": "dist/index.js", 6 | "module": "dist/index.mjs", 7 | "typings": "dist/index.d.ts", 8 | "exports": { 9 | ".": { 10 | "import": "./dist/index.mjs", 11 | "require": "./dist/index.js", 12 | "types": "./dist/index.d.ts" 13 | } 14 | }, 15 | "private": true, 16 | "scripts": { 17 | "test": "echo 'No tests for core package.'", 18 | "build": "yarn rollup -c" 19 | }, 20 | "devDependencies": { 21 | "@rollup/plugin-node-resolve": "^15.0.2", 22 | "@rollup/plugin-typescript": "^11.1.0", 23 | "@types/wicg-file-system-access": "^2020.9.1", 24 | "rollup": "3.20.2", 25 | "rollup-plugin-dts": "^5.3.0", 26 | "typescript": "^4.8.4" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/core/rollup.config.js: -------------------------------------------------------------------------------- 1 | import dts from 'rollup-plugin-dts' 2 | import typescript from '@rollup/plugin-typescript' 3 | 4 | export default [ 5 | { 6 | plugins: [typescript()], 7 | input: 'src/index.ts', 8 | output: [ 9 | { 10 | file: `dist/index.mjs`, 11 | format: 'es', 12 | sourcemap: true, 13 | }, 14 | { 15 | file: `dist/index.js`, 16 | format: 'cjs', 17 | sourcemap: true, 18 | }, 19 | ], 20 | }, 21 | { 22 | input: 'src/index.ts', 23 | output: [{ file: `dist/index.d.ts`, format: 'es' }], 24 | plugins: [dts()], 25 | }, 26 | ] 27 | -------------------------------------------------------------------------------- /packages/core/src/code_io/filesystem.ts: -------------------------------------------------------------------------------- 1 | import { FileSystemFileLoader } from './filesystem_file_loader' 2 | import { LocalStorageProjectLoader } from './local_storage_project_loader' 3 | import { Project, SerializedProject } from '../language/projects/project' 4 | import { SplootPackage } from '../language/projects/package' 5 | 6 | async function savePackage(directoryHandle: FileSystemDirectoryHandle, project: Project, pack: SplootPackage) { 7 | const packDir = await directoryHandle.getDirectoryHandle(pack.name, { create: true }) 8 | const packFile = await packDir.getFileHandle('package.sp', { create: true }) 9 | // @ts-ignore 10 | const writable = await packFile.createWritable() 11 | await writable.write(pack.serialize()) 12 | await writable.close() 13 | // Save each file 14 | const promises = pack.fileOrder.map(async (filename) => { 15 | const fileHandle = await packDir.getFileHandle(filename + '.sp', { create: true }) 16 | // @ts-ignore 17 | const writable = await fileHandle.createWritable() 18 | await writable.write(pack.files[filename].serialize()) 19 | await writable.close() 20 | }) 21 | await Promise.all(promises) 22 | } 23 | 24 | export async function exportProjectToFolder(directoryHandle: FileSystemDirectoryHandle, project: Project) { 25 | // Write project file 26 | const fileHandle = await directoryHandle.getFileHandle('project.sp', { create: true }) 27 | // @ts-ignore 28 | const writable = await fileHandle.createWritable() 29 | await writable.write(project.serialize()) 30 | await writable.close() 31 | // Save each package 32 | project.packages.forEach((pack) => { 33 | savePackage(directoryHandle, project, pack) 34 | }) 35 | } 36 | 37 | export async function loadProjectFromFolder(directoryHandle: FileSystemDirectoryHandle): Promise { 38 | const fileLoader = new FileSystemFileLoader(directoryHandle) 39 | const projStr = await (await (await directoryHandle.getFileHandle('project.sp')).getFile()).text() 40 | const proj = JSON.parse(projStr) as SerializedProject 41 | const packages = proj.packages.map(async (packRef) => { 42 | return fileLoader.loadPackage('unknown', proj.name, packRef.name) 43 | }) 44 | 45 | return new Project('local', proj, await Promise.all(packages), fileLoader, new LocalStorageProjectLoader()) 46 | } 47 | -------------------------------------------------------------------------------- /packages/core/src/code_io/filesystem_file_loader.ts: -------------------------------------------------------------------------------- 1 | import { FileLoader, SaveError } from '../language/projects/file_loader' 2 | import { SerializedNode, deserializeNode } from '../language/type_registry' 3 | import { SerializedSplootPackage, SplootPackage } from '../language/projects/package' 4 | import { SplootFile } from '../language/projects/file' 5 | import { SplootNode } from '../language/node' 6 | 7 | export class FileSystemFileLoader implements FileLoader { 8 | directoryHandle: FileSystemDirectoryHandle 9 | 10 | constructor(directoryHandle: FileSystemDirectoryHandle) { 11 | this.directoryHandle = directoryHandle 12 | } 13 | 14 | isReadOnly() { 15 | return true 16 | } 17 | 18 | async loadPackage(ownerID: string, projectId: string, packageId: string): Promise { 19 | const packDirHandle = await this.directoryHandle.getDirectoryHandle(packageId) 20 | const packStr = await (await (await packDirHandle.getFileHandle('package.sp')).getFile()).text() 21 | const pack = JSON.parse(packStr) as SerializedSplootPackage 22 | return new SplootPackage(ownerID, projectId, pack) 23 | } 24 | 25 | async loadFile(ownerID: string, projectId: string, packageId: string, filename: string): Promise { 26 | const packDirHandle = await this.directoryHandle.getDirectoryHandle(packageId) 27 | const fileStr = await (await (await packDirHandle.getFileHandle(filename + '.sp')).getFile()).text() 28 | const serNode = JSON.parse(fileStr) as SerializedNode 29 | const rootNode = deserializeNode(serNode) 30 | return rootNode 31 | } 32 | 33 | async saveFile(ownerID: string, projectId: string, packageId: string, file: SplootFile): Promise { 34 | throw new SaveError('Cannot save readonly file.') 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/core/src/code_io/local_storage_file_loader.ts: -------------------------------------------------------------------------------- 1 | import { FileLoader } from '../language/projects/file_loader' 2 | import { LocalStorageProjectLoader } from './local_storage_project_loader' 3 | import { SerializedNode, deserializeNode } from '../language/type_registry' 4 | import { SerializedSplootPackage, SplootPackage } from '../language/projects/package' 5 | import { SplootFile } from '../language/projects/file' 6 | import { SplootNode } from '../language/node' 7 | 8 | export class LocalStorageFileLoader implements FileLoader { 9 | projectLoader: LocalStorageProjectLoader 10 | 11 | constructor(projectLoader: LocalStorageProjectLoader) { 12 | this.projectLoader = projectLoader 13 | } 14 | 15 | isReadOnly() { 16 | return false 17 | } 18 | 19 | getProjKey(ownerId: string, projectId: string) { 20 | if (ownerId === 'local') { 21 | return `project/${projectId}` 22 | } 23 | return `project/${ownerId}/${projectId}` 24 | } 25 | 26 | async loadPackage(ownerID: string, projectID: string, packageID: string): Promise { 27 | const packageKey = `${this.getProjKey(ownerID, projectID)}/${packageID}` 28 | const packStr = window.localStorage.getItem(packageKey) 29 | const pack = JSON.parse(packStr) as SerializedSplootPackage 30 | return new SplootPackage(ownerID, projectID, pack) 31 | } 32 | 33 | async loadFile(ownerID: string, projectID: string, packageID: string, filename: string): Promise { 34 | const fileKey = `${this.getProjKey(ownerID, projectID)}/${packageID}/${filename}` 35 | const fileStr = window.localStorage.getItem(fileKey) 36 | const serNode = JSON.parse(fileStr) as SerializedNode 37 | const rootNode = deserializeNode(serNode) 38 | return rootNode 39 | } 40 | 41 | async saveFile( 42 | ownerID: string, 43 | projectID: string, 44 | packageID: string, 45 | file: SplootFile, 46 | base_version: string 47 | ): Promise { 48 | const fileKey = `${this.getProjKey(ownerID, projectID)}/${packageID}/${file.name}` 49 | window.localStorage.setItem(fileKey, file.serialize()) 50 | // Randomly generate new version 51 | const newVersion = (Math.random() + 1).toString(36) 52 | return newVersion 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /packages/core/src/code_io/static_file_loader.ts: -------------------------------------------------------------------------------- 1 | import { FileLoader, SaveError } from '../language/projects/file_loader' 2 | import { SerializedNode, deserializeNode } from '../language/type_registry' 3 | import { SerializedSplootPackage, SplootPackage } from '../language/projects/package' 4 | import { SplootFile } from '../language/projects/file' 5 | import { SplootNode } from '../language/node' 6 | 7 | export class StaticFileLoader implements FileLoader { 8 | rootProjectUrl: string 9 | 10 | constructor(rootProjectUrl: string) { 11 | if (!rootProjectUrl.endsWith('/')) { 12 | rootProjectUrl += '/' 13 | } 14 | this.rootProjectUrl = rootProjectUrl 15 | } 16 | 17 | isReadOnly() { 18 | return true 19 | } 20 | 21 | async loadPackage(ownerID: string, projectId: string, packageId: string): Promise { 22 | const packStr = await (await fetch(this.rootProjectUrl + packageId + '/package.sp')).text() 23 | const pack = JSON.parse(packStr) as SerializedSplootPackage 24 | return new SplootPackage(ownerID, projectId, pack) 25 | } 26 | 27 | async loadFile(ownerID: string, projectId: string, packageId: string, filename: string): Promise { 28 | const fileStr = await (await fetch(this.rootProjectUrl + packageId + '/' + filename + '.sp')).text() 29 | const serNode = JSON.parse(fileStr) as SerializedNode 30 | const rootNode = deserializeNode(serNode) 31 | return rootNode 32 | } 33 | 34 | async saveFile(ownerID: string, projectId: string, packageId: string, file: SplootFile): Promise { 35 | throw new SaveError('Cannot save readonly file.') 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/core/src/colors.ts: -------------------------------------------------------------------------------- 1 | export enum HighlightColorCategory { 2 | NONE = 0, 3 | FUNCTION, 4 | FUNCTION_DEFINITION, 5 | VARIABLE, 6 | VARIABLE_DECLARATION, 7 | LITERAL_NUMBER, 8 | LITERAL_STRING, 9 | LITERAL_LIST, 10 | CONTROL, 11 | KEYWORD, 12 | OPERATOR, 13 | HTML_ELEMENT, 14 | HTML_ATTRIBUTE, 15 | STYLE_RULE, 16 | STYLE_PROPERTY, 17 | COMMENT, 18 | } 19 | 20 | export enum ColorUsageType { 21 | NODE_TEXT = 0, 22 | CAP_TEXT, 23 | NODE_FILL, 24 | CAP_FILL, 25 | } 26 | 27 | function colorBase(category: HighlightColorCategory): string { 28 | switch (category) { 29 | case HighlightColorCategory.FUNCTION: 30 | return 'code-yellow' 31 | case HighlightColorCategory.FUNCTION_DEFINITION: 32 | return 'code-purple' 33 | case HighlightColorCategory.VARIABLE_DECLARATION: 34 | return 'code-purple' 35 | case HighlightColorCategory.VARIABLE: 36 | return 'code-lightblue' 37 | case HighlightColorCategory.LITERAL_NUMBER: 38 | return 'code-blue' 39 | case HighlightColorCategory.LITERAL_STRING: 40 | return 'code-neutral' 41 | case HighlightColorCategory.LITERAL_LIST: 42 | return 'code-purple' 43 | case HighlightColorCategory.KEYWORD: 44 | return 'code-purple' 45 | case HighlightColorCategory.CONTROL: 46 | return 'code-purple' 47 | case HighlightColorCategory.COMMENT: 48 | return 'code-comment' 49 | } 50 | return 'code-neutral' 51 | } 52 | 53 | export function getColor(category: HighlightColorCategory, usage: ColorUsageType): string { 54 | // Hardcode for now, but could be configurable in the future 55 | const base = colorBase(category) 56 | if (usage == ColorUsageType.CAP_TEXT) { 57 | return `var(--${base}-cap-text)` 58 | } 59 | if (usage === ColorUsageType.NODE_FILL) { 60 | return `var(--editor-node-block-fill)` 61 | } 62 | if (usage === ColorUsageType.CAP_FILL) { 63 | return `var(--editor-node-block-fill)` 64 | } 65 | return `var(--${base}-text)` 66 | } 67 | -------------------------------------------------------------------------------- /packages/core/src/feature_flags/feature_flags.ts: -------------------------------------------------------------------------------- 1 | export const ENABLE_DEPLOYMENTS_FLAG = 'ENABLE_DEPLOYMENTS' 2 | export const ENABLE_HTTP_APPS_FLAG = 'ENABLE_HTTP_APPS_FLAG' 3 | export const ENABLE_STREAMLIT_APPS_FLAG = 'ENABLE_STREAMLIT_APPS_FLAG' 4 | export const ENABLE_INSTALLABLE_PACKAGES_FLAG = 'ENABLE_INSTALLABLE_PACKAGES_FLAG' 5 | export const ENABLE_TUTORIALS_FLAG = 'ENABLE_TUTORIALS_FLAG' 6 | 7 | const supportedFlags = new Map([ 8 | [ENABLE_DEPLOYMENTS_FLAG, true], 9 | [ENABLE_HTTP_APPS_FLAG, false], 10 | [ENABLE_STREAMLIT_APPS_FLAG, false], 11 | [ENABLE_INSTALLABLE_PACKAGES_FLAG, false], 12 | [ENABLE_TUTORIALS_FLAG, false], 13 | ]) 14 | 15 | /* 16 | To enable for yourself in local storage: 17 | localStorage.setItem('SPLOOT_FEATURE_FLAGS', JSON.stringify({'ENABLE_INSTALLABLE_PACKAGES_FLAG': true, 'ENABLE_HTTP_APPS_FLAG': false, 'ENABLE_STREAMLIT_APPS_FLAG': false})) 18 | */ 19 | 20 | export function loadFeatureFlags(): Map { 21 | const flags = new Map(supportedFlags.entries()) 22 | const serializedFlags = localStorage.getItem('SPLOOT_FEATURE_FLAGS') 23 | if (serializedFlags) { 24 | const obj = JSON.parse(serializedFlags) as Record 25 | for (const key of Object.keys(obj)) { 26 | // Skip any flags that we don't use anymore. 27 | if (flags.has(key)) { 28 | flags.set(key, obj[key]) 29 | } 30 | } 31 | } 32 | return flags 33 | } 34 | -------------------------------------------------------------------------------- /packages/core/src/http_types.ts: -------------------------------------------------------------------------------- 1 | export interface HTTPScenario { 2 | id?: number 3 | name: string 4 | method: string 5 | path: string 6 | protocol: string 7 | rawQueryString: string 8 | headers: Record 9 | body: string 10 | isBase64Encoded: boolean 11 | } 12 | 13 | export interface HTTPRequestAWSEvent { 14 | version: string 15 | rawPath: string 16 | rawQueryString: string 17 | cookies: string[] 18 | headers: Record 19 | requestContext: RequestContextEvent 20 | body: string 21 | isBase64Encoded: boolean 22 | } 23 | 24 | export interface RequestContextEvent { 25 | http: HTTPEvent 26 | } 27 | 28 | export interface HTTPEvent { 29 | method: string 30 | path: string 31 | protocol: string 32 | sourceIp: string 33 | userAgent: string 34 | } 35 | 36 | export interface HTTP { 37 | method: string 38 | path: string 39 | protocol: string 40 | } 41 | 42 | export interface HTTPResponse { 43 | statusCode: number 44 | headers: Record 45 | body: string 46 | isBase64Encoded: boolean 47 | } 48 | 49 | export function httpScenarioToHTTPRequestEvent(scenario: HTTPScenario): HTTPRequestAWSEvent { 50 | const sourceIP = '' 51 | let userAgent = '' 52 | let cookies = [] 53 | 54 | const loweredHeaders = Object.fromEntries( 55 | Object.entries(scenario.headers).map(([key, value]) => [key.toLowerCase(), value]) 56 | ) 57 | 58 | if (loweredHeaders['cookie']) { 59 | cookies = loweredHeaders['cookie'].split(';') 60 | } 61 | 62 | if (loweredHeaders['user-agent']) { 63 | userAgent = loweredHeaders['user-agent'] 64 | } 65 | 66 | return { 67 | version: '2.0', 68 | rawPath: scenario.path, 69 | rawQueryString: scenario.rawQueryString, 70 | cookies: cookies, 71 | headers: scenario.headers, 72 | requestContext: { 73 | http: { 74 | method: scenario.method, 75 | path: scenario.path, 76 | protocol: scenario.protocol, 77 | sourceIp: sourceIP, 78 | userAgent: userAgent, 79 | }, 80 | }, 81 | body: scenario.body, 82 | isBase64Encoded: scenario.isBase64Encoded, 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /packages/core/src/language/annotations/annotations.ts: -------------------------------------------------------------------------------- 1 | import { StatementCapture } from '../capture/runtime_capture' 2 | 3 | export enum NodeAnnotationType { 4 | ParseError = 1, 5 | RuntimeError, 6 | Assignment, 7 | ReturnValue, 8 | SideEffect, 9 | LoopIterations, 10 | } 11 | 12 | export type NodeAnnotation = { 13 | type: NodeAnnotationType 14 | value: 15 | | RuntimeErrorAnnotation 16 | | AssignmentAnnotation 17 | | SideEffectAnnotation 18 | | ReturnValueAnnotation 19 | | ParseErrorAnnotation 20 | } 21 | 22 | export type ParseErrorAnnotation = { 23 | message: string 24 | blameChild: number 25 | } 26 | 27 | export type LoopAnnotation = { 28 | label: string 29 | iterations: number 30 | currentFrame: number 31 | } 32 | 33 | export type RuntimeErrorAnnotation = { 34 | errorType: string 35 | errorMessage: string 36 | } 37 | 38 | export type AssignmentAnnotation = { 39 | variableName: string 40 | value: string 41 | type: string 42 | } 43 | 44 | export type SideEffectAnnotation = { 45 | message: string 46 | } 47 | 48 | export type ReturnValueAnnotation = { 49 | type: string 50 | value: string 51 | } 52 | 53 | export function getSideEffectAnnotations(capture: StatementCapture): NodeAnnotation[] { 54 | if (!capture.sideEffects || capture.sideEffects.length === 0) { 55 | return [] 56 | } 57 | const annotations: NodeAnnotation[] = [] 58 | let stdout = capture.sideEffects 59 | .filter((sideEffect) => sideEffect.type === 'stdout') 60 | .map((sideEffect) => sideEffect.value) 61 | .join('') 62 | 63 | if (stdout.length > 20) { 64 | stdout = stdout.substring(0, 17) + '...' 65 | } 66 | return annotations 67 | } 68 | -------------------------------------------------------------------------------- /packages/core/src/language/autocomplete/autocompleter.ts: -------------------------------------------------------------------------------- 1 | import { ParentReference } from '../node' 2 | import { SuggestedNode } from './suggested_node' 3 | import { SuggestionGenerator } from '../node_category_registry' 4 | 5 | export class Autocompleter { 6 | constants: SuggestedNode[] 7 | staticFunctions: SuggestionGenerator[] 8 | dynamicFunctions: SuggestionGenerator[] 9 | prefixOverrides: Map 10 | 11 | constructor( 12 | contants: SuggestedNode[], 13 | staticFunctions: SuggestionGenerator[], 14 | dynamicFunctions: SuggestionGenerator[], 15 | prefixOverrides: Map 16 | ) { 17 | this.constants = contants 18 | this.staticFunctions = staticFunctions 19 | this.dynamicFunctions = dynamicFunctions 20 | this.prefixOverrides = prefixOverrides 21 | } 22 | 23 | getStaticSuggestions(parent: ParentReference, index: number): SuggestedNode[] { 24 | let suggestions: SuggestedNode[] = this.constants 25 | this.staticFunctions.forEach((generator: SuggestionGenerator) => { 26 | suggestions = suggestions.concat(generator.staticSuggestions(parent, index)) 27 | }) 28 | return suggestions 29 | } 30 | 31 | async getDynamicSuggestions(parent: ParentReference, index: number, userInput: string): Promise { 32 | const suggestionSets = await Promise.all( 33 | this.dynamicFunctions.map(async (generator: SuggestionGenerator) => { 34 | return generator.dynamicSuggestions(parent, index, userInput) 35 | }) 36 | ) 37 | 38 | return suggestionSets.flat() 39 | } 40 | 41 | async getPrefixSuggestions(parent: ParentReference, index: number, userInput: string): Promise { 42 | if (this.prefixOverrides.has(userInput[0])) { 43 | const generators = this.prefixOverrides.get(userInput[0]) 44 | 45 | const suggestionSets = await Promise.all( 46 | generators.map(async (generator: SuggestionGenerator) => { 47 | return generator.dynamicSuggestions(parent, index, userInput) 48 | }) 49 | ) 50 | 51 | return suggestionSets.flat() 52 | } 53 | return null 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /packages/core/src/language/autocomplete/suggested_node.ts: -------------------------------------------------------------------------------- 1 | import { ChildSet } from '../childset' 2 | import { SplootNode } from '../node' 3 | 4 | export class SuggestedNode { 5 | node: SplootNode 6 | key: string 7 | exactMatch: string 8 | searchTerms: string 9 | valid: boolean 10 | description: string 11 | wrapChildSetId: string 12 | insertPreviousChildSetId: string 13 | overrideLocationChildSet: ChildSet 14 | overrideLocationIndex: number 15 | 16 | constructor( 17 | node: SplootNode, 18 | key: string, 19 | searchTerms: string, 20 | valid: boolean, 21 | description = '', 22 | wrapChildSetId: string = null 23 | ) { 24 | this.node = node 25 | this.key = `${node.type}_${key}` 26 | this.exactMatch = key 27 | this.searchTerms = searchTerms 28 | this.description = description 29 | this.valid = valid 30 | this.wrapChildSetId = wrapChildSetId 31 | } 32 | 33 | isExactMatch(text: string) { 34 | // Allow exact matches to be case insensitive 35 | return text.toLowerCase() === this.exactMatch.toLowerCase() 36 | } 37 | 38 | isPrefixMatch(text: string) { 39 | // But prefixes only come into play when there's conflicting suggestions 40 | // e.g. "is" and "is not", "else" and "else if" so we'll be a little stricter 41 | // so we don't unnecessarily reject a match. 42 | return this.exactMatch.startsWith(text) 43 | } 44 | 45 | hasOverrideLocation(): boolean { 46 | return !!this.overrideLocationChildSet 47 | } 48 | 49 | setOverrideLocation(childSet: ChildSet, index: number, wrapChildSetId: string = null) { 50 | this.overrideLocationChildSet = childSet 51 | this.overrideLocationIndex = index 52 | this.wrapChildSetId = wrapChildSetId 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /packages/core/src/language/capture/runtime_capture.ts: -------------------------------------------------------------------------------- 1 | export interface SideEffect { 2 | type: string 3 | value: string 4 | } 5 | 6 | export interface WhileLoopData { 7 | frames?: StatementCapture[] 8 | } 9 | 10 | export interface WhileLoopIteration { 11 | condition: StatementCapture[] 12 | block?: StatementCapture[] 13 | } 14 | 15 | export interface ForLoopData { 16 | frames?: StatementCapture[] 17 | } 18 | 19 | export interface ForLoopIteration { 20 | iterable: StatementCapture[] 21 | block?: StatementCapture[] 22 | } 23 | 24 | export interface IfStatementData { 25 | condition: StatementCapture[] 26 | trueblock?: StatementCapture[] 27 | elseblocks?: StatementCapture[] 28 | } 29 | 30 | export interface ImportStatementData { 31 | import: StatementCapture[] 32 | } 33 | 34 | export interface ElseIfStatementData { 35 | condition: StatementCapture[] 36 | block: StatementCapture[] 37 | } 38 | 39 | export interface ElseStatementData { 40 | block: StatementCapture[] 41 | } 42 | 43 | export interface PythonFileData { 44 | body?: StatementCapture[] 45 | } 46 | 47 | export interface FunctionDeclarationData { 48 | count: number 49 | calls: StatementCapture[] 50 | exception: { 51 | frameno: number 52 | lineno: number 53 | message: string 54 | type: string 55 | } 56 | } 57 | 58 | export interface FunctionCallData { 59 | body: StatementCapture[] 60 | } 61 | 62 | export interface SingleStatementData { 63 | result: string 64 | resultType: string 65 | } 66 | 67 | export interface StatementCapture { 68 | type: string 69 | data?: 70 | | PythonFileData 71 | | WhileLoopData 72 | | WhileLoopIteration 73 | | IfStatementData 74 | | SingleStatementData 75 | | ElseStatementData 76 | | ImportStatementData 77 | | FunctionDeclarationData 78 | | FunctionCallData 79 | sideEffects?: SideEffect[] 80 | exceptionType?: string 81 | exceptionMessage?: string 82 | exceptionInFunction?: string 83 | } 84 | 85 | export interface CapturePayload { 86 | root: StatementCapture 87 | detached: { [key: string]: { count: number; frames: StatementCapture[] } } 88 | lastException?: { 89 | func_id: string 90 | frameno: number 91 | lineno: number 92 | message: string 93 | type: string 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /packages/core/src/language/fragment.ts: -------------------------------------------------------------------------------- 1 | import { InvariantViolationError, InvariantViolationType } from './invariants' 2 | import { NodeCategory, isNodeInCategory } from './node_category_registry' 3 | import { SerializedNode, deserializeNode } from './type_registry' 4 | import { SplootNode } from './node' 5 | 6 | export interface SerializedFragment { 7 | category: NodeCategory 8 | nodes: SerializedNode[] 9 | } 10 | 11 | export class SplootFragment { 12 | nodes: SplootNode[] 13 | nodeCategory: NodeCategory 14 | 15 | constructor(nodes: SplootNode[], nodeCategory: NodeCategory, trim = true) { 16 | this.nodeCategory = nodeCategory 17 | this.nodes = nodes 18 | if (trim) { 19 | this.trim() 20 | } 21 | this.nodes.forEach((node) => { 22 | if (!isNodeInCategory(node.type, this.nodeCategory)) { 23 | throw new InvariantViolationError( 24 | InvariantViolationType.FragmentNodeCategory, 25 | `${node.type} is not valid for fragment category ${this.nodeCategory}.` 26 | ) 27 | } 28 | }) 29 | } 30 | 31 | trim() { 32 | // Trim invisible nodes 33 | if (this.nodes.length === 1 && this.nodes[0].getNodeLayout().isInvisible()) { 34 | const node = this.nodes[0] 35 | if (node.childSetOrder.length === 1) { 36 | const childSet = node.getChildSet(node.childSetOrder[0]) 37 | this.nodes = childSet.children 38 | this.nodeCategory = childSet.nodeCategory 39 | } 40 | } 41 | } 42 | 43 | isEmpty() { 44 | return this.nodes.length === 0 45 | } 46 | 47 | clone(): SplootFragment { 48 | return new SplootFragment( 49 | this.nodes.map((node) => node.clone()), 50 | this.nodeCategory 51 | ) 52 | } 53 | 54 | isSingle() { 55 | return this.nodes.length == 1 56 | } 57 | 58 | serialize(): SerializedFragment { 59 | return { 60 | category: this.nodeCategory, 61 | nodes: this.nodes.map((node) => node.serialize()), 62 | } 63 | } 64 | } 65 | 66 | export function deserializeFragment(serFragment: SerializedFragment): SplootFragment { 67 | const nodes = serFragment.nodes.map((serNode) => deserializeNode(serNode)) 68 | return new SplootFragment(nodes, serFragment.category) 69 | } 70 | -------------------------------------------------------------------------------- /packages/core/src/language/invariants.ts: -------------------------------------------------------------------------------- 1 | export enum InvariantViolationType { 2 | Unknown = 0, 3 | ChildSetNodeCategory, 4 | FragmentNodeCategory, 5 | } 6 | 7 | export class InvariantViolationError extends Error { 8 | constructor(type: InvariantViolationType, msg: string) { 9 | super(msg) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/core/src/language/mutations/child_set_mutations.ts: -------------------------------------------------------------------------------- 1 | import { ChildSet } from '../childset' 2 | import { SplootNode } from '../node' 3 | 4 | export enum ChildSetMutationType { 5 | INSERT, 6 | DELETE, 7 | } 8 | 9 | export class ChildSetMutation { 10 | childSet: ChildSet 11 | type: ChildSetMutationType 12 | nodes: SplootNode[] 13 | index: number 14 | } 15 | -------------------------------------------------------------------------------- /packages/core/src/language/mutations/node_mutations.ts: -------------------------------------------------------------------------------- 1 | import { LoopAnnotation, NodeAnnotation } from '../annotations/annotations' 2 | import { SplootNode } from '../node' 3 | 4 | export enum NodeMutationType { 5 | SET_PROPERTY, 6 | SET_RUNTIME_ANNOTATIONS, 7 | SET_VALIDITY, 8 | UPDATE_NODE_LAYOUT, 9 | } 10 | 11 | export class NodeMutation { 12 | node: SplootNode 13 | type: NodeMutationType 14 | property: string 15 | value: string 16 | annotations: NodeAnnotation[] 17 | validity: { valid: boolean; reason: string; childset?: string; index?: number } 18 | loopAnnotation: LoopAnnotation 19 | } 20 | -------------------------------------------------------------------------------- /packages/core/src/language/mutations/project_mutations.ts: -------------------------------------------------------------------------------- 1 | import { Dependency } from '../projects/project' 2 | import { RunSettings } from '../projects/run_settings' 3 | 4 | export enum ProjectMutationType { 5 | SET_ENVIRONMENT_VAR, 6 | DELETE_ENVIRONMENT_VAR, 7 | UPDATE_RUN_SETTINGS, 8 | UPDATE_DEPENDENCIES, 9 | } 10 | 11 | export type ProjectMutation = 12 | | ProjectUpdateEnvVarMutation 13 | | ProjectDeleteEnvVarMutation 14 | | ProjectUpdateRunSettingsMutation 15 | | ProjectUpdateDependenciesMutation 16 | 17 | export class ProjectUpdateEnvVarMutation { 18 | type: ProjectMutationType.SET_ENVIRONMENT_VAR 19 | newName: string 20 | newValue: string 21 | secret: boolean 22 | } 23 | 24 | export class ProjectDeleteEnvVarMutation { 25 | type: ProjectMutationType.DELETE_ENVIRONMENT_VAR 26 | name: string 27 | } 28 | 29 | export class ProjectUpdateRunSettingsMutation { 30 | type: ProjectMutationType.UPDATE_RUN_SETTINGS 31 | newSettings: RunSettings 32 | } 33 | 34 | export class ProjectUpdateDependenciesMutation { 35 | type: ProjectMutationType.UPDATE_DEPENDENCIES 36 | newDependencies: Dependency[] 37 | } 38 | -------------------------------------------------------------------------------- /packages/core/src/language/mutations/scope_mutations.ts: -------------------------------------------------------------------------------- 1 | export enum ScopeMutationType { 2 | ADD_CHILD_SCOPE, 3 | REMOVE_CHILD_SCOPE, 4 | ADD_OR_UPDATE_ENTRY, 5 | REMOVE_ENTRY, 6 | RENAME_ENTRY, 7 | IMPORT_MODULE, 8 | } 9 | 10 | export type ScopeMutation = ChildScopeScopeMutation | RenameScopeMutation | EntryScopeMutation | ImportModuleMutation 11 | 12 | export interface ChildScopeScopeMutation { 13 | type: ScopeMutationType.ADD_CHILD_SCOPE | ScopeMutationType.REMOVE_CHILD_SCOPE 14 | } 15 | 16 | export interface EntryScopeMutation { 17 | type: ScopeMutationType.ADD_OR_UPDATE_ENTRY | ScopeMutationType.REMOVE_ENTRY 18 | name: string 19 | } 20 | 21 | export interface RenameScopeMutation { 22 | type: ScopeMutationType.RENAME_ENTRY 23 | previousName: string 24 | newName: string 25 | } 26 | 27 | export interface ImportModuleMutation { 28 | type: ScopeMutationType.IMPORT_MODULE 29 | moduleName: string 30 | } 31 | -------------------------------------------------------------------------------- /packages/core/src/language/observers.ts: -------------------------------------------------------------------------------- 1 | import { ChildSetMutation } from './mutations/child_set_mutations' 2 | import { NodeMutation } from './mutations/node_mutations' 3 | import { ProjectMutation } from './mutations/project_mutations' 4 | import { ScopeMutation } from './mutations/scope_mutations' 5 | 6 | export interface NodeObserver { 7 | handleNodeMutation(nodeMutation: NodeMutation): void 8 | } 9 | 10 | export interface ChildSetObserver { 11 | handleChildSetMutation(mutations: ChildSetMutation): void 12 | } 13 | 14 | export interface ScopeObserver { 15 | handleScopeMutation(mutation: ScopeMutation): void 16 | } 17 | 18 | export interface ProjectObserver { 19 | handleProjectMutation(mutation: ProjectMutation): void 20 | } 21 | -------------------------------------------------------------------------------- /packages/core/src/language/projects/file.ts: -------------------------------------------------------------------------------- 1 | import { SplootNode } from '../node' 2 | 3 | export interface SerializedSplootFileRef { 4 | name: string 5 | type: string 6 | } 7 | 8 | export class SplootFile { 9 | name: string 10 | type: string // Sploot node type 11 | rootNode: SplootNode 12 | isLoaded: boolean 13 | 14 | constructor(name: string, type: string) { 15 | this.rootNode = null 16 | this.name = name 17 | this.type = type 18 | this.isLoaded = false 19 | } 20 | 21 | fileLoaded(node: SplootNode) { 22 | this.rootNode = node 23 | this.isLoaded = true 24 | } 25 | 26 | serialize(): string { 27 | // We can't do this if we're not loaded. 28 | if (!this.isLoaded) { 29 | throw 'Error: Cannot serialize a file that is not loaded.' 30 | } 31 | const ser = this.rootNode.serialize() 32 | return JSON.stringify(ser) + '\n' 33 | } 34 | 35 | getSerializedRef(): SerializedSplootFileRef { 36 | return { 37 | name: this.name, 38 | type: this.type, 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/core/src/language/projects/file_loader.ts: -------------------------------------------------------------------------------- 1 | import { Dependency, Project } from './project' 2 | import { HTTPScenario } from '../../http_types' 3 | import { RunType } from './run_settings' 4 | import { SplootFile } from './file' 5 | import { SplootNode } from '../node' 6 | import { SplootPackage } from './package' 7 | 8 | export interface FileLoader { 9 | isReadOnly: () => boolean 10 | loadPackage: (ownerID: string, projectId: string, packageId: string) => Promise 11 | loadFile: (ownerID: string, projectId: string, packageId: string, filename: string) => Promise 12 | saveFile: ( 13 | ownerID: string, 14 | projectId: string, 15 | packageId: string, 16 | file: SplootFile, 17 | base_version: string 18 | ) => Promise 19 | } 20 | 21 | export interface ProjectMetadata { 22 | owner: string 23 | id: string 24 | title: string 25 | lastModified: string 26 | live?: boolean 27 | shared?: boolean 28 | runType: RunType 29 | } 30 | 31 | export interface ProjectLoader { 32 | listProjectMetadata: () => Promise 33 | isValidProjectId: (ownerId: string, projectId: string) => Promise 34 | generateValidProjectId: (ownerId: string, projectId: string, title: string) => Promise<[string, string]> 35 | loadProject: (ownerId: string, projectId: string) => Promise 36 | newProject: ( 37 | ownerId: string, 38 | projectId: string, 39 | title: string, 40 | layoutType: string, 41 | runType: RunType 42 | ) => Promise 43 | deleteProject: (ownerId: string, projectId: string) => Promise 44 | cloneProject: (newOwnerId: string, newProjectId: string, title: string, existingProject: Project) => Promise 45 | saveProject: (project: Project) => Promise 46 | deleteHTTPScenario: (project: Project, scenarioID: number) => Promise 47 | saveHTTPScenario: (project: Project, scenario: HTTPScenario) => Promise 48 | isCurrentVersion: (project: Project) => Promise 49 | saveDependency: (project: Project, dependency: Dependency) => Promise 50 | deleteDependency: (project: Project, dependencyID: number) => Promise 51 | } 52 | 53 | export class SaveError extends Error { 54 | constructor(msg: string) { 55 | super(msg) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /packages/core/src/language/projects/package.ts: -------------------------------------------------------------------------------- 1 | import { FileLoader } from './file_loader' 2 | import { SerializedSplootFileRef, SplootFile } from './file' 3 | import { SplootNode } from '../node' 4 | 5 | export interface SerializedSplootPackageRef { 6 | name: string 7 | buildType: number 8 | } 9 | 10 | export interface SerializedSplootPackage { 11 | name: string 12 | files: SerializedSplootFileRef[] 13 | buildType: number 14 | } 15 | 16 | export enum PackageBuildType { 17 | STATIC = 0, 18 | JS_BUNDLE = 1, 19 | STYLE_BUNDLE = 2, 20 | PYTHON = 4, 21 | } 22 | 23 | export class SplootPackage { 24 | ownerID: string 25 | projectId: string 26 | name: string 27 | files: { [key: string]: SplootFile } 28 | fileOrder: string[] 29 | buildType: PackageBuildType 30 | 31 | constructor(ownerID: string, projectId: string, pack: SerializedSplootPackage) { 32 | this.ownerID = ownerID 33 | this.projectId = projectId 34 | this.name = pack.name 35 | this.buildType = pack.buildType 36 | this.fileOrder = pack.files.map((file) => file.name) 37 | this.files = {} 38 | pack.files.forEach((file) => { 39 | this.files[file.name] = new SplootFile(file.name, file.type) 40 | }) 41 | } 42 | 43 | serialize(): string { 44 | const ser: SerializedSplootPackage = { 45 | name: this.name, 46 | buildType: this.buildType, 47 | files: [], 48 | } 49 | this.fileOrder.forEach((filename) => { 50 | ser.files.push(this.files[filename].getSerializedRef()) 51 | }) 52 | return JSON.stringify(ser, null, 2) + '\n' 53 | } 54 | 55 | getDefaultFile(): SplootFile { 56 | return this.files[this.fileOrder[0]] 57 | } 58 | 59 | async addFile(name: string, type: string, rootNode: SplootNode) { 60 | const splootFile = new SplootFile(name, type) 61 | splootFile.fileLoaded(rootNode) 62 | this.files[name] = splootFile 63 | if (!this.fileOrder.includes(name)) { 64 | this.fileOrder.push(name) 65 | } 66 | } 67 | 68 | async getLoadedFile(fileLoader: FileLoader, name: string): Promise { 69 | const file = this.files[name] 70 | if (!file.isLoaded) { 71 | return await fileLoader.loadFile(this.ownerID, this.projectId, this.name, name).then((rootNode: SplootNode) => { 72 | file.fileLoaded(rootNode) 73 | return file 74 | }) 75 | } 76 | return file 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /packages/core/src/language/projects/run_settings.ts: -------------------------------------------------------------------------------- 1 | import { HTTPScenario } from '../../http_types' 2 | 3 | export enum RunType { 4 | COMMAND_LINE = 'COMMAND_LINE', 5 | HTTP_REQUEST = 'HTTP_REQUEST', 6 | SCHEDULE = 'SCHEDULE', 7 | STREAMLIT = 'STREAMLIT', 8 | } 9 | 10 | export interface RunSettings { 11 | runType: RunType 12 | 13 | httpScenarios: HTTPScenario[] 14 | } 15 | -------------------------------------------------------------------------------- /packages/core/src/language/tray/tray.ts: -------------------------------------------------------------------------------- 1 | import { SerializedFragment } from '../fragment' 2 | import { SerializedNode } from '../type_registry' 3 | 4 | export type TrayListing = TrayCategory | TrayEntry 5 | 6 | export interface TrayCategory { 7 | category: string 8 | entries: TrayListing[] 9 | } 10 | 11 | export interface TrayExample { 12 | serializedNodes: SerializedFragment 13 | description: string 14 | } 15 | 16 | export interface TrayEntry { 17 | key: string 18 | title?: string 19 | serializedNode?: SerializedNode 20 | abstract: string 21 | examples?: TrayExample[] 22 | } 23 | -------------------------------------------------------------------------------- /packages/core/src/language/validation/validation_watcher.ts: -------------------------------------------------------------------------------- 1 | import { NodeMutation, NodeMutationType } from '../mutations/node_mutations' 2 | import { NodeObserver } from '../observers' 3 | import { SplootNode } from '../node' 4 | import { globalMutationDispatcher } from '../mutations/mutation_dispatcher' 5 | 6 | export class ValidationWatcher implements NodeObserver { 7 | private invalidNodes: Set 8 | 9 | constructor() { 10 | this.invalidNodes = new Set() 11 | } 12 | 13 | handleNodeMutation(nodeMutation: NodeMutation): void { 14 | if (nodeMutation.type === NodeMutationType.SET_VALIDITY) { 15 | if (nodeMutation.validity.valid) { 16 | this.invalidNodes.delete(nodeMutation.node) 17 | } else { 18 | this.invalidNodes.add(nodeMutation.node) 19 | } 20 | } 21 | } 22 | 23 | isValid(): boolean { 24 | return this.invalidNodes.size === 0 25 | } 26 | 27 | registerSelf() { 28 | globalMutationDispatcher.registerNodeObserver(this) 29 | } 30 | 31 | deregisterSelf() { 32 | globalMutationDispatcher.deregisterNodeObserver(this) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "ES2020", 5 | "lib": ["DOM", "ES2020"], 6 | "moduleResolution": "node", 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "experimentalDecorators": true, 10 | "resolveJsonModule": true, 11 | "skipDefaultLibCheck": true, 12 | "downlevelIteration": true, 13 | "noEmit": true, 14 | "baseUrl": ".", 15 | "types": ["@types/wicg-file-system-access"] 16 | }, 17 | "include": ["src"], 18 | "exclude": [ 19 | "node_modules", 20 | "dist", 21 | "out", 22 | "*.config.*" 23 | ], 24 | "rootDir": "src", 25 | } 26 | -------------------------------------------------------------------------------- /packages/editor/.gitignore: -------------------------------------------------------------------------------- 1 | # non-dev env files 2 | production.env 3 | 4 | # Firebase files 5 | .firebase 6 | 7 | lib 8 | 9 | # Terraform 10 | .terraform 11 | 12 | # Lambda zips 13 | 14 | # Mac files 15 | .DS_Store 16 | 17 | # Logs 18 | logs 19 | *.log 20 | npm-debug.log* 21 | yarn-debug.log* 22 | yarn-error.log* 23 | lerna-debug.log* 24 | 25 | # Diagnostic reports (https://nodejs.org/api/report.html) 26 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 27 | 28 | # Runtime data 29 | pids 30 | *.pid 31 | *.seed 32 | *.pid.lock 33 | 34 | # Directory for instrumented libs generated by jscoverage/JSCover 35 | lib-cov 36 | 37 | # Coverage directory used by tools like istanbul 38 | coverage 39 | *.lcov 40 | 41 | # nyc test coverage 42 | .nyc_output 43 | 44 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 45 | .grunt 46 | 47 | # Bower dependency directory (https://bower.io/) 48 | bower_components 49 | 50 | # node-waf configuration 51 | .lock-wscript 52 | 53 | # Compiled binary addons (https://nodejs.org/api/addons.html) 54 | build/Release 55 | 56 | # Dependency directories 57 | node_modules/ 58 | jspm_packages/ 59 | 60 | # TypeScript v1 declaration files 61 | typings/ 62 | 63 | # TypeScript cache 64 | *.tsbuildinfo 65 | 66 | # Optional npm cache directory 67 | .npm 68 | 69 | # Optional eslint cache 70 | .eslintcache 71 | 72 | # Microbundle cache 73 | .rpt2_cache/ 74 | .rts2_cache_cjs/ 75 | .rts2_cache_es/ 76 | .rts2_cache_umd/ 77 | 78 | # Optional REPL history 79 | .node_repl_history 80 | 81 | # Output of 'npm pack' 82 | *.tgz 83 | 84 | # Yarn Integrity file 85 | .yarn-integrity 86 | 87 | # parcel-bundler cache (https://parceljs.org/) 88 | .cache 89 | 90 | # Next.js build output 91 | .next 92 | 93 | # Nuxt.js build / generate output 94 | .nuxt 95 | dist 96 | out 97 | 98 | # Gatsby files 99 | .cache/ 100 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 101 | # https://nextjs.org/blog/next-9-1#public-directory-support 102 | # public 103 | 104 | # vuepress build output 105 | .vuepress/dist 106 | 107 | # Serverless directories 108 | .serverless/ 109 | 110 | # FuseBox cache 111 | .fusebox/ 112 | 113 | # DynamoDB Local files 114 | .dynamodb/ 115 | 116 | # TernJS port file 117 | .tern-port 118 | -------------------------------------------------------------------------------- /packages/editor/jest.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from '@jest/types' 2 | 3 | const config: Config.InitialOptions = { 4 | preset: 'ts-jest', 5 | verbose: true, 6 | testPathIgnorePatterns: ['node_modules', 'lib', 'dist', 'out'], 7 | moduleNameMapper: { 8 | '\\.(css|less)$': 'identity-obj-proxy', 9 | }, 10 | } 11 | export default config 12 | -------------------------------------------------------------------------------- /packages/editor/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@splootcode/editor", 3 | "version": "1.0.0", 4 | "type": "module", 5 | "main": "./dist/index.js", 6 | "module": "./dist/index.mjs", 7 | "styles": "./dist/index.css", 8 | "typings": "./dist/index.d.ts", 9 | "exports": { 10 | ".": { 11 | "import": "./dist/index.mjs", 12 | "require": "./dist/index.js" 13 | }, 14 | "./styles.css": { 15 | "import": "./dist/index.css" 16 | } 17 | }, 18 | "files": [ 19 | "dist/index.css" 20 | ], 21 | "private": true, 22 | "scripts": { 23 | "test": "jest", 24 | "build": "yarn rollup -c" 25 | }, 26 | "peerDependencies": { 27 | "@chakra-ui/icons": "^1.1.7", 28 | "@chakra-ui/react": "^1.6.9", 29 | "@emotion/react": "^11.1.4", 30 | "@emotion/styled": "^11.0.0", 31 | "react-router-dom": "^5.1.2" 32 | }, 33 | "devDependencies": { 34 | "@chakra-ui/icons": "^1.1.7", 35 | "@chakra-ui/react": "^1.6.9", 36 | "@rollup/plugin-node-resolve": "^15.0.2", 37 | "@rollup/plugin-typescript": "^11.1.0", 38 | "@types/content-type": "^1.1.5", 39 | "@types/jest": "^28.1.3", 40 | "@types/react": "^17.0.2", 41 | "@types/react-dom": "^17.0.2", 42 | "@types/react-router-dom": "^5.3.3", 43 | "allotment": "^1.11.1", 44 | "content-type": "^1.0.5", 45 | "fuse.js": "^6.6.2", 46 | "http-status-codes": "^2.2.0", 47 | "identity-obj-proxy": "^3.0.0", 48 | "jest": "^28.1.1", 49 | "jest-canvas-mock": "^2.4.0", 50 | "jest-environment-jsdom": "^28.1.1", 51 | "mobx": "^5.15.0", 52 | "mobx-react": "^6.2.5", 53 | "pretty": "^2.0.0", 54 | "react": "^17.0.2", 55 | "react-dom": "^17.0.2", 56 | "react-icons": "^4.4.0", 57 | "rollup": "3.20.2", 58 | "rollup-plugin-dts": "^5.3.0", 59 | "rollup-plugin-import-css": "^3.2.1", 60 | "ts-jest": "^28.0.5", 61 | "ts-node": "^10.8.1", 62 | "typescript": "^4.8.4", 63 | "xterm": "^5.1.0", 64 | "xterm-addon-fit": "^0.7.0" 65 | }, 66 | "dependencies": { 67 | "@rollup/plugin-commonjs": "^24.0.1" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /packages/editor/rollup.config.js: -------------------------------------------------------------------------------- 1 | import commonjs from '@rollup/plugin-commonjs' 2 | import css from 'rollup-plugin-import-css' 3 | import dts from 'rollup-plugin-dts' 4 | import typescript from '@rollup/plugin-typescript' 5 | import { nodeResolve } from '@rollup/plugin-node-resolve' 6 | 7 | export default [ 8 | { 9 | external: ['react', '@emotion/core', '@emotion/styled', '@chakra-ui/react', '@chakra-ui/icons'], 10 | plugins: [ 11 | typescript(), 12 | commonjs(), 13 | nodeResolve({ 14 | resolveOnly: ['react-icons', 'http-status-codes', 'content-type', 'xterm', 'structured-pyright'], 15 | }), 16 | css({ minify: true }), 17 | ], 18 | input: 'src/index.ts', 19 | output: [ 20 | { 21 | file: `dist/index.mjs`, 22 | format: 'es', 23 | sourcemap: true, 24 | }, 25 | { 26 | file: `dist/index.js`, 27 | format: 'cjs', 28 | sourcemap: true, 29 | }, 30 | ], 31 | }, 32 | { 33 | input: 'src/index.ts', 34 | output: [{ file: `dist/index.d.ts`, format: 'es' }], 35 | plugins: [dts(), css()], 36 | }, 37 | ] 38 | -------------------------------------------------------------------------------- /packages/editor/src/components/attached_child.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SplootCode/splootcode/d621bcd5f30df97150d3e1388f60307636568780/packages/editor/src/components/attached_child.css -------------------------------------------------------------------------------- /packages/editor/src/components/autosave_info.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react' 2 | import { EditorState } from '../context/editor_context' 3 | import { Text, useToast } from '@chakra-ui/react' 4 | import { observer } from 'mobx-react' 5 | 6 | interface AutosaveInfoProps { 7 | editorState: EditorState 8 | reloadProject: () => void 9 | } 10 | 11 | export const AutosaveInfo = observer((props: AutosaveInfoProps) => { 12 | const toast = useToast() 13 | 14 | const { editorState, reloadProject } = props 15 | 16 | useEffect(() => { 17 | if (!editorState?.project.isReadOnly) { 18 | const checkVersion = async () => { 19 | if (document['hidden'] === false) { 20 | const isCurrent = await editorState.autosaveWatcher.projectLoader.isCurrentVersion(editorState?.project) 21 | if (!isCurrent) { 22 | reloadProject() 23 | } 24 | } 25 | } 26 | window.addEventListener('visibilitychange', checkVersion) 27 | return () => { 28 | window.removeEventListener('visibilitychange', checkVersion) 29 | } 30 | } 31 | }, [editorState?.project]) 32 | 33 | useEffect(() => { 34 | if (!editorState?.autosaveWatcher?.failedSave) { 35 | return 36 | } 37 | 38 | toast({ 39 | title: editorState.autosaveWatcher.failedSaveInfo.title, 40 | position: 'top', 41 | status: 'warning', 42 | }) 43 | }, [editorState?.autosaveWatcher.failedSave]) 44 | 45 | if (editorState.project?.isReadOnly || editorState.autosaveWatcher.failedSave) { 46 | return {editorState.autosaveWatcher.needsSave ? 'Not saved' : ''} 47 | } 48 | return {editorState.autosaveWatcher.needsSave ? 'Saving...' : 'Saved'} 49 | }) 50 | -------------------------------------------------------------------------------- /packages/editor/src/components/cursor.css: -------------------------------------------------------------------------------- 1 | .inline-cursor-hover { 2 | fill: transparent; 3 | } 4 | 5 | .inline-cursor-group:hover line.inline-cursor { 6 | stroke:white; 7 | } 8 | 9 | .inline-cursor-group:hover line.inline-cursor-lite { 10 | stroke:white; 11 | } 12 | 13 | .inline-cursor-group:hover circle.inline-cursor-circle { 14 | fill: white; 15 | } 16 | 17 | .inline-cursor-circle { 18 | fill: transparent; 19 | } 20 | 21 | .inline-cursor { 22 | stroke: transparent; 23 | stroke-width: 2; 24 | } 25 | 26 | .inline-cursor-lite { 27 | stroke: transparent; 28 | stroke-width: 1; 29 | } 30 | 31 | .inline-cursor.selected { 32 | stroke: white; 33 | } 34 | 35 | .inline-cursor-lite.selected { 36 | stroke: white; 37 | stroke-width: 1; 38 | } 39 | 40 | .active-inline-cursor { 41 | stroke: var(--editor-cursor-color); 42 | fill: transparent; 43 | stroke-width: 2; 44 | animation-name: color; 45 | animation-duration: 3s; 46 | animation-iteration-count: infinite; 47 | } 48 | 49 | @keyframes color { 50 | 0% { 51 | stroke: var(--editor-cursor-color); 52 | } 53 | 50% { 54 | stroke: var(--editor-cursor-blink-color); 55 | } 56 | 100% { 57 | stroke: var(--editor-cursor-color); 58 | } 59 | } -------------------------------------------------------------------------------- /packages/editor/src/components/cursor.tsx: -------------------------------------------------------------------------------- 1 | import './cursor.css' 2 | 3 | import React from 'react' 4 | import { observer } from 'mobx-react' 5 | 6 | import { NODE_BLOCK_HEIGHT } from '../layout/layout_constants' 7 | import { NodeSelection, SelectionState } from '../context/selection' 8 | 9 | interface ActiveCursorProps { 10 | selection: NodeSelection 11 | } 12 | 13 | @observer 14 | export class ActiveCursor extends React.Component { 15 | render() { 16 | const selection = this.props.selection 17 | if (selection.state !== SelectionState.Cursor || selection.isHidden) { 18 | return null 19 | } 20 | 21 | const [x, y] = selection.getCursorXYPosition() 22 | return 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/editor/src/components/drag_overlay.css: -------------------------------------------------------------------------------- 1 | 2 | .drag-overlay { 3 | position: fixed; 4 | background-color: transparent; 5 | top: 0; 6 | left: 0; 7 | width: 100vw; 8 | height: 100vh; 9 | } -------------------------------------------------------------------------------- /packages/editor/src/components/edit_box.css: -------------------------------------------------------------------------------- 1 | .edit-box { 2 | box-sizing: border-box; 3 | background-color: var(--editor-selected-node-block-fill); 4 | display: block; 5 | padding: 0; 6 | border-width: 0; 7 | border: none; 8 | line-height: 1; 9 | } 10 | 11 | .edit-box-string { 12 | font-family: var(--string-font); 13 | box-sizing: border-box; 14 | margin: 0; 15 | } 16 | 17 | .edit-box textarea { 18 | background-color: rgba(0, 0, 0, 0); 19 | box-sizing: border-box; 20 | font-family: inherit; 21 | border: none; 22 | color: white; 23 | padding: 0px; 24 | line-height: 16px; 25 | font-size: 15px; 26 | resize: none; 27 | margin: 0; 28 | } 29 | 30 | .edit-box textarea::-webkit-scrollbar { 31 | width: 0; 32 | background: transparent; 33 | } 34 | 35 | .edit-box textarea:focus { 36 | outline: none; 37 | } 38 | 39 | .edit-box input[type=text] { 40 | background-color: rgba(0, 0, 0, 0); 41 | font-family: inherit; 42 | border-style: none; 43 | color:white; 44 | padding: 0px; 45 | line-height: 1; 46 | } 47 | 48 | .edit-box input[type=text]:focus { 49 | outline: none; 50 | } 51 | -------------------------------------------------------------------------------- /packages/editor/src/components/editor_banner.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | 3 | import { Button, Flex, Text } from '@chakra-ui/react' 4 | import { ChildSetMutation, NodeMutation, Project, ProjectMutation, globalMutationDispatcher } from '@splootcode/core' 5 | 6 | export function EditorBanner(props: { project: Project; onSaveAs: () => void }) { 7 | const { project, onSaveAs } = props 8 | const [mutationCount, setMutationCount] = useState(0) 9 | const [dismissed, setDismissed] = useState(false) 10 | 11 | useEffect(() => { 12 | if (!dismissed) { 13 | const mutationObserver = { 14 | handleNodeMutation: (mutation: NodeMutation) => { 15 | setMutationCount((count) => count + 1) 16 | }, 17 | handleChildSetMutation: (mutation: ChildSetMutation) => { 18 | setMutationCount((count) => count + 1) 19 | }, 20 | handleProjectMutation: (mutation: ProjectMutation) => { 21 | setMutationCount((count) => count + 1) 22 | }, 23 | } 24 | globalMutationDispatcher.registerChildSetObserver(mutationObserver) 25 | globalMutationDispatcher.registerNodeObserver(mutationObserver) 26 | globalMutationDispatcher.registerProjectObserver(mutationObserver) 27 | const cleanup = () => { 28 | globalMutationDispatcher.deregisterNodeObserver(mutationObserver) 29 | globalMutationDispatcher.deregisterChildSetObserver(mutationObserver) 30 | globalMutationDispatcher.deregisterProjectObserver(mutationObserver) 31 | } 32 | return cleanup 33 | } 34 | }, [project, dismissed]) 35 | 36 | if (mutationCount < 3 || dismissed) { 37 | return null 38 | } 39 | 40 | return ( 41 | 42 | This is an example, make a copy to save your progress. 43 | {' '} 46 | 49 | 50 | ) 51 | } 52 | -------------------------------------------------------------------------------- /packages/editor/src/components/editor_side_menu.css: -------------------------------------------------------------------------------- 1 | 2 | .editor-side-menu { 3 | --editor-bg-color: var(--chakra-colors-gray-900); 4 | width: 100%; 5 | height: 100%; 6 | overflow-y: scroll; 7 | overflow-x: hidden; 8 | border-right: 1px solid var(--section-separator-color); 9 | box-sizing: border-box; 10 | padding: 0; 11 | background-color: var(--chakra-colors-gray-900); 12 | color: var(--chakra-colors-gray-300) 13 | } 14 | 15 | .editor-side-menu::-webkit-scrollbar { 16 | width: 0; /* Remove scrollbar space */ 17 | background: transparent; /* Optional: just make scrollbar invisible */ 18 | } 19 | 20 | .editor-side-menu-bar { 21 | width: 46px; 22 | box-sizing: border-box; 23 | background-color: var(--chakra-colors-gray-900); 24 | display: flex; 25 | flex-direction: column; 26 | align-items: center; 27 | border-right: 2px solid var(--section-separator-color); 28 | padding: 0px; 29 | } 30 | 31 | .editor-side-menu-container { 32 | width: 100%; 33 | } 34 | 35 | .editor-side-menu-icon { 36 | fill: currentColor 37 | } 38 | -------------------------------------------------------------------------------- /packages/editor/src/components/fragment.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { EditorNodeBlock } from './node_block' 4 | import { NodeBlock } from '../layout/rendered_node' 5 | import { NodeSelectionState } from '../context/selection' 6 | import { RenderedFragment } from '../layout/rendered_fragment' 7 | 8 | interface FragmentViewProps { 9 | fragment: RenderedFragment 10 | } 11 | 12 | export class FragmentView extends React.Component { 13 | render() { 14 | const fragment = this.props.fragment 15 | 16 | return ( 17 | 18 | {fragment.nodes.map((nodeBlock: NodeBlock, idx: number) => { 19 | return ( 20 | 21 | 22 | 23 | ) 24 | })} 25 | 26 | ) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/editor/src/components/list_block.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SplootCode/splootcode/d621bcd5f30df97150d3e1388f60307636568780/packages/editor/src/components/list_block.css -------------------------------------------------------------------------------- /packages/editor/src/components/list_block.tsx: -------------------------------------------------------------------------------- 1 | import './list_block.css' 2 | 3 | import React from 'react' 4 | import { observer } from 'mobx-react' 5 | 6 | import { EditorNodeBlock } from './node_block' 7 | import { NodeBlock } from '../layout/rendered_node' 8 | import { RenderedChildSetBlock } from '../layout/rendered_childset_block' 9 | 10 | interface ExpandedListBlockViewProps { 11 | block: RenderedChildSetBlock 12 | isSelected: boolean 13 | } 14 | 15 | @observer 16 | export class ExpandedListBlockView extends React.Component { 17 | render() { 18 | const block = this.props.block 19 | 20 | return ( 21 | 22 | {block.nodes.map((nodeBlock: NodeBlock, idx: number) => { 23 | const selectionState = block.getChildSelectionState(idx) 24 | return ( 25 | 26 | 27 | 28 | ) 29 | })} 30 | 31 | ) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/editor/src/components/node_block.css: -------------------------------------------------------------------------------- 1 | 2 | .inline-component { 3 | display: inline-block; 4 | padding-left: 4px; 5 | padding-right: 4px; 6 | padding-bottom: 2px; 7 | padding-top: 2px; 8 | margin: 2px; 9 | } 10 | 11 | .svgsplootnode { 12 | stroke: var(--editor-node-block-edge); 13 | fill: var(--editor-node-block-fill); 14 | } 15 | 16 | .svgsplootnode:focus { 17 | outline: 0; 18 | } 19 | 20 | .svgsplootnode.cap-line { 21 | stroke: var(--editor-cap-line); 22 | } 23 | 24 | .svgsplootnode.gap { 25 | fill: var(--editor-bg-color); 26 | stroke: var(--editor-bg-color); 27 | } 28 | 29 | .svgsplootnode.gap.selected { 30 | fill: var(--editor-bg-color); 31 | stroke: var(--editor-bg-color); 32 | } 33 | 34 | .svgsplootnode.gap.placeholder-outline { 35 | stroke-dasharray: 3px 3px; 36 | stroke: var(--editor-placeholder-stroke); 37 | } 38 | 39 | .svgsplootnode.gap.selected.placeholder-outline { 40 | stroke: var(--editor-placeholder-stroke); 41 | } 42 | 43 | .svgsplootnode.selected { 44 | stroke: var(--editor-selected-node-block-edge); 45 | fill: var(--editor-selected-node-block-fill); 46 | } 47 | 48 | .svgsplootnode.invalid { 49 | stroke: var(--editor-invalid-node-block-edge); 50 | } 51 | 52 | .invisible-splootnode-invalid { 53 | fill: transparent; 54 | stroke: var(--editor-invalid-node-block-edge); 55 | } 56 | 57 | .indented-rule { 58 | stroke: var(--editor-indented-block-stroke); 59 | stroke-width: 1.4px; 60 | } 61 | 62 | .indented-rule.selected { 63 | stroke: var(--editor-selected-indented-block-stroke); 64 | stroke-width: 1.4px; 65 | } -------------------------------------------------------------------------------- /packages/editor/src/components/node_block.test.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @jest-environment jsdom 3 | */ 4 | import 'jest-canvas-mock' 5 | import React from 'react' 6 | import pretty from 'pretty' 7 | import { act } from 'react-dom/test-utils' 8 | import { render, unmountComponentAtNode } from 'react-dom' 9 | 10 | import { PythonStringLiteral, loadPythonTypes } from '@splootcode/language-python' 11 | 12 | import { EditorNodeBlock } from './node_block' 13 | import { NodeBlock } from '../layout/rendered_node' 14 | import { NodeSelectionState } from '../context/selection' 15 | 16 | let container: HTMLElement = null 17 | 18 | beforeAll(() => { 19 | loadPythonTypes() 20 | }) 21 | 22 | beforeEach(() => { 23 | container = document.createElement('div') 24 | document.body.appendChild(container) 25 | }) 26 | 27 | afterEach(() => { 28 | unmountComponentAtNode(container) 29 | container.remove() 30 | container = null 31 | }) 32 | 33 | it('renders a single string literal node', () => { 34 | const node = new PythonStringLiteral(null, 'hello this is string') 35 | const nodeBlock = new NodeBlock(null, node, null, 0) 36 | nodeBlock.calculateDimensions(0, 0, null) 37 | 38 | act(() => { 39 | render( 40 | 41 | 42 | , 43 | container 44 | ) 45 | }) 46 | expect(container.children).toHaveLength(1) 47 | expect(pretty(container.innerHTML)).toMatchInlineSnapshot(` 48 | " 49 | 50 | 51 | ' 52 |
hello this is string
53 |
54 | 55 | ' 56 |
" 57 | `) 58 | }) 59 | -------------------------------------------------------------------------------- /packages/editor/src/components/placeholder_label.css: -------------------------------------------------------------------------------- 1 | 2 | .placeholder-label { 3 | fill: var(--editor-placeholder-label); 4 | font-style: italic; 5 | } -------------------------------------------------------------------------------- /packages/editor/src/components/placeholder_label.tsx: -------------------------------------------------------------------------------- 1 | import './placeholder_label.css' 2 | 3 | import React from 'react' 4 | 5 | import { EXPRESSION_TOKEN_SPACING, NODE_TEXT_OFFSET } from '../layout/layout_constants' 6 | 7 | export function PlaceholderLabel(props: { x: number; y: number; label: string }) { 8 | const { x, y, label } = props 9 | return ( 10 | 11 | {label} 12 | 13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /packages/editor/src/components/property.css: -------------------------------------------------------------------------------- 1 | 2 | .inline-component-property { 3 | display: inline-block; 4 | padding-left: 4px; 5 | padding-right: 4px; 6 | padding-bottom: 2px; 7 | padding-top: 2px; 8 | margin: 2px; 9 | } 10 | 11 | 12 | .editing.inline-component-property { 13 | padding-bottom: 1px; 14 | padding-top: 1px; 15 | } 16 | 17 | .inline-component-property > input { 18 | font-family: inherit; 19 | border: none; 20 | } 21 | 22 | .inline-component-property > input:focus { 23 | outline: none; 24 | } 25 | -------------------------------------------------------------------------------- /packages/editor/src/components/property.tsx: -------------------------------------------------------------------------------- 1 | import './property.css' 2 | 3 | import React from 'react' 4 | 5 | import { NODE_TEXT_OFFSET } from '../layout/layout_constants' 6 | import { NodeBlock } from '../layout/rendered_node' 7 | import { NodeSelectionState } from '../context/selection' 8 | 9 | interface PropertyProps { 10 | block: NodeBlock 11 | leftPos: number 12 | topPos: number 13 | propertyName: string 14 | selectState: NodeSelectionState 15 | } 16 | 17 | export class InlineProperty extends React.Component { 18 | render() { 19 | const { block, leftPos, topPos, propertyName } = this.props 20 | const { node } = this.props.block 21 | 22 | return ( 23 | 24 | {node.getProperty(propertyName)} 25 | 26 | ) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/editor/src/components/runtime_annotations.css: -------------------------------------------------------------------------------- 1 | 2 | .annotation { 3 | fill: var(--editor-annotation-label); 4 | font-size: 11pt; 5 | pointer-events: none; 6 | } 7 | 8 | .error-annotation { 9 | fill: var(--editor-annotation-error); 10 | font-size: 11pt; 11 | pointer-events: none; 12 | } 13 | 14 | .slider-track { 15 | fill: var(--chakra-colors-gray-600); 16 | } 17 | 18 | .slider-fill { 19 | fill: var(--chakra-colors-gray-400); 20 | transition: width 0.5s; 21 | } 22 | 23 | .slider-handle { 24 | fill: var(--chakra-colors-gray-400); 25 | transition: cx 0.5s; 26 | } 27 | 28 | .slider-background { 29 | fill: transparent; 30 | } 31 | 32 | .slider-button { 33 | fill: var(--editor-node-block-fill); 34 | } 35 | 36 | .slider-button-label { 37 | fill: var(--chakra-colors-gray-400); 38 | pointer-events: none; 39 | -webkit-user-select: none; 40 | -moz-user-select: none; 41 | -ms-user-select: none; 42 | user-select: none; 43 | } -------------------------------------------------------------------------------- /packages/editor/src/components/separator.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { NODE_BLOCK_HEIGHT, NODE_TEXT_OFFSET } from '../layout/layout_constants' 3 | 4 | interface StringLiteralProps { 5 | x: number 6 | y: number 7 | selectorType: string 8 | isSelected: boolean 9 | } 10 | 11 | export class Separator extends React.Component { 12 | render() { 13 | const { x, y, selectorType, isSelected } = this.props 14 | const className = 'separator' + (isSelected ? ' selected' : '') 15 | const nodeClassname = 'svgsplootnode' + (isSelected ? ' selected' : '') 16 | return ( 17 | 18 | 19 | 20 | {selectorType} 21 | 22 | 23 | ) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/editor/src/components/string_literal.css: -------------------------------------------------------------------------------- 1 | 2 | .string-literal { 3 | display: inline-block; 4 | padding-left: 4px; 5 | padding-right: 4px; 6 | padding-bottom: 2px; 7 | padding-top: 2px; 8 | margin: 2px; 9 | } 10 | 11 | .editing.string-literal { 12 | padding-bottom: 1px; 13 | padding-top: 1px; 14 | } 15 | 16 | .string-literal > input { 17 | font-family: inherit; 18 | border: none; 19 | } 20 | 21 | .string-literal > input:focus { 22 | outline: none; 23 | } 24 | 25 | -------------------------------------------------------------------------------- /packages/editor/src/components/string_literal.tsx: -------------------------------------------------------------------------------- 1 | import './string_literal.css' 2 | 3 | import React from 'react' 4 | 5 | import { NODE_TEXT_OFFSET } from '../layout/layout_constants' 6 | import { NodeBlock } from '../layout/rendered_node' 7 | import { NodeSelectionState } from '../context/selection' 8 | 9 | interface StringLiteralProps { 10 | block: NodeBlock 11 | leftPos: number 12 | topPos: number 13 | propertyName: string 14 | selectState: NodeSelectionState 15 | } 16 | 17 | export class InlineStringLiteral extends React.Component { 18 | render() { 19 | const { block, propertyName, selectState, leftPos, topPos } = this.props 20 | const { node } = this.props.block 21 | const isEditing = selectState === NodeSelectionState.EDITING 22 | const className = isEditing ? 'editing' : '' 23 | 24 | return ( 25 | 32 | "{node.getProperty(propertyName)}" 33 | 34 | ) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/editor/src/components/string_node.css: -------------------------------------------------------------------------------- 1 | 2 | .string-node { 3 | font-family: var(--string-font); 4 | font-size: 15px; 5 | line-height: 18px; 6 | white-space: pre-wrap; 7 | color: var(--code-green-text); 8 | margin: 0; 9 | font-weight: normal; 10 | padding: 0px; 11 | } 12 | 13 | .svgsplootnode.stringnode { 14 | fill: var(--editor-string-block-outline); 15 | stroke: var(--editor-string-block-outline); 16 | } 17 | 18 | .svgsplootnode.stringnode.selected { 19 | fill: var(--editor-selected-node-block-fill); 20 | stroke: var(--editor-selected-node-block-edge); 21 | } 22 | 23 | .svgsplootnode.stringnode.selected.invalid { 24 | fill: var(--editor-selected-node-block-fill); 25 | stroke: var(--editor-invalid-node-block-edge); 26 | } 27 | 28 | 29 | .svgsplootnode.stringnode.invalid { 30 | fill: var(--editor-string-block-outline); 31 | stroke: var(--editor-invalid-node-block-edge); 32 | } 33 | 34 | 35 | .string-node.measurement { 36 | position: absolute; 37 | left: -1000px; 38 | top: 0; 39 | } 40 | 41 | .string-node-cap-text { 42 | fill: var(--code-green-text); 43 | font-family: 'Inconsolata'; 44 | font-size: 15px; 45 | font-weight: bold; 46 | } 47 | 48 | .editing.string-node { 49 | padding-bottom: 1px; 50 | padding-top: 1px; 51 | } 52 | 53 | .string-node > input { 54 | font-family: inherit; 55 | border: none; 56 | font-size: 15px; 57 | } 58 | 59 | .string-node > input:focus { 60 | outline: none; 61 | } 62 | 63 | .string-node-background { 64 | fill: var(--editor-string-block-fill); 65 | stroke: var(--editor-string-block-fill); 66 | } 67 | 68 | 69 | .string-node-selected-background { 70 | fill: var(--editor-selected-node-block-fill); 71 | stroke: transparent 72 | } 73 | 74 | .string-node-selected-background:focus { 75 | outline: 0; 76 | } -------------------------------------------------------------------------------- /packages/editor/src/components/token_list_block.tsx: -------------------------------------------------------------------------------- 1 | import './tree_list_block.css' 2 | 3 | import React from 'react' 4 | import { observer } from 'mobx-react' 5 | 6 | import { EditorNodeBlock } from './node_block' 7 | import { NODE_BLOCK_HEIGHT } from '../layout/layout_constants' 8 | import { NodeBlock } from '../layout/rendered_node' 9 | import { PlaceholderLabel } from './placeholder_label' 10 | import { RenderedChildSetBlock } from '../layout/rendered_childset_block' 11 | 12 | interface TokenListBlockViewProps { 13 | block: RenderedChildSetBlock 14 | isSelected: boolean 15 | isValid: boolean 16 | invalidIndex: number 17 | isInline: boolean 18 | } 19 | 20 | @observer 21 | export class TokenListBlockView extends React.Component { 22 | render() { 23 | const { block, isValid, isInline, invalidIndex } = this.props 24 | let shape = null 25 | const childsetInvalid = !isValid && invalidIndex === undefined 26 | 27 | const classname = 'svgsplootnode gap' + (childsetInvalid ? ' invalid' : ' ') 28 | if (isInline || childsetInvalid) { 29 | shape = ( 30 | 31 | ) 32 | } 33 | 34 | const label = 35 | block.nodes.length === 0 && block.labels.length > 0 ? ( 36 | 37 | ) : undefined 38 | 39 | return ( 40 | 41 | {shape} 42 | {label} 43 | {block.nodes.map((nodeBlock: NodeBlock, idx: number) => { 44 | const selectionState = block.getChildSelectionState(idx) 45 | const invalidChild = idx === invalidIndex 46 | const label = block.labels.length > idx ? block.labels[idx] : undefined 47 | return ( 48 | 49 | 55 | 56 | ) 57 | })} 58 | 59 | ) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /packages/editor/src/components/tray/entry.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { Box, Text } from '@chakra-ui/react' 4 | import { MicroNode } from './category' 5 | import { RenderedFragment } from '../../layout/rendered_fragment' 6 | import { TrayEntry, deserializeFragment } from '@splootcode/core' 7 | 8 | export interface EntryProps { 9 | entry: TrayEntry 10 | startDrag: (fragment: RenderedFragment, offsetX: number, offsetY: number) => any 11 | } 12 | 13 | export const Entry = (props: EntryProps) => { 14 | const { entry, startDrag } = props 15 | return ( 16 | <> 17 | 18 | {entry.abstract} 19 | 20 | {!entry.examples || entry.examples?.length === 0 ? null : ( 21 | 30 | Examples: 31 | 32 | )} 33 | {entry.examples?.map((example, idx) => { 34 | const fragment = new RenderedFragment(deserializeFragment(example.serializedNodes), true) 35 | return ( 36 | 37 | 38 | 39 | {example.description} 40 | 41 | 42 | ) 43 | })} 44 | 45 | ) 46 | } 47 | -------------------------------------------------------------------------------- /packages/editor/src/components/tray/scope_tray.css: -------------------------------------------------------------------------------- 1 | 2 | .scope-tray-entry { 3 | padding: 2px 4 | } -------------------------------------------------------------------------------- /packages/editor/src/components/tray/tray.css: -------------------------------------------------------------------------------- 1 | .tray-expanded-category { 2 | border-left: 1px solid var(--chakra-colors-gray-600); 3 | } 4 | 5 | .tray-expanded-entry { 6 | border-top: 1px solid var(--chakra-colors-gray-500); 7 | border-bottom: 1px solid var(--chakra-colors-gray-500); 8 | font-size: 14px; 9 | } 10 | 11 | .tray-entry-abstract { 12 | font-style: italic; 13 | line-height: 1.1; 14 | } 15 | 16 | .tray-entry-example-label { 17 | font-style: italic; 18 | line-height: 1.1; 19 | } -------------------------------------------------------------------------------- /packages/editor/src/components/tree_list_block.css: -------------------------------------------------------------------------------- 1 | .svg-anchor-dot { 2 | fill: var(--editor-anchor-dot); 3 | } 4 | 5 | .svg-anchor-dot.selected { 6 | fill: var(--editor-selected-anchor-dot); 7 | } 8 | 9 | .tree-connector { 10 | stroke: var(--editor-connector-stroke); 11 | stroke-width: 2px; 12 | } 13 | 14 | .tree-connector.selected { 15 | stroke: var(--editor-selected-connector-stroke); 16 | } 17 | 18 | .tree-label { 19 | fill: var(--editor-connector-label); 20 | } 21 | 22 | .tree-label.selected { 23 | fill: var(--editor-selected-connector-label); 24 | } -------------------------------------------------------------------------------- /packages/editor/src/context/edit_box.ts: -------------------------------------------------------------------------------- 1 | import { NodeBlock } from '../layout/rendered_node' 2 | 3 | export class EditBoxData { 4 | x: number 5 | y: number 6 | property: string 7 | contents: string 8 | node: NodeBlock 9 | 10 | constructor(nodeBlock: NodeBlock, property: string, coordindates: number[]) { 11 | this.node = nodeBlock 12 | this.property = property 13 | this.contents = this.node.node.getProperty(property) 14 | this.x = coordindates[0] 15 | this.y = coordindates[1] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/editor/src/context/insert_box.ts: -------------------------------------------------------------------------------- 1 | export class InsertBoxData { 2 | x: number 3 | y: number 4 | contents: string 5 | 6 | constructor(coordindates: number[]) { 7 | this.x = coordindates[0] + 3 8 | this.y = coordindates[1] 9 | this.contents = '' 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/editor/src/context/multiselect_deleter.ts: -------------------------------------------------------------------------------- 1 | import { ChildSetType, SplootFragment } from '@splootcode/core' 2 | import { NodeBlock } from '../layout/rendered_node' 3 | import { NodeCursor } from './selection' 4 | import { RenderedChildSetBlock } from '../layout/rendered_childset_block' 5 | import { RenderedTreeIterator } from './rendered_tree_iterator' 6 | 7 | export interface DeleteSet { 8 | node: NodeBlock 9 | keep: SplootFragment[] 10 | } 11 | 12 | export class MultiselectDeleter extends RenderedTreeIterator { 13 | childSetStack: boolean[] 14 | toFullyDelete: DeleteSet[] 15 | toKeep: SplootFragment[] 16 | 17 | constructor(start: NodeCursor, end: NodeCursor) { 18 | super(start, end) 19 | this.childSetStack = [] 20 | this.toFullyDelete = [] 21 | this.toKeep = [] 22 | } 23 | 24 | visitNodeMiddle(node: NodeBlock): void { 25 | if (this.childSetStack.length <= 1 && node.parentChildSet.childSet.type !== ChildSetType.Immutable) { 26 | this.toFullyDelete.push({ node: node, keep: [] }) 27 | } 28 | } 29 | 30 | startRangeRight(listBlock: RenderedChildSetBlock): void { 31 | if (listBlock.childSet.type !== ChildSetType.Immutable) { 32 | this.childSetStack.push(true) 33 | } 34 | } 35 | 36 | visitedRangeRight(listBlock: RenderedChildSetBlock, startIndex: number, endIndex: number) { 37 | if (listBlock.childSet.type !== ChildSetType.Immutable) { 38 | this.childSetStack.pop() 39 | } 40 | // Add leftovers to return set 41 | if (endIndex < listBlock.nodes.length && this.childSetStack.length !== 0) { 42 | const nodes = listBlock.childSet.children 43 | .slice(endIndex) 44 | .filter((node) => !node.isEmpty()) 45 | .map((node) => node.clone()) 46 | 47 | if (nodes.length > 0) { 48 | const fragment = new SplootFragment(nodes, listBlock.childSet.nodeCategory) 49 | this.toFullyDelete[this.toFullyDelete.length - 1].keep.push(fragment) 50 | } 51 | } 52 | } 53 | 54 | getDeletions(): DeleteSet[] { 55 | return this.toFullyDelete 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /packages/editor/src/context/multiselect_tree_walker.ts: -------------------------------------------------------------------------------- 1 | import { NodeCursor, SelectionState } from './selection' 2 | import { RenderedChildSetBlock } from '../layout/rendered_childset_block' 3 | import { RenderedTreeIterator } from './rendered_tree_iterator' 4 | 5 | export class MultiselectTreeWalker extends RenderedTreeIterator { 6 | selectedListBlocks: Set 7 | 8 | constructor(start: NodeCursor, end: NodeCursor) { 9 | super(start, end) 10 | this.selectedListBlocks = new Set() 11 | } 12 | 13 | visitedRangeLeft(listBlock: RenderedChildSetBlock, startIndex: number, endIndex: number) { 14 | listBlock.selectionState = SelectionState.MultiNode 15 | listBlock.selectedIndexStart = startIndex 16 | listBlock.selectedIndexEnd = endIndex 17 | this.selectedListBlocks.add(listBlock) 18 | } 19 | 20 | visitedRangeRight(listBlock: RenderedChildSetBlock, startIndex: number, endIndex: number) { 21 | listBlock.selectionState = SelectionState.MultiNode 22 | listBlock.selectedIndexStart = startIndex 23 | listBlock.selectedIndexEnd = endIndex 24 | this.selectedListBlocks.add(listBlock) 25 | } 26 | 27 | getSelectedListBlocks(): Set { 28 | return this.selectedListBlocks 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/editor/src/editor_hosting_config.ts: -------------------------------------------------------------------------------- 1 | export interface EditorHostingConfig { 2 | FRAME_VIEW_SCHEME: 'http' | 'https' 3 | FRAME_VIEW_DOMAIN: string 4 | TYPESHED_PATH: string 5 | } 6 | -------------------------------------------------------------------------------- /packages/editor/src/index.ts: -------------------------------------------------------------------------------- 1 | export { Tray } from './components/tray/tray' 2 | export { EditorState, EditorStateContext } from './context/editor_context' 3 | export { Editor } from './components/editor' 4 | export type { EditorHostingConfig } from './editor_hosting_config' 5 | export { EditorBanner } from './components/editor_banner' 6 | export { preloadFonts } from './layout/layout_constants' 7 | export { NodeBlock } from './layout/rendered_node' 8 | export { ExpandedListBlockView } from './components/list_block' 9 | export { ActiveCursor } from './components/cursor' 10 | export { NodeSelection } from './context/selection' 11 | export { EditBox } from './components/edit_box' 12 | export { InsertBox } from './components/insert_box' 13 | export { PythonFrame, RuntimeToken } from './runtime/python_frame' 14 | export { FileChangeWatcher, FileSpec } from './runtime/file_change_watcher' 15 | export { EditorSideMenu, EditorSideMenuView, EditorSideMenuPane, ModuleTrayLoader } from './components/editor_side_menu' 16 | export { PythonRuntimePanel } from './runtime/python_runtime_panel' 17 | export { RenderedFragment } from './layout/rendered_fragment' 18 | export { ConfigPanel } from './components/config_panel' 19 | export { AutosaveInfo } from './components/autosave_info' 20 | export { AutosaveWatcher, AutosaveWatcherFailedSaveInfo } from './context/autosave_watcher' 21 | export { TestRequestPanel } from './components/test_request_panel' 22 | export { MicroNode } from './components/tray/category' 23 | export { Headers, ResponseViewerInfo, BodyInfo, getHeader } from './runtime/response_viewer' 24 | -------------------------------------------------------------------------------- /packages/editor/src/layout/childset_layout_handler.ts: -------------------------------------------------------------------------------- 1 | import { CursorMap } from '../context/cursor_map' 2 | import { LayoutComponent } from '@splootcode/core' 3 | import { NodeBlock } from './rendered_node' 4 | import { NodeSelection } from '../context/selection' 5 | import { RenderedChildSetBlock } from './rendered_childset_block' 6 | 7 | export interface ChildSetLayoutHandler { 8 | x: number 9 | y: number 10 | width: number 11 | height: number 12 | marginTop: number 13 | 14 | updateLayout(layoutComponent: LayoutComponent): void 15 | calculateDimensions( 16 | x: number, 17 | y: number, 18 | nodes: NodeBlock[], 19 | selection: NodeSelection, 20 | allowInsert: boolean, 21 | insertCursor: number, 22 | insertBoxWidth: number, 23 | marginAlreadyApplied?: boolean 24 | ): void 25 | 26 | getInsertCoordinates(insertIndex: number, cursorOnly?: boolean): [number, number] 27 | allowInsertCursor(insertIndex: number): boolean 28 | 29 | registerCursorPositions(cursorMap: CursorMap, renderedChildSet: RenderedChildSetBlock): void 30 | } 31 | -------------------------------------------------------------------------------- /packages/editor/src/runtime/file_change_watcher.ts: -------------------------------------------------------------------------------- 1 | import { SerializedNode } from '@splootcode/core' 2 | 3 | export type FileSpec = SplootFile | BlobFile 4 | export interface SplootFile { 5 | type: 'sploot' 6 | content: SerializedNode 7 | } 8 | 9 | export interface BlobFile { 10 | type: 'blob' 11 | content: Uint8Array 12 | } 13 | 14 | export interface FileChangeWatcher { 15 | registerObservers: (setDirty: () => void) => void 16 | deregisterObservers: () => void 17 | isValid: () => boolean 18 | getUpdatedFileState: () => Promise> 19 | getAllFileState: () => Promise> 20 | getEnvVars: () => Map 21 | } 22 | -------------------------------------------------------------------------------- /packages/editor/src/runtime/python_frame.css: -------------------------------------------------------------------------------- 1 | 2 | #python-frame-container { 3 | position: relative; 4 | height: 100%; 5 | background-color: inherit; 6 | } 7 | 8 | .view-python-frame { 9 | position: absolute; 10 | border: none; 11 | display: block; 12 | width: 0; 13 | height: 0; 14 | } 15 | 16 | .streamlit-frame { 17 | height: calc(100% - 42px); 18 | width: 100%; 19 | border: none; 20 | display: block; 21 | } -------------------------------------------------------------------------------- /packages/editor/src/runtime/python_runtime_panel.tsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { PythonFrame, RuntimeToken } from './python_frame' 3 | import { RuntimeContextManager } from '../context/runtime_context_manager' 4 | 5 | export type RuntimePanelProps = { 6 | frameScheme: 'http' | 'https' 7 | frameDomain: string 8 | refreshToken?: () => Promise 9 | runtimeContextManager: RuntimeContextManager 10 | } 11 | 12 | interface RuntimePanelState {} 13 | 14 | export class PythonRuntimePanel extends Component { 15 | constructor(props: RuntimePanelProps) { 16 | super(props) 17 | } 18 | 19 | render() { 20 | const { frameScheme, frameDomain, runtimeContextManager, refreshToken } = this.props 21 | return ( 22 | 28 | ) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/editor/src/runtime/response_viewer.css: -------------------------------------------------------------------------------- 1 | #response-viewer::-webkit-scrollbar { 2 | background-color: var(--chakra-colors-gray-900); 3 | width: 8px; 4 | } 5 | 6 | #response-viewer::-webkit-scrollbar-thumb { 7 | background: var(--chakra-colors-gray-500); 8 | border-radius: 4px; 9 | } 10 | -------------------------------------------------------------------------------- /packages/editor/src/runtime/terminal.css: -------------------------------------------------------------------------------- 1 | .terminal-menu { 2 | background-color: var(--chakra-colors-gray-900); 3 | border-bottom: 1px solid var(--chakra-colors-gray-800); 4 | } 5 | 6 | #terminal-container { 7 | height: 100%; 8 | } 9 | 10 | #terminal { 11 | height: calc(100% - 42px); 12 | background-color: var(--chakra-colors-gray-900); 13 | box-sizing: border-box; 14 | scrollbar-color: auto; 15 | } 16 | 17 | .xterm .xterm-viewport { 18 | padding-right: 10px; 19 | background-color: var(--chakra-colors-gray-900); 20 | } 21 | 22 | .xterm-viewport::-webkit-scrollbar { 23 | background-color: var(--chakra-colors-gray-900); 24 | width: 8px; 25 | } 26 | 27 | .xterm-viewport::-webkit-scrollbar-thumb { 28 | background: var(--chakra-colors-gray-500); 29 | border-radius: 4px; 30 | } 31 | -------------------------------------------------------------------------------- /packages/editor/src/runtime/web_runtime.css: -------------------------------------------------------------------------------- 1 | 2 | #frame-container { 3 | position: relative; 4 | height: 100vh; 5 | background-color: inherit; 6 | } 7 | 8 | #view-frame { 9 | position: absolute; 10 | border: none; 11 | background-color: white; 12 | display: block; 13 | width: 100%; 14 | height: calc(100% - 65px); 15 | } -------------------------------------------------------------------------------- /packages/editor/src/tests/check_observer_mapping.ts: -------------------------------------------------------------------------------- 1 | import { ChildSet, SplootNode } from '@splootcode/core' 2 | 3 | export function checkNodeObserversRecursively(splootNode: SplootNode) { 4 | const observers = splootNode.mutationObservers 5 | if (observers.length !== 1) { 6 | throw new Error(`${splootNode.type} node has ${observers.length} observers. Expected 1`) 7 | } 8 | 9 | splootNode.childSetOrder.forEach((childSetID) => { 10 | const childSet = splootNode.getChildSet(childSetID) 11 | checkChildSetObserversRecursively(childSet) 12 | }) 13 | } 14 | 15 | function checkChildSetObserversRecursively(childSet: ChildSet) { 16 | const observers = childSet.mutationObservers 17 | if (observers.length !== 1) { 18 | throw new Error(`ChildSet ${childSet.getParentRef()} has ${observers.length} observers. Expected 1`) 19 | } 20 | 21 | childSet.children.forEach((childNode) => { 22 | checkNodeObserversRecursively(childNode) 23 | }) 24 | } 25 | -------------------------------------------------------------------------------- /packages/editor/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "ES2020", 5 | "lib": ["DOM", "ES2020"], 6 | "moduleResolution": "node", 7 | "jsx": "react", 8 | "esModuleInterop": true, 9 | "skipLibCheck": true, 10 | "allowSyntheticDefaultImports": true, 11 | "experimentalDecorators": true, 12 | "resolveJsonModule": true, 13 | "skipDefaultLibCheck": true, 14 | "downlevelIteration": true, 15 | "noEmit": true, 16 | "baseUrl": ".", 17 | "types": ["jest"] 18 | }, 19 | "include": ["src"], 20 | "exclude": [ 21 | "node_modules", 22 | "dist", 23 | "out", 24 | "*.config.*" 25 | ], 26 | "rootDir": "src", 27 | } 28 | -------------------------------------------------------------------------------- /packages/language-python/.gitignore: -------------------------------------------------------------------------------- 1 | # non-dev env files 2 | production.env 3 | 4 | # Firebase files 5 | .firebase 6 | 7 | lib 8 | 9 | # Terraform 10 | .terraform 11 | 12 | # Lambda zips 13 | 14 | # Mac files 15 | .DS_Store 16 | 17 | # Logs 18 | logs 19 | *.log 20 | npm-debug.log* 21 | yarn-debug.log* 22 | yarn-error.log* 23 | lerna-debug.log* 24 | 25 | # Diagnostic reports (https://nodejs.org/api/report.html) 26 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 27 | 28 | # Runtime data 29 | pids 30 | *.pid 31 | *.seed 32 | *.pid.lock 33 | 34 | # Directory for instrumented libs generated by jscoverage/JSCover 35 | lib-cov 36 | 37 | # Coverage directory used by tools like istanbul 38 | coverage 39 | *.lcov 40 | 41 | # nyc test coverage 42 | .nyc_output 43 | 44 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 45 | .grunt 46 | 47 | # Bower dependency directory (https://bower.io/) 48 | bower_components 49 | 50 | # node-waf configuration 51 | .lock-wscript 52 | 53 | # Compiled binary addons (https://nodejs.org/api/addons.html) 54 | build/Release 55 | 56 | # Dependency directories 57 | node_modules/ 58 | jspm_packages/ 59 | 60 | # TypeScript v1 declaration files 61 | typings/ 62 | 63 | # TypeScript cache 64 | *.tsbuildinfo 65 | 66 | # Optional npm cache directory 67 | .npm 68 | 69 | # Optional eslint cache 70 | .eslintcache 71 | 72 | # Microbundle cache 73 | .rpt2_cache/ 74 | .rts2_cache_cjs/ 75 | .rts2_cache_es/ 76 | .rts2_cache_umd/ 77 | 78 | # Optional REPL history 79 | .node_repl_history 80 | 81 | # Output of 'npm pack' 82 | *.tgz 83 | 84 | # Yarn Integrity file 85 | .yarn-integrity 86 | 87 | # parcel-bundler cache (https://parceljs.org/) 88 | .cache 89 | 90 | # Next.js build output 91 | .next 92 | 93 | # Nuxt.js build / generate output 94 | .nuxt 95 | dist 96 | out 97 | 98 | # Gatsby files 99 | .cache/ 100 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 101 | # https://nextjs.org/blog/next-9-1#public-directory-support 102 | # public 103 | 104 | # vuepress build output 105 | .vuepress/dist 106 | 107 | # Serverless directories 108 | .serverless/ 109 | 110 | # FuseBox cache 111 | .fusebox/ 112 | 113 | # DynamoDB Local files 114 | .dynamodb/ 115 | 116 | # TernJS port file 117 | .tern-port 118 | -------------------------------------------------------------------------------- /packages/language-python/jest.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from '@jest/types' 2 | 3 | const config: Config.InitialOptions = { 4 | preset: 'ts-jest', 5 | verbose: true, 6 | testPathIgnorePatterns: ['node_modules', 'lib', 'dist', 'out'], 7 | moduleNameMapper: { 8 | '\\.(css|less)$': 'identity-obj-proxy', 9 | }, 10 | } 11 | export default config 12 | -------------------------------------------------------------------------------- /packages/language-python/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@splootcode/language-python", 3 | "version": "1.0.0", 4 | "type": "module", 5 | "main": "dist/index.js", 6 | "module": "dist/index.mjs", 7 | "typings": "dist/index.d.ts", 8 | "exports": { 9 | ".": { 10 | "import": "./dist/index.mjs", 11 | "require": "./dist/index.js", 12 | "types": "./dist/index.d.ts" 13 | } 14 | }, 15 | "private": true, 16 | "scripts": { 17 | "test": "jest", 18 | "build": "yarn rollup -c" 19 | }, 20 | "dependencies": {}, 21 | "devDependencies": { 22 | "@jest/globals": "^29.2.2", 23 | "@rollup/plugin-json": "^6.0.0", 24 | "@rollup/plugin-node-resolve": "^15.0.2", 25 | "@rollup/plugin-typescript": "^11.1.0", 26 | "rollup": "3.20.2", 27 | "rollup-plugin-copy": "^3.4.0", 28 | "rollup-plugin-dts": "^5.3.0", 29 | "structured-pyright": "^1.0.11", 30 | "typescript": "^4.8.4" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/language-python/rollup.config.js: -------------------------------------------------------------------------------- 1 | import copy from 'rollup-plugin-copy' 2 | import dts from 'rollup-plugin-dts' 3 | import json from '@rollup/plugin-json' 4 | import typescript from '@rollup/plugin-typescript' 5 | 6 | export default [ 7 | { 8 | plugins: [ 9 | typescript(), 10 | json(), 11 | copy({ 12 | targets: [{ src: 'tray/**', dest: 'dist/tray' }], 13 | }), 14 | ], 15 | input: 'src/index.ts', 16 | output: [ 17 | { 18 | file: `dist/index.mjs`, 19 | format: 'es', 20 | sourcemap: true, 21 | }, 22 | { 23 | file: `dist/index.js`, 24 | format: 'cjs', 25 | sourcemap: true, 26 | }, 27 | ], 28 | }, 29 | { 30 | input: 'src/index.ts', 31 | output: [{ file: `dist/index.d.ts`, format: 'es' }], 32 | plugins: [dts(), json()], 33 | }, 34 | ] 35 | -------------------------------------------------------------------------------- /packages/language-python/src/generated/.gitignore: -------------------------------------------------------------------------------- 1 | * -------------------------------------------------------------------------------- /packages/language-python/src/index.ts: -------------------------------------------------------------------------------- 1 | export { PythonAnalyzer } from './analyzer/python_analyzer' 2 | export { PythonModuleInfo, ModuleInfoFile, SupportedModuleList, PythonLanguageTray } from './tray/language' 3 | export { generatePythonScope } from './scope/python_scope' 4 | export { formatPythonAssingment, formatPythonReturnValue } from './nodes/utils' 5 | export { PythonScope } from './scope/python_scope' 6 | export { loadPythonTypes } from './type_loader' 7 | export { PythonNode } from './nodes/python_node' 8 | 9 | export { TypeCategory, FunctionSignature, FunctionArgType } from './scope/types' 10 | 11 | export { isPythonNode } from './nodes/python_node' 12 | 13 | export { PythonIfStatement } from './nodes/python_if' 14 | export { PythonStringLiteral, PYTHON_STRING } from './nodes/python_string' 15 | export { PythonStatement, PYTHON_STATEMENT } from './nodes/python_statement' 16 | export { PythonExpression, PYTHON_EXPRESSION } from './nodes/python_expression' 17 | export { PythonCallVariable, PYTHON_CALL_VARIABLE } from './nodes/python_call_variable' 18 | export { PythonCallMember, PYTHON_CALL_MEMBER } from './nodes/python_call_member' 19 | export { PythonArgument, PYTHON_ARGUMENT } from './nodes/python_argument' 20 | export { PythonDictionary, PYTHON_DICT } from './nodes/python_dictionary' 21 | export { PythonKeyValue, PYTHON_KEYVALUE } from './nodes/python_keyvalue' 22 | export { PythonAssignment, AssignmentWrapGenerator, PYTHON_ASSIGNMENT } from './nodes/python_assignment' 23 | export { PythonFile, PYTHON_FILE } from './nodes/python_file' 24 | export type { PotentialHandlers } from './nodes/python_file' 25 | export { PythonBinaryOperator } from './nodes/python_binary_operator' 26 | export { PythonIdentifier } from './nodes/python_identifier' 27 | export type { PythonModuleSpec } from './scope/python' 28 | export type { 29 | ParseTreeCommunicator, 30 | ParseTreeInfo, 31 | ExpressionTypeRequest, 32 | ExpressionTypeResponse, 33 | ParseTrees, 34 | AutocompleteInfo, 35 | AutocompleteEntryFunction, 36 | AutocompleteEntryVariable, 37 | AutocompleteEntryFunctionArgument, 38 | AutocompleteEntryCategory, 39 | } from './analyzer/python_analyzer' 40 | -------------------------------------------------------------------------------- /packages/language-python/src/nodes/declared_identifier.ts: -------------------------------------------------------------------------------- 1 | import { 2 | HighlightColorCategory, 3 | LayoutComponent, 4 | LayoutComponentType, 5 | NodeLayout, 6 | ParentReference, 7 | SerializedNode, 8 | SplootNode, 9 | TypeRegistration, 10 | registerType, 11 | } from '@splootcode/core' 12 | import { PYTHON_IDENTIFIER, PythonIdentifier } from './python_identifier' 13 | 14 | export const PYTHON_DECLARED_IDENTIFIER = 'PYTHON_DECLARED_IDENTIFIER' 15 | 16 | /** @deprecated Use PythonIdentifier */ 17 | export class PythonDeclaredIdentifier extends SplootNode { 18 | constructor(parentReference: ParentReference, name: string) { 19 | super(parentReference, PYTHON_DECLARED_IDENTIFIER) 20 | this.setProperty('identifier', name) 21 | } 22 | 23 | setName(name: string) { 24 | this.setProperty('identifier', name) 25 | } 26 | 27 | getName() { 28 | return this.getProperty('identifier') 29 | } 30 | 31 | static deserializer(serializedNode: SerializedNode): PythonDeclaredIdentifier { 32 | const node = new PythonDeclaredIdentifier(null, serializedNode.properties.identifier) 33 | return node 34 | } 35 | 36 | static register() { 37 | const typeRegistration = new TypeRegistration() 38 | typeRegistration.typeName = PYTHON_DECLARED_IDENTIFIER 39 | typeRegistration.deserializer = PythonDeclaredIdentifier.deserializer 40 | typeRegistration.properties = ['identifier'] 41 | typeRegistration.layout = new NodeLayout(HighlightColorCategory.VARIABLE, [ 42 | new LayoutComponent(LayoutComponentType.PROPERTY, 'identifier'), 43 | ]) 44 | typeRegistration.pasteAdapters[PYTHON_IDENTIFIER] = (node: SplootNode) => { 45 | const varDec = node as PythonDeclaredIdentifier 46 | const newNode = new PythonIdentifier(null, varDec.getName()) 47 | return newNode 48 | } 49 | 50 | registerType(typeRegistration) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /packages/language-python/src/nodes/tests/serialization.test.ts: -------------------------------------------------------------------------------- 1 | import { NodeCategory, SplootNode, deserializeNode, getNodesForCategory } from '@splootcode/core' 2 | import { beforeAll, describe, test } from '@jest/globals' 3 | import { deepEquals, nodeSanityCheck } from './test_utils' 4 | import { getEmptyStatementNodes, getExpressionTokenNodes } from './single_examples' 5 | import { loadPythonTypes } from '../../type_loader' 6 | 7 | describe('python node serialization', () => { 8 | beforeAll(() => { 9 | loadPythonTypes() 10 | }) 11 | 12 | test('statement nodes serialization roundtrips accurately', () => { 13 | const examples: SplootNode[] = getEmptyStatementNodes() 14 | examples.forEach((node) => { 15 | nodeSanityCheck(node) 16 | const cloneNode = deserializeNode(node.serialize()) 17 | deepEquals(node, cloneNode) 18 | }) 19 | }) 20 | 21 | test('all statement types accounted for', () => { 22 | const examples: SplootNode[] = getEmptyStatementNodes() 23 | const testedTypes = new Set() 24 | examples.forEach((exampleNode) => { 25 | testedTypes.add(exampleNode.type) 26 | }) 27 | 28 | const statementTypes = getNodesForCategory(NodeCategory.PythonStatementContents) 29 | statementTypes.forEach((statementNodeType) => { 30 | if (!testedTypes.has(statementNodeType)) { 31 | throw new Error(`Node type ${statementNodeType} was not tested for serialization roundtripping.`) 32 | } 33 | }) 34 | }) 35 | 36 | test('expression token node serialization roundtrips accurately', () => { 37 | const examples: SplootNode[] = getExpressionTokenNodes() 38 | examples.forEach((node) => { 39 | nodeSanityCheck(node) 40 | const cloneNode = deserializeNode(node.serialize()) 41 | deepEquals(node, cloneNode) 42 | }) 43 | }) 44 | 45 | test('all expression token types accounted for', () => { 46 | const examples: SplootNode[] = getExpressionTokenNodes() 47 | const testedTypes = new Set() 48 | examples.forEach((exampleNode) => { 49 | testedTypes.add(exampleNode.type) 50 | }) 51 | 52 | const tokenTypes = getNodesForCategory(NodeCategory.PythonExpressionToken) 53 | tokenTypes.forEach((nodeType) => { 54 | if (!testedTypes.has(nodeType)) { 55 | throw new Error(`Node type ${nodeType} was not tested for serialization roundtripping.`) 56 | } 57 | }) 58 | }) 59 | }) 60 | -------------------------------------------------------------------------------- /packages/language-python/src/nodes/tests/test_utils.ts: -------------------------------------------------------------------------------- 1 | import { ChildSetType, SplootNode, isNodeInCategory } from '@splootcode/core' 2 | import { expect } from '@jest/globals' 3 | 4 | export function deepEquals(node1: SplootNode, node2: SplootNode) { 5 | expect(node1.type).toEqual(node2.type) 6 | expect(node1.properties).toEqual(node2.properties) 7 | for (const childSetID of node1.childSetOrder) { 8 | const childset1 = node1.getChildSet(childSetID) 9 | const childset2 = node2.getChildSet(childSetID) 10 | expect(childset1.getCount()).toEqual(childset2.getCount()) 11 | childset1.children.forEach((child, idx) => { 12 | deepEquals(child, childset2.getChild(idx)) 13 | }) 14 | } 15 | } 16 | 17 | export function nodeSanityCheck(node: SplootNode) { 18 | expect(node).not.toBeNull() 19 | for (const childSetID of node.childSetOrder) { 20 | const childSet = node.getChildSet(childSetID) 21 | if (childSet.type === ChildSetType.Single) { 22 | expect(childSet.maxChildren).toEqual(1) 23 | } 24 | if (childSet.type === ChildSetType.Immutable) { 25 | expect(childSet.minChildren).not.toEqual(0) 26 | expect(childSet.maxChildren).toEqual(childSet.minChildren) 27 | } 28 | expect(childSet.getCount()).toBeGreaterThanOrEqual(childSet.minChildren) 29 | if (childSet.maxChildren !== -1) { 30 | expect(childSet.getCount()).toBeLessThanOrEqual(childSet.maxChildren) 31 | } 32 | childSet.children.forEach((child, idx) => { 33 | if (!isNodeInCategory(child.type, childSet.nodeCategory)) { 34 | throw new Error(`Child node type ${child.type} is incompatible with childset ${childSetID} of ${node.type}`) 35 | } 36 | nodeSanityCheck(child) 37 | }) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/language-python/src/nodes/tests/tree_walker.ts: -------------------------------------------------------------------------------- 1 | import { ParseNode, ParseNodeArray, ParseTreeWalker } from 'structured-pyright' 2 | 3 | export class TestWalker extends ParseTreeWalker { 4 | constructor() { 5 | super() 6 | } 7 | 8 | override visitNode(node: ParseNode) { 9 | const children = super.visitNode(node) 10 | this._verifyParentChildLinks(node, children) 11 | 12 | return children 13 | } 14 | 15 | // Make sure that all of the children point to their parent. 16 | private _verifyParentChildLinks(node: ParseNode, children: ParseNodeArray) { 17 | children.forEach((child) => { 18 | if (child) { 19 | if (child.parent !== node) { 20 | throw new Error(`Child node ${child.nodeType} does not contain a reference to its parent ${node.nodeType}`) 21 | } 22 | } 23 | }) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/language-python/src/nodes/variable_reference.ts: -------------------------------------------------------------------------------- 1 | import { 2 | HighlightColorCategory, 3 | LayoutComponent, 4 | LayoutComponentType, 5 | NodeLayout, 6 | ParentReference, 7 | SerializedNode, 8 | SplootNode, 9 | TypeRegistration, 10 | registerType, 11 | } from '@splootcode/core' 12 | import { PYTHON_IDENTIFIER, PythonIdentifier } from './python_identifier' 13 | 14 | export const PYTHON_VARIABLE_REFERENCE = 'PYTHON_VARIABLE_REFERENCE' 15 | 16 | /** @deprecated Use PythonIdentifier */ 17 | export class PythonVariableReference extends SplootNode { 18 | constructor(parentReference: ParentReference, name: string) { 19 | super(parentReference, PYTHON_VARIABLE_REFERENCE) 20 | this.setProperty('identifier', name) 21 | } 22 | 23 | setName(name: string) { 24 | this.setProperty('identifier', name) 25 | } 26 | 27 | getName() { 28 | return this.getProperty('identifier') 29 | } 30 | 31 | static deserializer(serializedNode: SerializedNode): PythonVariableReference { 32 | return new PythonVariableReference(null, serializedNode.properties.identifier) 33 | } 34 | 35 | static register() { 36 | const varType = new TypeRegistration() 37 | varType.typeName = PYTHON_VARIABLE_REFERENCE 38 | varType.deserializer = PythonVariableReference.deserializer 39 | varType.properties = ['identifier'] 40 | varType.layout = new NodeLayout(HighlightColorCategory.VARIABLE, [ 41 | new LayoutComponent(LayoutComponentType.PROPERTY, 'identifier'), 42 | ]) 43 | varType.pasteAdapters[PYTHON_IDENTIFIER] = (node: SplootNode) => { 44 | const identifier = node as PythonIdentifier 45 | return new PythonIdentifier(null, identifier.getName()) 46 | } 47 | 48 | registerType(varType) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /packages/language-python/src/scope/types.ts: -------------------------------------------------------------------------------- 1 | export enum TypeCategory { 2 | Value, 3 | Function, 4 | Type, 5 | Module, 6 | ModuleAttribute, 7 | } 8 | 9 | export interface ValueType { 10 | category: TypeCategory.Value 11 | typeName: string 12 | shortDoc?: string 13 | typeIfAttr?: string 14 | } 15 | 16 | export enum FunctionArgType { 17 | PositionalOnly, 18 | PositionalOrKeyword, 19 | Vargs, 20 | KeywordOnly, 21 | Kwargs, 22 | } 23 | 24 | export interface FunctionArgument { 25 | name: string 26 | type: FunctionArgType 27 | defaultValue?: string 28 | } 29 | 30 | export interface FunctionSignature { 31 | category: TypeCategory.Function 32 | arguments: FunctionArgument[] 33 | shortDoc: string 34 | typeIfMethod?: string 35 | } 36 | 37 | export interface TypeDefinition { 38 | category: TypeCategory.Type 39 | attributes: Map 40 | } 41 | 42 | export interface ModuleDefinition { 43 | category: TypeCategory.Module 44 | attributes: Map 45 | loaded: boolean 46 | } 47 | 48 | export interface ModuleAttribute { 49 | category: TypeCategory.ModuleAttribute 50 | module: string 51 | attribute: string 52 | } 53 | 54 | export type VariableTypeInfo = ValueType | FunctionSignature | TypeDefinition | ModuleDefinition | ModuleAttribute 55 | -------------------------------------------------------------------------------- /packages/language-python/src/tests/test_data/blank_main.json: -------------------------------------------------------------------------------- 1 | {"type":"PYTHON_FILE","properties":{},"childSets":{"body":[]}} 2 | -------------------------------------------------------------------------------- /packages/language-python/src/tests/test_data/helloname_main.json: -------------------------------------------------------------------------------- 1 | {"type":"PYTHON_FILE","properties":{},"childSets":{"body":[{"type":"PYTHON_EXPRESSION","properties":{},"childSets":{"tokens":[{"type":"PYTHON_CALL_VARIABLE","properties":{"identifier":"print"},"childSets":{"arguments":[{"type":"PYTHON_EXPRESSION","properties":{},"childSets":{"tokens":[{"type":"STRING_LITERAL","properties":{"value":"Hello!"},"childSets":{}}]}}]}}]}},{"type":"PYTHON_ASSIGNMENT","properties":{},"childSets":{"left":[{"type":"PYTHON_DECLARED_IDENTIFIER","properties":{"identifier":"name"},"childSets":{}}],"right":[{"type":"PYTHON_EXPRESSION","properties":{},"childSets":{"tokens":[{"type":"PYTHON_CALL_VARIABLE","properties":{"identifier":"input"},"childSets":{"arguments":[{"type":"PYTHON_EXPRESSION","properties":{},"childSets":{"tokens":[{"type":"STRING_LITERAL","properties":{"value":"What is your name? "},"childSets":{}}]}}]}}]}}]}},{"type":"PYTHON_EXPRESSION","properties":{},"childSets":{"tokens":[{"type":"PYTHON_CALL_VARIABLE","properties":{"identifier":"print"},"childSets":{"arguments":[{"type":"PYTHON_EXPRESSION","properties":{},"childSets":{"tokens":[{"type":"STRING_LITERAL","properties":{"value":"Hello, "},"childSets":{}},{"type":"PYTHON_BINARY_OPERATOR","properties":{"operator":"+"},"childSets":{}},{"type":"PYTHON_VARIABLE_REFERENCE","properties":{"identifier":"name"},"childSets":{}}]}}]}}]}}]}} 2 | -------------------------------------------------------------------------------- /packages/language-python/src/tests/test_data/missing_expressions_main.json: -------------------------------------------------------------------------------- 1 | {"type":"PYTHON_FILE","properties":{},"childSets":{ 2 | "body":[ 3 | {"type":"PYTHON_EXPRESSION","properties":{},"childSets":{ 4 | "tokens":[ 5 | {"type":"PY_BRACKET"}, 6 | {"type":"PYTHON_LIST"}, 7 | {"type":"PY_DICT"}, 8 | {"type":"PY_DICT", "childSets": { 9 | "elements": [{"type": "PY_KEYVALUE"}] 10 | }} 11 | ] 12 | }}, 13 | {"type":"PYTHON_ASSIGNMENT","properties":{},"childSets":{ 14 | "left":[{"type":"PYTHON_DECLARED_IDENTIFIER","properties":{"identifier":"name"},"childSets":{}}], 15 | "right":[] 16 | }}, 17 | {"type":"PYTHON_IF_STATEMENT","properties":{},"childSets":{ 18 | "condition":[], 19 | "trueblock":[], 20 | "elseblocks": [] 21 | }}, 22 | {"type":"PYTHON_IF_STATEMENT","properties":{},"childSets":{ 23 | "condition":[], 24 | "trueblock":[], 25 | "elseblocks": [ 26 | {"type": "PYTHON_ELIF_STATEMENT","properties":{}} 27 | ] 28 | }}, 29 | {"type":"PYTHON_FOR_LOOP","properties":{},"childSets":{}}, 30 | {"type":"PYTHON_SUBSCRIPT"}, 31 | {"type":"PYTHON_WHILE_LOOP","properties":{},"childSets":{}}, 32 | {"type":"PYTHON_FUNCTION_DECLARATION","properties":{},"childSets":{ 33 | "body": [ 34 | {"type": "PYTHON_RETURN"} 35 | ] 36 | }} 37 | ] 38 | }} 39 | -------------------------------------------------------------------------------- /packages/language-python/src/tests/test_data/missing_values_main.json: -------------------------------------------------------------------------------- 1 | {"type":"PYTHON_FILE","properties":{},"childSets":{ 2 | "body":[ 3 | {"type":"PYTHON_EXPRESSION","properties":{},"childSets":{ 4 | "tokens":[ 5 | {"type":"PYTHON_CALL_VARIABLE","properties":{"identifier":"print"},"childSets":{}} 6 | ] 7 | }}, 8 | {"type":"PYTHON_EXPRESSION","childSets":{"tokens":[]}}, 9 | {"type":"PYTHON_EXPRESSION","properties":{},"childSets":{ 10 | "tokens":[] 11 | }}, 12 | {"type":"PYTHON_EXPRESSION"}, 13 | {"type":"PYTHON_STATEMENT","properties":{},"childSets":{ 14 | "statement":[] 15 | }}, 16 | {"type":"PYTHON_STATEMENT","childSets":{ 17 | "statement":[] 18 | }}, 19 | {"type":"PYTHON_STATEMENT","properties":{},"childSets":{}}, 20 | {"type":"PYTHON_STATEMENT","properties":{}}, 21 | {"type":"PYTHON_STATEMENT"} 22 | ] 23 | }} 24 | -------------------------------------------------------------------------------- /packages/language-python/src/tests/test_data/secret_password_main.json: -------------------------------------------------------------------------------- 1 | {"type":"PYTHON_FILE","properties":{},"childSets":{"body":[{"type":"PYTHON_ASSIGNMENT","properties":{},"childSets":{"left":[{"type":"PYTHON_DECLARED_IDENTIFIER","properties":{"identifier":"guess"},"childSets":{}}],"right":[{"type":"PYTHON_EXPRESSION","properties":{},"childSets":{"tokens":[{"type":"PYTHON_CALL_VARIABLE","properties":{"identifier":"input"},"childSets":{"arguments":[{"type":"PYTHON_EXPRESSION","properties":{},"childSets":{"tokens":[{"type":"STRING_LITERAL","properties":{"value":"Enter the secret password: "},"childSets":{}}]}}]}}]}}]}},{"type":"PYTHON_WHILE_LOOP","properties":{},"childSets":{"condition":[{"type":"PYTHON_EXPRESSION","properties":{},"childSets":{"tokens":[{"type":"PYTHON_VARIABLE_REFERENCE","properties":{"identifier":"guess"},"childSets":{}},{"type":"PYTHON_BINARY_OPERATOR","properties":{"operator":"!="},"childSets":{}},{"type":"STRING_LITERAL","properties":{"value":"OpenSesame"},"childSets":{}}]}}],"block":[{"type":"PYTHON_EXPRESSION","properties":{},"childSets":{"tokens":[{"type":"PYTHON_CALL_VARIABLE","properties":{"identifier":"print"},"childSets":{"arguments":[{"type":"PYTHON_EXPRESSION","properties":{},"childSets":{"tokens":[{"type":"STRING_LITERAL","properties":{"value":"Wrong! Try again."},"childSets":{}}]}}]}}]}},{"type":"PYTHON_ASSIGNMENT","properties":{},"childSets":{"left":[{"type":"PYTHON_DECLARED_IDENTIFIER","properties":{"identifier":"guess"},"childSets":{}}],"right":[{"type":"PYTHON_EXPRESSION","properties":{},"childSets":{"tokens":[{"type":"PYTHON_CALL_VARIABLE","properties":{"identifier":"input"},"childSets":{"arguments":[{"type":"PYTHON_EXPRESSION","properties":{},"childSets":{"tokens":[{"type":"STRING_LITERAL","properties":{"value":"Enter the secret password: "},"childSets":{}}]}}]}}]}}]}}]}},{"type":"PYTHON_EXPRESSION","properties":{},"childSets":{"tokens":[{"type":"PYTHON_CALL_VARIABLE","properties":{"identifier":"print"},"childSets":{"arguments":[{"type":"PYTHON_EXPRESSION","properties":{},"childSets":{"tokens":[{"type":"STRING_LITERAL","properties":{"value":"Access granted!"},"childSets":{}}]}}]}}]}}]}} 2 | -------------------------------------------------------------------------------- /packages/language-python/src/tray/language.test.ts: -------------------------------------------------------------------------------- 1 | import { PythonLanguageTray } from './language' 2 | import { TrayCategory, TrayEntry, TrayListing, deserializeNode } from '@splootcode/core' 3 | import { beforeAll, describe, expect, test } from '@jest/globals' 4 | import { loadPythonTypes } from '../type_loader' 5 | import { nodeSanityCheck } from '../nodes/tests/test_utils' 6 | 7 | function flattenTrayEntries(category: TrayCategory): TrayEntry[] { 8 | const entries: TrayEntry[] = [] 9 | category.entries.forEach((listing: TrayListing) => { 10 | if ('category' in listing) { 11 | entries.push(...flattenTrayEntries(listing)) 12 | } else { 13 | entries.push(listing) 14 | } 15 | }) 16 | return entries 17 | } 18 | 19 | describe('python tray entries', () => { 20 | beforeAll(() => { 21 | loadPythonTypes() 22 | }) 23 | 24 | test('sanity check all tray entries and examples', () => { 25 | const tray = PythonLanguageTray 26 | const allEntries = flattenTrayEntries(tray) 27 | // Make sure we are working with a valid set of tray entries 28 | expect(allEntries.length).toBeGreaterThan(10) 29 | allEntries.forEach((entry) => { 30 | const node = deserializeNode(entry.serializedNode) 31 | nodeSanityCheck(node) 32 | if (entry.examples && entry.examples.length !== 0) { 33 | entry.examples.forEach((example) => { 34 | example.serializedNodes.nodes.forEach((serNode) => { 35 | const node = deserializeNode(serNode) 36 | nodeSanityCheck(node) 37 | }) 38 | }) 39 | } 40 | }) 41 | }) 42 | }) 43 | -------------------------------------------------------------------------------- /packages/language-python/src/tray/language.ts: -------------------------------------------------------------------------------- 1 | import { TrayCategory } from '@splootcode/core' 2 | 3 | import * as pythonLib from '../generated/python_tray.json' 4 | import * as standardLibModules from '../standard_lib_modules.json' 5 | 6 | export const PythonLanguageTray: TrayCategory = pythonLib as TrayCategory 7 | 8 | export interface PythonModuleInfo { 9 | isStandardLib: boolean 10 | name: string 11 | description: string 12 | } 13 | 14 | export interface ModuleInfoFile { 15 | allModules: PythonModuleInfo[] 16 | } 17 | 18 | const moduleInfoFile = standardLibModules as ModuleInfoFile 19 | export const SupportedModuleList = moduleInfoFile.allModules 20 | -------------------------------------------------------------------------------- /packages/language-python/tray/atexit.json: -------------------------------------------------------------------------------- 1 | { 2 | "category": "atexit", 3 | "entries": [ 4 | { 5 | "key": "atexit.register", 6 | "abstract": "", 7 | "serializedNode": { 8 | "type": "PYTHON_CALL_MEMBER", 9 | "properties": { 10 | "member": "register" 11 | }, 12 | "meta": { 13 | "params": [ 14 | "func" 15 | ] 16 | }, 17 | "childSets": { 18 | "object": [ 19 | { 20 | "type": "PY_IDENTIFIER", 21 | "properties": { 22 | "identifier": "atexit" 23 | }, 24 | "childSets": {} 25 | } 26 | ], 27 | "arguments": [ 28 | { 29 | "type": "PY_ARG", 30 | "childSets": { 31 | "argument": [ 32 | { 33 | "type": "PYTHON_EXPRESSION", 34 | "properties": {}, 35 | "childSets": { 36 | "tokens": [] 37 | } 38 | } 39 | ] 40 | }, 41 | "properties": {} 42 | } 43 | ] 44 | } 45 | } 46 | }, 47 | { 48 | "key": "atexit.unregister", 49 | "abstract": "", 50 | "serializedNode": { 51 | "type": "PYTHON_CALL_MEMBER", 52 | "properties": { 53 | "member": "unregister" 54 | }, 55 | "meta": { 56 | "params": [ 57 | "func" 58 | ] 59 | }, 60 | "childSets": { 61 | "object": [ 62 | { 63 | "type": "PY_IDENTIFIER", 64 | "properties": { 65 | "identifier": "atexit" 66 | }, 67 | "childSets": {} 68 | } 69 | ], 70 | "arguments": [ 71 | { 72 | "type": "PY_ARG", 73 | "childSets": { 74 | "argument": [ 75 | { 76 | "type": "PYTHON_EXPRESSION", 77 | "properties": {}, 78 | "childSets": { 79 | "tokens": [] 80 | } 81 | } 82 | ] 83 | }, 84 | "properties": {} 85 | } 86 | ] 87 | } 88 | } 89 | } 90 | ] 91 | } -------------------------------------------------------------------------------- /packages/language-python/tray/bisect.json: -------------------------------------------------------------------------------- 1 | { 2 | "category": "bisect", 3 | "entries": [] 4 | } -------------------------------------------------------------------------------- /packages/language-python/tray/chunk.json: -------------------------------------------------------------------------------- 1 | { 2 | "category": "chunk", 3 | "entries": [ 4 | { 5 | "key": "chunk.Chunk", 6 | "abstract": "", 7 | "serializedNode": { 8 | "type": "PYTHON_CALL_MEMBER", 9 | "properties": { 10 | "member": "Chunk" 11 | }, 12 | "meta": { 13 | "params": [ 14 | "file", 15 | "align", 16 | "bigendian", 17 | "inclheader" 18 | ] 19 | }, 20 | "childSets": { 21 | "object": [ 22 | { 23 | "type": "PY_IDENTIFIER", 24 | "properties": { 25 | "identifier": "chunk" 26 | }, 27 | "childSets": {} 28 | } 29 | ], 30 | "arguments": [ 31 | { 32 | "type": "PY_ARG", 33 | "childSets": { 34 | "argument": [ 35 | { 36 | "type": "PYTHON_EXPRESSION", 37 | "properties": {}, 38 | "childSets": { 39 | "tokens": [] 40 | } 41 | } 42 | ] 43 | }, 44 | "properties": {} 45 | } 46 | ] 47 | } 48 | } 49 | } 50 | ] 51 | } -------------------------------------------------------------------------------- /packages/language-python/tray/cmd.json: -------------------------------------------------------------------------------- 1 | { 2 | "category": "cmd", 3 | "entries": [ 4 | { 5 | "key": "cmd.PROMPT", 6 | "abstract": "", 7 | "serializedNode": { 8 | "type": "PYTHON_MEMBER", 9 | "properties": { 10 | "member": "PROMPT" 11 | }, 12 | "childSets": { 13 | "object": [ 14 | { 15 | "type": "PY_IDENTIFIER", 16 | "properties": { 17 | "identifier": "cmd" 18 | }, 19 | "childSets": {} 20 | } 21 | ] 22 | } 23 | } 24 | }, 25 | { 26 | "key": "cmd.IDENTCHARS", 27 | "abstract": "", 28 | "serializedNode": { 29 | "type": "PYTHON_MEMBER", 30 | "properties": { 31 | "member": "IDENTCHARS" 32 | }, 33 | "childSets": { 34 | "object": [ 35 | { 36 | "type": "PY_IDENTIFIER", 37 | "properties": { 38 | "identifier": "cmd" 39 | }, 40 | "childSets": {} 41 | } 42 | ] 43 | } 44 | } 45 | }, 46 | { 47 | "key": "cmd.Cmd", 48 | "abstract": "A simple framework for writing line-oriented command interpreters.", 49 | "serializedNode": { 50 | "type": "PYTHON_CALL_MEMBER", 51 | "properties": { 52 | "member": "Cmd" 53 | }, 54 | "meta": { 55 | "params": [ 56 | "completekey", 57 | "stdin", 58 | "stdout" 59 | ] 60 | }, 61 | "childSets": { 62 | "object": [ 63 | { 64 | "type": "PY_IDENTIFIER", 65 | "properties": { 66 | "identifier": "cmd" 67 | }, 68 | "childSets": {} 69 | } 70 | ], 71 | "arguments": [ 72 | { 73 | "type": "PY_ARG", 74 | "childSets": { 75 | "argument": [ 76 | { 77 | "type": "PYTHON_EXPRESSION", 78 | "properties": {}, 79 | "childSets": { 80 | "tokens": [] 81 | } 82 | } 83 | ] 84 | }, 85 | "properties": {} 86 | } 87 | ] 88 | } 89 | } 90 | } 91 | ] 92 | } -------------------------------------------------------------------------------- /packages/language-python/tray/fractions.json: -------------------------------------------------------------------------------- 1 | { 2 | "category": "fractions", 3 | "entries": [ 4 | { 5 | "key": "fractions.Fraction", 6 | "abstract": "This class implements rational numbers.", 7 | "serializedNode": { 8 | "type": "PYTHON_CALL_MEMBER", 9 | "properties": { 10 | "member": "Fraction" 11 | }, 12 | "meta": { 13 | "params": [ 14 | "numerator", 15 | "denominator" 16 | ] 17 | }, 18 | "childSets": { 19 | "object": [ 20 | { 21 | "type": "PY_IDENTIFIER", 22 | "properties": { 23 | "identifier": "fractions" 24 | }, 25 | "childSets": {} 26 | } 27 | ], 28 | "arguments": [ 29 | { 30 | "type": "PY_ARG", 31 | "childSets": { 32 | "argument": [ 33 | { 34 | "type": "PYTHON_EXPRESSION", 35 | "properties": {}, 36 | "childSets": { 37 | "tokens": [] 38 | } 39 | } 40 | ] 41 | }, 42 | "properties": {} 43 | } 44 | ] 45 | } 46 | } 47 | } 48 | ] 49 | } -------------------------------------------------------------------------------- /packages/language-python/tray/graphlib.json: -------------------------------------------------------------------------------- 1 | { 2 | "category": "graphlib", 3 | "entries": [ 4 | { 5 | "key": "graphlib.TopologicalSorter", 6 | "abstract": "Provides functionality to topologically sort a graph of hashable nodes", 7 | "serializedNode": { 8 | "type": "PYTHON_CALL_MEMBER", 9 | "properties": { 10 | "member": "TopologicalSorter" 11 | }, 12 | "meta": { 13 | "params": [ 14 | "graph" 15 | ] 16 | }, 17 | "childSets": { 18 | "object": [ 19 | { 20 | "type": "PY_IDENTIFIER", 21 | "properties": { 22 | "identifier": "graphlib" 23 | }, 24 | "childSets": {} 25 | } 26 | ], 27 | "arguments": [ 28 | { 29 | "type": "PY_ARG", 30 | "childSets": { 31 | "argument": [ 32 | { 33 | "type": "PYTHON_EXPRESSION", 34 | "properties": {}, 35 | "childSets": { 36 | "tokens": [] 37 | } 38 | } 39 | ] 40 | }, 41 | "properties": {} 42 | } 43 | ] 44 | } 45 | } 46 | }, 47 | { 48 | "key": "graphlib.CycleError", 49 | "abstract": "Subclass of ValueError raised by TopologicalSorter.prepare if cycles", 50 | "serializedNode": { 51 | "type": "PYTHON_CALL_MEMBER", 52 | "properties": { 53 | "member": "CycleError" 54 | }, 55 | "meta": { 56 | "params": [] 57 | }, 58 | "childSets": { 59 | "object": [ 60 | { 61 | "type": "PY_IDENTIFIER", 62 | "properties": { 63 | "identifier": "graphlib" 64 | }, 65 | "childSets": {} 66 | } 67 | ], 68 | "arguments": [] 69 | } 70 | } 71 | } 72 | ] 73 | } -------------------------------------------------------------------------------- /packages/language-python/tray/http.json: -------------------------------------------------------------------------------- 1 | { 2 | "category": "http", 3 | "entries": [ 4 | { 5 | "key": "http.HTTPStatus", 6 | "abstract": "HTTP status codes and reason phrases", 7 | "serializedNode": { 8 | "type": "PYTHON_CALL_MEMBER", 9 | "properties": { 10 | "member": "HTTPStatus" 11 | }, 12 | "meta": { 13 | "params": [] 14 | }, 15 | "childSets": { 16 | "object": [ 17 | { 18 | "type": "PY_IDENTIFIER", 19 | "properties": { 20 | "identifier": "http" 21 | }, 22 | "childSets": {} 23 | } 24 | ], 25 | "arguments": [] 26 | } 27 | } 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /packages/language-python/tray/imghdr.json: -------------------------------------------------------------------------------- 1 | { 2 | "category": "imghdr", 3 | "entries": [ 4 | { 5 | "key": "imghdr.what", 6 | "abstract": "", 7 | "serializedNode": { 8 | "type": "PYTHON_CALL_MEMBER", 9 | "properties": { 10 | "member": "what" 11 | }, 12 | "meta": { 13 | "params": [ 14 | "file", 15 | "h" 16 | ] 17 | }, 18 | "childSets": { 19 | "object": [ 20 | { 21 | "type": "PY_IDENTIFIER", 22 | "properties": { 23 | "identifier": "imghdr" 24 | }, 25 | "childSets": {} 26 | } 27 | ], 28 | "arguments": [ 29 | { 30 | "type": "PY_ARG", 31 | "childSets": { 32 | "argument": [ 33 | { 34 | "type": "PYTHON_EXPRESSION", 35 | "properties": {}, 36 | "childSets": { 37 | "tokens": [] 38 | } 39 | } 40 | ] 41 | }, 42 | "properties": {} 43 | } 44 | ] 45 | } 46 | } 47 | }, 48 | { 49 | "key": "imghdr.tests", 50 | "abstract": "", 51 | "serializedNode": { 52 | "type": "PYTHON_MEMBER", 53 | "properties": { 54 | "member": "tests" 55 | }, 56 | "childSets": { 57 | "object": [ 58 | { 59 | "type": "PY_IDENTIFIER", 60 | "properties": { 61 | "identifier": "imghdr" 62 | }, 63 | "childSets": {} 64 | } 65 | ] 66 | } 67 | } 68 | } 69 | ] 70 | } -------------------------------------------------------------------------------- /packages/language-python/tray/itertools.json: -------------------------------------------------------------------------------- 1 | { 2 | "category": "itertools", 3 | "entries": [] 4 | } -------------------------------------------------------------------------------- /packages/language-python/tray/operator.json: -------------------------------------------------------------------------------- 1 | { 2 | "category": "operator", 3 | "entries": [] 4 | } -------------------------------------------------------------------------------- /packages/language-python/tray/pipes.json: -------------------------------------------------------------------------------- 1 | { 2 | "category": "pipes", 3 | "entries": [ 4 | { 5 | "key": "pipes.Template", 6 | "abstract": "Class representing a pipeline template.", 7 | "serializedNode": { 8 | "type": "PYTHON_CALL_MEMBER", 9 | "properties": { 10 | "member": "Template" 11 | }, 12 | "meta": { 13 | "params": [] 14 | }, 15 | "childSets": { 16 | "object": [ 17 | { 18 | "type": "PY_IDENTIFIER", 19 | "properties": { 20 | "identifier": "pipes" 21 | }, 22 | "childSets": {} 23 | } 24 | ], 25 | "arguments": [] 26 | } 27 | } 28 | }, 29 | { 30 | "key": "pipes.quote", 31 | "abstract": "", 32 | "serializedNode": { 33 | "type": "PYTHON_CALL_MEMBER", 34 | "properties": { 35 | "member": "quote" 36 | }, 37 | "meta": { 38 | "params": [ 39 | "s" 40 | ] 41 | }, 42 | "childSets": { 43 | "object": [ 44 | { 45 | "type": "PY_IDENTIFIER", 46 | "properties": { 47 | "identifier": "pipes" 48 | }, 49 | "childSets": {} 50 | } 51 | ], 52 | "arguments": [ 53 | { 54 | "type": "PY_ARG", 55 | "childSets": { 56 | "argument": [ 57 | { 58 | "type": "PYTHON_EXPRESSION", 59 | "properties": {}, 60 | "childSets": { 61 | "tokens": [] 62 | } 63 | } 64 | ] 65 | }, 66 | "properties": {} 67 | } 68 | ] 69 | } 70 | } 71 | } 72 | ] 73 | } -------------------------------------------------------------------------------- /packages/language-python/tray/pydoc_data.json: -------------------------------------------------------------------------------- 1 | { 2 | "category": "pydoc_data", 3 | "entries": [] 4 | } -------------------------------------------------------------------------------- /packages/language-python/tray/rlcompleter.json: -------------------------------------------------------------------------------- 1 | { 2 | "category": "rlcompleter", 3 | "entries": [ 4 | { 5 | "key": "rlcompleter.Completer", 6 | "abstract": "", 7 | "serializedNode": { 8 | "type": "PYTHON_CALL_MEMBER", 9 | "properties": { 10 | "member": "Completer" 11 | }, 12 | "meta": { 13 | "params": [ 14 | "namespace" 15 | ] 16 | }, 17 | "childSets": { 18 | "object": [ 19 | { 20 | "type": "PY_IDENTIFIER", 21 | "properties": { 22 | "identifier": "rlcompleter" 23 | }, 24 | "childSets": {} 25 | } 26 | ], 27 | "arguments": [ 28 | { 29 | "type": "PY_ARG", 30 | "childSets": { 31 | "argument": [ 32 | { 33 | "type": "PYTHON_EXPRESSION", 34 | "properties": {}, 35 | "childSets": { 36 | "tokens": [] 37 | } 38 | } 39 | ] 40 | }, 41 | "properties": {} 42 | } 43 | ] 44 | } 45 | } 46 | } 47 | ] 48 | } -------------------------------------------------------------------------------- /packages/language-python/tray/sched.json: -------------------------------------------------------------------------------- 1 | { 2 | "category": "sched", 3 | "entries": [ 4 | { 5 | "key": "sched.Event", 6 | "abstract": "", 7 | "serializedNode": { 8 | "type": "PYTHON_CALL_MEMBER", 9 | "properties": { 10 | "member": "Event" 11 | }, 12 | "meta": { 13 | "params": [] 14 | }, 15 | "childSets": { 16 | "object": [ 17 | { 18 | "type": "PY_IDENTIFIER", 19 | "properties": { 20 | "identifier": "sched" 21 | }, 22 | "childSets": {} 23 | } 24 | ], 25 | "arguments": [] 26 | } 27 | } 28 | }, 29 | { 30 | "key": "sched.scheduler", 31 | "abstract": "", 32 | "serializedNode": { 33 | "type": "PYTHON_CALL_MEMBER", 34 | "properties": { 35 | "member": "scheduler" 36 | }, 37 | "meta": { 38 | "params": [ 39 | "timefunc", 40 | "delayfunc" 41 | ] 42 | }, 43 | "childSets": { 44 | "object": [ 45 | { 46 | "type": "PY_IDENTIFIER", 47 | "properties": { 48 | "identifier": "sched" 49 | }, 50 | "childSets": {} 51 | } 52 | ], 53 | "arguments": [ 54 | { 55 | "type": "PY_ARG", 56 | "childSets": { 57 | "argument": [ 58 | { 59 | "type": "PYTHON_EXPRESSION", 60 | "properties": {}, 61 | "childSets": { 62 | "tokens": [] 63 | } 64 | } 65 | ] 66 | }, 67 | "properties": {} 68 | } 69 | ] 70 | } 71 | } 72 | } 73 | ] 74 | } -------------------------------------------------------------------------------- /packages/language-python/tray/shlex.json: -------------------------------------------------------------------------------- 1 | { 2 | "category": "shlex", 3 | "entries": [] 4 | } -------------------------------------------------------------------------------- /packages/language-python/tray/struct.json: -------------------------------------------------------------------------------- 1 | { 2 | "category": "struct", 3 | "entries": [] 4 | } -------------------------------------------------------------------------------- /packages/language-python/tray/tempfile.json: -------------------------------------------------------------------------------- 1 | { 2 | "category": "tempfile", 3 | "entries": [] 4 | } -------------------------------------------------------------------------------- /packages/language-python/tray/this.json: -------------------------------------------------------------------------------- 1 | { 2 | "category": "this", 3 | "entries": [ 4 | { 5 | "key": "this.s", 6 | "abstract": "", 7 | "serializedNode": { 8 | "type": "PYTHON_MEMBER", 9 | "properties": { 10 | "member": "s" 11 | }, 12 | "childSets": { 13 | "object": [ 14 | { 15 | "type": "PY_IDENTIFIER", 16 | "properties": { 17 | "identifier": "this" 18 | }, 19 | "childSets": {} 20 | } 21 | ] 22 | } 23 | } 24 | }, 25 | { 26 | "key": "this.d", 27 | "abstract": "", 28 | "serializedNode": { 29 | "type": "PYTHON_MEMBER", 30 | "properties": { 31 | "member": "d" 32 | }, 33 | "childSets": { 34 | "object": [ 35 | { 36 | "type": "PY_IDENTIFIER", 37 | "properties": { 38 | "identifier": "this" 39 | }, 40 | "childSets": {} 41 | } 42 | ] 43 | } 44 | } 45 | } 46 | ] 47 | } -------------------------------------------------------------------------------- /packages/language-python/tray/urllib.json: -------------------------------------------------------------------------------- 1 | { 2 | "category": "urllib", 3 | "entries": [] 4 | } -------------------------------------------------------------------------------- /packages/language-python/tray/wsgiref.json: -------------------------------------------------------------------------------- 1 | { 2 | "category": "wsgiref", 3 | "entries": [] 4 | } -------------------------------------------------------------------------------- /packages/language-python/tray/xml.json: -------------------------------------------------------------------------------- 1 | { 2 | "category": "xml", 3 | "entries": [] 4 | } -------------------------------------------------------------------------------- /packages/language-python/tray/xmlrpc.json: -------------------------------------------------------------------------------- 1 | { 2 | "category": "xmlrpc", 3 | "entries": [] 4 | } -------------------------------------------------------------------------------- /packages/language-python/tray/zipimport.json: -------------------------------------------------------------------------------- 1 | { 2 | "category": "zipimport", 3 | "entries": [ 4 | { 5 | "key": "zipimport.ZipImportError", 6 | "abstract": "", 7 | "serializedNode": { 8 | "type": "PYTHON_CALL_MEMBER", 9 | "properties": { 10 | "member": "ZipImportError" 11 | }, 12 | "meta": { 13 | "params": [] 14 | }, 15 | "childSets": { 16 | "object": [ 17 | { 18 | "type": "PY_IDENTIFIER", 19 | "properties": { 20 | "identifier": "zipimport" 21 | }, 22 | "childSets": {} 23 | } 24 | ], 25 | "arguments": [] 26 | } 27 | } 28 | }, 29 | { 30 | "key": "zipimport.zipimporter", 31 | "abstract": "zipimporter(archivepath) -> zipimporter object", 32 | "serializedNode": { 33 | "type": "PYTHON_CALL_MEMBER", 34 | "properties": { 35 | "member": "zipimporter" 36 | }, 37 | "meta": { 38 | "params": [ 39 | "path" 40 | ] 41 | }, 42 | "childSets": { 43 | "object": [ 44 | { 45 | "type": "PY_IDENTIFIER", 46 | "properties": { 47 | "identifier": "zipimport" 48 | }, 49 | "childSets": {} 50 | } 51 | ], 52 | "arguments": [ 53 | { 54 | "type": "PY_ARG", 55 | "childSets": { 56 | "argument": [ 57 | { 58 | "type": "PYTHON_EXPRESSION", 59 | "properties": {}, 60 | "childSets": { 61 | "tokens": [] 62 | } 63 | } 64 | ] 65 | }, 66 | "properties": {} 67 | } 68 | ] 69 | } 70 | } 71 | } 72 | ] 73 | } -------------------------------------------------------------------------------- /packages/language-python/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "ES2020", 5 | "lib": ["DOM", "ES2020"], 6 | "moduleResolution": "node", 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "experimentalDecorators": true, 10 | "resolveJsonModule": true, 11 | "skipDefaultLibCheck": true, 12 | "downlevelIteration": true, 13 | "noEmit": true, 14 | "baseUrl": ".", 15 | "types": ["@types/wicg-file-system-access"] 16 | }, 17 | "include": ["src"], 18 | "exclude": [ 19 | "node_modules", 20 | "dist", 21 | "out", 22 | "*.config.*" 23 | ], 24 | } 25 | -------------------------------------------------------------------------------- /packages/language-web/.gitignore: -------------------------------------------------------------------------------- 1 | # non-dev env files 2 | production.env 3 | 4 | # Firebase files 5 | .firebase 6 | 7 | lib 8 | 9 | # Terraform 10 | .terraform 11 | 12 | # Lambda zips 13 | 14 | # Mac files 15 | .DS_Store 16 | 17 | # Logs 18 | logs 19 | *.log 20 | npm-debug.log* 21 | yarn-debug.log* 22 | yarn-error.log* 23 | lerna-debug.log* 24 | 25 | # Diagnostic reports (https://nodejs.org/api/report.html) 26 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 27 | 28 | # Runtime data 29 | pids 30 | *.pid 31 | *.seed 32 | *.pid.lock 33 | 34 | # Directory for instrumented libs generated by jscoverage/JSCover 35 | lib-cov 36 | 37 | # Coverage directory used by tools like istanbul 38 | coverage 39 | *.lcov 40 | 41 | # nyc test coverage 42 | .nyc_output 43 | 44 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 45 | .grunt 46 | 47 | # Bower dependency directory (https://bower.io/) 48 | bower_components 49 | 50 | # node-waf configuration 51 | .lock-wscript 52 | 53 | # Compiled binary addons (https://nodejs.org/api/addons.html) 54 | build/Release 55 | 56 | # Dependency directories 57 | node_modules/ 58 | jspm_packages/ 59 | 60 | # TypeScript v1 declaration files 61 | typings/ 62 | 63 | # TypeScript cache 64 | *.tsbuildinfo 65 | 66 | # Optional npm cache directory 67 | .npm 68 | 69 | # Optional eslint cache 70 | .eslintcache 71 | 72 | # Microbundle cache 73 | .rpt2_cache/ 74 | .rts2_cache_cjs/ 75 | .rts2_cache_es/ 76 | .rts2_cache_umd/ 77 | 78 | # Optional REPL history 79 | .node_repl_history 80 | 81 | # Output of 'npm pack' 82 | *.tgz 83 | 84 | # Yarn Integrity file 85 | .yarn-integrity 86 | 87 | # parcel-bundler cache (https://parceljs.org/) 88 | .cache 89 | 90 | # Next.js build output 91 | .next 92 | 93 | # Nuxt.js build / generate output 94 | .nuxt 95 | dist 96 | frame-dist 97 | 98 | # Gatsby files 99 | .cache/ 100 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 101 | # https://nextjs.org/blog/next-9-1#public-directory-support 102 | # public 103 | 104 | # vuepress build output 105 | .vuepress/dist 106 | 107 | # Serverless directories 108 | .serverless/ 109 | 110 | # FuseBox cache 111 | .fusebox/ 112 | 113 | # DynamoDB Local files 114 | .dynamodb/ 115 | 116 | # TernJS port file 117 | .tern-port 118 | -------------------------------------------------------------------------------- /packages/language-web/index.ts: -------------------------------------------------------------------------------- 1 | export { loadTypes } from './type_loader' 2 | -------------------------------------------------------------------------------- /packages/language-web/javascript_node.ts: -------------------------------------------------------------------------------- 1 | import * as recast from 'recast' 2 | 3 | import { ASTNode } from 'ast-types' 4 | import { SplootNode } from '@splootcode/core' 5 | 6 | export class JavaScriptSplootNode extends SplootNode { 7 | generateJsAst(): ASTNode { 8 | console.warn('Missing generateJsAst implementation for: ', this.type) 9 | return null 10 | } 11 | 12 | generateCodeString(): string { 13 | const ast = this.generateJsAst() 14 | return recast.print(ast).code 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/language-web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@splootcode/language-web", 3 | "version": "1.0.0", 4 | "main": "dist/index.js", 5 | "module": "dist/index.mjs", 6 | "typings": "dist/index.d.ts", 7 | "type": "module", 8 | "private": true, 9 | "scripts": { 10 | "test": "echo 'No tests for language-web.'", 11 | "build": "yarn rollup -c" 12 | }, 13 | "dependencies": { 14 | "@splootcode/core": "^1.0.0", 15 | "babylon": "^6.18.0", 16 | "css-tree": "^2.2.1", 17 | "react-spreadsheet": "^0.7.7", 18 | "recast": "^0.21.5", 19 | "vscode-web-custom-data": "^0.3.6", 20 | "xmldom": "^0.6.0" 21 | }, 22 | "devDependencies": { 23 | "@rollup/plugin-json": "^6.0.0", 24 | "@rollup/plugin-node-resolve": "^15.0.2", 25 | "@rollup/plugin-typescript": "^11.1.0", 26 | "@types/css-tree": "^2.0.0", 27 | "@types/xmldom": "^0.1.31", 28 | "rollup": "3.20.2", 29 | "rollup-plugin-dts": "^5.3.0", 30 | "typescript": "^4.8.4" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/language-web/rollup.config.js: -------------------------------------------------------------------------------- 1 | import dts from 'rollup-plugin-dts' 2 | import json from '@rollup/plugin-json' 3 | import typescript from '@rollup/plugin-typescript' 4 | 5 | export default [ 6 | { 7 | plugins: [typescript(), json()], 8 | input: 'index.ts', 9 | output: [ 10 | { 11 | file: `dist/index.mjs`, 12 | format: 'es', 13 | sourcemap: true, 14 | }, 15 | { 16 | file: `dist/index.js`, 17 | format: 'cjs', 18 | sourcemap: true, 19 | }, 20 | ], 21 | }, 22 | { 23 | input: 'index.ts', 24 | output: [{ file: `dist/index.d.ts`, format: 'es' }], 25 | plugins: [dts(), json()], 26 | }, 27 | ] 28 | -------------------------------------------------------------------------------- /packages/language-web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "ES2020", 5 | "lib": ["DOM", "ES2020"], 6 | "moduleResolution": "node", 7 | "esModuleInterop": true, 8 | "jsx": "react", 9 | "allowSyntheticDefaultImports": true, 10 | "experimentalDecorators": true, 11 | "resolveJsonModule": true, 12 | "downlevelIteration": true, 13 | "skipDefaultLibCheck": true, 14 | "skipLibCheck": true, 15 | "noEmit": true, 16 | "baseUrl": ".", 17 | }, 18 | "include": [ 19 | "code_io/**/*", 20 | "types/**/*", 21 | "javascript_node.ts", 22 | "type_loader.ts" 23 | ], 24 | } 25 | -------------------------------------------------------------------------------- /packages/language-web/types/css/css_properties.ts: -------------------------------------------------------------------------------- 1 | // https://github.com/microsoft/vscode-custom-data/blob/master/web-data/data/browsers.html-data.json 2 | import * as vscodeCssData from 'vscode-web-custom-data/data/browsers.css-data.json' 3 | 4 | interface Value { 5 | name: string 6 | description: string 7 | } 8 | 9 | interface Property { 10 | name: string 11 | browsers: string[] 12 | syntax: string 13 | relevance: number 14 | description: string 15 | restrictions: string[] 16 | values: Value[] 17 | } 18 | 19 | interface CssData { 20 | version: number 21 | properties: Property[] 22 | } 23 | 24 | export function getCssProperties(): string[] { 25 | const cssData = vscodeCssData as CssData 26 | const propertyNames = cssData.properties.map((prop) => { 27 | return prop.name 28 | }) 29 | return propertyNames 30 | } 31 | -------------------------------------------------------------------------------- /packages/language-web/types/dataset/field_declaration.ts: -------------------------------------------------------------------------------- 1 | import { 2 | NodeCategory, 3 | ParentReference, 4 | SerializedNode, 5 | SplootNode, 6 | TypeRegistration, 7 | registerNodeCateogry, 8 | registerType, 9 | } from '@splootcode/core' 10 | 11 | export const DATA_FIELD_DECLARATION = 'DATA_FIELD_DECLARATION' 12 | 13 | export class SplootDataFieldDeclaration extends SplootNode { 14 | constructor(parentReference: ParentReference, key: string, fieldName: string) { 15 | super(parentReference, DATA_FIELD_DECLARATION) 16 | this.setProperty('key', key) 17 | this.setProperty('name', fieldName) 18 | } 19 | 20 | getName(): string { 21 | return this.getProperty('name') 22 | } 23 | 24 | getKey(): string { 25 | return this.getProperty('key') 26 | } 27 | 28 | static deserializer(serializedNode: SerializedNode): SplootDataFieldDeclaration { 29 | const node = new SplootDataFieldDeclaration( 30 | null, 31 | serializedNode.properties['key'], 32 | serializedNode.properties['name'] 33 | ) 34 | return node 35 | } 36 | 37 | static register() { 38 | const typeRegistration = new TypeRegistration() 39 | typeRegistration.typeName = DATA_FIELD_DECLARATION 40 | typeRegistration.deserializer = SplootDataFieldDeclaration.deserializer 41 | typeRegistration.childSets = {} 42 | typeRegistration.layout = null 43 | 44 | registerType(typeRegistration) 45 | registerNodeCateogry(DATA_FIELD_DECLARATION, NodeCategory.DataSheetFieldDeclaration) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /packages/language-web/types/dataset/string_entry.ts: -------------------------------------------------------------------------------- 1 | import { 2 | NodeCategory, 3 | ParentReference, 4 | SerializedNode, 5 | SplootNode, 6 | TypeRegistration, 7 | registerNodeCateogry, 8 | registerType, 9 | } from '@splootcode/core' 10 | 11 | export const DATA_STRING_ENTRY = 'DATA_STRING_ENTRY' 12 | 13 | export class SplootDataStringEntry extends SplootNode { 14 | constructor(parentReference: ParentReference, fieldName: string, value: string) { 15 | super(parentReference, DATA_STRING_ENTRY) 16 | this.setProperty('fieldname', fieldName) 17 | this.setProperty('value', value) 18 | } 19 | 20 | getFieldName(): string { 21 | return this.getProperty('fieldname') 22 | } 23 | 24 | getValue(): string { 25 | return this.getProperty('value') 26 | } 27 | 28 | setValue(value: string) { 29 | this.setProperty('value', value) 30 | } 31 | 32 | static deserializer(serializedNode: SerializedNode): SplootDataStringEntry { 33 | const node = new SplootDataStringEntry( 34 | null, 35 | serializedNode.properties['fieldname'], 36 | serializedNode.properties['value'] 37 | ) 38 | return node 39 | } 40 | 41 | static register() { 42 | const typeRegistration = new TypeRegistration() 43 | typeRegistration.typeName = DATA_STRING_ENTRY 44 | typeRegistration.deserializer = SplootDataStringEntry.deserializer 45 | typeRegistration.childSets = {} 46 | typeRegistration.layout = null 47 | 48 | registerType(typeRegistration) 49 | registerNodeCateogry(DATA_STRING_ENTRY, NodeCategory.DataSheetEntry) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /packages/runtime-python/.gitignore: -------------------------------------------------------------------------------- 1 | # non-dev env files 2 | production.env 3 | 4 | # Firebase files 5 | .firebase 6 | 7 | lib 8 | 9 | # Terraform 10 | .terraform 11 | 12 | # Lambda zips 13 | 14 | # Mac files 15 | .DS_Store 16 | 17 | # Logs 18 | logs 19 | *.log 20 | npm-debug.log* 21 | yarn-debug.log* 22 | yarn-error.log* 23 | lerna-debug.log* 24 | 25 | # Diagnostic reports (https://nodejs.org/api/report.html) 26 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 27 | 28 | # Runtime data 29 | pids 30 | *.pid 31 | *.seed 32 | *.pid.lock 33 | 34 | # Directory for instrumented libs generated by jscoverage/JSCover 35 | lib-cov 36 | 37 | # Coverage directory used by tools like istanbul 38 | coverage 39 | *.lcov 40 | 41 | # nyc test coverage 42 | .nyc_output 43 | 44 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 45 | .grunt 46 | 47 | # Bower dependency directory (https://bower.io/) 48 | bower_components 49 | 50 | # node-waf configuration 51 | .lock-wscript 52 | 53 | # Compiled binary addons (https://nodejs.org/api/addons.html) 54 | build/Release 55 | 56 | # Dependency directories 57 | node_modules/ 58 | jspm_packages/ 59 | 60 | # TypeScript v1 declaration files 61 | typings/ 62 | 63 | # TypeScript cache 64 | *.tsbuildinfo 65 | 66 | # Optional npm cache directory 67 | .npm 68 | 69 | # Optional eslint cache 70 | .eslintcache 71 | 72 | # Microbundle cache 73 | .rpt2_cache/ 74 | .rts2_cache_cjs/ 75 | .rts2_cache_es/ 76 | .rts2_cache_umd/ 77 | 78 | # Optional REPL history 79 | .node_repl_history 80 | 81 | # Output of 'npm pack' 82 | *.tgz 83 | 84 | # Yarn Integrity file 85 | .yarn-integrity 86 | 87 | # parcel-bundler cache (https://parceljs.org/) 88 | .cache 89 | 90 | # Next.js build output 91 | .next 92 | 93 | # Nuxt.js build / generate output 94 | .nuxt 95 | dist 96 | frame-dist 97 | 98 | # Gatsby files 99 | .cache/ 100 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 101 | # https://nextjs.org/blog/next-9-1#public-directory-support 102 | # public 103 | 104 | # vuepress build output 105 | .vuepress/dist 106 | 107 | # Serverless directories 108 | .serverless/ 109 | 110 | # FuseBox cache 111 | .fusebox/ 112 | 113 | # DynamoDB Local files 114 | .dynamodb/ 115 | 116 | # TernJS port file 117 | .tern-port 118 | -------------------------------------------------------------------------------- /packages/runtime-python/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@splootcode/runtime-python", 3 | "version": "1.0.0", 4 | "type": "module", 5 | "main": "./dist/index.js", 6 | "module": "./dist/index.mjs", 7 | "exports": { 8 | ".": { 9 | "import": "./dist/index.mjs", 10 | "require": "./dist/index.js", 11 | "types": "./dist/index.d.ts" 12 | }, 13 | "./worker": { 14 | "import": "./dist/worker.mjs", 15 | "require": "./dist/worker.js", 16 | "types": "./dist/worker.d.ts" 17 | }, 18 | "./autocomplete_worker": { 19 | "import": "./dist/autocomplete_worker.mjs", 20 | "require": "./dist/autocomplete_worker.js", 21 | "types": "./dist/autocomplete_worker.d.ts" 22 | } 23 | }, 24 | "typesVersions": { 25 | "*": { 26 | "worker": [ 27 | "dist/worker.d.ts" 28 | ], 29 | "autocomplete_worker": [ 30 | "dist/autocomplete_worker.d.ts" 31 | ] 32 | } 33 | }, 34 | "files": [ 35 | "./dist" 36 | ], 37 | "private": true, 38 | "scripts": { 39 | "test": "echo 'No tests for runtime-python.'", 40 | "build": "yarn rollup -c" 41 | }, 42 | "dependencies": {}, 43 | "devDependencies": { 44 | "@rollup/plugin-node-resolve": "^15.0.2", 45 | "@rollup/plugin-typescript": "^11.1.0", 46 | "rollup": "3.20.2", 47 | "rollup-plugin-dts": "^5.3.0", 48 | "typescript": "^4.8.4" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /packages/runtime-python/rollup.config.js: -------------------------------------------------------------------------------- 1 | import dts from 'rollup-plugin-dts' 2 | import typescript from '@rollup/plugin-typescript' 3 | 4 | export default [ 5 | { 6 | plugins: [typescript()], 7 | input: 'src/index.ts', 8 | output: [ 9 | { 10 | file: `dist/index.mjs`, 11 | format: 'es', 12 | sourcemap: true, 13 | }, 14 | { 15 | file: `dist/index.js`, 16 | format: 'cjs', 17 | sourcemap: true, 18 | }, 19 | ], 20 | }, 21 | { 22 | input: 'src/index.ts', 23 | output: [{ file: `dist/index.d.ts`, format: 'es' }], 24 | plugins: [dts()], 25 | }, 26 | { 27 | plugins: [typescript()], 28 | input: 'src/webworker.ts', 29 | output: [ 30 | { 31 | file: `dist/worker.mjs`, 32 | format: 'es', 33 | sourcemap: true, 34 | }, 35 | { 36 | file: `dist/worker.js`, 37 | format: 'cjs', 38 | sourcemap: true, 39 | }, 40 | ], 41 | }, 42 | { 43 | plugins: [typescript()], 44 | input: 'src/autocomplete_webworker.ts', 45 | output: [ 46 | { 47 | file: `dist/autocomplete_worker.mjs`, 48 | format: 'es', 49 | sourcemap: true, 50 | }, 51 | { 52 | file: `dist/autocomplete_worker.js`, 53 | format: 'cjs', 54 | sourcemap: true, 55 | }, 56 | ], 57 | }, 58 | { 59 | input: 'src/webworker.ts', 60 | output: [{ file: `dist/worker.d.ts`, format: 'es' }], 61 | plugins: [dts()], 62 | }, 63 | { 64 | input: 'src/autocomplete_webworker.ts', 65 | output: [{ file: `dist/autocomplete_worker.d.ts`, format: 'es' }], 66 | plugins: [dts()], 67 | }, 68 | ] 69 | -------------------------------------------------------------------------------- /packages/runtime-python/src/index.ts: -------------------------------------------------------------------------------- 1 | export { initialize } from './runtime' 2 | 3 | export { FetchHandler, FetchData, ResponseData, FetchSyncErrorType } from './runtime/common' 4 | 5 | export * from './message_types' 6 | 7 | export * from './pyodide' 8 | export * from './pyright' 9 | 10 | export { getAutocompleteInfo, getShortDoc } from './autocomplete' 11 | 12 | export { StaticURLs } from './static_urls' 13 | -------------------------------------------------------------------------------- /packages/runtime-python/src/pyodide.ts: -------------------------------------------------------------------------------- 1 | import { Dependency } from '@splootcode/core' 2 | import { StaticURLs } from './static_urls' 3 | 4 | export function tryNonModuleLoadPyodide() { 5 | // If we're not in a module context (prod build is non-module) 6 | // Then we need to imoprt Pyodide this way, but it fails in a module context (local dev). 7 | if (importScripts) { 8 | try { 9 | importScripts('https://cdn.jsdelivr.net/pyodide/v0.21.3/full/pyodide.js') 10 | } catch (e) { 11 | console.warn(e) 12 | } 13 | } 14 | } 15 | 16 | export async function tryModuleLoadPyodide() { 17 | // @ts-ignore 18 | if (typeof loadPyodide == 'undefined') { 19 | // import is a syntax error in non-module context (which we need to be in for Firefox...) 20 | // But we use module context for local dev because... Vite does that. 21 | await eval("import('https://cdn.jsdelivr.net/pyodide/v0.21.3/full/pyodide.js')") 22 | } 23 | } 24 | 25 | export async function setupPyodide(urls: string[]) { 26 | // @ts-ignore 27 | const pyodide = await loadPyodide({ fullStdLib: false, indexURL: 'https://cdn.jsdelivr.net/pyodide/v0.21.3/full/' }) 28 | 29 | await pyodide.loadPackage('micropip') 30 | const micropip = pyodide.pyimport('micropip') 31 | const promises = [ 32 | ...urls.map((url) => micropip.install(url)), 33 | micropip.install('types-requests==2.28.1'), 34 | micropip.install('ast-comments'), 35 | ] 36 | 37 | await Promise.all(promises) 38 | 39 | return pyodide 40 | } 41 | 42 | export const loadDependencies = async (pyodide: any, newDependencies: Dependency[], urls: StaticURLs) => { 43 | const micropip = pyodide.pyimport('micropip') 44 | 45 | const imports = newDependencies 46 | .map(({ name, version }): any => { 47 | let types = [] 48 | 49 | if (name === 'pandas') { 50 | types = ['pandas-stubs~=1.5.3'] 51 | } else if (name === 'streamlit') { 52 | return [urls.pyarrowPackageURL, urls.streamlitPackageURL] 53 | } else if (name === 'requests') { 54 | return [urls.requestsPackageURL] 55 | } 56 | 57 | return [name, ...types] 58 | }) 59 | .map((dependencies) => micropip.install(dependencies)) 60 | 61 | await Promise.all(imports) 62 | } 63 | -------------------------------------------------------------------------------- /packages/runtime-python/src/static_urls.ts: -------------------------------------------------------------------------------- 1 | export interface StaticURLs { 2 | executorURL: string 3 | moduleLoaderURL: string 4 | requestsPackageURL: string 5 | textGeneratorURL: string 6 | streamlitPackageURL: string 7 | pyarrowPackageURL: string 8 | } 9 | -------------------------------------------------------------------------------- /packages/runtime-python/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "ES2020", 5 | "lib": ["DOM", "ES2020", "webworker"], 6 | "moduleResolution": "node", 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "skipDefaultLibCheck": true, 10 | "skipLibCheck": true, 11 | "downlevelIteration": true, 12 | "resolveJsonModule": true, 13 | "noEmit": true, 14 | "baseUrl": ".", 15 | }, 16 | "include": ["src"], 17 | "exclude": [ 18 | "node_modules", 19 | "dist", 20 | "packages", 21 | "lib", 22 | "*.config.*" 23 | ], 24 | } 25 | -------------------------------------------------------------------------------- /packages/runtime-web/.gitignore: -------------------------------------------------------------------------------- 1 | # non-dev env files 2 | production.env 3 | 4 | # Firebase files 5 | .firebase 6 | 7 | lib 8 | 9 | # Terraform 10 | .terraform 11 | 12 | # Lambda zips 13 | 14 | # Mac files 15 | .DS_Store 16 | 17 | # Logs 18 | logs 19 | *.log 20 | npm-debug.log* 21 | yarn-debug.log* 22 | yarn-error.log* 23 | lerna-debug.log* 24 | 25 | # Diagnostic reports (https://nodejs.org/api/report.html) 26 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 27 | 28 | # Runtime data 29 | pids 30 | *.pid 31 | *.seed 32 | *.pid.lock 33 | 34 | # Directory for instrumented libs generated by jscoverage/JSCover 35 | lib-cov 36 | 37 | # Coverage directory used by tools like istanbul 38 | coverage 39 | *.lcov 40 | 41 | # nyc test coverage 42 | .nyc_output 43 | 44 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 45 | .grunt 46 | 47 | # Bower dependency directory (https://bower.io/) 48 | bower_components 49 | 50 | # node-waf configuration 51 | .lock-wscript 52 | 53 | # Compiled binary addons (https://nodejs.org/api/addons.html) 54 | build/Release 55 | 56 | # Dependency directories 57 | node_modules/ 58 | jspm_packages/ 59 | 60 | # TypeScript v1 declaration files 61 | typings/ 62 | 63 | # TypeScript cache 64 | *.tsbuildinfo 65 | 66 | # Optional npm cache directory 67 | .npm 68 | 69 | # Optional eslint cache 70 | .eslintcache 71 | 72 | # Microbundle cache 73 | .rpt2_cache/ 74 | .rts2_cache_cjs/ 75 | .rts2_cache_es/ 76 | .rts2_cache_umd/ 77 | 78 | # Optional REPL history 79 | .node_repl_history 80 | 81 | # Output of 'npm pack' 82 | *.tgz 83 | 84 | # Yarn Integrity file 85 | .yarn-integrity 86 | 87 | # parcel-bundler cache (https://parceljs.org/) 88 | .cache 89 | 90 | # Next.js build output 91 | .next 92 | 93 | # Nuxt.js build / generate output 94 | .nuxt 95 | dist 96 | frame-dist 97 | 98 | # Gatsby files 99 | .cache/ 100 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 101 | # https://nextjs.org/blog/next-9-1#public-directory-support 102 | # public 103 | 104 | # vuepress build output 105 | .vuepress/dist 106 | 107 | # Serverless directories 108 | .serverless/ 109 | 110 | # FuseBox cache 111 | .fusebox/ 112 | 113 | # DynamoDB Local files 114 | .dynamodb/ 115 | 116 | # TernJS port file 117 | .tern-port 118 | -------------------------------------------------------------------------------- /packages/runtime-web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "declarationMap": true, 5 | "target": "ES2020", 6 | "module": "ES2020", 7 | "lib": ["DOM", "ES2020", "webworker"], 8 | "moduleResolution": "node", 9 | "esModuleInterop": true, 10 | "composite": true, 11 | "allowSyntheticDefaultImports": true, 12 | "experimentalDecorators": true, 13 | "resolveJsonModule": true, 14 | "skipDefaultLibCheck": true, 15 | "downlevelIteration": true, 16 | "skipLibCheck": true, 17 | "noEmit": true, 18 | "baseUrl": ".", 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /public/static/generated/.gitignore: -------------------------------------------------------------------------------- 1 | *.json -------------------------------------------------------------------------------- /public/static/projects/blank/main/index.html.sp: -------------------------------------------------------------------------------- 1 | {"type":"HTML_DOCUMENT","properties":{},"childSets":{"body":[{"type":"HTML_ELEMENT","properties":{"tag":"html"},"childSets":{"attributes":[],"content":[{"type":"HTML_ELEMENT","properties":{"tag":"head"},"childSets":{"attributes":[],"content":[]}},{"type":"HTML_ELEMENT","properties":{"tag":"body"},"childSets":{"attributes":[],"content":[]}}]}}]}} 2 | -------------------------------------------------------------------------------- /public/static/projects/blank/main/package.sp: -------------------------------------------------------------------------------- 1 | { 2 | "name": "main", 3 | "buildType": "STATIC", 4 | "files": [ 5 | { 6 | "name": "index.html", 7 | "type": "HTML_DOCUMENT" 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /public/static/projects/blank/project.sp: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blank", 3 | "title": "Untitled Project", 4 | "packages": [ 5 | { 6 | "name": "main", 7 | "buildType": "0" 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /public/static/projects/blankpython/main/main.py.sp: -------------------------------------------------------------------------------- 1 | {"type":"PYTHON_FILE","properties":{},"childSets":{"body":[]}} 2 | -------------------------------------------------------------------------------- /public/static/projects/blankpython/main/package.sp: -------------------------------------------------------------------------------- 1 | { 2 | "name": "main", 3 | "buildType": "STATIC", 4 | "files": [ 5 | { 6 | "name": "main.py", 7 | "type": "PYTHON_FILE" 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /public/static/projects/blankpython/project.sp: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blank", 3 | "layouttype": "PYTHON_CLI", 4 | "title": "Untitled Project", 5 | "packages": [ 6 | { 7 | "name": "main", 8 | "buildType": "0" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /public/static/projects/bouncing/main/index.html.sp: -------------------------------------------------------------------------------- 1 | {"type":"HTML_DOCUMENT","properties":{},"childSets":{"body":[{"type":"HTML_ELEMENT","properties":{"tag":"html"},"childSets":{"attributes":[],"content":[{"type":"HTML_ELEMENT","properties":{"tag":"head"},"childSets":{"attributes":[],"content":[{"type":"HTML_STYLE_ELEMENT","properties":{},"childSets":{"attributes":[],"content":[{"type":"STYLE_RULE","properties":{},"childSets":{"selector":[{"type":"STYLE_SELECTOR_BASIC","properties":{"selectortype":"element"},"childSets":{"value":[{"type":"STRING_LITERAL","properties":{"value":"body"},"childSets":{}}]}}],"properties":[{"type":"STYLE_PROPERTY","properties":{"property":"margin"},"childSets":{"value":[{"type":"STRING_LITERAL","properties":{"value":"0"},"childSets":{}}]}}]}}]}},{"type":"HTML_ELEMENT","properties":{"tag":"title"},"childSets":{"attributes":[],"content":[{"type":"STRING_LITERAL","properties":{"value":"Bouncy ball"},"childSets":{}}]}}]}},{"type":"HTML_ELEMENT","properties":{"tag":"body"},"childSets":{"attributes":[],"content":[{"type":"HTML_ELEMENT","properties":{"tag":"canvas"},"childSets":{"attributes":[{"type":"HTML_ATTRIBUTE","properties":{"name":"id"},"childSets":{"value":[{"type":"STRING_LITERAL","properties":{"value":"canvas"},"childSets":{}}]}}],"content":[]}},{"type":"HTML_SCRIPT_ELEMENT","properties":{},"childSets":{"attributes":[{"type":"HTML_ATTRIBUTE","properties":{"name":"src"},"childSets":{"value":[{"type":"STRING_LITERAL","properties":{"value":"/app.js"},"childSets":{}}]}}],"content":[]}}]}}]}}]}} 2 | -------------------------------------------------------------------------------- /public/static/projects/bouncing/main/package.sp: -------------------------------------------------------------------------------- 1 | { 2 | "name": "main", 3 | "buildType": "STATIC", 4 | "files": [ 5 | { 6 | "name": "index.html", 7 | "type": "HTML_DOCUMENT" 8 | }, 9 | { 10 | "name": "app.js", 11 | "type": "JAVASCRIPT_FILE" 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /public/static/projects/bouncing/project.sp: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bouncing", 3 | "title": "Bouncy ball canvas example", 4 | "packages": [ 5 | { 6 | "name": "main", 7 | "buildType": "0" 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /public/static/projects/flashcards/main/package.sp: -------------------------------------------------------------------------------- 1 | { 2 | "name": "main", 3 | "buildType": "STATIC", 4 | "files": [ 5 | { 6 | "name": "index.html", 7 | "type": "HTML_DOCUMENT" 8 | }, 9 | { 10 | "name": "app.js", 11 | "type": "JAVASCRIPT_FILE" 12 | }, 13 | { 14 | "name": "words.sheet", 15 | "type": "DATA_SHEET" 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /public/static/projects/flashcards/project.sp: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flashcards", 3 | "title": "Chinese practice flash cards", 4 | "packages": [ 5 | { 6 | "name": "main", 7 | "buildType": "0" 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /public/static/projects/gallery/main/package.sp: -------------------------------------------------------------------------------- 1 | { 2 | "name": "main", 3 | "buildType": "STATIC", 4 | "files": [ 5 | { 6 | "name": "index.html", 7 | "type": "HTML_DOCUMENT" 8 | }, 9 | { 10 | "name": "gallery.js", 11 | "type": "JAVASCRIPT_FILE" 12 | }, 13 | { 14 | "name": "setupjss.js", 15 | "type": "JAVASCRIPT_FILE" 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /public/static/projects/gallery/main/setupjss.js.sp: -------------------------------------------------------------------------------- 1 | {"type":"JAVASCRIPT_FILE","properties":{},"childSets":{"body":[{"type":"IMPORT_DEFAULT","properties":{},"childSets":{"source":[{"type":"STRING_LITERAL","properties":{"value":"https://cdn.skypack.dev/jss"},"childSets":{}}],"identifier":[{"type":"DECLARED_IDENTIFIER","properties":{"identifier":"jss"},"childSets":{}}]}},{"type":"IMPORT_DEFAULT","properties":{},"childSets":{"source":[{"type":"STRING_LITERAL","properties":{"value":"https://cdn.skypack.dev/jss-preset-default"},"childSets":{}}],"identifier":[{"type":"DECLARED_IDENTIFIER","properties":{"identifier":"preset"},"childSets":{}}]}},{"type":"SPLOOT_EXPRESSION","properties":{},"childSets":{"tokens":[{"type":"CALL_MEMBER","properties":{"member":"setup"},"childSets":{"object":[{"type":"VARIABLE_REFERENCE","properties":{"identifier":"jss"},"childSets":{}}],"arguments":[{"type":"SPLOOT_EXPRESSION","properties":{},"childSets":{"tokens":[{"type":"CALL_VARIABLE","properties":{"identifier":"preset"},"childSets":{"arguments":[]}}]}}]}}]}}]}} 2 | -------------------------------------------------------------------------------- /public/static/projects/gallery/project.sp: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blank", 3 | "layouttype": "WEB", 4 | "title": "Untitled Project", 5 | "packages": [ 6 | { 7 | "name": "main", 8 | "buildType": "0" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /public/static/projects/helloname/main/main.py.sp: -------------------------------------------------------------------------------- 1 | {"type":"PYTHON_FILE","properties":{},"childSets":{"body":[{"type":"PYTHON_EXPRESSION","properties":{},"childSets":{"tokens":[{"type":"PYTHON_CALL_VARIABLE","properties":{"identifier":"print"},"childSets":{"arguments":[{"type":"PYTHON_EXPRESSION","properties":{},"childSets":{"tokens":[{"type":"STRING_LITERAL","properties":{"value":"Hello!"},"childSets":{}}]}}]}}]}},{"type":"PYTHON_ASSIGNMENT","properties":{},"childSets":{"left":[{"type":"PYTHON_DECLARED_IDENTIFIER","properties":{"identifier":"name"},"childSets":{}}],"right":[{"type":"PYTHON_EXPRESSION","properties":{},"childSets":{"tokens":[{"type":"PYTHON_CALL_VARIABLE","properties":{"identifier":"input"},"childSets":{"arguments":[{"type":"PYTHON_EXPRESSION","properties":{},"childSets":{"tokens":[{"type":"STRING_LITERAL","properties":{"value":"What is your name? "},"childSets":{}}]}}]}}]}}]}},{"type":"PYTHON_EXPRESSION","properties":{},"childSets":{"tokens":[{"type":"PYTHON_CALL_VARIABLE","properties":{"identifier":"print"},"childSets":{"arguments":[{"type":"PYTHON_EXPRESSION","properties":{},"childSets":{"tokens":[{"type":"STRING_LITERAL","properties":{"value":"Hello, "},"childSets":{}},{"type":"PYTHON_BINARY_OPERATOR","properties":{"operator":"+"},"childSets":{}},{"type":"PYTHON_VARIABLE_REFERENCE","properties":{"identifier":"name"},"childSets":{}}]}}]}}]}}]}} 2 | -------------------------------------------------------------------------------- /public/static/projects/helloname/main/package.sp: -------------------------------------------------------------------------------- 1 | { 2 | "name": "main", 3 | "buildType": "STATIC", 4 | "files": [ 5 | { 6 | "name": "main.py", 7 | "type": "PYTHON_FILE" 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /public/static/projects/helloname/project.sp: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello-name", 3 | "layouttype": "PYTHON_CLI", 4 | "title": "Hello Name", 5 | "packages": [ 6 | { 7 | "name": "main", 8 | "buildType": "0" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /public/static/projects/secret_password/main/main.py.sp: -------------------------------------------------------------------------------- 1 | {"type":"PYTHON_FILE","properties":{},"childSets":{"body":[{"type":"PYTHON_ASSIGNMENT","properties":{},"childSets":{"left":[{"type":"PYTHON_DECLARED_IDENTIFIER","properties":{"identifier":"guess"},"childSets":{}}],"right":[{"type":"PYTHON_EXPRESSION","properties":{},"childSets":{"tokens":[{"type":"PYTHON_CALL_VARIABLE","properties":{"identifier":"input"},"childSets":{"arguments":[{"type":"PYTHON_EXPRESSION","properties":{},"childSets":{"tokens":[{"type":"STRING_LITERAL","properties":{"value":"Enter the secret password: "},"childSets":{}}]}}]}}]}}]}},{"type":"PYTHON_WHILE_LOOP","properties":{},"childSets":{"condition":[{"type":"PYTHON_EXPRESSION","properties":{},"childSets":{"tokens":[{"type":"PYTHON_VARIABLE_REFERENCE","properties":{"identifier":"guess"},"childSets":{}},{"type":"PYTHON_BINARY_OPERATOR","properties":{"operator":"!="},"childSets":{}},{"type":"STRING_LITERAL","properties":{"value":"OpenSesame"},"childSets":{}}]}}],"block":[{"type":"PYTHON_EXPRESSION","properties":{},"childSets":{"tokens":[{"type":"PYTHON_CALL_VARIABLE","properties":{"identifier":"print"},"childSets":{"arguments":[{"type":"PYTHON_EXPRESSION","properties":{},"childSets":{"tokens":[{"type":"STRING_LITERAL","properties":{"value":"Wrong! Try again."},"childSets":{}}]}}]}}]}},{"type":"PYTHON_ASSIGNMENT","properties":{},"childSets":{"left":[{"type":"PYTHON_DECLARED_IDENTIFIER","properties":{"identifier":"guess"},"childSets":{}}],"right":[{"type":"PYTHON_EXPRESSION","properties":{},"childSets":{"tokens":[{"type":"PYTHON_CALL_VARIABLE","properties":{"identifier":"input"},"childSets":{"arguments":[{"type":"PYTHON_EXPRESSION","properties":{},"childSets":{"tokens":[{"type":"STRING_LITERAL","properties":{"value":"Enter the secret password: "},"childSets":{}}]}}]}}]}}]}}]}},{"type":"PYTHON_EXPRESSION","properties":{},"childSets":{"tokens":[{"type":"PYTHON_CALL_VARIABLE","properties":{"identifier":"print"},"childSets":{"arguments":[{"type":"PYTHON_EXPRESSION","properties":{},"childSets":{"tokens":[{"type":"STRING_LITERAL","properties":{"value":"Access granted!"},"childSets":{}}]}}]}}]}}]}} 2 | -------------------------------------------------------------------------------- /public/static/projects/secret_password/main/package.sp: -------------------------------------------------------------------------------- 1 | { 2 | "name": "main", 3 | "buildType": "STATIC", 4 | "files": [ 5 | { 6 | "name": "main.py", 7 | "type": "PYTHON_FILE" 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /public/static/projects/secret_password/project.sp: -------------------------------------------------------------------------------- 1 | { 2 | "name": "secret-password", 3 | "layouttype": "PYTHON_CLI", 4 | "title": "Secret password", 5 | "packages": [ 6 | { 7 | "name": "main", 8 | "buildType": "0" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /public/static/projects/temperature_conversion/main/main.py.sp: -------------------------------------------------------------------------------- 1 | {"type":"PYTHON_FILE","properties":{},"childSets":{"body":[{"type":"PYTHON_ASSIGNMENT","properties":{},"childSets":{"left":[{"type":"PYTHON_DECLARED_IDENTIFIER","properties":{"identifier":"celsius"},"childSets":{}}],"right":[{"type":"PYTHON_EXPRESSION","properties":{},"childSets":{"tokens":[{"type":"PYTHON_CALL_VARIABLE","properties":{"identifier":"input"},"childSets":{"arguments":[{"type":"PYTHON_EXPRESSION","properties":{},"childSets":{"tokens":[{"type":"STRING_LITERAL","properties":{"value":"Temperature in C: "},"childSets":{}}]}}]}}]}}]}},{"type":"PYTHON_ASSIGNMENT","properties":{},"childSets":{"left":[{"type":"PYTHON_DECLARED_IDENTIFIER","properties":{"identifier":"celsius"},"childSets":{}}],"right":[{"type":"PYTHON_EXPRESSION","properties":{},"childSets":{"tokens":[{"type":"PYTHON_CALL_VARIABLE","properties":{"identifier":"int"},"childSets":{"arguments":[{"type":"PYTHON_EXPRESSION","properties":{},"childSets":{"tokens":[{"type":"PYTHON_VARIABLE_REFERENCE","properties":{"identifier":"celsius"},"childSets":{}}]}}]}}]}}]}},{"type":"PYTHON_ASSIGNMENT","properties":{},"childSets":{"left":[{"type":"PYTHON_DECLARED_IDENTIFIER","properties":{"identifier":"fahrenheit"},"childSets":{}}],"right":[{"type":"PYTHON_EXPRESSION","properties":{},"childSets":{"tokens":[{"type":"PYTHON_VARIABLE_REFERENCE","properties":{"identifier":"celsius"},"childSets":{}},{"type":"PYTHON_BINARY_OPERATOR","properties":{"operator":"*"},"childSets":{}},{"type":"NUMERIC_LITERAL","properties":{"value":9},"childSets":{}},{"type":"PYTHON_BINARY_OPERATOR","properties":{"operator":"/"},"childSets":{}},{"type":"NUMERIC_LITERAL","properties":{"value":5},"childSets":{}},{"type":"PYTHON_BINARY_OPERATOR","properties":{"operator":"+"},"childSets":{}},{"type":"NUMERIC_LITERAL","properties":{"value":32},"childSets":{}}]}}]}},{"type":"PYTHON_EXPRESSION","properties":{},"childSets":{"tokens":[{"type":"PYTHON_CALL_VARIABLE","properties":{"identifier":"print"},"childSets":{"arguments":[{"type":"PYTHON_EXPRESSION","properties":{},"childSets":{"tokens":[{"type":"STRING_LITERAL","properties":{"value":"Temperature in F is: "},"childSets":{}},{"type":"PYTHON_BINARY_OPERATOR","properties":{"operator":"+"},"childSets":{}},{"type":"PYTHON_CALL_VARIABLE","properties":{"identifier":"str"},"childSets":{"arguments":[{"type":"PYTHON_EXPRESSION","properties":{},"childSets":{"tokens":[{"type":"PYTHON_VARIABLE_REFERENCE","properties":{"identifier":"fahrenheit"},"childSets":{}}]}}]}}]}}]}}]}}]}} 2 | -------------------------------------------------------------------------------- /public/static/projects/temperature_conversion/main/package.sp: -------------------------------------------------------------------------------- 1 | { 2 | "name": "main", 3 | "buildType": "STATIC", 4 | "files": [ 5 | { 6 | "name": "main.py", 7 | "type": "PYTHON_FILE" 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /public/static/projects/temperature_conversion/project.sp: -------------------------------------------------------------------------------- 1 | { 2 | "name": "temperature-conversion", 3 | "layouttype": "PYTHON_CLI", 4 | "title": "Temperature Conversion", 5 | "packages": [ 6 | { 7 | "name": "main", 8 | "buildType": "0" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /python/.gitignore: -------------------------------------------------------------------------------- 1 | venv 2 | 3 | -------------------------------------------------------------------------------- /python/generate.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eux 4 | 5 | if [ -x "$(command -v deactivate)" ] 6 | then 7 | # Attempt to deactivate any virutal env. If deactivating the virtual env fails 8 | # then we're probably not in a virtual env, there's another deactivate command. 9 | deactivate || true 10 | fi 11 | 12 | if [ ! -d "venv" ] 13 | then 14 | echo "Creating venv" 15 | python3 -m venv venv 16 | source venv/bin/activate 17 | pip install -r requirements.txt 18 | else 19 | source venv/bin/activate 20 | fi 21 | 22 | python generate_tray.py -o ../packages/language-python/src/generated/python_tray.json 23 | python generate_builtins.py -o ../packages/language-python/src/generated/python_builtins.json 24 | -------------------------------------------------------------------------------- /python/generate_builtins.py: -------------------------------------------------------------------------------- 1 | import json 2 | import yaml 3 | import builtins 4 | 5 | from module_loader import get_type_data, get_value_data 6 | 7 | def generate_builtins_docs(): 8 | from generate_tray import processExample 9 | 10 | overrides = {} 11 | with open("./library/overrides.yaml", "r") as f: 12 | overrides_list = yaml.safe_load(f)['overrides'] 13 | for override in overrides_list: 14 | overrides[override['key']] = override 15 | 16 | builtins_data = { 17 | 'moduleName': 'builtins', 18 | 'values': {}, 19 | 'types': {} 20 | } 21 | 22 | for name in dir(builtins): 23 | if name in ['None', 'True', 'False']: 24 | continue 25 | 26 | thing = getattr(builtins, name) 27 | data = get_value_data(name, thing) 28 | 29 | thingKey = f'builtins.{name}' 30 | if thingKey in overrides: 31 | overrideData = overrides[thingKey] 32 | if 'abstract' in overrideData: 33 | data['shortDoc'] = overrideData['abstract'] 34 | data['examples'] = [] 35 | if 'examples' in overrideData: 36 | data['examples'] = [processExample(ex) for ex in overrideData['examples']] 37 | if 'parameters' in overrideData: 38 | data['parameters'] = overrideData['parameters'] 39 | 40 | builtins_data['values'][name] = data 41 | 42 | if (data['typeName'] == 'type'): 43 | type_data = get_type_data(thing, overrides) 44 | builtins_data['types'][name] = type_data 45 | 46 | return builtins_data 47 | 48 | 49 | 50 | if __name__ == '__main__': 51 | import argparse 52 | parser = argparse.ArgumentParser(description='Generate documentation for Python language and libraries.') 53 | parser.add_argument('-o', '--outfile', type=str, required=True, help='file path to write the output to, should be .json') 54 | args = parser.parse_args() 55 | 56 | filename = args.outfile 57 | 58 | builtin_stuff = generate_builtins_docs() 59 | 60 | with open(filename, 'w') as f: 61 | json.dump(builtin_stuff, f) -------------------------------------------------------------------------------- /python/packages/requests-2.28.1-py3-none-any.whl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SplootCode/splootcode/d621bcd5f30df97150d3e1388f60307636568780/python/packages/requests-2.28.1-py3-none-any.whl -------------------------------------------------------------------------------- /python/packages/requests-2.28.2-py3-none-any.whl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SplootCode/splootcode/d621bcd5f30df97150d3e1388f60307636568780/python/packages/requests-2.28.2-py3-none-any.whl -------------------------------------------------------------------------------- /python/packages/stlite_pyarrow-0.1.0-py3-none-any.whl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SplootCode/splootcode/d621bcd5f30df97150d3e1388f60307636568780/python/packages/stlite_pyarrow-0.1.0-py3-none-any.whl -------------------------------------------------------------------------------- /python/packages/streamlit-1.19.0-py2.py3-none-any.whl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SplootCode/splootcode/d621bcd5f30df97150d3e1388f60307636568780/python/packages/streamlit-1.19.0-py2.py3-none-any.whl -------------------------------------------------------------------------------- /python/requirements.txt: -------------------------------------------------------------------------------- 1 | PyYAML==6.0 2 | ast-comments==1.0.1 -------------------------------------------------------------------------------- /python/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SplootCode/splootcode/d621bcd5f30df97150d3e1388f60307636568780/python/tests/__init__.py -------------------------------------------------------------------------------- /python/tests/test_text_generator.py: -------------------------------------------------------------------------------- 1 | import io 2 | import contextlib 3 | import unittest 4 | 5 | from text_generator import convertSplootToText 6 | from convert_ast import splootFromPython 7 | 8 | 9 | 10 | class TextGeneratorTest(unittest.TestCase): 11 | def testHelloWorld(self): 12 | original = "print('Hello, World!')" 13 | sploot = splootFromPython(original) 14 | result = convertSplootToText(sploot) 15 | self.assertEqual(result, original) 16 | 17 | def testFunctionWithDecorator(self): 18 | original = "@app.route('/hello')\ndef hello():\n print('Hello, World!')" 19 | sploot = splootFromPython(original) 20 | result = convertSplootToText(sploot) 21 | self.assertEqual(result, original) 22 | 23 | def testComments(self): 24 | original = "# Something is going on here!\nprint('Hello, World!')" 25 | sploot = splootFromPython(original) 26 | result = convertSplootToText(sploot) 27 | self.assertEqual(result, original) 28 | 29 | def testLineGaps(self): 30 | original = "# Something is going on here!\n##SPLOOTCODEEMPTYLINE\nprint('Hello, World!')\n##SPLOOTCODEEMPTYLINE\n##SPLOOTCODEEMPTYLINE" 31 | expected = "# Something is going on here!\n\nprint('Hello, World!')\n\n" 32 | sploot = splootFromPython(original) 33 | result = convertSplootToText(sploot) 34 | self.assertEqual(result, expected) 35 | 36 | def test_empty_if_block(self): 37 | original = "if True:\n pass" 38 | sploot = splootFromPython(original) 39 | result = convertSplootToText(sploot) 40 | self.assertEqual(result, original) 41 | 42 | def test_empty_function_block(self): 43 | original = "def myfunc():\n # Some comment\n pass" 44 | sploot = splootFromPython(original) 45 | result = convertSplootToText(sploot) 46 | self.assertEqual(result, original) -------------------------------------------------------------------------------- /python/tests/test_tray_generation.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from generate_tray import generate_sploot_node_docs 4 | 5 | 6 | class TrayGenerationTests(unittest.TestCase): 7 | 8 | def testSplootNodeAbstracts(self): 9 | sploot_nodes = generate_sploot_node_docs() 10 | 11 | missing_abstract = [] 12 | 13 | for key, node in sploot_nodes.items(): 14 | if 'abstract' not in node or node['abstract'].strip() == '': 15 | missing_abstract.append(key) 16 | self.assertEqual(len(missing_abstract), 0, f'{len(missing_abstract)}/{len(sploot_nodes)} nodes missing abstracts: f{missing_abstract}') 17 | 18 | def testSplootNodeExamples(self): 19 | sploot_nodes = generate_sploot_node_docs() 20 | 21 | missing_examples = [] 22 | for key, node in sploot_nodes.items(): 23 | if 'examples' not in node or len(node['examples']) == 0: 24 | missing_examples.append(key) 25 | 26 | self.assertEqual(len(missing_examples), 0, f'{len(missing_examples)}/{len(sploot_nodes)} nodes missing examples: f{missing_examples}') 27 | -------------------------------------------------------------------------------- /reactpreview.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | wrapper: { 3 | path: 'src/providers.tsx', 4 | componentName: 'AppProviders', 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /scripts/dummy.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | // These are here to be explicit references to the built-in type definitions 3 | // I couldn't find a way to get from a type keyword like 'string' to the actual 'String' definition. 4 | // TBH this is sort of ridiculous. 5 | let a: string 6 | let b: number 7 | let c: boolean 8 | -------------------------------------------------------------------------------- /splootframepythonclient.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /splootstreamlitpythonclient.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Streamlit app 9 | 13 | 14 |
Loading...
15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src-runtime/autocomplete_webworker.ts: -------------------------------------------------------------------------------- 1 | import { initialize } from '@splootcode/runtime-python/autocomplete_worker' 2 | import { staticPythonURLs } from './static_urls' 3 | 4 | initialize(staticPythonURLs, import.meta.env.SPLOOT_TYPESHED_PATH) 5 | -------------------------------------------------------------------------------- /src-runtime/index.ts: -------------------------------------------------------------------------------- 1 | import { initialize } from '@splootcode/runtime-python' 2 | 3 | import AutocompleteWorker from './autocomplete_webworker?worker' 4 | import RuntimeWorker from './runtime_webworker?worker' 5 | import { staticPythonURLs } from './static_urls' 6 | 7 | initialize(import.meta.env.SPLOOT_EDITOR_DOMAIN, RuntimeWorker, AutocompleteWorker, staticPythonURLs) 8 | -------------------------------------------------------------------------------- /src-runtime/python.d.ts: -------------------------------------------------------------------------------- 1 | // Allow importing static asset URLs with these extensions: 2 | declare module '*.py' 3 | 4 | declare module '*.whl' 5 | -------------------------------------------------------------------------------- /src-runtime/runtime_webworker.ts: -------------------------------------------------------------------------------- 1 | import { initialize } from '@splootcode/runtime-python/worker' 2 | import { staticPythonURLs } from './static_urls' 3 | 4 | initialize(staticPythonURLs, import.meta.env.SPLOOT_TYPESHED_PATH) 5 | -------------------------------------------------------------------------------- /src-runtime/static_urls.ts: -------------------------------------------------------------------------------- 1 | import executorURL from '../python/executor.py' 2 | import moduleLoaderURL from '../python/module_loader.py' 3 | import pyarrowPackageURL from '../python/packages/stlite_pyarrow-0.1.0-py3-none-any.whl' 4 | import requestsPackageURL from '../python/packages/requests-2.28.2-py3-none-any.whl' 5 | import streamlitPackageURL from '../python/packages/streamlit-1.19.0-py2.py3-none-any.whl' 6 | 7 | import textGeneratorURL from '../python/text_generator.py' 8 | import { StaticURLs } from '@splootcode/runtime-python' 9 | 10 | export const staticPythonURLs: StaticURLs = { 11 | executorURL: executorURL, 12 | moduleLoaderURL: moduleLoaderURL, 13 | 14 | textGeneratorURL: textGeneratorURL, 15 | requestsPackageURL: requestsPackageURL, 16 | streamlitPackageURL: streamlitPackageURL, 17 | pyarrowPackageURL: pyarrowPackageURL, 18 | } 19 | -------------------------------------------------------------------------------- /src/app.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | pre { 6 | margin: 0; 7 | font-family: Monaco, 'Courier New', monospace; 8 | } 9 | 10 | body { 11 | font-size: 12pt; 12 | letter-spacing: -0.03rem; 13 | } 14 | 15 | button { 16 | letter-spacing: -0.03rem 17 | } 18 | 19 | svg { 20 | letter-spacing: normal; 21 | } 22 | 23 | :root { 24 | --section-separator-color: var(--chakra-colors-gray-800); 25 | } -------------------------------------------------------------------------------- /src/app.tsx: -------------------------------------------------------------------------------- 1 | import './app.css' 2 | 3 | import React from 'react' 4 | import { BrowserRouter, Route, Switch } from 'react-router-dom' 5 | import { LocalStorageProjectLoader } from '@splootcode/core' 6 | import { ProjectEditor } from './pages/project_editor' 7 | import { UserHomePage } from './pages/user_home' 8 | 9 | export class App extends React.Component { 10 | localStorageProjectLoader = new LocalStorageProjectLoader() 11 | render() { 12 | return ( 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | ) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/code_io/static_projects.ts: -------------------------------------------------------------------------------- 1 | import { LocalStorageProjectLoader, Project, SerializedProject, StaticFileLoader } from '@splootcode/core' 2 | 3 | export async function loadExampleProject(projectId: string): Promise { 4 | const rootUrl = '/static/projects/' + projectId + '/' 5 | const fileLoader = new StaticFileLoader(rootUrl) 6 | const projStr = await (await fetch(rootUrl + 'project.sp')).text() 7 | const proj = JSON.parse(projStr) as SerializedProject 8 | const packages = proj.packages.map(async (packRef) => { 9 | return fileLoader.loadPackage('examples', proj.name, packRef.name) 10 | }) 11 | return new Project('examples', proj, await Promise.all(packages), fileLoader, new LocalStorageProjectLoader()) 12 | } 13 | -------------------------------------------------------------------------------- /src/index.d.ts: -------------------------------------------------------------------------------- 1 | interface ImportMeta { 2 | env: { 3 | SPLOOT_FRAME_VIEW_SCHEME: 'http' | 'https' 4 | SPLOOT_FRAME_VIEW_DOMAIN: string 5 | SPLOOT_RUNTIME_PYTHON_STATIC_FOLDER: string 6 | SPLOOT_TYPESHED_PATH: string 7 | SPLOOT_EDITOR_DOMAIN: string 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import 'focus-visible/dist/focus-visible' 2 | import 'tslib' 3 | import React from 'react' 4 | import ReactDOM from 'react-dom' 5 | import { App } from './app' 6 | import { AppProviders } from './providers' 7 | import { loadPythonTypes } from '@splootcode/language-python' 8 | import { preloadFonts } from '@splootcode/editor' 9 | 10 | import '@splootcode/components/styles.css' 11 | import '@splootcode/editor/styles.css' 12 | 13 | const root = document.getElementById('app-root') 14 | 15 | // Force the web font to be loaded as soon as the page loads (before we try to render the editor). 16 | preloadFonts() 17 | // Load all the types 18 | loadPythonTypes() 19 | 20 | ReactDOM.render( 21 | 22 | 23 | , 24 | root 25 | ) 26 | -------------------------------------------------------------------------------- /src/module_loader.ts: -------------------------------------------------------------------------------- 1 | import { TrayCategory } from '@splootcode/core' 2 | 3 | export async function getTrayForModule(moduleName: string): Promise { 4 | const res = (await import(`../packages/language-python/tray/${moduleName}.json`)) as TrayCategory 5 | return res 6 | } 7 | -------------------------------------------------------------------------------- /src/pages/project_editor.css: -------------------------------------------------------------------------------- 1 | .project-editor-container { 2 | height: calc(100vh - 41px); 3 | } -------------------------------------------------------------------------------- /src/pages/python_editor.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SplootCode/splootcode/d621bcd5f30df97150d3e1388f60307636568780/src/pages/python_editor.css -------------------------------------------------------------------------------- /src/providers.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { ChakraProvider, ColorModeScript, ThemeConfig, extendTheme } from '@chakra-ui/react' 3 | 4 | const config: ThemeConfig = { 5 | initialColorMode: 'dark', 6 | useSystemColorMode: false, 7 | } 8 | 9 | const colors = { 10 | gray: { 11 | 50: '#F7FAFC', // Original chakra 12 | 100: '#EDF2F7', // Original chakra 13 | 200: '#F3F5F9', // Sploot neutral 200 14 | 300: '#C8D3E9', // Sploot neutral 300 15 | 400: '#9DAAC1', // Sploot neutral 400 16 | 500: '#5E6C86', // Sploot neutral 500 17 | 600: '#3E4A60', // Sploot neutral 600 18 | 700: '#2F3848', // Sploot neutral 700 19 | 800: '#182134', // Sploot neutral 800 20 | 900: '#040810', // Sploot neutral 900 21 | }, 22 | purple: { 23 | 200: '#A98BEE', 24 | }, 25 | } 26 | 27 | const styles = { 28 | global: { 29 | body: { 30 | bg: 'gray.900', 31 | }, 32 | }, 33 | } 34 | const fonts = { 35 | heading: 'Karla', 36 | body: 'Karla', 37 | } 38 | 39 | const theme = extendTheme({ config, colors, styles, fonts }) 40 | 41 | export const AppProviders: React.FC = ({ children }) => ( 42 | 43 | 44 | {children} 45 | 46 | ) 47 | -------------------------------------------------------------------------------- /src/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "ES2020", 5 | "lib": ["dom", "ES2020", "webworker"], 6 | "moduleResolution": "node", 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "experimentalDecorators": true, 10 | "jsx": "react", 11 | "resolveJsonModule": true, 12 | "skipDefaultLibCheck": true, 13 | "downlevelIteration": true, 14 | "skipLibCheck": true, 15 | "noEmit": true, 16 | "baseUrl": ".", 17 | "types": ["vite/client", "jest", "@types/wicg-file-system-access"] 18 | }, 19 | "ts-node": { 20 | "esm": true, 21 | "moduleResolution": "node", 22 | "esModuleInterop": true, 23 | "module": "ES2020", 24 | }, 25 | "include": ["src", "src-runtime", "scripts"], 26 | "exclude": [ 27 | "node_modules", 28 | "dist", 29 | "dist-runtime", 30 | "packages" 31 | ], 32 | } 33 | -------------------------------------------------------------------------------- /vite-runtime.config.ts: -------------------------------------------------------------------------------- 1 | import react from '@vitejs/plugin-react' 2 | import { defineConfig } from 'vite' 3 | import { resolve } from 'path' 4 | import { viteStaticCopy } from 'vite-plugin-static-copy' 5 | 6 | export default defineConfig({ 7 | envPrefix: 'SPLOOT_', 8 | server: { 9 | port: 3001, 10 | strictPort: true, 11 | }, 12 | assetsInclude: ['**/*.py', '**/*.whl'], 13 | build: { 14 | outDir: 'dist-runtime', 15 | rollupOptions: { 16 | input: { 17 | main: resolve(__dirname, 'splootframepythonclient.html'), 18 | streamlit: resolve(__dirname, 'splootstreamlitpythonclient.html'), 19 | }, 20 | output: { 21 | assetFileNames: 'assets/[name].[hash][extname]', 22 | }, 23 | }, 24 | }, 25 | worker: { 26 | rollupOptions: { 27 | output: { 28 | assetFileNames: 'assets/[name].[hash][extname]', 29 | }, 30 | }, 31 | }, 32 | plugins: [ 33 | viteStaticCopy({ 34 | targets: [{ src: resolve(__dirname, 'node_modules', 'structured-pyright', 'dist', 'static'), dest: '' }], 35 | }), 36 | react(), 37 | { 38 | name: 'configure-response-headers', 39 | configureServer: (server) => { 40 | server.middlewares.use((_req, res, next) => { 41 | res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp') 42 | res.setHeader('Cross-Origin-Opener-Policy', 'same-origin') 43 | res.setHeader('Cross-Origin-Resource-Policy', 'same-site') 44 | next() 45 | }) 46 | }, 47 | }, 48 | ], 49 | }) 50 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import react from '@vitejs/plugin-react' 2 | import tsconfigPaths from 'vite-tsconfig-paths' 3 | import { defineConfig } from 'vite' 4 | import { resolve } from 'path' 5 | import { viteStaticCopy } from 'vite-plugin-static-copy' 6 | 7 | export default defineConfig({ 8 | envPrefix: 'SPLOOT_', 9 | server: { 10 | port: 3000, 11 | strictPort: true, 12 | }, 13 | publicDir: 'public', 14 | plugins: [ 15 | viteStaticCopy({ 16 | targets: [{ src: resolve(__dirname, 'node_modules', 'structured-pyright', 'dist', 'static'), dest: '' }], 17 | }), 18 | tsconfigPaths(), 19 | react({ 20 | babel: { 21 | configFile: true, 22 | }, 23 | }), 24 | { 25 | name: 'configure-response-headers', 26 | configureServer: (server) => { 27 | server.middlewares.use((_req, res, next) => { 28 | res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp') 29 | res.setHeader('Cross-Origin-Opener-Policy', 'same-origin') 30 | res.setHeader('Cross-Origin-Resource-Policy', 'same-origin') 31 | next() 32 | }) 33 | }, 34 | }, 35 | ], 36 | optimizeDeps: { 37 | include: ['@chakra-ui/react', '@chakra-ui/icons'], 38 | }, 39 | build: { 40 | rollupOptions: { 41 | output: { 42 | manualChunks: { 43 | chakra: [ 44 | '@chakra-ui/react', 45 | '@chakra-ui/icons', 46 | '@emotion/react', 47 | '@emotion/styled', 48 | 'focus-visible', 49 | 'framer-motion', 50 | ], 51 | pyright: ['structured-pyright'], 52 | editor: ['@splootcode/editor'], 53 | python: ['@splootcode/language-python'], 54 | }, 55 | }, 56 | }, 57 | }, 58 | }) 59 | --------------------------------------------------------------------------------