├── .editorconfig ├── .github └── workflows │ └── main.yml ├── .gitignore ├── .npmrc ├── .vscode ├── extensions.json ├── launch.json └── tasks.json ├── LICENSE ├── README.md ├── VERSION.md ├── angular.json ├── brand ├── diandeng │ └── logo.png ├── emakefun │ └── logo.png ├── keyes │ └── logo.png ├── openjumper │ ├── logo.png │ └── logo1.png ├── seeedstudio │ ├── logo.png │ ├── logo1.png │ └── logo2.png ├── seekfree │ ├── logo-b.png │ └── logo.png └── titlab │ └── logo.png ├── build └── installer.nsh ├── child ├── 7za.exe ├── arduino-cli.exe └── node-v9.11.2-win-x64.7z ├── develop.md ├── electron ├── .gitignore ├── cmd.js ├── config.js ├── config.json ├── dev-app-update.yml ├── logger.js ├── main.js ├── npm.js ├── package-lock.json ├── package.json ├── platform.js ├── preload.js ├── terminal.js ├── types │ └── config.ts ├── updater.js └── window.js ├── img └── sf.webp ├── package-lock.json ├── package.json ├── proxy.conf.json ├── public ├── fonts │ ├── MiSans-Regular.woff2 │ ├── fontawesome6 │ │ ├── css │ │ │ ├── all.css │ │ │ ├── all.min.css │ │ │ ├── brands.css │ │ │ ├── brands.min.css │ │ │ ├── duotone.css │ │ │ ├── duotone.min.css │ │ │ ├── fontawesome.css │ │ │ ├── fontawesome.min.css │ │ │ ├── light.css │ │ │ ├── light.min.css │ │ │ ├── regular.css │ │ │ ├── regular.min.css │ │ │ ├── sharp-light.css │ │ │ ├── sharp-light.min.css │ │ │ ├── sharp-regular.css │ │ │ ├── sharp-regular.min.css │ │ │ ├── sharp-solid.css │ │ │ ├── sharp-solid.min.css │ │ │ ├── solid.css │ │ │ ├── solid.min.css │ │ │ ├── svg-with-js.css │ │ │ ├── svg-with-js.min.css │ │ │ ├── thin.css │ │ │ ├── thin.min.css │ │ │ ├── v4-font-face.css │ │ │ ├── v4-font-face.min.css │ │ │ ├── v4-shims.css │ │ │ ├── v4-shims.min.css │ │ │ ├── v5-font-face.css │ │ │ └── v5-font-face.min.css │ │ └── webfonts │ │ │ ├── fa-brands-400.ttf │ │ │ ├── fa-brands-400.woff2 │ │ │ ├── fa-duotone-900.ttf │ │ │ ├── fa-duotone-900.woff2 │ │ │ ├── fa-light-300.ttf │ │ │ ├── fa-light-300.woff2 │ │ │ ├── fa-regular-400.ttf │ │ │ ├── fa-regular-400.woff2 │ │ │ ├── fa-sharp-light-300.ttf │ │ │ ├── fa-sharp-light-300.woff2 │ │ │ ├── fa-sharp-regular-400.ttf │ │ │ ├── fa-sharp-regular-400.woff2 │ │ │ ├── fa-sharp-solid-900.ttf │ │ │ ├── fa-sharp-solid-900.woff2 │ │ │ ├── fa-solid-900.ttf │ │ │ ├── fa-solid-900.woff2 │ │ │ ├── fa-thin-100.ttf │ │ │ ├── fa-thin-100.woff2 │ │ │ ├── fa-v4compatibility.ttf │ │ │ └── fa-v4compatibility.woff2 │ └── iconfont │ │ ├── demo.css │ │ ├── demo_index.html │ │ ├── iconfont.css │ │ ├── iconfont.js │ │ ├── iconfont.json │ │ └── iconfont.ttf ├── i18n │ ├── ar │ │ ├── ar.jpg │ │ └── ar.json │ ├── de │ │ ├── de.jpg │ │ └── de.json │ ├── en │ │ ├── en.jpg │ │ └── en.json │ ├── es │ │ ├── es.jpg │ │ └── es.json │ ├── fr │ │ ├── fr.jpg │ │ └── fr.json │ ├── i18n.json │ ├── ja │ │ ├── ja.jpg │ │ └── ja.json │ ├── ko │ │ ├── ko.jpg │ │ └── ko.json │ ├── pt │ │ ├── pt.jpg │ │ └── pt.json │ ├── pt_br │ │ └── pt_br.json │ ├── ru │ │ ├── ru.jpg │ │ └── ru.json │ ├── zh_cn │ │ ├── zh_cn.jpg │ │ └── zh_cn.json │ └── zh_hk │ │ ├── zh_hk.jpg │ │ └── zh_hk.json ├── icon.ico └── imgs │ ├── serial-selector.webp │ └── subject.webp ├── src ├── app │ ├── app.component.html │ ├── app.component.scss │ ├── app.component.ts │ ├── app.config.ts │ ├── app.routes.ts │ ├── blockly │ │ ├── abf.ts │ │ ├── blockly.component.html │ │ ├── blockly.component.scss │ │ ├── blockly.component.ts │ │ ├── blockly.service.ts │ │ ├── color.config.ts │ │ ├── components │ │ │ └── prompt-dialog │ │ │ │ ├── prompt-dialog.component.html │ │ │ │ ├── prompt-dialog.component.scss │ │ │ │ └── prompt-dialog.component.ts │ │ ├── custom-category.ts │ │ ├── custom-field │ │ │ ├── field-angle.ts │ │ │ ├── field-angle180.ts │ │ │ ├── field-bitmap-u8g2.ts │ │ │ ├── field-bitmap.ts │ │ │ ├── field-code.ts │ │ │ ├── field-image.ts │ │ │ ├── field-multilineinput.ts │ │ │ ├── field-slider.ts │ │ │ └── field-tone.ts │ │ ├── generators │ │ │ ├── arduino │ │ │ │ └── arduino.ts │ │ │ ├── javascript │ │ │ │ ├── javascript.ts │ │ │ │ └── javascript │ │ │ │ │ ├── javascript_generator.ts │ │ │ │ │ ├── lists.ts │ │ │ │ │ ├── logic.ts │ │ │ │ │ ├── loops.ts │ │ │ │ │ ├── math.ts │ │ │ │ │ ├── procedures.ts │ │ │ │ │ ├── text.ts │ │ │ │ │ ├── variables.ts │ │ │ │ │ └── variables_dynamic.ts │ │ │ └── micropython │ │ │ │ └── micropython.ts │ │ └── plugins │ │ │ ├── block-plus-minus │ │ │ ├── README.md │ │ │ ├── package.json │ │ │ └── src │ │ │ │ ├── field_minus.js │ │ │ │ ├── field_plus.js │ │ │ │ ├── if.js │ │ │ │ ├── index.js │ │ │ │ ├── list_create.js │ │ │ │ ├── procedures.js │ │ │ │ ├── serialization_helper.js │ │ │ │ └── text_join.js │ │ │ ├── continuous-toolbox │ │ │ ├── CHANGELOG.md │ │ │ ├── GETSTARTED.md │ │ │ ├── README.md │ │ │ ├── package-lock.json │ │ │ ├── package.json │ │ │ ├── screenshot.png │ │ │ ├── src │ │ │ │ ├── ContinuousCategory.js │ │ │ │ ├── ContinuousFlyout.js │ │ │ │ ├── ContinuousMetrics.js │ │ │ │ ├── ContinuousMetricsFlyout.js │ │ │ │ ├── ContinuousToolbox.js │ │ │ │ └── index.js │ │ │ └── test │ │ │ │ ├── index.html │ │ │ │ └── index.js │ │ │ ├── toolbox-search │ │ │ ├── package.json │ │ │ └── src │ │ │ │ ├── block_searcher.ts │ │ │ │ ├── index.ts │ │ │ │ └── toolbox_search.ts │ │ │ └── workspace-multiselect │ │ │ ├── global.js │ │ │ ├── index.js │ │ │ ├── multiselect.js │ │ │ ├── multiselect_contextmenu.js │ │ │ ├── multiselect_controls.js │ │ │ ├── multiselect_draggable.js │ │ │ └── multiselect_shortcut.js │ ├── components │ │ ├── about-us │ │ │ ├── about-us.component.html │ │ │ ├── about-us.component.scss │ │ │ └── about-us.component.ts │ │ ├── aily-blockly │ │ │ ├── aily-blockly.component.html │ │ │ ├── aily-blockly.component.scss │ │ │ └── aily-blockly.component.ts │ │ ├── aily-coding │ │ │ ├── aily-coding.component.html │ │ │ ├── aily-coding.component.scss │ │ │ └── aily-coding.component.ts │ │ ├── image-cropper │ │ │ ├── image-cropper.component.html │ │ │ ├── image-cropper.component.scss │ │ │ └── image-cropper.component.ts │ │ ├── inner-window │ │ │ ├── inner-window.component.html │ │ │ ├── inner-window.component.scss │ │ │ └── inner-window.component.ts │ │ ├── menu │ │ │ ├── menu.component.html │ │ │ ├── menu.component.scss │ │ │ └── menu.component.ts │ │ ├── monaco-editor │ │ │ ├── monaco-editor.component.html │ │ │ ├── monaco-editor.component.scss │ │ │ └── monaco-editor.component.ts │ │ ├── notification │ │ │ ├── notification.component.html │ │ │ ├── notification.component.scss │ │ │ └── notification.component.ts │ │ ├── project-manager │ │ │ ├── project-manager.component.html │ │ │ ├── project-manager.component.scss │ │ │ └── project-manager.component.ts │ │ ├── sub-window │ │ │ ├── readme.md │ │ │ ├── sub-window.component.html │ │ │ ├── sub-window.component.scss │ │ │ └── sub-window.component.ts │ │ └── tool-container │ │ │ ├── tool-container.component.html │ │ │ ├── tool-container.component.scss │ │ │ └── tool-container.component.ts │ ├── configs │ │ ├── api.config.ts │ │ ├── app.config.ts │ │ └── menu.config.ts │ ├── desktop │ │ ├── desktop.component.html │ │ ├── desktop.component.scss │ │ └── desktop.component.ts │ ├── editors │ │ ├── blockly-editor │ │ │ ├── blockly-editor.component.html │ │ │ ├── blockly-editor.component.scss │ │ │ ├── blockly-editor.component.ts │ │ │ └── readme.md │ │ └── code-editor │ │ │ ├── code-editor.component.html │ │ │ ├── code-editor.component.scss │ │ │ ├── code-editor.component.ts │ │ │ ├── components │ │ │ └── file-tree │ │ │ │ ├── file-tree.component.html │ │ │ │ ├── file-tree.component.scss │ │ │ │ └── file-tree.component.ts │ │ │ └── file.service.ts │ ├── func │ │ └── func.ts │ ├── main-window │ │ ├── components │ │ │ ├── act-btn │ │ │ │ ├── act-btn.component.html │ │ │ │ ├── act-btn.component.scss │ │ │ │ └── act-btn.component.ts │ │ │ ├── footer │ │ │ │ ├── footer.component.html │ │ │ │ ├── footer.component.scss │ │ │ │ └── footer.component.ts │ │ │ ├── header │ │ │ │ ├── header.component.html │ │ │ │ ├── header.component.scss │ │ │ │ └── header.component.ts │ │ │ ├── serial-dialog │ │ │ │ ├── serial-dialog.component.html │ │ │ │ ├── serial-dialog.component.scss │ │ │ │ └── serial-dialog.component.ts │ │ │ ├── unsave-dialog │ │ │ │ ├── unsave-dialog.component.html │ │ │ │ ├── unsave-dialog.component.scss │ │ │ │ └── unsave-dialog.component.ts │ │ │ └── update-dialog │ │ │ │ ├── update-dialog.component.html │ │ │ │ ├── update-dialog.component.scss │ │ │ │ └── update-dialog.component.ts │ │ ├── main-window.component.html │ │ ├── main-window.component.scss │ │ └── main-window.component.ts │ ├── pages │ │ ├── guide │ │ │ ├── guide.component.html │ │ │ ├── guide.component.scss │ │ │ └── guide.component.ts │ │ ├── lib-editor │ │ │ ├── block-item │ │ │ │ ├── block-item.component.html │ │ │ │ ├── block-item.component.scss │ │ │ │ └── block-item.component.ts │ │ │ ├── block-test │ │ │ │ ├── block-test.component.html │ │ │ │ ├── block-test.component.scss │ │ │ │ └── block-test.component.ts │ │ │ ├── block-visual-editor │ │ │ │ ├── block-visual-editor.component.html │ │ │ │ ├── block-visual-editor.component.scss │ │ │ │ └── block-visual-editor.component.ts │ │ │ ├── lib-content │ │ │ │ ├── lib-content.component.html │ │ │ │ ├── lib-content.component.scss │ │ │ │ └── lib-content.component.ts │ │ │ ├── lib-editor.component.html │ │ │ ├── lib-editor.component.scss │ │ │ ├── lib-editor.component.ts │ │ │ └── lib-editor.service.ts │ │ ├── lib-manager │ │ │ ├── components │ │ │ │ └── compatible-dialog │ │ │ │ │ ├── compatible-dialog.component.html │ │ │ │ │ ├── compatible-dialog.component.scss │ │ │ │ │ └── compatible-dialog.component.ts │ │ │ ├── lib-manager.component.html │ │ │ ├── lib-manager.component.scss │ │ │ └── lib-manager.component.ts │ │ └── playground │ │ │ ├── data.ts │ │ │ ├── playground.component.html │ │ │ ├── playground.component.scss │ │ │ ├── playground.component.ts │ │ │ ├── subject-item │ │ │ ├── subject-item.component.html │ │ │ ├── subject-item.component.scss │ │ │ └── subject-item.component.ts │ │ │ └── subject-list │ │ │ ├── subject-list.component.html │ │ │ ├── subject-list.component.scss │ │ │ └── subject-list.component.ts │ ├── services │ │ ├── arduino-parser.service.ts │ │ ├── builder.service.ts │ │ ├── cmd.service.ts │ │ ├── config.service.ts │ │ ├── converter.service.ts │ │ ├── electron.service.ts │ │ ├── iwindow.service.ts │ │ ├── log.service.ts │ │ ├── notice.service.ts │ │ ├── npm.service.ts │ │ ├── project.service.ts │ │ ├── serial.service.ts │ │ ├── settings.service.ts │ │ ├── translation.service.ts │ │ ├── ui.service.ts │ │ ├── update.service.ts │ │ └── uploader.service.ts │ ├── tools │ │ ├── aily-chat │ │ │ ├── aily-chat.component.html │ │ │ ├── aily-chat.component.scss │ │ │ ├── aily-chat.component.ts │ │ │ ├── components │ │ │ │ ├── aily-blockly-viewer │ │ │ │ │ ├── aily-blockly-viewer.component.html │ │ │ │ │ ├── aily-blockly-viewer.component.scss │ │ │ │ │ └── aily-blockly-viewer.component.ts │ │ │ │ ├── aily-board-viewer │ │ │ │ │ ├── aily-board-viewer.component.html │ │ │ │ │ ├── aily-board-viewer.component.scss │ │ │ │ │ └── aily-board-viewer.component.ts │ │ │ │ ├── aily-button-viewer │ │ │ │ │ ├── aily-button-viewer.component.html │ │ │ │ │ ├── aily-button-viewer.component.scss │ │ │ │ │ └── aily-button-viewer.component.ts │ │ │ │ ├── aily-library-viewer │ │ │ │ │ ├── aily-library-viewer.component.html │ │ │ │ │ ├── aily-library-viewer.component.scss │ │ │ │ │ └── aily-library-viewer.component.ts │ │ │ │ ├── aily-state-viewer │ │ │ │ │ ├── aily-state-viewer.component.html │ │ │ │ │ ├── aily-state-viewer.component.scss │ │ │ │ │ └── aily-state-viewer.component.ts │ │ │ │ └── dialog │ │ │ │ │ ├── dialog.component.html │ │ │ │ │ ├── dialog.component.scss │ │ │ │ │ └── dialog.component.ts │ │ │ ├── directives │ │ │ │ └── aily-dynamic-component.directive.ts │ │ │ ├── pipes │ │ │ │ └── markdown.pipe.ts │ │ │ ├── readme.md │ │ │ └── services │ │ │ │ ├── chat.service.ts │ │ │ │ └── speech.service.ts │ │ ├── app-store │ │ │ ├── app-store.component.html │ │ │ ├── app-store.component.scss │ │ │ └── app-store.component.ts │ │ ├── code-viewer │ │ │ ├── code-viewer.component.html │ │ │ ├── code-viewer.component.scss │ │ │ └── code-viewer.component.ts │ │ ├── data-chart │ │ │ ├── data-chart.component.html │ │ │ ├── data-chart.component.scss │ │ │ ├── data-chart.component.ts │ │ │ └── sample-data.ts │ │ ├── serial-monitor │ │ │ ├── components │ │ │ │ ├── data-item │ │ │ │ │ ├── add-newline.pipe.ts │ │ │ │ │ ├── data-item.component.html │ │ │ │ │ ├── data-item.component.scss │ │ │ │ │ ├── data-item.component.ts │ │ │ │ │ ├── show-hex.pipe.ts │ │ │ │ │ └── show-nr.pipe.ts │ │ │ │ ├── history-message-list │ │ │ │ │ ├── history-message-list.component.html │ │ │ │ │ ├── history-message-list.component.scss │ │ │ │ │ └── history-message-list.component.ts │ │ │ │ ├── quick-send-editor │ │ │ │ │ ├── quick-send-editor.component.html │ │ │ │ │ ├── quick-send-editor.component.scss │ │ │ │ │ └── quick-send-editor.component.ts │ │ │ │ ├── quick-send-list │ │ │ │ │ ├── quick-send-list.component.html │ │ │ │ │ ├── quick-send-list.component.scss │ │ │ │ │ └── quick-send-list.component.ts │ │ │ │ ├── search-box │ │ │ │ │ ├── search-box.component.html │ │ │ │ │ ├── search-box.component.scss │ │ │ │ │ └── search-box.component.ts │ │ │ │ ├── setting-more │ │ │ │ │ ├── setting-more.component.html │ │ │ │ │ ├── setting-more.component.scss │ │ │ │ │ └── setting-more.component.ts │ │ │ │ └── widget-data │ │ │ │ │ ├── widget-data.component.html │ │ │ │ │ ├── widget-data.component.scss │ │ │ │ │ └── widget-data.component.ts │ │ │ ├── config.ts │ │ │ ├── right-menu.config.ts │ │ │ ├── serial-monitor.component.html │ │ │ ├── serial-monitor.component.scss │ │ │ ├── serial-monitor.component.ts │ │ │ ├── serial-monitor.service.ts │ │ │ └── test-data.ts │ │ ├── simulator │ │ │ ├── readme.md │ │ │ ├── simulator-editor │ │ │ │ ├── elements.config.ts │ │ │ │ ├── pinmap.config.ts │ │ │ │ ├── simulator-editor.component.html │ │ │ │ ├── simulator-editor.component.scss │ │ │ │ ├── simulator-editor.component.ts │ │ │ │ └── simulator.service.ts │ │ │ ├── simulator.component.html │ │ │ ├── simulator.component.scss │ │ │ └── simulator.component.ts │ │ └── terminal │ │ │ ├── ansi.pipe.ts │ │ │ ├── readme.md │ │ │ ├── terminal.component.html │ │ │ ├── terminal.component.scss │ │ │ ├── terminal.component.ts │ │ │ └── terminal.service.ts │ └── windows │ │ ├── about │ │ ├── about.component.html │ │ ├── about.component.scss │ │ └── about.component.ts │ │ ├── project-new │ │ ├── project-new.component.html │ │ ├── project-new.component.scss │ │ └── project-new.component.ts │ │ └── settings │ │ ├── readme.md │ │ ├── settings.component.html │ │ ├── settings.component.scss │ │ └── settings.component.ts ├── index.html ├── main.ts └── styles.scss ├── test-blocks.json ├── tsconfig.app.json ├── tsconfig.json └── tsconfig.spec.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.ts] 12 | quote_type = single 13 | ij_typescript_use_double_quotes = false 14 | 15 | [*.md] 16 | max_line_length = off 17 | trim_trailing_whitespace = false 18 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI&&CD 2 | 3 | on: 4 | push: 5 | branches: 6 | - deploy 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v1 14 | 15 | - name: Use Node.js 18.x 16 | uses: actions/setup-node@v1 17 | with: 18 | node-version: '18.x' 19 | 20 | - name: Dependent environment 21 | run: | 22 | npm i -g @angular/cli 23 | npm i 24 | 25 | - name: Compile 26 | run: | 27 | npm run build 28 | 29 | - name: Deploy 30 | uses: Dylan700/sftp-upload-action@latest 31 | with: 32 | server: ${{ secrets.HOST }} 33 | username: ubuntu 34 | key: ${{ secrets.PRIVATEKEY }} 35 | uploads: | 36 | dist/aily-software => ${{ secrets.TARGET }} 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files. 2 | 3 | # Compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | /bazel-out 8 | /out 9 | 10 | # Node 11 | /node_modules 12 | npm-debug.log 13 | yarn-error.log 14 | /child/node 15 | 16 | # IDEs and editors 17 | .idea/ 18 | .project 19 | .classpath 20 | .c9/ 21 | *.launch 22 | .settings/ 23 | *.sublime-workspace 24 | 25 | # Visual Studio Code 26 | .vscode/* 27 | !.vscode/settings.json 28 | !.vscode/tasks.json 29 | !.vscode/launch.json 30 | !.vscode/extensions.json 31 | .history/* 32 | 33 | # Miscellaneous 34 | /.angular/cache 35 | .sass-cache/ 36 | /connect.lock 37 | /coverage 38 | /libpeerconnection.log 39 | testem.log 40 | /typings 41 | 42 | # System files 43 | .DS_Store 44 | Thumbs.db 45 | temp 46 | child/node* 47 | !child/node-*.* 48 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | electron_mirror=https://npmmirror.com/mirrors/electron/ 2 | electron_builder_binaries_mirror=https://npmmirror.com/mirrors/electron-builder-binaries/ -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846 3 | "recommendations": ["angular.ng-template"] 4 | } 5 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 3 | "version": "0.2.0", 4 | "configurations": [ 5 | { 6 | "name": "ng serve", 7 | "type": "chrome", 8 | "request": "launch", 9 | "preLaunchTask": "npm: start", 10 | "url": "http://localhost:4200/" 11 | }, 12 | { 13 | "name": "ng test", 14 | "type": "chrome", 15 | "request": "launch", 16 | "preLaunchTask": "npm: test", 17 | "url": "http://localhost:9876/debug.html" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558 3 | "version": "2.0.0", 4 | "tasks": [ 5 | { 6 | "type": "npm", 7 | "script": "start", 8 | "isBackground": true, 9 | "problemMatcher": { 10 | "owner": "typescript", 11 | "pattern": "$tsc", 12 | "background": { 13 | "activeOnStart": true, 14 | "beginsPattern": { 15 | "regexp": "(.*?)" 16 | }, 17 | "endsPattern": { 18 | "regexp": "bundle generation complete" 19 | } 20 | } 21 | } 22 | }, 23 | { 24 | "type": "npm", 25 | "script": "test", 26 | "isBackground": true, 27 | "problemMatcher": { 28 | "owner": "typescript", 29 | "pattern": "$tsc", 30 | "background": { 31 | "activeOnStart": true, 32 | "beginsPattern": { 33 | "regexp": "(.*?)" 34 | }, 35 | "endsPattern": { 36 | "regexp": "bundle generation complete" 37 | } 38 | } 39 | } 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /VERSION.md: -------------------------------------------------------------------------------- 1 | # aily blockly版本说明 2 | 3 | > 非正式版注意事项 4 | > 本次测试的alpha版本,仅保证最低限度的能用,很多计划的亮点功能还未完成设计和开发。 5 | > 当前版本不建议实际用于工作,因为后期我们做出的诸多调整,可能会导致版本间的不兼容。 6 | 7 | 8 | ## 0.2.0-alpha 9 | 1. 工程化项目管理(基本完成) 10 | 使用npm进行项目管理,做到以项目为单位进行开发板和库的管理。解决了诸多传统嵌入式开发环境的工程化不足的问题。如,使用Arduino IDE可能出现board package、库和当前项目不匹配,造成编译失败,运行错误的问题。在本软件上,各项目中的开发板版本和库版本是独立的,项目间互不影响。 11 | 2. 库管理器(基本完成) 12 | 虽然我们已经准备了很多库(几乎涵盖了常用模组),但实际上这些库都是AI生成的,我们没有经过详细验证。需要内测参与者和我们一道进行验证和完善。 13 | 3. 全能且小巧的串口调试工具(基本完成) 14 | 试图打造一个全能的串口工具,欢迎大家测试、反馈、提出新的想法。 15 | 4. AI开发板配置生成(完善中,预计5月下旬提供) 16 | 基于大模型的配置生成,添加开发板时不用再纯手写新配置,只用提供开发板文档(md格式),AI自动分析,帮你生成开发板配置文件。(仅支持esp32、avr、renesas、rp2040、stm32为核心的开发板,因为编译器和核心sdk,还是需要我们提前准备的到仓库的) 17 | 5. block配置生成(完善中,预计5月下旬提供) 18 | 基于大模型的配置生成,开发过程中,如果想使用arduino库,但没有对应的blockly库,只用将arduino库提供给AI,AI自动分析,生成对应的blockly库。借助该功能,本软件可以成为blockly最多的开发平台。 19 | -------------------------------------------------------------------------------- /brand/diandeng/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/brand/diandeng/logo.png -------------------------------------------------------------------------------- /brand/emakefun/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/brand/emakefun/logo.png -------------------------------------------------------------------------------- /brand/keyes/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/brand/keyes/logo.png -------------------------------------------------------------------------------- /brand/openjumper/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/brand/openjumper/logo.png -------------------------------------------------------------------------------- /brand/openjumper/logo1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/brand/openjumper/logo1.png -------------------------------------------------------------------------------- /brand/seeedstudio/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/brand/seeedstudio/logo.png -------------------------------------------------------------------------------- /brand/seeedstudio/logo1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/brand/seeedstudio/logo1.png -------------------------------------------------------------------------------- /brand/seeedstudio/logo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/brand/seeedstudio/logo2.png -------------------------------------------------------------------------------- /brand/seekfree/logo-b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/brand/seekfree/logo-b.png -------------------------------------------------------------------------------- /brand/seekfree/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/brand/seekfree/logo.png -------------------------------------------------------------------------------- /brand/titlab/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/brand/titlab/logo.png -------------------------------------------------------------------------------- /build/installer.nsh: -------------------------------------------------------------------------------- 1 | !macro customInit 2 | ; 多种方式尝试关闭可能运行的实例 3 | nsExec::Exec 'taskkill /F /IM aily-blockly.exe /T' 4 | nsExec::Exec 'taskkill /F /IM ${PRODUCT_NAME}.exe /T' 5 | nsExec::Exec 'taskkill /F /IM "Aily Blockly.exe" /T' 6 | 7 | ; 等待确保进程完全终止 8 | Sleep 2000 9 | 10 | ; 强制释放可能被锁定的文件 11 | ${if} ${FileExists} "$INSTDIR" 12 | ClearErrors 13 | RMDir /r "$INSTDIR\app" 14 | RMDir /r "$INSTDIR\locales" 15 | RMDir /r "$INSTDIR\resources" 16 | Delete "$INSTDIR\*.dll" 17 | Delete "$INSTDIR\*.exe" 18 | Delete "$INSTDIR\*.pak" 19 | Delete "$INSTDIR\*.bin" 20 | Delete "$INSTDIR\*.dat" 21 | ${endif} 22 | 23 | ; 最后再等待一下确保文件系统操作完成 24 | Sleep 1000 25 | !macroend 26 | 27 | !macro customInstall 28 | 29 | ; 使用7za.exe解压node-v9.11.2-win-x64.7z到node目录 30 | nsExec::ExecToStack '"$INSTDIR\resources\app\child\7za.exe" x "$INSTDIR\resources\app\child\node-v9.11.2-win-x64.7z" -o"$INSTDIR\resources\app\child\node" -y' 31 | 32 | ; 等待解压完成 33 | Sleep 2000 34 | 35 | ; 删除解压后的压缩包,节省磁盘空间 36 | Delete "$INSTDIR\resources\app\child\node-v9.11.2-win-x64.7z" 37 | 38 | !macroend 39 | 40 | !macro customUnInstall 41 | ; 创建临时空目录用于 Robocopy 镜像删除 42 | CreateDirectory "$TEMP\empty_dir_for_cleanup" 43 | 44 | ; 使用 Robocopy 将空目录镜像到安装目录(实现删除效果) 45 | nsExec::ExecToStack 'cmd.exe /c robocopy "$TEMP\empty_dir_for_cleanup" "$INSTDIR" /MIR /NFL /NDL /NJH /NJS /NC /NS /MT:16' 46 | 47 | ; 删除临时空目录 48 | RMDir "$TEMP\empty_dir_for_cleanup" 49 | 50 | Sleep 2000 51 | 52 | ; 再次尝试直接删除安装目录(此时应该为空或几乎为空) 53 | nsExec::ExecToStack 'cmd.exe /c rd /s /q "$INSTDIR"' 54 | Sleep 1000 55 | RMDir /r "$INSTDIR" 56 | Sleep 1000 57 | RMDir "$INSTDIR" 58 | !macroend -------------------------------------------------------------------------------- /child/7za.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/child/7za.exe -------------------------------------------------------------------------------- /child/arduino-cli.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/child/arduino-cli.exe -------------------------------------------------------------------------------- /child/node-v9.11.2-win-x64.7z: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/child/node-v9.11.2-win-x64.7z -------------------------------------------------------------------------------- /develop.md: -------------------------------------------------------------------------------- 1 | # 开发人员须知 2 | 3 | ## 软件框架 4 | 软件主体使用electron开发,渲染端使用angular开发 5 | 6 | ## 开发&&打包 7 | 8 | **库安装** 9 | ``` 10 | git clone https://github.com/ailyProject/aily-blockly.git 11 | cd aily-blockly 12 | npm i 13 | cd electron 14 | npm i 15 | ``` 16 | 17 | **electron运行** 18 | ``` 19 | npm run electron 20 | ``` 21 | 22 | **electron打包** 23 | ``` 24 | npm run build 25 | ``` 26 | 打包需要开启windows的开发者模式 27 | 打包后生成的安装包在路径为dist\aily-blockly 28 | 29 | ## 相关目录 30 | 31 | ### /child 32 | 内为程序必须的组件: 33 | 1. node:程序使用npm和node进行包管理和执行必要脚本,该npm中添加了npmrc文件,用以指向到aily blockly仓库 34 | 2. 7za:为了减少部分包的大小,我们使用7z极限压缩来降低部分包(如编译器)的大小 35 | 3. arduino-cli:用于构建arduino项目 36 | 37 | ### /build 38 | 该部分是安装/卸载程序的脚本。 39 | 在安装应用时,安装程序会将`child\node-v9.11.2-win-x64.7z`解压到`child\node`。 40 | 41 | ### /src/app/blockly/plugins 42 | blockly相关插件 43 | 44 | ### /src/app/blockly/custom-field 45 | 自定义的特殊block -------------------------------------------------------------------------------- /electron/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files. 2 | 3 | # Compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | /bazel-out 8 | /out 9 | 10 | # Node 11 | /node_modules 12 | npm-debug.log 13 | yarn-error.log 14 | 15 | # IDEs and editors 16 | .idea/ 17 | .project 18 | .classpath 19 | .c9/ 20 | *.launch 21 | .settings/ 22 | *.sublime-workspace 23 | 24 | # Visual Studio Code 25 | .vscode/* 26 | !.vscode/settings.json 27 | !.vscode/tasks.json 28 | !.vscode/launch.json 29 | !.vscode/extensions.json 30 | .history/* 31 | 32 | # Miscellaneous 33 | /.angular/cache 34 | .sass-cache/ 35 | /connect.lock 36 | /coverage 37 | /libpeerconnection.log 38 | testem.log 39 | /typings 40 | 41 | # System files 42 | .DS_Store 43 | Thumbs.db 44 | -------------------------------------------------------------------------------- /electron/config.js: -------------------------------------------------------------------------------- 1 | 2 | // 加载配置文件 3 | export function loadConfigFile() { 4 | const path = path.join(__dirname, "config.json"); 5 | } 6 | 7 | // 创建配置文件 8 | export function creatConfigFile() { 9 | console.log(process.env.LANG); 10 | } -------------------------------------------------------------------------------- /electron/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "lang": "zh_CN", 3 | "theme": "default", 4 | "font": "default", 5 | "appdata_path": { 6 | "win32": "%HOMEPATH%\\AppData\\Local\\aily-project", 7 | "linux": "~/.config/aily-project", 8 | "darwin": "~/Library/Application Support/aily-project" 9 | }, 10 | "project_path": "%HOMEPATH%\\Documents\\aily-project", 11 | "npm_registry": [ 12 | "https://registry.diandeng.tech" 13 | ], 14 | "resource": [ 15 | "https://blockly.diandeng.tech" 16 | ], 17 | "updater": [ 18 | "https://dl.diandeng.tech/blockly" 19 | ], 20 | "compile": { 21 | "verbose": true, 22 | "warnings": "error" 23 | }, 24 | "upload": { 25 | "verbose": true, 26 | "warnings": "error" 27 | }, 28 | "platform": "win32", 29 | "devmode": false, 30 | "blockly": { 31 | "renderer": "thrasos" 32 | } 33 | } -------------------------------------------------------------------------------- /electron/dev-app-update.yml: -------------------------------------------------------------------------------- 1 | provider: generic 2 | url: http://dl.aily.pro/blockly -------------------------------------------------------------------------------- /electron/logger.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const electronLog = require('electron-log'); 3 | const fs = require('fs'); 4 | 5 | // 初始化日志系统 6 | function initLogger(appDataPath) { 7 | // 配置日志文件路径 8 | const logDir = path.join(appDataPath, 'logs'); 9 | // 检查目录是否存在,如果不存在则创建 10 | if (!fs.existsSync(logDir)) { 11 | fs.mkdirSync(logDir, { recursive: true }); 12 | } 13 | electronLog.transports.file.resolvePathFn = () => path.join(logDir, 'app.log'); 14 | 15 | // 配置日志格式 16 | electronLog.transports.file.format = '[{y}-{m}-{d} {h}:{i}:{s}.{ms}] [{level}] {text}'; 17 | 18 | // 配置日志文件大小限制 (1MB) 19 | electronLog.transports.file.maxSize = 1024 * 1024; 20 | 21 | // 配置日志级别 22 | electronLog.transports.file.level = 'info'; 23 | electronLog.transports.console.level = 'info'; 24 | 25 | // 将原生console重定向到electron-log 26 | console.log = electronLog.info.bind(electronLog); 27 | console.error = electronLog.error.bind(electronLog); 28 | console.warn = electronLog.warn.bind(electronLog); 29 | console.info = electronLog.info.bind(electronLog); 30 | 31 | // 捕获未处理的异常和承诺拒绝 32 | process.on('uncaughtException', (err) => { 33 | electronLog.error('Uncaught Exception:', err); 34 | }); 35 | 36 | process.on('unhandledRejection', (reason, promise) => { 37 | electronLog.error('Unhandled Rejection at:', promise, 'reason:', reason); 38 | }); 39 | 40 | electronLog.info('日志系统已初始化,日志文件路径:', electronLog.transports.file.getFile().path); 41 | return electronLog.transports.file.getFile().path; 42 | } 43 | 44 | module.exports = { 45 | initLogger, 46 | // 导出日志对象,方便在其他地方直接使用 47 | log: electronLog 48 | }; -------------------------------------------------------------------------------- /electron/npm.js: -------------------------------------------------------------------------------- 1 | // 这个文件用于和npm交互,获取仓库信息 2 | const { ipcMain } = require("electron"); 3 | const { exec } = require('child_process'); 4 | 5 | 6 | function registerNpmHandlers(mainWindow) { 7 | ipcMain.handle('npm-run', async (event, { cmd }) => { 8 | console.log('npm run cmd: ', cmd); 9 | return new Promise((resolve, reject) => { 10 | exec(cmd, (error, stdout, stderr) => { 11 | if (error) { 12 | console.error(`执行命令出错: ${error}`); 13 | return reject(error); 14 | } 15 | if (stderr && !stdout) { 16 | return reject(new Error(stderr)); 17 | } 18 | try { 19 | resolve(stdout); 20 | } catch (e) { 21 | reject(new Error(e.message)); 22 | } 23 | }); 24 | }) 25 | }); 26 | } 27 | 28 | module.exports = { 29 | registerNpmHandlers, 30 | }; -------------------------------------------------------------------------------- /electron/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "electron", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "main.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@lydell/node-pty": "^1.0.3", 13 | "electron-log": "^5.3.3", 14 | "electron-updater": "^6.6.2", 15 | "lodash": "^4.17.21", 16 | "serialport": "^13.0.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /electron/platform.js: -------------------------------------------------------------------------------- 1 | const platform = { 2 | isWin32: process.platform === "win32", 3 | isDarwin: process.platform === "darwin", 4 | isLinux: process.platform === "linux", 5 | } 6 | 7 | // console.log("platform", process.platform, platform); 8 | 9 | module.exports = platform; 10 | -------------------------------------------------------------------------------- /electron/types/config.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/electron/types/config.ts -------------------------------------------------------------------------------- /img/sf.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/img/sf.webp -------------------------------------------------------------------------------- /proxy.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api": { 3 | "target": "https://blockly-api.diandeng.tech", 4 | "changeOrigin": true, 5 | "secure": false, 6 | "logLevel": "debug" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /public/fonts/MiSans-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/public/fonts/MiSans-Regular.woff2 -------------------------------------------------------------------------------- /public/fonts/fontawesome6/css/light.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Pro 6.4.2 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license (Commercial License) 4 | * Copyright 2023 Fonticons, Inc. 5 | */ 6 | :root, :host { 7 | --fa-style-family-classic: 'Font Awesome 6 Pro'; 8 | --fa-font-light: normal 300 1em/1 'Font Awesome 6 Pro'; } 9 | 10 | @font-face { 11 | font-family: 'Font Awesome 6 Pro'; 12 | font-style: normal; 13 | font-weight: 300; 14 | font-display: block; 15 | src: url("../webfonts/fa-light-300.woff2") format("woff2"), url("../webfonts/fa-light-300.ttf") format("truetype"); } 16 | 17 | .fal, 18 | .fa-light { 19 | font-weight: 300; } 20 | -------------------------------------------------------------------------------- /public/fonts/fontawesome6/css/light.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Pro 6.4.2 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license (Commercial License) 4 | * Copyright 2023 Fonticons, Inc. 5 | */ 6 | :host,:root{--fa-style-family-classic:"Font Awesome 6 Pro";--fa-font-light:normal 300 1em/1 "Font Awesome 6 Pro"}@font-face{font-family:"Font Awesome 6 Pro";font-style:normal;font-weight:300;font-display:block;src:url(../webfonts/fa-light-300.woff2) format("woff2"),url(../webfonts/fa-light-300.ttf) format("truetype")}.fa-light,.fal{font-weight:300} -------------------------------------------------------------------------------- /public/fonts/fontawesome6/css/regular.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Pro 6.4.2 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license (Commercial License) 4 | * Copyright 2023 Fonticons, Inc. 5 | */ 6 | :root, :host { 7 | --fa-style-family-classic: 'Font Awesome 6 Pro'; 8 | --fa-font-regular: normal 400 1em/1 'Font Awesome 6 Pro'; } 9 | 10 | @font-face { 11 | font-family: 'Font Awesome 6 Pro'; 12 | font-style: normal; 13 | font-weight: 400; 14 | font-display: block; 15 | src: url("../webfonts/fa-regular-400.woff2") format("woff2"), url("../webfonts/fa-regular-400.ttf") format("truetype"); } 16 | 17 | .far, 18 | .fa-regular { 19 | font-weight: 400; } 20 | -------------------------------------------------------------------------------- /public/fonts/fontawesome6/css/regular.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Pro 6.4.2 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license (Commercial License) 4 | * Copyright 2023 Fonticons, Inc. 5 | */ 6 | :host,:root{--fa-style-family-classic:"Font Awesome 6 Pro";--fa-font-regular:normal 400 1em/1 "Font Awesome 6 Pro"}@font-face{font-family:"Font Awesome 6 Pro";font-style:normal;font-weight:400;font-display:block;src:url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.ttf) format("truetype")}.fa-regular,.far{font-weight:400} -------------------------------------------------------------------------------- /public/fonts/fontawesome6/css/sharp-light.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Pro 6.4.2 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license (Commercial License) 4 | * Copyright 2023 Fonticons, Inc. 5 | */ 6 | :root, :host { 7 | --fa-style-family-sharp: 'Font Awesome 6 Sharp'; 8 | --fa-font-sharp-light: normal 300 1em/1 'Font Awesome 6 Sharp'; } 9 | 10 | @font-face { 11 | font-family: 'Font Awesome 6 Sharp'; 12 | font-style: normal; 13 | font-weight: 300; 14 | font-display: block; 15 | src: url("../webfonts/fa-sharp-light-300.woff2") format("woff2"), url("../webfonts/fa-sharp-light-300.ttf") format("truetype"); } 16 | 17 | .fasl, 18 | .fa-light { 19 | font-weight: 300; } 20 | -------------------------------------------------------------------------------- /public/fonts/fontawesome6/css/sharp-light.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Pro 6.4.2 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license (Commercial License) 4 | * Copyright 2023 Fonticons, Inc. 5 | */ 6 | :host,:root{--fa-style-family-sharp:"Font Awesome 6 Sharp";--fa-font-sharp-light:normal 300 1em/1 "Font Awesome 6 Sharp"}@font-face{font-family:"Font Awesome 6 Sharp";font-style:normal;font-weight:300;font-display:block;src:url(../webfonts/fa-sharp-light-300.woff2) format("woff2"),url(../webfonts/fa-sharp-light-300.ttf) format("truetype")}.fa-light,.fasl{font-weight:300} -------------------------------------------------------------------------------- /public/fonts/fontawesome6/css/sharp-regular.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Pro 6.4.2 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license (Commercial License) 4 | * Copyright 2023 Fonticons, Inc. 5 | */ 6 | :root, :host { 7 | --fa-style-family-sharp: 'Font Awesome 6 Sharp'; 8 | --fa-font-sharp-regular: normal 400 1em/1 'Font Awesome 6 Sharp'; } 9 | 10 | @font-face { 11 | font-family: 'Font Awesome 6 Sharp'; 12 | font-style: normal; 13 | font-weight: 400; 14 | font-display: block; 15 | src: url("../webfonts/fa-sharp-regular-400.woff2") format("woff2"), url("../webfonts/fa-sharp-regular-400.ttf") format("truetype"); } 16 | 17 | .fasr, 18 | .fa-regular { 19 | font-weight: 400; } 20 | -------------------------------------------------------------------------------- /public/fonts/fontawesome6/css/sharp-regular.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Pro 6.4.2 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license (Commercial License) 4 | * Copyright 2023 Fonticons, Inc. 5 | */ 6 | :host,:root{--fa-style-family-sharp:"Font Awesome 6 Sharp";--fa-font-sharp-regular:normal 400 1em/1 "Font Awesome 6 Sharp"}@font-face{font-family:"Font Awesome 6 Sharp";font-style:normal;font-weight:400;font-display:block;src:url(../webfonts/fa-sharp-regular-400.woff2) format("woff2"),url(../webfonts/fa-sharp-regular-400.ttf) format("truetype")}.fa-regular,.fasr{font-weight:400} -------------------------------------------------------------------------------- /public/fonts/fontawesome6/css/sharp-solid.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Pro 6.4.2 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license (Commercial License) 4 | * Copyright 2023 Fonticons, Inc. 5 | */ 6 | :root, :host { 7 | --fa-style-family-sharp: 'Font Awesome 6 Sharp'; 8 | --fa-font-sharp-solid: normal 900 1em/1 'Font Awesome 6 Sharp'; } 9 | 10 | @font-face { 11 | font-family: 'Font Awesome 6 Sharp'; 12 | font-style: normal; 13 | font-weight: 900; 14 | font-display: block; 15 | src: url("../webfonts/fa-sharp-solid-900.woff2") format("woff2"), url("../webfonts/fa-sharp-solid-900.ttf") format("truetype"); } 16 | 17 | .fass, 18 | .fa-solid { 19 | font-weight: 900; } 20 | -------------------------------------------------------------------------------- /public/fonts/fontawesome6/css/sharp-solid.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Pro 6.4.2 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license (Commercial License) 4 | * Copyright 2023 Fonticons, Inc. 5 | */ 6 | :host,:root{--fa-style-family-sharp:"Font Awesome 6 Sharp";--fa-font-sharp-solid:normal 900 1em/1 "Font Awesome 6 Sharp"}@font-face{font-family:"Font Awesome 6 Sharp";font-style:normal;font-weight:900;font-display:block;src:url(../webfonts/fa-sharp-solid-900.woff2) format("woff2"),url(../webfonts/fa-sharp-solid-900.ttf) format("truetype")}.fa-solid,.fass{font-weight:900} -------------------------------------------------------------------------------- /public/fonts/fontawesome6/css/solid.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Pro 6.4.2 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license (Commercial License) 4 | * Copyright 2023 Fonticons, Inc. 5 | */ 6 | :root, :host { 7 | --fa-style-family-classic: 'Font Awesome 6 Pro'; 8 | --fa-font-solid: normal 900 1em/1 'Font Awesome 6 Pro'; } 9 | 10 | @font-face { 11 | font-family: 'Font Awesome 6 Pro'; 12 | font-style: normal; 13 | font-weight: 900; 14 | font-display: block; 15 | src: url("../webfonts/fa-solid-900.woff2") format("woff2"), url("../webfonts/fa-solid-900.ttf") format("truetype"); } 16 | 17 | .fas, 18 | .fa-solid { 19 | font-weight: 900; } 20 | -------------------------------------------------------------------------------- /public/fonts/fontawesome6/css/solid.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Pro 6.4.2 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license (Commercial License) 4 | * Copyright 2023 Fonticons, Inc. 5 | */ 6 | :host,:root{--fa-style-family-classic:"Font Awesome 6 Pro";--fa-font-solid:normal 900 1em/1 "Font Awesome 6 Pro"}@font-face{font-family:"Font Awesome 6 Pro";font-style:normal;font-weight:900;font-display:block;src:url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.ttf) format("truetype")}.fa-solid,.fas{font-weight:900} -------------------------------------------------------------------------------- /public/fonts/fontawesome6/css/thin.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Pro 6.4.2 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license (Commercial License) 4 | * Copyright 2023 Fonticons, Inc. 5 | */ 6 | :root, :host { 7 | --fa-style-family-classic: 'Font Awesome 6 Pro'; 8 | --fa-font-thin: normal 100 1em/1 'Font Awesome 6 Pro'; } 9 | 10 | @font-face { 11 | font-family: 'Font Awesome 6 Pro'; 12 | font-style: normal; 13 | font-weight: 100; 14 | font-display: block; 15 | src: url("../webfonts/fa-thin-100.woff2") format("woff2"), url("../webfonts/fa-thin-100.ttf") format("truetype"); } 16 | 17 | .fat, 18 | .fa-thin { 19 | font-weight: 100; } 20 | -------------------------------------------------------------------------------- /public/fonts/fontawesome6/css/thin.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Pro 6.4.2 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license (Commercial License) 4 | * Copyright 2023 Fonticons, Inc. 5 | */ 6 | :host,:root{--fa-style-family-classic:"Font Awesome 6 Pro";--fa-font-thin:normal 100 1em/1 "Font Awesome 6 Pro"}@font-face{font-family:"Font Awesome 6 Pro";font-style:normal;font-weight:100;font-display:block;src:url(../webfonts/fa-thin-100.woff2) format("woff2"),url(../webfonts/fa-thin-100.ttf) format("truetype")}.fa-thin,.fat{font-weight:100} -------------------------------------------------------------------------------- /public/fonts/fontawesome6/css/v4-font-face.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Pro 6.4.2 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license (Commercial License) 4 | * Copyright 2023 Fonticons, Inc. 5 | */ 6 | @font-face { 7 | font-family: 'FontAwesome'; 8 | font-display: block; 9 | src: url("../webfonts/fa-solid-900.woff2") format("woff2"), url("../webfonts/fa-solid-900.ttf") format("truetype"); } 10 | 11 | @font-face { 12 | font-family: 'FontAwesome'; 13 | font-display: block; 14 | src: url("../webfonts/fa-brands-400.woff2") format("woff2"), url("../webfonts/fa-brands-400.ttf") format("truetype"); } 15 | 16 | @font-face { 17 | font-family: 'FontAwesome'; 18 | font-display: block; 19 | src: url("../webfonts/fa-regular-400.woff2") format("woff2"), url("../webfonts/fa-regular-400.ttf") format("truetype"); 20 | unicode-range: U+F003,U+F006,U+F014,U+F016-F017,U+F01A-F01B,U+F01D,U+F022,U+F03E,U+F044,U+F046,U+F05C-F05D,U+F06E,U+F070,U+F087-F088,U+F08A,U+F094,U+F096-F097,U+F09D,U+F0A0,U+F0A2,U+F0A4-F0A7,U+F0C5,U+F0C7,U+F0E5-F0E6,U+F0EB,U+F0F6-F0F8,U+F10C,U+F114-F115,U+F118-F11A,U+F11C-F11D,U+F133,U+F147,U+F14E,U+F150-F152,U+F185-F186,U+F18E,U+F190-F192,U+F196,U+F1C1-F1C9,U+F1D9,U+F1DB,U+F1E3,U+F1EA,U+F1F7,U+F1F9,U+F20A,U+F247-F248,U+F24A,U+F24D,U+F255-F25B,U+F25D,U+F271-F274,U+F278,U+F27B,U+F28C,U+F28E,U+F29C,U+F2B5,U+F2B7,U+F2BA,U+F2BC,U+F2BE,U+F2C0-F2C1,U+F2C3,U+F2D0,U+F2D2,U+F2D4,U+F2DC; } 21 | 22 | @font-face { 23 | font-family: 'FontAwesome'; 24 | font-display: block; 25 | src: url("../webfonts/fa-v4compatibility.woff2") format("woff2"), url("../webfonts/fa-v4compatibility.ttf") format("truetype"); 26 | unicode-range: U+F041,U+F047,U+F065-F066,U+F07D-F07E,U+F080,U+F08B,U+F08E,U+F090,U+F09A,U+F0AC,U+F0AE,U+F0B2,U+F0D0,U+F0D6,U+F0E4,U+F0EC,U+F10A-F10B,U+F123,U+F13E,U+F148-F149,U+F14C,U+F156,U+F15E,U+F160-F161,U+F163,U+F175-F178,U+F195,U+F1F8,U+F219,U+F27A; } 27 | -------------------------------------------------------------------------------- /public/fonts/fontawesome6/css/v4-font-face.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Pro 6.4.2 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license (Commercial License) 4 | * Copyright 2023 Fonticons, Inc. 5 | */ 6 | @font-face{font-family:"FontAwesome";font-display:block;src:url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.ttf) format("truetype")}@font-face{font-family:"FontAwesome";font-display:block;src:url(../webfonts/fa-brands-400.woff2) format("woff2"),url(../webfonts/fa-brands-400.ttf) format("truetype")}@font-face{font-family:"FontAwesome";font-display:block;src:url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.ttf) format("truetype");unicode-range:u+f003,u+f006,u+f014,u+f016-f017,u+f01a-f01b,u+f01d,u+f022,u+f03e,u+f044,u+f046,u+f05c-f05d,u+f06e,u+f070,u+f087-f088,u+f08a,u+f094,u+f096-f097,u+f09d,u+f0a0,u+f0a2,u+f0a4-f0a7,u+f0c5,u+f0c7,u+f0e5-f0e6,u+f0eb,u+f0f6-f0f8,u+f10c,u+f114-f115,u+f118-f11a,u+f11c-f11d,u+f133,u+f147,u+f14e,u+f150-f152,u+f185-f186,u+f18e,u+f190-f192,u+f196,u+f1c1-f1c9,u+f1d9,u+f1db,u+f1e3,u+f1ea,u+f1f7,u+f1f9,u+f20a,u+f247-f248,u+f24a,u+f24d,u+f255-f25b,u+f25d,u+f271-f274,u+f278,u+f27b,u+f28c,u+f28e,u+f29c,u+f2b5,u+f2b7,u+f2ba,u+f2bc,u+f2be,u+f2c0-f2c1,u+f2c3,u+f2d0,u+f2d2,u+f2d4,u+f2dc}@font-face{font-family:"FontAwesome";font-display:block;src:url(../webfonts/fa-v4compatibility.woff2) format("woff2"),url(../webfonts/fa-v4compatibility.ttf) format("truetype");unicode-range:u+f041,u+f047,u+f065-f066,u+f07d-f07e,u+f080,u+f08b,u+f08e,u+f090,u+f09a,u+f0ac,u+f0ae,u+f0b2,u+f0d0,u+f0d6,u+f0e4,u+f0ec,u+f10a-f10b,u+f123,u+f13e,u+f148-f149,u+f14c,u+f156,u+f15e,u+f160-f161,u+f163,u+f175-f178,u+f195,u+f1f8,u+f219,u+f27a} -------------------------------------------------------------------------------- /public/fonts/fontawesome6/css/v5-font-face.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Pro 6.4.2 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license (Commercial License) 4 | * Copyright 2023 Fonticons, Inc. 5 | */ 6 | @font-face { 7 | font-family: 'Font Awesome 5 Brands'; 8 | font-display: block; 9 | font-weight: 400; 10 | src: url("../webfonts/fa-brands-400.woff2") format("woff2"), url("../webfonts/fa-brands-400.ttf") format("truetype"); } 11 | 12 | @font-face { 13 | font-family: 'Font Awesome 5 Pro'; 14 | font-display: block; 15 | font-weight: 900; 16 | src: url("../webfonts/fa-solid-900.woff2") format("woff2"), url("../webfonts/fa-solid-900.ttf") format("truetype"); } 17 | 18 | @font-face { 19 | font-family: 'Font Awesome 5 Pro'; 20 | font-display: block; 21 | font-weight: 400; 22 | src: url("../webfonts/fa-regular-400.woff2") format("woff2"), url("../webfonts/fa-regular-400.ttf") format("truetype"); } 23 | 24 | @font-face { 25 | font-family: 'Font Awesome 5 Pro'; 26 | font-display: block; 27 | font-weight: 300; 28 | src: url("../webfonts/fa-light-300.woff2") format("woff2"), url("../webfonts/fa-light-300.ttf") format("truetype"); } 29 | 30 | @font-face { 31 | font-family: 'Font Awesome 5 Duotone'; 32 | font-display: block; 33 | font-weight: 900; 34 | src: url("../webfonts/fa-duotone-900.woff2") format("woff2"), url("../webfonts/fa-duotone-900.ttf") format("truetype"); } 35 | -------------------------------------------------------------------------------- /public/fonts/fontawesome6/css/v5-font-face.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Pro 6.4.2 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license (Commercial License) 4 | * Copyright 2023 Fonticons, Inc. 5 | */ 6 | @font-face{font-family:"Font Awesome 5 Brands";font-display:block;font-weight:400;src:url(../webfonts/fa-brands-400.woff2) format("woff2"),url(../webfonts/fa-brands-400.ttf) format("truetype")}@font-face{font-family:"Font Awesome 5 Pro";font-display:block;font-weight:900;src:url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.ttf) format("truetype")}@font-face{font-family:"Font Awesome 5 Pro";font-display:block;font-weight:400;src:url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.ttf) format("truetype")}@font-face{font-family:"Font Awesome 5 Pro";font-display:block;font-weight:300;src:url(../webfonts/fa-light-300.woff2) format("woff2"),url(../webfonts/fa-light-300.ttf) format("truetype")}@font-face{font-family:"Font Awesome 5 Duotone";font-display:block;font-weight:900;src:url(../webfonts/fa-duotone-900.woff2) format("woff2"),url(../webfonts/fa-duotone-900.ttf) format("truetype")} -------------------------------------------------------------------------------- /public/fonts/fontawesome6/webfonts/fa-brands-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/public/fonts/fontawesome6/webfonts/fa-brands-400.ttf -------------------------------------------------------------------------------- /public/fonts/fontawesome6/webfonts/fa-brands-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/public/fonts/fontawesome6/webfonts/fa-brands-400.woff2 -------------------------------------------------------------------------------- /public/fonts/fontawesome6/webfonts/fa-duotone-900.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/public/fonts/fontawesome6/webfonts/fa-duotone-900.ttf -------------------------------------------------------------------------------- /public/fonts/fontawesome6/webfonts/fa-duotone-900.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/public/fonts/fontawesome6/webfonts/fa-duotone-900.woff2 -------------------------------------------------------------------------------- /public/fonts/fontawesome6/webfonts/fa-light-300.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/public/fonts/fontawesome6/webfonts/fa-light-300.ttf -------------------------------------------------------------------------------- /public/fonts/fontawesome6/webfonts/fa-light-300.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/public/fonts/fontawesome6/webfonts/fa-light-300.woff2 -------------------------------------------------------------------------------- /public/fonts/fontawesome6/webfonts/fa-regular-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/public/fonts/fontawesome6/webfonts/fa-regular-400.ttf -------------------------------------------------------------------------------- /public/fonts/fontawesome6/webfonts/fa-regular-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/public/fonts/fontawesome6/webfonts/fa-regular-400.woff2 -------------------------------------------------------------------------------- /public/fonts/fontawesome6/webfonts/fa-sharp-light-300.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/public/fonts/fontawesome6/webfonts/fa-sharp-light-300.ttf -------------------------------------------------------------------------------- /public/fonts/fontawesome6/webfonts/fa-sharp-light-300.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/public/fonts/fontawesome6/webfonts/fa-sharp-light-300.woff2 -------------------------------------------------------------------------------- /public/fonts/fontawesome6/webfonts/fa-sharp-regular-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/public/fonts/fontawesome6/webfonts/fa-sharp-regular-400.ttf -------------------------------------------------------------------------------- /public/fonts/fontawesome6/webfonts/fa-sharp-regular-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/public/fonts/fontawesome6/webfonts/fa-sharp-regular-400.woff2 -------------------------------------------------------------------------------- /public/fonts/fontawesome6/webfonts/fa-sharp-solid-900.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/public/fonts/fontawesome6/webfonts/fa-sharp-solid-900.ttf -------------------------------------------------------------------------------- /public/fonts/fontawesome6/webfonts/fa-sharp-solid-900.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/public/fonts/fontawesome6/webfonts/fa-sharp-solid-900.woff2 -------------------------------------------------------------------------------- /public/fonts/fontawesome6/webfonts/fa-solid-900.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/public/fonts/fontawesome6/webfonts/fa-solid-900.ttf -------------------------------------------------------------------------------- /public/fonts/fontawesome6/webfonts/fa-solid-900.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/public/fonts/fontawesome6/webfonts/fa-solid-900.woff2 -------------------------------------------------------------------------------- /public/fonts/fontawesome6/webfonts/fa-thin-100.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/public/fonts/fontawesome6/webfonts/fa-thin-100.ttf -------------------------------------------------------------------------------- /public/fonts/fontawesome6/webfonts/fa-thin-100.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/public/fonts/fontawesome6/webfonts/fa-thin-100.woff2 -------------------------------------------------------------------------------- /public/fonts/fontawesome6/webfonts/fa-v4compatibility.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/public/fonts/fontawesome6/webfonts/fa-v4compatibility.ttf -------------------------------------------------------------------------------- /public/fonts/fontawesome6/webfonts/fa-v4compatibility.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/public/fonts/fontawesome6/webfonts/fa-v4compatibility.woff2 -------------------------------------------------------------------------------- /public/fonts/iconfont/iconfont.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "iconfont"; /* Project id */ 3 | src: url('iconfont.ttf?t=1659948763119') format('truetype'); 4 | } 5 | 6 | .iconfont { 7 | font-family: "iconfont" !important; 8 | font-size: 16px; 9 | font-style: normal; 10 | -webkit-font-smoothing: antialiased; 11 | -moz-osx-font-smoothing: grayscale; 12 | } 13 | 14 | .icon-servo:before { 15 | content: "\e63d"; 16 | } 17 | 18 | .icon-bmp180:before { 19 | content: "\e63e"; 20 | } 21 | 22 | .icon-digital-tube:before { 23 | content: "\e63f"; 24 | } 25 | 26 | .icon-blinker:before { 27 | content: "\e640"; 28 | } 29 | 30 | .icon-dht11:before { 31 | content: "\e641"; 32 | } 33 | 34 | .icon-ir-temp:before { 35 | content: "\e642"; 36 | } 37 | 38 | .icon-sht30:before { 39 | content: "\e643"; 40 | } 41 | 42 | .icon-oled12864:before { 43 | content: "\e644"; 44 | } 45 | 46 | .icon-motor2:before { 47 | content: "\e645"; 48 | } 49 | 50 | .icon-lcd1602:before { 51 | content: "\e646"; 52 | } 53 | 54 | .icon-motor:before { 55 | content: "\e647"; 56 | } 57 | 58 | .icon-keyboard4x4:before { 59 | content: "\e648"; 60 | } 61 | 62 | .icon-sr04:before { 63 | content: "\e649"; 64 | } 65 | 66 | .icon-buzzer:before { 67 | content: "\e64a"; 68 | } 69 | 70 | .icon-ws2812:before { 71 | content: "\e64b"; 72 | } 73 | 74 | .icon-tft:before { 75 | content: "\e64c"; 76 | } 77 | 78 | .icon-a-8x8lattice:before { 79 | content: "\e64d"; 80 | } 81 | 82 | .icon-dht22:before { 83 | content: "\e64e"; 84 | } 85 | 86 | -------------------------------------------------------------------------------- /public/fonts/iconfont/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/public/fonts/iconfont/iconfont.ttf -------------------------------------------------------------------------------- /public/i18n/ar/ar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/public/i18n/ar/ar.jpg -------------------------------------------------------------------------------- /public/i18n/de/de.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/public/i18n/de/de.jpg -------------------------------------------------------------------------------- /public/i18n/en/en.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/public/i18n/en/en.jpg -------------------------------------------------------------------------------- /public/i18n/es/es.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/public/i18n/es/es.jpg -------------------------------------------------------------------------------- /public/i18n/fr/fr.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/public/i18n/fr/fr.jpg -------------------------------------------------------------------------------- /public/i18n/i18n.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "中文", 4 | "code": "zh_cn" 5 | }, 6 | { 7 | "name": "中文(繁体)", 8 | "code": "zh_hk" 9 | }, 10 | { 11 | "name": "English", 12 | "code": "en" 13 | }, 14 | { 15 | "name": "العربية", 16 | "code": "ar" 17 | }, 18 | { 19 | "name": "Deutsch", 20 | "code": "de" 21 | }, 22 | { 23 | "name": "Español", 24 | "code": "es" 25 | }, 26 | { 27 | "name": "Français", 28 | "code": "fr" 29 | }, 30 | { 31 | "name": "日本語", 32 | "code": "ja" 33 | }, 34 | { 35 | "name": "한국어", 36 | "code": "ko" 37 | }, 38 | { 39 | "name": "Português", 40 | "code": "pt" 41 | }, 42 | { 43 | "name": "Русский", 44 | "code": "ru" 45 | } 46 | ] -------------------------------------------------------------------------------- /public/i18n/ja/ja.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/public/i18n/ja/ja.jpg -------------------------------------------------------------------------------- /public/i18n/ko/ko.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/public/i18n/ko/ko.jpg -------------------------------------------------------------------------------- /public/i18n/pt/pt.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/public/i18n/pt/pt.jpg -------------------------------------------------------------------------------- /public/i18n/pt_br/pt_br.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/public/i18n/pt_br/pt_br.json -------------------------------------------------------------------------------- /public/i18n/ru/ru.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/public/i18n/ru/ru.jpg -------------------------------------------------------------------------------- /public/i18n/zh_cn/zh_cn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/public/i18n/zh_cn/zh_cn.jpg -------------------------------------------------------------------------------- /public/i18n/zh_hk/zh_hk.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/public/i18n/zh_hk/zh_hk.jpg -------------------------------------------------------------------------------- /public/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/public/icon.ico -------------------------------------------------------------------------------- /public/imgs/serial-selector.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/public/imgs/serial-selector.webp -------------------------------------------------------------------------------- /public/imgs/subject.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/public/imgs/subject.webp -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/app/app.component.scss: -------------------------------------------------------------------------------- 1 | iframe { 2 | display: none; 3 | } -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { RouterOutlet } from '@angular/router'; 3 | import { CommonModule } from '@angular/common'; 4 | import { ElectronService } from './services/electron.service'; 5 | import { ConfigService } from './services/config.service'; 6 | import { TranslationService } from './services/translation.service'; 7 | 8 | @Component({ 9 | selector: 'app-root', 10 | standalone: true, 11 | imports: [RouterOutlet, CommonModule], 12 | templateUrl: './app.component.html', 13 | styleUrl: './app.component.scss', 14 | }) 15 | export class AppComponent { 16 | title = 'aily-blockly'; 17 | 18 | constructor( 19 | private electronService: ElectronService, 20 | private configService:ConfigService, 21 | private translationService: TranslationService 22 | ) {} 23 | 24 | async ngOnInit() { 25 | await this.electronService.init(); 26 | await this.configService.init(); 27 | await this.translationService.init(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/app/app.config.ts: -------------------------------------------------------------------------------- 1 | import { ApplicationConfig, importProvidersFrom, provideZoneChangeDetection } from '@angular/core'; 2 | import { provideRouter, withHashLocation } from '@angular/router'; 3 | import { provideTranslateService } from "@ngx-translate/core"; 4 | import { routes } from './app.routes'; 5 | import { provideHttpClient } from '@angular/common/http'; 6 | import { provideAnimations } from '@angular/platform-browser/animations'; 7 | import { NzModalModule } from 'ng-zorro-antd/modal'; 8 | 9 | export const appConfig: ApplicationConfig = { 10 | providers: [ 11 | provideZoneChangeDetection({ eventCoalescing: true }), 12 | provideRouter(routes, withHashLocation()), 13 | provideTranslateService(), 14 | provideHttpClient(), 15 | provideAnimations(), 16 | importProvidersFrom(NzModalModule) 17 | ] 18 | }; 19 | -------------------------------------------------------------------------------- /src/app/blockly/blockly.component.html: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /src/app/blockly/color.config.ts: -------------------------------------------------------------------------------- 1 | export let colorList = [ 2 | "#BE52F2","#D48CF6","#E9C5FB", 3 | "#FFCF5C","#FFDF92","#FFEFC9", 4 | "#0084F4","#4D9BFF","#99C2FF", 5 | "#FF647C","#FF8E9D","#FFC2D1", 6 | "#6979F8","#8E9CFF","#C2C9FF", 7 | "#F2994A","#F5B17B","#F8D0AC", 8 | "#00C48C","#4ED8B8","#99E2D9", 9 | ] -------------------------------------------------------------------------------- /src/app/blockly/components/prompt-dialog/prompt-dialog.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
{{ title }}
4 |
5 |
6 | 7 |
8 |
9 |
10 |
11 | 12 | 15 |
16 |
-------------------------------------------------------------------------------- /src/app/blockly/components/prompt-dialog/prompt-dialog.component.scss: -------------------------------------------------------------------------------- 1 | .prompt-box { 2 | background: #2b2d30; 3 | border-radius: 5px; 4 | flex-direction: column; 5 | width: 100%; 6 | overflow: hidden; 7 | } 8 | 9 | .header { 10 | display: flex; 11 | align-items: center; 12 | height: 34px; 13 | border-bottom: 1px solid #222427; 14 | background: #2b2d30; 15 | } 16 | 17 | .title { 18 | height: 100%; 19 | padding-left: 10px; 20 | display: flex; 21 | align-items: center; 22 | flex-grow: 1; 23 | } 24 | 25 | .win-btns { 26 | display: flex; 27 | align-items: center; 28 | font-size: 15px; 29 | 30 | .btn { 31 | font-size: 16px; 32 | width: 33px; 33 | height: 33px; 34 | margin-right: 0; 35 | 36 | &:hover { 37 | background: #3a3c3f; 38 | } 39 | } 40 | 41 | .minimize { 42 | font-size: 18px; 43 | } 44 | 45 | .go-main { 46 | // border-right: 1px solid #333; 47 | margin-right: 10px; 48 | font-size: 17px; 49 | background: transparent !important; 50 | 51 | &:hover { 52 | color: rgb(75, 151, 221); 53 | } 54 | } 55 | 56 | .close { 57 | font-size: 19px; 58 | 59 | &:hover { 60 | color: rgb(145, 0, 0); 61 | } 62 | } 63 | } 64 | 65 | .content { 66 | padding: 10px; 67 | background: #323437; 68 | 69 | input { 70 | background: #3a3c3f; 71 | } 72 | } 73 | 74 | .footer { 75 | margin-top: 10px; 76 | text-align: right; 77 | 78 | button { 79 | margin-left: 10px; 80 | } 81 | } -------------------------------------------------------------------------------- /src/app/blockly/components/prompt-dialog/prompt-dialog.component.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from '@angular/common'; 2 | import { Component, EventEmitter, Inject, Input } from '@angular/core'; 3 | import { FormsModule } from '@angular/forms'; 4 | import { NzButtonModule } from 'ng-zorro-antd/button'; 5 | import { NzInputModule } from 'ng-zorro-antd/input'; 6 | import { NZ_MODAL_DATA, NzModalRef } from 'ng-zorro-antd/modal'; 7 | import { BlocklyService } from '../../blockly.service'; 8 | import * as Blockly from 'blockly'; 9 | import { NzMessageService } from 'ng-zorro-antd/message'; 10 | 11 | @Component({ 12 | selector: 'app-prompt-dialog', 13 | imports: [FormsModule, CommonModule, NzButtonModule, NzInputModule], 14 | templateUrl: './prompt-dialog.component.html', 15 | styleUrl: './prompt-dialog.component.scss' 16 | }) 17 | export class PromptDialogComponent { 18 | @Input() title = 'Title'; 19 | 20 | value = ''; 21 | 22 | constructor( 23 | private modal: NzModalRef, 24 | @Inject(NZ_MODAL_DATA) public data: any, 25 | private blocklyService: BlocklyService, 26 | private message: NzMessageService 27 | ) { 28 | } 29 | 30 | ngOnInit(): void { 31 | this.title = this.data.title 32 | } 33 | 34 | onConfirm() { 35 | const isNameUsed = Blockly.Variables.nameUsedWithAnyType(this.value, this.blocklyService.workspace); 36 | const cVariableFormatRegex = /^[a-zA-Z_][a-zA-Z0-9_]*$/; 37 | const isValidFormat = cVariableFormatRegex.test(this.value); 38 | if (!isValidFormat) { 39 | this.message.error('变量名格式不正确,请重新输入', { nzDuration: 3000 }); 40 | return 41 | } 42 | if (isNameUsed) { 43 | console.log('Name already used'); 44 | this.message.error('变量名已被使用,请重新输入', { nzDuration: 3000 }); 45 | return 46 | } 47 | this.modal.triggerOk() 48 | } 49 | 50 | onClose() { 51 | this.modal.triggerCancel() 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/app/blockly/custom-category.ts: -------------------------------------------------------------------------------- 1 | // 给toolbox中的lib添加自定义icon显示 2 | import * as Blockly from 'blockly'; 3 | 4 | class CustomCategory extends Blockly.ToolboxCategory { 5 | /** 6 | * Constructor for a custom category. 7 | * @override 8 | */ 9 | constructor(categoryDef, toolbox, opt_parent) { 10 | super(categoryDef, toolbox, opt_parent); 11 | } 12 | 13 | override createIconDom_() { 14 | let iconDiv = document.createElement('div'); 15 | iconDiv.className = 'tbc-icon-box'; 16 | let toolboxIcon = document.createElement('i'); 17 | iconDiv.appendChild(toolboxIcon); 18 | let iconClass = 'fa-light fa-cube'; 19 | if (this.toolboxItemDef_['icon']) { 20 | iconClass = this.toolboxItemDef_['icon']; 21 | } 22 | Blockly.utils.dom.addClass(toolboxIcon, iconClass); 23 | return iconDiv; 24 | } 25 | } 26 | 27 | Blockly.registry.register( 28 | Blockly.registry.Type.TOOLBOX_ITEM, 29 | Blockly.ToolboxCategory.registrationName, 30 | CustomCategory, 31 | true, 32 | ); 33 | -------------------------------------------------------------------------------- /src/app/blockly/generators/javascript/javascript.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2021 Google LLC 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | /** 8 | * @file Instantiate a JavascriptGenerator and populate it with the 9 | * complete set of block generator functions for JavaScript. This is 10 | * the entrypoint for javascript_compressed.js. 11 | */ 12 | 13 | // Former goog.module ID: Blockly.JavaScript.all 14 | 15 | import { JavascriptGenerator } from './javascript/javascript_generator.js'; 16 | import * as lists from './javascript/lists.js'; 17 | import * as logic from './javascript/logic.js'; 18 | import * as loops from './javascript/loops.js'; 19 | import * as math from './javascript/math.js'; 20 | import * as procedures from './javascript/procedures.js'; 21 | import * as text from './javascript/text.js'; 22 | import * as variables from './javascript/variables.js'; 23 | import * as variablesDynamic from './javascript/variables_dynamic.js'; 24 | 25 | export * from './javascript/javascript_generator.js'; 26 | 27 | /** 28 | * JavaScript code generator instance. 29 | * @type {!JavascriptGenerator} 30 | */ 31 | export const javascriptGenerator: any = new JavascriptGenerator(); 32 | 33 | // Install per-block-type generator functions: 34 | const generators: typeof javascriptGenerator.forBlock = { 35 | ...lists, 36 | ...logic, 37 | ...loops, 38 | ...math, 39 | ...procedures, 40 | ...text, 41 | ...variables, 42 | ...variablesDynamic, 43 | }; 44 | for (const name in generators) { 45 | javascriptGenerator.forBlock[name] = generators[name]; 46 | } 47 | -------------------------------------------------------------------------------- /src/app/blockly/generators/javascript/javascript/variables.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2012 Google LLC 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | /** 8 | * @file Generating JavaScript for variable blocks. 9 | */ 10 | 11 | // Former goog.module ID: Blockly.JavaScript.variables 12 | 13 | import type {Block} from '../../core/block.js'; 14 | import type {JavascriptGenerator} from './javascript_generator.js'; 15 | import {Order} from './javascript_generator.js'; 16 | 17 | export function variables_get( 18 | block: Block, 19 | generator: JavascriptGenerator, 20 | ): [string, Order] { 21 | // Variable getter. 22 | const code = generator.getVariableName(block.getFieldValue('VAR')); 23 | return [code, Order.ATOMIC]; 24 | } 25 | 26 | export function variables_set(block: Block, generator: JavascriptGenerator) { 27 | // Variable setter. 28 | const argument0 = 29 | generator.valueToCode(block, 'VALUE', Order.ASSIGNMENT) || '0'; 30 | const varName = generator.getVariableName(block.getFieldValue('VAR')); 31 | return varName + ' = ' + argument0 + ';\n'; 32 | } 33 | -------------------------------------------------------------------------------- /src/app/blockly/generators/javascript/javascript/variables_dynamic.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2018 Google LLC 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | /** 8 | * @file Generating JavaScript for dynamic variable blocks. 9 | */ 10 | 11 | // Former goog.module ID: Blockly.JavaScript.variablesDynamic 12 | 13 | // JavaScript is dynamically typed. 14 | export { 15 | variables_get as variables_get_dynamic, 16 | variables_set as variables_set_dynamic, 17 | } from './variables.js'; 18 | -------------------------------------------------------------------------------- /src/app/blockly/generators/micropython/micropython.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/src/app/blockly/generators/micropython/micropython.ts -------------------------------------------------------------------------------- /src/app/blockly/plugins/block-plus-minus/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@blockly/block-plus-minus", 3 | "version": "8.0.14", 4 | "description": "A group of blocks that replace the built-in mutator UI with a +/- based UI.", 5 | "scripts": { 6 | "audit:fix": "blockly-scripts auditFix", 7 | "build": "blockly-scripts build", 8 | "clean": "blockly-scripts clean", 9 | "lint": "eslint .", 10 | "predeploy": "blockly-scripts predeploy", 11 | "prepublishOnly": "npm run clean && npm run build", 12 | "start": "blockly-scripts start", 13 | "test": "blockly-scripts test" 14 | }, 15 | "main": "./dist/index.js", 16 | "unpkg": "./dist/index.js", 17 | "author": "Beka Westberg", 18 | "keywords": [ 19 | "blockly", 20 | "block", 21 | "mutator" 22 | ], 23 | "homepage": "https://github.com/google/blockly-samples/tree/master/plugins/block-plus-minus#readme", 24 | "bugs": { 25 | "url": "https://github.com/google/blockly-samples/issues" 26 | }, 27 | "repository": { 28 | "type": "git", 29 | "url": "https://github.com/google/blockly-samples.git", 30 | "directory": "plugins/block-plus-minus" 31 | }, 32 | "license": "Apache-2.0", 33 | "directories": { 34 | "dist": "dist", 35 | "src": "src" 36 | }, 37 | "files": [ 38 | "dist", 39 | "src" 40 | ], 41 | "devDependencies": { 42 | "@blockly/dev-scripts": "^4.0.7", 43 | "@blockly/dev-tools": "^8.1.0", 44 | "chai": "^4.2.0", 45 | "mocha": "^10.2.0", 46 | "sinon": "^9.0.1" 47 | }, 48 | "peerDependencies": { 49 | "blockly": "^11.0.0" 50 | }, 51 | "publishConfig": { 52 | "access": "public", 53 | "registry": "https://wombat-dressing-room.appspot.com" 54 | }, 55 | "engines": { 56 | "node": ">=8.17.0" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/app/blockly/plugins/block-plus-minus/src/field_minus.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2020 Google LLC 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | /** 8 | * @fileoverview A function that creates a minus button used for mutation. 9 | */ 10 | 'use strict'; 11 | 12 | import * as Blockly from 'blockly/core'; 13 | import {getExtraBlockState} from './serialization_helper'; 14 | 15 | /** 16 | * Creates a minus image field used for mutation. 17 | * @param {Object=} args Untyped args passed to block.minus when the field 18 | * is clicked. 19 | * @returns {Blockly.FieldImage} The minus field. 20 | */ 21 | export function createMinusField(args = undefined) { 22 | const minus = new Blockly.FieldImage(minusImage, 15, 15, undefined, onClick_); 23 | /** 24 | * Untyped args passed to block.minus when the field is clicked. 25 | * @type {?(Object|undefined)} 26 | * @private 27 | */ 28 | minus.args_ = args; 29 | return minus; 30 | } 31 | 32 | /** 33 | * Calls block.minus(args) when the minus field is clicked. 34 | * @param {Blockly.FieldImage} minusField The field being clicked. 35 | * @private 36 | */ 37 | function onClick_(minusField) { 38 | // TODO: This is a dupe of the mutator code, anyway to unify? 39 | const block = minusField.getSourceBlock(); 40 | 41 | if (block.isInFlyout) { 42 | return; 43 | } 44 | 45 | Blockly.Events.setGroup(true); 46 | const oldExtraState = getExtraBlockState(block); 47 | block.minus(minusField.args_); 48 | const newExtraState = getExtraBlockState(block); 49 | 50 | if (oldExtraState != newExtraState) { 51 | Blockly.Events.fire( 52 | new Blockly.Events.BlockChange( 53 | block, 54 | 'mutation', 55 | null, 56 | oldExtraState, 57 | newExtraState, 58 | ), 59 | ); 60 | } 61 | Blockly.Events.setGroup(false); 62 | } 63 | 64 | const minusImage = 65 | 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAw' + 66 | 'MC9zdmciIHZlcnNpb249IjEuMSIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0Ij48cGF0aCBkPS' + 67 | 'JNMTggMTFoLTEyYy0xLjEwNCAwLTIgLjg5Ni0yIDJzLjg5NiAyIDIgMmgxMmMxLjEwNCAw' + 68 | 'IDItLjg5NiAyLTJzLS44OTYtMi0yLTJ6IiBmaWxsPSJ3aGl0ZSIgLz48L3N2Zz4K'; 69 | -------------------------------------------------------------------------------- /src/app/blockly/plugins/block-plus-minus/src/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2020 Google LLC 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | /** 8 | * @fileoverview Adds blocks that replace the built-in mutator UI with a +/- UI. 9 | */ 10 | 11 | import './if.js'; 12 | import './list_create.js'; 13 | import './procedures.js'; 14 | import './text_join'; 15 | -------------------------------------------------------------------------------- /src/app/blockly/plugins/block-plus-minus/src/serialization_helper.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2022 Google LLC 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | import * as Blockly from 'blockly/core'; 8 | 9 | /** 10 | * Returns the extra state of the given block (either as XML or a JSO, depending 11 | * on the block's definition). 12 | * @param {!Blockly.BlockSvg} block The block to get the extra state of. 13 | * @returns {string} A stringified version of the extra state of the given 14 | * block. 15 | */ 16 | export function getExtraBlockState(block) { 17 | // TODO: This is a dupe of the BlockChange.getExtraBlockState code, do we 18 | // want to make that public? 19 | if (block.saveExtraState) { 20 | const state = block.saveExtraState(); 21 | return state ? JSON.stringify(state) : ''; 22 | } else if (block.mutationToDom) { 23 | const state = block.mutationToDom(); 24 | return state ? Blockly.Xml.domToText(state) : ''; 25 | } 26 | return ''; 27 | } 28 | -------------------------------------------------------------------------------- /src/app/blockly/plugins/continuous-toolbox/GETSTARTED.md: -------------------------------------------------------------------------------- 1 | ## Available Scripts 2 | 3 | In this directory, you can run: 4 | 5 | ### `npm start` 6 | 7 | Runs the package in development mode. 8 | 9 | Open [http://localhost:3000/test](http://localhost:3000/test) to view the test 10 | playground in the browser. The page will reload if you make edits. 11 | 12 | ### `npm run build` 13 | 14 | Builds the package into the `dist` directory. 15 | 16 | ### `npm run lint` 17 | 18 | Runs eslint on the `src` and `test` directories. 19 | 20 | ### `npm run clean` 21 | 22 | Deletes the `dist` and `build` directories if they exist. 23 | -------------------------------------------------------------------------------- /src/app/blockly/plugins/continuous-toolbox/README.md: -------------------------------------------------------------------------------- 1 | # @blockly/continuous-toolbox [![Built on Blockly](https://tinyurl.com/built-on-blockly)](https://github.com/google/blockly) 2 | 3 | A [Blockly](https://www.npmjs.com/package/blockly) plugin that creates a continuous-scrolling style toolbox/flyout that is always open. All of the blocks from all categories are in the flyout together, and the user can either click a category name in the toolbox or scroll to the category they're looking for. This flyout only works as a vertical flyout and it works in both left-to-right and right-to-left modes. Parts of the toolbox style have been changed already, but you can customize the style further by following the [toolbox documentation](https://developers.google.com/blockly/guides/configure/web/toolbox). 4 | 5 | ![Screenshot of continuous toolbox showing multiple categories of blocks in the flyout at once](https://github.com/google/blockly-samples/blob/master/plugins/continuous-toolbox/screenshot.png?raw=true) 6 | The continuous toolbox is shown here with the 'Zelos' theme, and the style can be further customized. 7 | 8 | ## Installation 9 | 10 | ### Yarn 11 | 12 | ``` 13 | yarn add @blockly/continuous-toolbox 14 | ``` 15 | 16 | ### npm 17 | 18 | ``` 19 | npm install @blockly/continuous-toolbox --save 20 | ``` 21 | 22 | ## Usage 23 | 24 | Include the toolbox, flyout, and metrics manager classes from the plugin in the options struct used when injecting Blockly. This style of flyout works best with a toolbox definition that does not use collapsible categories. 25 | 26 | Note that this plugin uses APIs introduced in the `3.20200924.3` release of Blockly, so you will need to use at least this version or higher. 27 | 28 | ```js 29 | import * as Blockly from 'blockly'; 30 | import { 31 | ContinuousToolbox, 32 | ContinuousFlyout, 33 | ContinuousMetrics, 34 | } from '@blockly/continuous-toolbox'; 35 | 36 | // Inject Blockly. 37 | const workspace = Blockly.inject('blocklyDiv', { 38 | plugins: { 39 | toolbox: ContinuousToolbox, 40 | flyoutsVerticalToolbox: ContinuousFlyout, 41 | metricsManager: ContinuousMetrics, 42 | }, 43 | toolbox: toolboxCategories, 44 | // ... your other options here ... 45 | }); 46 | ``` 47 | 48 | ## License 49 | 50 | Apache 2.0 51 | -------------------------------------------------------------------------------- /src/app/blockly/plugins/continuous-toolbox/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@blockly/continuous-toolbox", 3 | "version": "6.0.11", 4 | "description": "A Blockly plugin that adds a continous-scrolling style toolbox and flyout", 5 | "scripts": { 6 | "build": "blockly-scripts build", 7 | "clean": "blockly-scripts clean", 8 | "lint": "eslint .", 9 | "prepublishOnly": "npm run clean && npm run build", 10 | "start": "blockly-scripts start", 11 | "audit:fix": "blockly-scripts auditFix", 12 | "predeploy": "npm run build && blockly-scripts predeploy" 13 | }, 14 | "main": "./dist/index.js", 15 | "module": "./src/index.js", 16 | "unpkg": "./dist/index.js", 17 | "author": "Blockly Team", 18 | "keywords": [ 19 | "blockly", 20 | "blockly-plugin", 21 | "continuous-toolbox" 22 | ], 23 | "homepage": "https://github.com/google/blockly-samples/tree/master/plugins/continuous-toolbox#readme", 24 | "bugs": { 25 | "url": "https://github.com/google/blockly-samples/issues" 26 | }, 27 | "repository": { 28 | "type": "git", 29 | "url": "https://github.com/google/blockly-samples.git", 30 | "directory": "plugins/continuous-toolbox" 31 | }, 32 | "license": "Apache-2.0", 33 | "directories": { 34 | "dist": "dist", 35 | "src": "src" 36 | }, 37 | "files": [ 38 | "dist", 39 | "src" 40 | ], 41 | "devDependencies": { 42 | "@blockly/dev-scripts": "^4.0.6", 43 | "@blockly/dev-tools": "^8.0.11" 44 | }, 45 | "peerDependencies": { 46 | "blockly": "^11.0.0" 47 | }, 48 | "publishConfig": { 49 | "access": "public", 50 | "registry": "https://wombat-dressing-room.appspot.com" 51 | }, 52 | "engines": { 53 | "node": ">=8.17.0" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/app/blockly/plugins/continuous-toolbox/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/src/app/blockly/plugins/continuous-toolbox/screenshot.png -------------------------------------------------------------------------------- /src/app/blockly/plugins/continuous-toolbox/src/ContinuousCategory.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2020 Google LLC 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | /** 8 | * @fileoverview Toolbox category with styling for continuous toolbox. 9 | */ 10 | 11 | import * as Blockly from 'blockly/core'; 12 | 13 | /** Toolbox category for continuous toolbox. */ 14 | export class ContinuousCategory extends Blockly.ToolboxCategory { 15 | /** 16 | * Constructor for ContinuousCategory which is used in ContinuousToolbox. 17 | * @override 18 | */ 19 | constructor(categoryDef, toolbox) { 20 | super(categoryDef, toolbox); 21 | } 22 | 23 | /** @override */ 24 | createLabelDom_(name) { 25 | const label = document.createElement('div'); 26 | label.setAttribute('id', this.getId() + '.label'); 27 | label.textContent = name; 28 | label.classList.add(this.cssConfig_['label']); 29 | return label; 30 | } 31 | 32 | /** @override */ 33 | createIconDom_() { 34 | const icon = document.createElement('div'); 35 | icon.classList.add('categoryBubble'); 36 | icon.style.backgroundColor = this.colour_; 37 | return icon; 38 | } 39 | 40 | /** @override */ 41 | addColourBorder_() { 42 | // No-op 43 | } 44 | 45 | /** @override */ 46 | setSelected(isSelected) { 47 | if (isSelected) { 48 | this.rowDiv_.style.backgroundColor = 'gray'; 49 | Blockly.utils.dom.addClass(this.rowDiv_, this.cssConfig_['selected']); 50 | } else { 51 | this.rowDiv_.style.backgroundColor = ''; 52 | Blockly.utils.dom.removeClass(this.rowDiv_, this.cssConfig_['selected']); 53 | } 54 | Blockly.utils.aria.setState( 55 | /** @type {!Element} */ (this.htmlDiv_), 56 | Blockly.utils.aria.State.SELECTED, 57 | isSelected, 58 | ); 59 | } 60 | } 61 | 62 | Blockly.registry.register( 63 | Blockly.registry.Type.TOOLBOX_ITEM, 64 | Blockly.ToolboxCategory.registrationName, 65 | ContinuousCategory, 66 | true, 67 | ); 68 | -------------------------------------------------------------------------------- /src/app/blockly/plugins/continuous-toolbox/src/ContinuousMetricsFlyout.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2021 Google LLC 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | import * as Blockly from 'blockly/core'; 8 | 9 | /** Adds additional padding to the bottom of the flyout if needed. */ 10 | export class ContinuousFlyoutMetrics extends Blockly.FlyoutMetricsManager { 11 | /** @override */ 12 | constructor(workspace, flyout) { 13 | super(workspace, flyout); 14 | } 15 | /** 16 | * Adds additional padding to the bottom of the flyout if needed, 17 | * in order to make it possible to scroll to the top of the last category. 18 | * @override 19 | */ 20 | getScrollMetrics( 21 | getWorkspaceCoordinates = undefined, 22 | cachedViewMetrics = undefined, 23 | cachedContentMetrics = undefined, 24 | ) { 25 | const scrollMetrics = super.getScrollMetrics( 26 | getWorkspaceCoordinates, 27 | cachedViewMetrics, 28 | cachedContentMetrics, 29 | ); 30 | const contentMetrics = 31 | cachedContentMetrics || this.getContentMetrics(getWorkspaceCoordinates); 32 | const viewMetrics = 33 | cachedViewMetrics || this.getViewMetrics(getWorkspaceCoordinates); 34 | 35 | if (scrollMetrics) { 36 | scrollMetrics.height += this.flyout_.calculateBottomPadding( 37 | contentMetrics, 38 | viewMetrics, 39 | ); 40 | } 41 | return scrollMetrics; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/app/blockly/plugins/continuous-toolbox/src/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2020 Google LLC 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | /** 8 | * @fileoverview Continuous-scroll toolbox and flyout that is always open. 9 | */ 10 | 11 | export * from './ContinuousCategory'; 12 | export * from './ContinuousFlyout'; 13 | export * from './ContinuousMetrics'; 14 | export * from './ContinuousToolbox'; 15 | -------------------------------------------------------------------------------- /src/app/blockly/plugins/continuous-toolbox/test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Blockly Continuous Toolbox Plugin Test 6 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/app/blockly/plugins/continuous-toolbox/test/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2020 Google LLC 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | /** 8 | * @fileoverview Continuous toolbox plugin test. 9 | */ 10 | 11 | import * as Blockly from 'blockly'; 12 | import {toolboxCategories, createPlayground} from '@blockly/dev-tools'; 13 | import { 14 | ContinuousToolbox, 15 | ContinuousFlyout, 16 | ContinuousMetrics, 17 | } from '../src/index'; 18 | 19 | /** 20 | * Create a workspace. 21 | * @param {HTMLElement} blocklyDiv The blockly container div. 22 | * @param {!Blockly.BlocklyOptions} options The Blockly options. 23 | * @returns {!Blockly.WorkspaceSvg} The created workspace. 24 | */ 25 | function createWorkspace(blocklyDiv, options) { 26 | const workspace = Blockly.inject(blocklyDiv, options); 27 | 28 | return workspace; 29 | } 30 | 31 | document.addEventListener('DOMContentLoaded', function () { 32 | const defaultOptions = { 33 | toolbox: toolboxCategories, 34 | plugins: { 35 | toolbox: ContinuousToolbox, 36 | flyoutsVerticalToolbox: ContinuousFlyout, 37 | metricsManager: ContinuousMetrics, 38 | }, 39 | }; 40 | createPlayground( 41 | document.getElementById('root'), 42 | createWorkspace, 43 | defaultOptions, 44 | ); 45 | }); 46 | -------------------------------------------------------------------------------- /src/app/blockly/plugins/toolbox-search/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@blockly/toolbox-search", 3 | "version": "3.0.1", 4 | "description": "A Blockly plugin that adds a toolbox category that allows searching for blocks.", 5 | "scripts": { 6 | "audit:fix": "blockly-scripts auditFix", 7 | "build": "blockly-scripts build", 8 | "clean": "blockly-scripts clean", 9 | "lint": "eslint .", 10 | "predeploy": "blockly-scripts predeploy", 11 | "start": "blockly-scripts start", 12 | "test": "blockly-scripts test" 13 | }, 14 | "main": "./dist/index.js", 15 | "types": "./dist/index.d.ts", 16 | "unpkg": "./dist/index.js", 17 | "author": "", 18 | "keywords": [ 19 | "blockly", 20 | "blockly-plugin", 21 | "toolbox search", 22 | "block search" 23 | ], 24 | "homepage": "https://github.com/google/blockly-samples/tree/master/plugins/toolbox-search#readme", 25 | "bugs": { 26 | "url": "https://github.com/google/blockly-samples/issues" 27 | }, 28 | "repository": { 29 | "type": "git", 30 | "url": "https://github.com/google/blockly-samples.git", 31 | "directory": "plugins/toolbox-search" 32 | }, 33 | "license": "Apache-2.0", 34 | "directories": { 35 | "dist": "dist", 36 | "src": "src" 37 | }, 38 | "files": [ 39 | "dist", 40 | "src" 41 | ], 42 | "devDependencies": { 43 | "@blockly/dev-scripts": "^4.0.9", 44 | "@blockly/dev-tools": "^9.0.1", 45 | "chai": "^4.3.7", 46 | "typescript": "^5.4.5" 47 | }, 48 | "peerDependencies": { 49 | "blockly": "^12.0.0" 50 | }, 51 | "publishConfig": { 52 | "access": "public", 53 | "registry": "https://wombat-dressing-room.appspot.com" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/app/blockly/plugins/toolbox-search/src/index.ts: -------------------------------------------------------------------------------- 1 | // This file exists solely as an entrypoint. 2 | import './toolbox_search'; 3 | -------------------------------------------------------------------------------- /src/app/blockly/plugins/workspace-multiselect/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2022 MIT 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | /** 8 | * @fileoverview specify the plugin exported API. 9 | */ 10 | 11 | export {Multiselect} from './multiselect'; 12 | export {dragSelectionWeakMap, inMultipleSelectionModeWeakMap} from './global'; 13 | -------------------------------------------------------------------------------- /src/app/components/about-us/about-us.component.html: -------------------------------------------------------------------------------- 1 |
2 |
本项目的开发得到了以下企业和个人的支持
3 | 4 | 5 |
企业赞助
6 |
7 |
8 |
9 | 10 | 11 |
个人赞助
12 |
13 |
14 |
15 |
-------------------------------------------------------------------------------- /src/app/components/about-us/about-us.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/src/app/components/about-us/about-us.component.scss -------------------------------------------------------------------------------- /src/app/components/about-us/about-us.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-about-us', 5 | imports: [], 6 | templateUrl: './about-us.component.html', 7 | styleUrl: './about-us.component.scss' 8 | }) 9 | export class AboutUsComponent { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/app/components/aily-blockly/aily-blockly.component.html: -------------------------------------------------------------------------------- 1 |
6 |
7 |
8 | -------------------------------------------------------------------------------- /src/app/components/aily-blockly/aily-blockly.component.scss: -------------------------------------------------------------------------------- 1 | :host {} 2 | 3 | #blockly-area { 4 | position: relative; 5 | } 6 | 7 | #blockly-div { 8 | ::ng-deep { 9 | 10 | .blocklyFlyoutBackground, 11 | .blocklyTrash { 12 | opacity: 0; 13 | display: none; 14 | } 15 | 16 | .blocklyToolboxContents { 17 | padding-bottom: 35px; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/app/components/aily-coding/aily-coding.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | blockly 4 | 9 | 10 | 11 |
12 |
13 |
14 | {{ JSON.stringify(data, null, 2) }} 15 |
16 |
17 | 18 |
19 |
20 |
21 | -------------------------------------------------------------------------------- /src/app/components/aily-coding/aily-coding.component.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | } 3 | 4 | .blockly-coding { 5 | padding: 0 10px; 6 | } 7 | 8 | .action { 9 | display: flex; 10 | justify-content: space-between; 11 | align-items: center; 12 | gap: 20px; 13 | border-bottom: 1px solid #ccc; 14 | padding: 3px 0; 15 | 16 | .type { 17 | flex: 1; 18 | } 19 | } 20 | 21 | .coding { 22 | word-break: break-all; 23 | white-space: break-spaces; 24 | padding: 10px 0; 25 | } 26 | -------------------------------------------------------------------------------- /src/app/components/aily-coding/aily-coding.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | import { NgIf } from '@angular/common'; 3 | import { NzIconDirective } from 'ng-zorro-antd/icon'; 4 | import { AilyBlocklyComponent } from '../aily-blockly/aily-blockly.component'; 5 | 6 | @Component({ 7 | selector: 'app-aily-coding', 8 | imports: [NgIf, NzIconDirective, AilyBlocklyComponent], 9 | templateUrl: './aily-coding.component.html', 10 | styleUrl: './aily-coding.component.scss', 11 | }) 12 | export class AilyCodingComponent { 13 | @Input() data: any = {}; 14 | 15 | protected readonly JSON = JSON; 16 | type: number = 1; // 0 code 1 blockly 17 | 18 | constructor() {} 19 | 20 | toggleType() { 21 | this.type = this.type === 0 ? 1 : 0; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/app/components/inner-window/inner-window.component.html: -------------------------------------------------------------------------------- 1 |
24 |
25 |
26 | {{ opts.title }} 27 |
28 |
29 |
30 | 31 |
32 |
33 | 34 |
35 |
36 | 37 |
38 |
39 |
40 |
41 | 42 |
43 |
44 | -------------------------------------------------------------------------------- /src/app/components/inner-window/inner-window.component.scss: -------------------------------------------------------------------------------- 1 | .inner-window { 2 | background: #CCC; 3 | position: absolute; 4 | z-index: 9; 5 | border-radius: 5px; 6 | overflow: hidden; 7 | box-shadow: 0 0 3px rgba(0, 0, 0, 0.5); 8 | transition: scale 0.1s; 9 | ::ng-deep{ 10 | .ng-resizable-handle{ 11 | z-index: 999; 12 | } 13 | } 14 | } 15 | 16 | .header { 17 | position: absolute; 18 | width: 100%; 19 | height: 32px; 20 | background: #666; 21 | display: flex; 22 | align-items: center; 23 | color: #dddddd; 24 | 25 | .title { 26 | font-size: 12px; 27 | padding-left: 10px; 28 | height: 100%; 29 | flex-grow: 1; 30 | cursor: move; 31 | justify-content: flex-start; 32 | } 33 | 34 | .btns { 35 | font-size: 16px; 36 | 37 | .fa-minus{ 38 | font-size: 14px; 39 | } 40 | 41 | .fa-square{ 42 | font-size: 14px; 43 | } 44 | 45 | >div { 46 | width: 30px; 47 | height: 30px; 48 | cursor: pointer; 49 | 50 | &:hover { 51 | color: #FFF; 52 | } 53 | } 54 | } 55 | } 56 | 57 | .content { 58 | position: absolute; 59 | top: 32px; 60 | background-color: #333; 61 | height: calc(100% - 32px); 62 | width: 100%; 63 | } 64 | 65 | .animate__faster{ 66 | animation-duration: 0.2s !important; 67 | } 68 | 69 | .ng-resizable-diagonal{ 70 | opacity: 0; 71 | } 72 | // .maximize{ 73 | // transform: scale(1.2); 74 | // } -------------------------------------------------------------------------------- /src/app/components/menu/menu.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/components/menu/menu.component.scss: -------------------------------------------------------------------------------- 1 | .menu-box { 2 | background: #2b2d30; 3 | border-radius: 5px; 4 | position: fixed; 5 | z-index: 999; 6 | flex-direction: column; 7 | padding: 3px 0; 8 | 9 | .menu-item { 10 | padding: 0 10px; 11 | height: 30px; 12 | justify-content: flex-start; 13 | margin: 0 3px; 14 | border-radius: 5px; 15 | cursor: pointer; 16 | color: #ddd; 17 | 18 | .name { 19 | white-space: nowrap; 20 | } 21 | 22 | .icon { 23 | width: 20px; 24 | margin-right: 10px; 25 | flex-shrink: 0; 26 | } 27 | 28 | .text { 29 | margin-left: 10px; 30 | white-space: nowrap; 31 | color: #999; 32 | flex-shrink: 1; 33 | } 34 | 35 | &:hover { 36 | background: #405e8d; 37 | } 38 | 39 | &.disabled:hover { 40 | background: transparent; 41 | } 42 | 43 | &.highlight { 44 | .icon { 45 | color: #f7ce42; 46 | } 47 | } 48 | 49 | &.disabled { 50 | opacity: 0.6; 51 | } 52 | } 53 | 54 | .sep { 55 | height: 1px; 56 | background: #444; 57 | margin: 2px 0; 58 | } 59 | } -------------------------------------------------------------------------------- /src/app/components/monaco-editor/monaco-editor.component.html: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /src/app/components/monaco-editor/monaco-editor.component.scss: -------------------------------------------------------------------------------- 1 | nz-code-editor { 2 | height: 100% !important; 3 | width: 100% !important; 4 | background-color:transparent; 5 | } -------------------------------------------------------------------------------- /src/app/components/notification/notification.component.html: -------------------------------------------------------------------------------- 1 | @if(data){ 2 |
4 | @switch (data.state) { 5 | @case ("doing") { 6 |
7 |
8 | 9 |
10 |
11 | {{progressValue}}% 12 |
13 |
14 | } 15 | @case ("done") { 16 |
17 |
18 | 19 |
20 |
21 | } 22 | @case ("error") { 23 |
24 |
25 | 26 |
27 |
28 | } 29 | @case ("warn") { 30 |
31 |
32 | 33 |
34 |
35 | } 36 | } 37 |
38 |
{{data.title}}
39 |
{{data.text}}
40 |
41 | @if (data.state=='doing'&& data.stop) { 42 | 43 | } 44 | @if (data.state=='error'&& data.detail) { 45 | 46 | 47 | } 48 |
49 |
50 | 53 |
54 | } -------------------------------------------------------------------------------- /src/app/components/project-manager/project-manager.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
项目名称
4 |
5 | 6 |
7 |
8 |
9 |
开发平台
10 |
11 | 16 |
17 |
18 |
19 |
20 |
库管理
21 |
22 | 27 |
28 |
29 |
30 |
31 | @for (color of presetColors; track color) { 32 | {{ color }} 33 | } 34 |
35 |
36 |
37 | @for (v of dataList; track v.package?.name) { 38 |
39 |
40 |
{{ v.package?.name }}
41 |
{{ v.package?.author?.name }}
42 |
43 |
{{ v.package?.description }}
44 |
45 | 49 |
50 | 51 | 54 |
55 |
56 |
57 | } 58 |
59 | 66 |
67 |
68 | -------------------------------------------------------------------------------- /src/app/components/project-manager/project-manager.component.scss: -------------------------------------------------------------------------------- 1 | .project-manager { 2 | position: fixed; 3 | left: 180px; 4 | top: 0; 5 | width: calc(100% - 180px); 6 | height: 100%; 7 | background: #ccc; 8 | } 9 | 10 | .item { 11 | display: flex; 12 | align-items: center; 13 | } 14 | 15 | .lib-list { 16 | display: flex; 17 | flex-wrap: wrap; 18 | 19 | .lib { 20 | width: 300px; 21 | height: 180px; 22 | margin: 6px; 23 | padding: 6px; 24 | background: #fff; 25 | display: flex; 26 | flex-direction: column; 27 | } 28 | 29 | .desc { 30 | flex: 1; 31 | word-break: break-all; 32 | max-height: 7em; 33 | overflow: auto; 34 | } 35 | 36 | [nz-button] { 37 | margin-right: 8px; 38 | margin-bottom: 5px; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/app/components/project-manager/project-manager.component.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from '@angular/common'; 2 | import { Component, OnInit } from '@angular/core'; 3 | import { FormsModule } from '@angular/forms'; 4 | import { NzInputModule } from 'ng-zorro-antd/input'; 5 | import { NzTagModule } from 'ng-zorro-antd/tag'; 6 | import { presetColors } from 'ng-zorro-antd/core/color'; 7 | import { NzSelectModule } from 'ng-zorro-antd/select'; 8 | import { NzButtonModule } from 'ng-zorro-antd/button'; 9 | import { NzPaginationModule } from 'ng-zorro-antd/pagination'; 10 | import { ProjectService } from '../../services/project.service'; 11 | 12 | @Component({ 13 | selector: 'app-project-manager', 14 | standalone: true, 15 | imports: [ 16 | NzInputModule, 17 | FormsModule, 18 | CommonModule, 19 | NzTagModule, 20 | NzSelectModule, 21 | NzButtonModule, 22 | NzPaginationModule, 23 | ], 24 | templateUrl: './project-manager.component.html', 25 | styleUrl: './project-manager.component.scss', 26 | }) 27 | export class ProjectManagerComponent implements OnInit { 28 | keywords = ''; 29 | platform = ''; 30 | libraries = ''; 31 | 32 | version = '1.0.1'; 33 | readonly presetColors = presetColors; 34 | 35 | page: number = 1; 36 | perPage = 10; 37 | total: number = 0; 38 | dataList: any[] = []; 39 | 40 | constructor(private projectService: ProjectService) {} 41 | 42 | ngOnInit(): void { 43 | this.getSearch(); 44 | } 45 | 46 | getSearch() { 47 | this.projectService 48 | .search({ 49 | text: this.keywords, 50 | size: this.perPage, 51 | from: this.platform, 52 | quality: 0.65, 53 | popularity: 0.98, 54 | maintenance: 0.5, 55 | }) 56 | .subscribe((res) => { 57 | this.dataList = (res as any)?.objects; 58 | this.total = (res as any)?.total; 59 | }); 60 | } 61 | 62 | getList() { 63 | this.projectService.list({}).subscribe((res) => {}); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/app/components/sub-window/readme.md: -------------------------------------------------------------------------------- 1 | # sub window 2 | 这是用于electron打开的子窗口最外层的组件。 -------------------------------------------------------------------------------- /src/app/components/sub-window/sub-window.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
{{ title }}
4 |
5 | @for (btn of winBtns; track btn) { 6 | @if (btn === "go-main") { 7 |
8 | 9 |
10 | } 11 | @if (btn === "minimize") { 12 |
13 | 14 |
15 | } 16 | @if (btn === "maximize") { 17 |
18 | 19 |
20 | } 21 | @if (btn === "close") { 22 |
23 | 24 |
25 | } 26 | } 27 |
28 |
29 |
30 | 31 |
32 |
33 | -------------------------------------------------------------------------------- /src/app/components/sub-window/sub-window.component.scss: -------------------------------------------------------------------------------- 1 | .sub-window-box { 2 | border-radius: 5px; 3 | overflow: hidden; 4 | } 5 | 6 | .header { 7 | display: flex; 8 | align-items: center; 9 | height: 35px; 10 | border-bottom: 1px solid #222427; 11 | background: #2b2d30; 12 | } 13 | 14 | .title { 15 | height: 100%; 16 | padding-left: 10px; 17 | display: flex; 18 | align-items: center; 19 | flex-grow: 1; 20 | -webkit-app-region: drag; 21 | } 22 | 23 | .win-btns { 24 | display: flex; 25 | align-items: center; 26 | font-size: 15px; 27 | 28 | .btn { 29 | font-size: 16px; 30 | width: 33px; 31 | height: 33px; 32 | margin-right: 0; 33 | 34 | &:hover { 35 | background: #3a3c3f; 36 | } 37 | } 38 | 39 | .minimize { 40 | font-size: 18px; 41 | } 42 | 43 | .go-main{ 44 | // border-right: 1px solid #333; 45 | margin-right: 10px; 46 | font-size: 17px; 47 | background: transparent !important; 48 | &:hover { 49 | color: rgb(75, 151, 221); 50 | } 51 | } 52 | 53 | .close { 54 | font-size: 19px; 55 | 56 | &:hover { 57 | color: rgb(145, 0, 0); 58 | } 59 | } 60 | } 61 | 62 | .content{ 63 | height: calc(100vh - 38px); 64 | background: #323437; 65 | } -------------------------------------------------------------------------------- /src/app/components/sub-window/sub-window.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | 4 | @Component({ 5 | selector: 'app-sub-window', 6 | imports: [], 7 | templateUrl: './sub-window.component.html', 8 | styleUrl: './sub-window.component.scss', 9 | }) 10 | export class SubWindowComponent { 11 | @Input() title = 'sub-window'; 12 | @Input() winBtns = ['gomain', 'minimize', 'maximize', 'close']; 13 | 14 | currentUrl; 15 | 16 | constructor( 17 | private router: Router, 18 | ) {} 19 | 20 | ngOnInit(): void { 21 | this.currentUrl = this.router.url; 22 | } 23 | 24 | goMain() { 25 | window['iWindow'].goMain(this.currentUrl); 26 | } 27 | 28 | minimize() { 29 | window['iWindow'].minimize(); 30 | } 31 | 32 | maximize() { 33 | window['iWindow'].maximize(); 34 | } 35 | 36 | close() { 37 | window['iWindow'].close(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/app/components/tool-container/tool-container.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | {{ title }} 5 |
6 |
7 | 16 | 17 | @if (path) { 18 |
19 | 20 |
21 | } 22 |
23 | 24 |
25 |
26 |
27 |
28 | 29 |
30 |
31 | -------------------------------------------------------------------------------- /src/app/components/tool-container/tool-container.component.scss: -------------------------------------------------------------------------------- 1 | .tool-container { 2 | background: #2b2d30; 3 | height: 100%; 4 | } 5 | 6 | .header { 7 | display: flex; 8 | justify-content: space-between; 9 | height: 30px; 10 | 11 | .title { 12 | padding-left: 10px; 13 | } 14 | 15 | .tool-btns { 16 | display: flex; 17 | 18 | ::ng-deep { 19 | .btn { 20 | width: 30px; 21 | height: 30px; 22 | 23 | &:hover { 24 | background: #3a3c3f; 25 | } 26 | } 27 | } 28 | 29 | 30 | .fa-arrow-up-right-from-square { 31 | font-size: 12px; 32 | } 33 | } 34 | } 35 | 36 | .content { 37 | height: calc(100% - 30px); 38 | background: #323437; 39 | } -------------------------------------------------------------------------------- /src/app/components/tool-container/tool-container.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Input, Output } from '@angular/core'; 2 | import { NzTabsModule } from 'ng-zorro-antd/tabs'; 3 | import { UiService, WindowOpts } from '../../services/ui.service'; 4 | 5 | @Component({ 6 | selector: 'app-tool-container', 7 | imports: [NzTabsModule], 8 | templateUrl: './tool-container.component.html', 9 | styleUrl: './tool-container.component.scss', 10 | }) 11 | export class ToolContainerComponent { 12 | @Input() title: string = '工具'; 13 | @Input() path: string; 14 | 15 | @Output() closeEvent = new EventEmitter(); 16 | 17 | @Output() copyEvent = new EventEmitter(); 18 | 19 | @Output() trashEvent = new EventEmitter(); 20 | 21 | @Output() refreshEvent = new EventEmitter(); 22 | 23 | constructor(private uiService: UiService) {} 24 | 25 | close() { 26 | this.closeEvent.emit(); 27 | } 28 | 29 | trash() { 30 | this.trashEvent.emit(); 31 | } 32 | 33 | refresh() { 34 | this.refreshEvent.emit(); 35 | } 36 | 37 | copy() { 38 | this.copyEvent.emit(); 39 | } 40 | 41 | openWindow() { 42 | this.uiService.openWindow({ 43 | path: this.path, 44 | title: this.title, 45 | // alwaysOnTop: true, 46 | width: 1200, 47 | height: 800, 48 | }); 49 | this.close(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/app/configs/api.config.ts: -------------------------------------------------------------------------------- 1 | // const SERVER_URL: string = "http://localhost:8000"; 2 | const SERVER_URL: string = ''; 3 | const SERVER_API_URL: string = 'http://114.132.150.141:8000'; 4 | 5 | export const API = { 6 | projectList: `${SERVER_URL}/-/verdaccio/data/packages`, 7 | projectSearch: `${SERVER_URL}/-/v1/search`, 8 | // ai 9 | startSession: `${SERVER_API_URL}/api/v1/start_session`, 10 | closeSession: `${SERVER_API_URL}/api/v1/close_session`, 11 | streamConnect: `${SERVER_API_URL}/api/v1/stream`, 12 | sendMessage: `${SERVER_API_URL}/api/v1/send_message`, 13 | getHistory: `${SERVER_API_URL}/api/v1/conversation_history`, 14 | }; 15 | -------------------------------------------------------------------------------- /src/app/configs/app.config.ts: -------------------------------------------------------------------------------- 1 | export const APP = { 2 | name: "B4A", 3 | website: "https://aily.pro", // 官网地址 4 | updateUrl: "https://aily.pro/update.json", // 软件升级地址 5 | boardUrl: "https://b4a.clz.me/boards.json", // 开发板地址 6 | libraryUrl: "https://b4a.clz.me/libraries.json", // 库地址 7 | exampleUrl: "https://b4a.clz.me/examples.json", // 示例程序地址 8 | } -------------------------------------------------------------------------------- /src/app/desktop/desktop.component.html: -------------------------------------------------------------------------------- 1 |

desktop works!

2 | -------------------------------------------------------------------------------- /src/app/desktop/desktop.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/src/app/desktop/desktop.component.scss -------------------------------------------------------------------------------- /src/app/desktop/desktop.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-desktop', 5 | imports: [], 6 | templateUrl: './desktop.component.html', 7 | styleUrl: './desktop.component.scss' 8 | }) 9 | export class DesktopComponent { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/app/editors/blockly-editor/blockly-editor.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
{{'LIB_MANAGER.TITLE'| translate}}
4 | @if(devmode){ 5 | 8 | 11 | } 12 | 13 |
14 | @if (showProjectManager) { 15 | 16 | } 17 | 18 | @if (showLibEditor) { 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/app/editors/blockly-editor/blockly-editor.component.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | position: relative; 3 | } 4 | 5 | blockly-main { 6 | ::ng-deep { 7 | .blocklyBox { 8 | width: 100%; 9 | height: 100%; 10 | } 11 | } 12 | } 13 | 14 | .project-mangager-btn { 15 | position: absolute; 16 | bottom: 0; 17 | left: 0; 18 | height: 40px; 19 | width: 179px; 20 | cursor: pointer; 21 | background-color: #323232; 22 | z-index: 99; 23 | 24 | .mangager { 25 | width: 100%; 26 | height: 100%; 27 | &:hover { 28 | color: #2a74ff; 29 | } 30 | } 31 | 32 | .reload { 33 | position: absolute; 34 | right: 0; 35 | height: 40px; 36 | width: 40px; 37 | } 38 | 39 | .edit{ 40 | position: absolute; 41 | left: 0; 42 | height: 40px; 43 | width: 40px; 44 | } 45 | 46 | } 47 | 48 | // .test { 49 | // position: fixed; 50 | // top: 300px; 51 | // right: 100px; 52 | // display: flex; 53 | 54 | // >div { 55 | // cursor: pointer; 56 | // width: 50px; 57 | // height: 30px; 58 | // background: red; 59 | // color: white; 60 | // margin-right: 2px; 61 | // display: flex; 62 | // justify-content: center; 63 | // align-items: center; 64 | // } 65 | // } -------------------------------------------------------------------------------- /src/app/editors/blockly-editor/readme.md: -------------------------------------------------------------------------------- 1 | # blockly编辑器 -------------------------------------------------------------------------------- /src/app/editors/code-editor/code-editor.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 5 |
6 |
7 | @if (openedFiles.length > 0) { 8 | 10 | @for (file of openedFiles; track file.path; let i = $index) { 11 | 12 | 13 | 14 | {{ file.title }} 15 | 16 | 17 | 18 | } 19 | 20 | 22 | } @else { 23 |
24 |
这是代码编辑器,是一个测试功能,还不能正常使用
25 |
进入到这个页面是因为你打开了一个不存在.abi文件的目录
26 |
你可以通过界面左上角的菜单重新打开正确的项目
27 |
奈何col 2025.3.20
28 |
29 | } 30 |
31 |
32 | 33 | -------------------------------------------------------------------------------- /src/app/editors/code-editor/file.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { NzTreeNodeOptions } from 'ng-zorro-antd/tree'; 3 | 4 | @Injectable({ 5 | providedIn: 'root' 6 | }) 7 | export class FileService { 8 | 9 | currentPath; 10 | 11 | constructor() { } 12 | 13 | 14 | readDir(path: string): NzTreeNodeOptions[] { 15 | let entries = window['fs'].readDirSync(path); 16 | console.log('entries', entries); 17 | 18 | let result = []; 19 | let dirs = []; 20 | let files = []; 21 | for (const entry of entries) { 22 | let path = entry.path + '\\' + entry.name; 23 | let isDir = window['path'].isDir(path) 24 | let item: NzTreeNodeOptions = { 25 | title: entry.name, 26 | key: path, 27 | path, 28 | isLeaf: !isDir, 29 | expanded: false, 30 | selectable: true 31 | } 32 | if (isDir) { 33 | dirs.push(item); 34 | } else { 35 | files.push(item); 36 | } 37 | } 38 | result = dirs.concat(files); 39 | return result; 40 | } 41 | 42 | readFile(path: string): string { 43 | this.currentPath = path; 44 | // 读取文件内容 45 | return ''; 46 | 47 | } 48 | } -------------------------------------------------------------------------------- /src/app/func/func.ts: -------------------------------------------------------------------------------- 1 | /* 2 | 根据日期生成字符串,如 5月1日,生成为“may01”,字符串最后再加一个a-z的随机字符, 如“may01x” 3 | */ 4 | export function generateDateString(date: Date = new Date()): string { 5 | const monthAbbr = ["jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"]; 6 | // 获取月份(getMonth 返回值为 0-11) 7 | const month = monthAbbr[date.getMonth()]; 8 | // 获取日期并格式化为两位数字 9 | const day = date.getDate().toString().padStart(2, '0'); 10 | // 返回形如 "may01x" 的字符串 11 | return `${month}${day}`; 12 | } -------------------------------------------------------------------------------- /src/app/main-window/components/act-btn/act-btn.component.html: -------------------------------------------------------------------------------- 1 |
2 | @switch(state){ 3 | @case('default'){ 4 |
5 |
6 | 7 |
8 |
9 | } 10 | @case('doing'){ 11 |
12 |
13 | 14 |
15 |
16 | 17 |
18 |
19 | } 20 | @case('done'){ 21 |
22 | @if (toWink) { 23 | 24 | }@else{ 25 | 26 | } 27 |
28 | } 29 | @case('error'){ 30 |
31 |
32 | 33 |
34 |
35 | } 36 | @case('warn'){ 37 |
38 |
39 | 40 |
41 |
42 | } 43 | } 44 |
-------------------------------------------------------------------------------- /src/app/main-window/components/act-btn/act-btn.component.scss: -------------------------------------------------------------------------------- 1 | .act-btn { 2 | width: 38px; 3 | height: 38px; 4 | transform: all 0.3s; 5 | position: relative; 6 | 7 | .default{ 8 | cursor: pointer; 9 | } 10 | 11 | .lloading{ 12 | cursor: wait; 13 | } 14 | 15 | .qq { 16 | width: 22px; 17 | height: 22px; 18 | border-radius: 50%; 19 | border: 2px solid; 20 | } 21 | 22 | .qq2 { 23 | // border-color: rgba(255, 0, 0, 0.6) !important; 24 | border-color: rgba(255, 255, 255, 0.2); 25 | position: absolute; 26 | } 27 | 28 | >div { 29 | width: 100%; 30 | height: 100%; 31 | 32 | i { 33 | font-size: 19px; 34 | } 35 | 36 | &.lloading { 37 | i.fa-spinner-third { 38 | font-size: 22px; 39 | } 40 | } 41 | 42 | // 编译 43 | .fa-check { 44 | font-size: 14px; 45 | margin-top: 2px; 46 | color: #006adc; 47 | } 48 | 49 | // 运行 50 | .fa-play { 51 | font-size: 14px; 52 | margin-left: 2px; 53 | margin-top: 0.5px; 54 | color: #009600; 55 | } 56 | 57 | // 调试 58 | .fa-rocket { 59 | font-size: 13px; 60 | margin-top: 1px; 61 | margin-left: -2px; 62 | color: #f18800; 63 | } 64 | 65 | .fa-xmark { 66 | color: red; 67 | font-size: 14px; 68 | margin-left: -1px; 69 | margin-top: 1px; 70 | } 71 | 72 | .fa-face-smile { 73 | font-size: 22px; 74 | } 75 | 76 | .fa-face-smile-wink { 77 | font-size: 22px; 78 | } 79 | 80 | .fa-exclamation { 81 | color: #f0b506; 82 | font-size: 13px; 83 | } 84 | } 85 | } -------------------------------------------------------------------------------- /src/app/main-window/components/act-btn/act-btn.component.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from '@angular/common'; 2 | import { Component, EventEmitter, Input, Output, SimpleChanges } from '@angular/core'; 3 | import { FormsModule } from '@angular/forms'; 4 | 5 | @Component({ 6 | selector: 'app-act-btn', 7 | imports: [CommonModule, FormsModule], 8 | templateUrl: './act-btn.component.html', 9 | styleUrl: './act-btn.component.scss' 10 | }) 11 | export class ActBtnComponent { 12 | @Input() icon: string; 13 | @Input() color: string = '#FFF'; 14 | @Input() state: 'default' | 'doing' | 'done' | 'error' | 'warn' = 'default'; 15 | 16 | @Output() stateChange = new EventEmitter<'default' | 'doing' | 'done' | 'error' | 'warn'>(); 17 | 18 | disabled = false; 19 | 20 | onClick() { 21 | 22 | } 23 | 24 | constructor() { 25 | } 26 | 27 | ngOnInit() { 28 | 29 | } 30 | 31 | toWink = false; 32 | ngOnChanges(changes: SimpleChanges) { 33 | if (changes['state']) { 34 | if (this.state != 'doing' && this.state != 'default') { 35 | setTimeout(() => { 36 | this.stateChange.emit('default'); 37 | this.toWink = false; 38 | }, 6000); 39 | } 40 | if (this.state == 'done') { 41 | setTimeout(() => { 42 | this.toWink = true; 43 | setTimeout(() => { 44 | this.toWink = false; 45 | }, 1000); 46 | }, 1000); 47 | } 48 | } 49 | } 50 | 51 | 52 | // test() { 53 | // const states: ('default' | 'loading' | 'success' | 'error' | 'warn')[] = 54 | // ['default', 'loading', 'success', 'error', 'warn']; 55 | // let currentIndex = 0; 56 | 57 | // console.log('开始状态循环测试'); 58 | 59 | // // 先立即设置一次,然后每10秒改变一次状态 60 | // this.state = states[currentIndex]; 61 | // console.log(`状态已设置为: ${this.state}`); 62 | 63 | // setInterval(() => { 64 | // currentIndex = (currentIndex + 1) % states.length; 65 | // this.state = states[currentIndex]; 66 | // console.log(`状态已更改为: ${this.state}`); 67 | // }, 10000); 68 | // } 69 | } 70 | -------------------------------------------------------------------------------- /src/app/main-window/components/footer/footer.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/main-window/components/footer/footer.component.scss: -------------------------------------------------------------------------------- 1 | $footer-height: 30px; 2 | $footer-inner-height: 28px; 3 | 4 | .footer-box { 5 | display: flex; 6 | justify-content: flex-start; 7 | align-items: center; 8 | height: $footer-height; 9 | background: #2b2d30; 10 | border-top: 2px solid #222427; 11 | position: relative; 12 | } 13 | 14 | .btn { 15 | width: $footer-inner-height; 16 | height: $footer-inner-height; 17 | 18 | &:hover { 19 | background: #3b3d40; 20 | } 21 | } 22 | 23 | .state { 24 | position: absolute; 25 | top: 0; 26 | right: 10px; 27 | height: 26px; 28 | display: flex; 29 | align-items: center; 30 | 31 | .icon { 32 | width: 26px; 33 | height: 26px; 34 | } 35 | 36 | .fa-circle-xmark{ 37 | color: rgb(210, 0, 0); 38 | } 39 | 40 | .fa-triangle-exclamation{ 41 | color: rgb(255, 136, 0); 42 | } 43 | 44 | .fa-circle-check{ 45 | color: rgb(0, 200, 0); 46 | } 47 | } -------------------------------------------------------------------------------- /src/app/main-window/components/footer/footer.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ChangeDetectorRef } from '@angular/core'; 2 | import { ActionState, UiService } from '../../../services/ui.service'; 3 | 4 | @Component({ 5 | selector: 'app-footer', 6 | imports: [], 7 | templateUrl: './footer.component.html', 8 | styleUrl: './footer.component.scss' 9 | }) 10 | export class FooterComponent { 11 | actionData: ActionState | null; 12 | timer; 13 | 14 | constructor( 15 | private uiService: UiService, 16 | private cd: ChangeDetectorRef 17 | ) { 18 | this.uiService.stateSubject.subscribe((state: ActionState) => { 19 | this.changeState(state); 20 | }); 21 | // 其他窗口通过electron侧改变主窗口状态 22 | window['ipcRenderer'].on('state-update', (event, state: ActionState) => { 23 | this.changeState(state); 24 | }); 25 | } 26 | 27 | changeState(e: ActionState) { 28 | this.actionData = e; 29 | this.cd.detectChanges(); 30 | // 默认超时设置10秒, warn 和 error 不超时 31 | if (!this.actionData.timeout && this.actionData.state === 'loading' || this.actionData.state === 'done') { 32 | this.actionData.timeout = 10000; 33 | } 34 | if (this.actionData.timeout) { 35 | clearTimeout(this.timer); 36 | this.timer = setTimeout(() => { 37 | this.actionData = null; 38 | this.cd.detectChanges(); 39 | }, this.actionData.timeout); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/app/main-window/components/serial-dialog/serial-dialog.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
请先选择设备端口
4 |
5 |
6 | 7 |
8 |
9 |
10 |
11 |
点击左上的设备名选择设备端口
12 |
13 | 14 |
15 | 18 |
19 |
-------------------------------------------------------------------------------- /src/app/main-window/components/serial-dialog/serial-dialog.component.scss: -------------------------------------------------------------------------------- 1 | @use "../update-dialog/update-dialog.component.scss"; -------------------------------------------------------------------------------- /src/app/main-window/components/serial-dialog/serial-dialog.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, inject } from '@angular/core'; 2 | import { NzModalRef } from 'ng-zorro-antd/modal'; 3 | import { CommonModule } from '@angular/common'; 4 | import { NzButtonModule } from 'ng-zorro-antd/button'; 5 | 6 | @Component({ 7 | selector: 'app-serial-dialog', 8 | imports: [NzButtonModule, CommonModule], 9 | templateUrl: './serial-dialog.component.html', 10 | styleUrl: './serial-dialog.component.scss' 11 | }) 12 | export class SerialDialogComponent { 13 | 14 | readonly modal = inject(NzModalRef); 15 | 16 | constructor( 17 | ) { 18 | } 19 | 20 | ngOnInit(): void { 21 | } 22 | 23 | cancel(): void { 24 | this.modal.close({ result: 'cancel' }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/app/main-window/components/unsave-dialog/unsave-dialog.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
{{title}}
4 |
5 |
6 | 7 |
8 |
9 |
10 |
11 |
{{text}}
12 | 17 |
18 |
-------------------------------------------------------------------------------- /src/app/main-window/components/unsave-dialog/unsave-dialog.component.scss: -------------------------------------------------------------------------------- 1 | @use "../update-dialog/update-dialog.component.scss"; -------------------------------------------------------------------------------- /src/app/main-window/components/unsave-dialog/unsave-dialog.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, inject } from '@angular/core'; 2 | import { NZ_MODAL_DATA, NzModalRef } from 'ng-zorro-antd/modal'; 3 | import { CommonModule } from '@angular/common'; 4 | import { NzButtonModule } from 'ng-zorro-antd/button'; 5 | 6 | @Component({ 7 | selector: 'app-unsave-dialog', 8 | imports: [NzButtonModule, CommonModule], 9 | templateUrl: './unsave-dialog.component.html', 10 | styleUrl: './unsave-dialog.component.scss' 11 | }) 12 | export class UnsaveDialogComponent { 13 | 14 | readonly modal = inject(NzModalRef); 15 | readonly data: { title: string; text: string } = inject(NZ_MODAL_DATA); 16 | 17 | get title(): string { 18 | return this.data.title; 19 | } 20 | 21 | get text(): string { 22 | return this.data.text; 23 | } 24 | 25 | constructor( 26 | ) { 27 | } 28 | 29 | ngOnInit(): void { 30 | } 31 | 32 | cancel(): void { 33 | this.modal.close({ result: 'cancel' }); 34 | } 35 | 36 | continueWithoutSave(): void { 37 | this.modal.close({ result: 'continue' }); 38 | } 39 | 40 | saveAndContinue(): void { 41 | this.modal.close({ result: 'save' }); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/app/main-window/components/update-dialog/update-dialog.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
{{title}}
4 |
5 |
6 | 7 |
8 |
9 |
10 |
11 | @if(mode === 'available'){ 12 |
13 | } 14 | 15 | @if(mode === 'downloading' || mode === 'downloaded'){ 16 |
17 | 19 |
20 | } 21 | 22 | @if (mode === 'error') { 23 |
24 | 25 | 下载失败 26 |
27 | } 28 | 29 | 50 |
51 |
-------------------------------------------------------------------------------- /src/app/main-window/components/update-dialog/update-dialog.component.scss: -------------------------------------------------------------------------------- 1 | .prompt-box { 2 | background: #2b2d30; 3 | border-radius: 5px; 4 | flex-direction: column; 5 | width: 100%; 6 | overflow: hidden; 7 | } 8 | 9 | .header { 10 | display: flex; 11 | align-items: center; 12 | height: 34px; 13 | border-bottom: 1px solid #222427; 14 | background: #2b2d30; 15 | } 16 | 17 | .title { 18 | height: 100%; 19 | padding-left: 10px; 20 | display: flex; 21 | align-items: center; 22 | flex-grow: 1; 23 | } 24 | 25 | .win-btns { 26 | display: flex; 27 | align-items: center; 28 | font-size: 15px; 29 | 30 | .btn { 31 | font-size: 16px; 32 | width: 33px; 33 | height: 33px; 34 | margin-right: 0; 35 | 36 | &:hover { 37 | background: #3a3c3f; 38 | } 39 | } 40 | 41 | .minimize { 42 | font-size: 18px; 43 | } 44 | 45 | .go-main { 46 | // border-right: 1px solid #333; 47 | margin-right: 10px; 48 | font-size: 17px; 49 | background: transparent !important; 50 | 51 | &:hover { 52 | color: rgb(75, 151, 221); 53 | } 54 | } 55 | 56 | .close { 57 | font-size: 19px; 58 | 59 | &:hover { 60 | color: rgb(145, 0, 0); 61 | } 62 | } 63 | } 64 | 65 | .content { 66 | padding: 10px; 67 | background: #323437; 68 | 69 | input { 70 | background: #3a3c3f; 71 | } 72 | 73 | .text { 74 | min-height: 32px; 75 | line-height: 25px; 76 | } 77 | 78 | .progress-container { 79 | height: 32px; 80 | // padding-right: 10px; 81 | } 82 | 83 | } 84 | 85 | .footer { 86 | margin-top: 10px; 87 | text-align: right; 88 | 89 | button { 90 | margin-left: 10px; 91 | } 92 | } -------------------------------------------------------------------------------- /src/app/main-window/main-window.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 | 6 |
7 | 8 | 9 | 10 |
11 | @if (showBbox) { 12 |
14 | 15 |
16 |
17 |
18 | 19 |
20 |
21 | } 22 |
23 | @if (showRbox) { 24 | 26 | 27 |
28 |
29 |
30 | 31 | @switch (topTool) { 32 | @case ("code-viewer") { 33 | 34 | } 35 | @case ("serial-monitor") { 36 | 37 | } 38 | @case ("aily-chat") { 39 | 40 | } 41 | @case ("simulator") { 42 | 43 | } 44 | @case ("app-store") { 45 | 46 | } 47 | } 48 | 49 |
50 |
51 | } 52 |
53 |
54 | 55 | 56 |
-------------------------------------------------------------------------------- /src/app/main-window/main-window.component.scss: -------------------------------------------------------------------------------- 1 | .main-window-box { 2 | height: 100vh; 3 | width: 100vw; 4 | border-radius: 5px; 5 | overflow: hidden; 6 | 7 | .content-box { 8 | height: calc(100vh - 70px); 9 | width: 100%; 10 | position: relative; 11 | } 12 | } 13 | 14 | app-blockly-editor { 15 | width: 100%; 16 | height: 100%; 17 | // display: block; 18 | } 19 | 20 | app-guide { 21 | width: 100%; 22 | height: 100%; 23 | // display: block; 24 | } 25 | 26 | .middle-box { 27 | width: 100%; 28 | height: 100%; 29 | position: relative; 30 | min-height: 0; 31 | 32 | ngx-simplebar { 33 | height: 100%; 34 | width: 100%; 35 | 36 | ::ng-deep { 37 | .simplebar-content-wrapper { 38 | height: 100%; 39 | } 40 | 41 | .simplebar-content { 42 | height: 100%; 43 | } 44 | } 45 | } 46 | } 47 | 48 | .bottom-box { 49 | width: 100%; 50 | height: 100%; 51 | position: relative; 52 | } 53 | 54 | .right-box { 55 | width: 100%; 56 | height: 100%; 57 | position: relative; 58 | } 59 | 60 | nz-layout { 61 | height: 100%; 62 | 63 | nz-content { 64 | height: 100%; 65 | } 66 | 67 | .editor-box { 68 | height: 100%; 69 | width: 100%; 70 | } 71 | } 72 | 73 | nz-sider.nz-resizable-resizing { 74 | transition: none; 75 | } 76 | 77 | nz-content { 78 | display: flex; 79 | flex-direction: column; 80 | } 81 | 82 | nz-content>div { 83 | width: 100%; 84 | } 85 | 86 | nz-content .resizable-box { 87 | flex: none; 88 | } 89 | 90 | nz-content, 91 | nz-header, 92 | ::ng-deep nz-sider>.ant-layout-sider-children { 93 | display: flex; 94 | align-items: center; 95 | justify-content: center; 96 | } 97 | 98 | .sider-resize-line { 99 | height: 100%; 100 | width: 5px; 101 | border-right: 2px solid #222427; 102 | } 103 | 104 | .content-resize-line { 105 | width: 100%; 106 | height: 5px; 107 | border-bottom: 2px solid #222427; 108 | } -------------------------------------------------------------------------------- /src/app/pages/lib-editor/block-item/block-item.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | ⚠️ 5 | {{ error }} 6 |
7 |
-------------------------------------------------------------------------------- /src/app/pages/lib-editor/block-item/block-item.component.scss: -------------------------------------------------------------------------------- 1 | .block-item-container { 2 | width: 100%; 3 | 4 | .blockPreview { 5 | height: 200px; 6 | padding-left: 10px; 7 | width: 100%; 8 | position: relative; 9 | 10 | ::ng-deep { 11 | .blocklyMainBackground { 12 | stroke: none; 13 | } 14 | } 15 | } 16 | 17 | .error-message { 18 | display: flex; 19 | align-items: center; 20 | gap: 8px; 21 | margin-top: 4px; 22 | padding: 8px 12px; 23 | background-color: #fff5f5; 24 | border: 1px solid #ffcccc; 25 | border-radius: 4px; 26 | color: #d63384; 27 | font-size: 12px; 28 | 29 | .error-icon { 30 | font-size: 14px; 31 | } 32 | 33 | span { 34 | flex: 1; 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /src/app/pages/lib-editor/block-test/block-test.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | ⚠️ 5 | {{ error }} 6 |
7 |
-------------------------------------------------------------------------------- /src/app/pages/lib-editor/block-test/block-test.component.scss: -------------------------------------------------------------------------------- 1 | .block-item-container { 2 | width: 100%; 3 | height: 100%; 4 | display: flex; 5 | flex-direction: column; 6 | 7 | .blockPreview { 8 | height: 100%; 9 | padding-left: 10px; 10 | width: 100%; 11 | position: relative; 12 | flex: 1; 13 | min-height: 0; 14 | 15 | ::ng-deep { 16 | .blocklyMainBackground { 17 | stroke: none; 18 | } 19 | 20 | // 确保 Blockly 工作区填充满容器 21 | .injectionDiv { 22 | width: 100% !important; 23 | height: 100% !important; 24 | } 25 | 26 | .blocklyDiv { 27 | width: 100% !important; 28 | height: 100% !important; 29 | } 30 | } 31 | } 32 | 33 | .error-message { 34 | display: flex; 35 | align-items: center; 36 | gap: 8px; 37 | margin-top: 4px; 38 | padding: 8px 12px; 39 | background-color: #fff5f5; 40 | border: 1px solid #ffcccc; 41 | border-radius: 4px; 42 | color: #d63384; 43 | font-size: 12px; 44 | flex-shrink: 0; 45 | 46 | .error-icon { 47 | font-size: 14px; 48 | } 49 | 50 | span { 51 | flex: 1; 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /src/app/pages/lib-editor/block-visual-editor/block-visual-editor.component.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/src/app/pages/lib-editor/block-visual-editor/block-visual-editor.component.ts -------------------------------------------------------------------------------- /src/app/pages/lib-editor/lib-content/lib-content.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 | @for(block of blocks; track block.type) { 6 |
7 | 8 | 9 |
10 | } 11 |
12 |
13 |
14 |
15 | 16 |
17 |
18 |
19 |
20 | 21 |
22 |
23 | 24 |
25 |
26 | 28 | @for (file of openedFiles; track $index) { 29 | 30 | 31 | 32 | {{ file.title }} 33 | 34 | 35 | 36 | } 37 | 38 | @if(openedFiles.length > 0) { 39 | 40 | } 41 |
42 |
-------------------------------------------------------------------------------- /src/app/pages/lib-editor/lib-editor.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |
6 |
7 | @if(!currentLib) { 8 | 选择要编辑的库 9 | }@else { 10 | {{currentLib?.name}} 11 | } 12 |
13 |
14 | 20 |
21 |
22 | 23 |
24 |
25 | 26 | 27 | @if (showLibList) { 28 | 30 | } -------------------------------------------------------------------------------- /src/app/pages/lib-editor/lib-editor.component.scss: -------------------------------------------------------------------------------- 1 | .lib-manager-box { 2 | position: absolute; 3 | background: #1f1f1f; 4 | width: 100%; 5 | height: 100%; 6 | top: 0; 7 | left: 0; 8 | z-index: 99; 9 | 10 | .header { 11 | height: 40px; 12 | display: flex; 13 | align-items: center; 14 | border-bottom: 1px solid #333; 15 | position: sticky; 16 | top: 0; 17 | z-index: 99; 18 | background: #222; 19 | 20 | .btn { 21 | height: 40px; 22 | width: 40px; 23 | font-size: 19px; 24 | flex-shrink: 0; 25 | } 26 | 27 | nz-input-group { 28 | width: 160px; 29 | flex-shrink: 0; 30 | margin: 0 15px 0 10px; 31 | } 32 | 33 | .lib-selector { 34 | min-width: 200px; 35 | cursor: pointer; 36 | height: calc(100% - 6px); 37 | border-radius: 5px; 38 | position: relative; 39 | color: #666; 40 | background: #151515; 41 | 42 | &:hover { 43 | background: #181818; 44 | } 45 | 46 | .down { 47 | position: absolute; 48 | right: 10px; 49 | color: #888; 50 | } 51 | } 52 | } 53 | 54 | .content { 55 | height: calc(100% - 40px); 56 | } 57 | } 58 | 59 | .libs { 60 | ::ng-deep { 61 | .name { 62 | flex-shrink: 0; 63 | flex-grow: 1; 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /src/app/pages/lib-editor/lib-editor.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | @Injectable({ 4 | providedIn: 'root' 5 | }) 6 | export class LibEditorService { 7 | 8 | constructor() { } 9 | } 10 | -------------------------------------------------------------------------------- /src/app/pages/lib-manager/components/compatible-dialog/compatible-dialog.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
{{ 'COMPATIBILITY_DIALOG.TITLE' | translate }}
4 |
5 |
6 | 7 |
8 |
9 |
10 |
11 |
12 |
{{ 'COMPATIBILITY_DIALOG.WARNING_MESSAGE' | translate }}
13 |
14 |
15 | {{ 'COMPATIBILITY_DIALOG.SUPPORTED_CORES' | translate }} 16 |
17 |
18 | @for(tag of libCompatibility; track $index) { 19 | {{tag}} 20 | } 21 |
22 |
23 |
{{ 'COMPATIBILITY_DIALOG.CURRENT_BOARD_CORE' | translate }}{{boardCore}}
24 |
25 | 29 |
30 |
-------------------------------------------------------------------------------- /src/app/pages/lib-manager/components/compatible-dialog/compatible-dialog.component.scss: -------------------------------------------------------------------------------- 1 | .prompt-box { 2 | background: #2b2d30; 3 | border-radius: 5px; 4 | flex-direction: column; 5 | width: 100%; 6 | overflow: hidden; 7 | } 8 | 9 | .header { 10 | display: flex; 11 | align-items: center; 12 | height: 34px; 13 | border-bottom: 1px solid #222427; 14 | background: #2b2d30; 15 | } 16 | 17 | .title { 18 | height: 100%; 19 | padding-left: 10px; 20 | display: flex; 21 | align-items: center; 22 | flex-grow: 1; 23 | 24 | i { 25 | color: #e05a00; 26 | margin-right: 5px; 27 | font-size: 16px; 28 | } 29 | } 30 | 31 | .win-btns { 32 | display: flex; 33 | align-items: center; 34 | font-size: 15px; 35 | 36 | .btn { 37 | font-size: 16px; 38 | width: 33px; 39 | height: 33px; 40 | margin-right: 0; 41 | 42 | &:hover { 43 | background: #3a3c3f; 44 | } 45 | } 46 | 47 | .minimize { 48 | font-size: 18px; 49 | } 50 | 51 | .go-main { 52 | // border-right: 1px solid #333; 53 | margin-right: 10px; 54 | font-size: 17px; 55 | background: transparent !important; 56 | 57 | &:hover { 58 | color: rgb(75, 151, 221); 59 | } 60 | } 61 | 62 | .close { 63 | font-size: 19px; 64 | 65 | &:hover { 66 | color: rgb(145, 0, 0); 67 | } 68 | } 69 | } 70 | 71 | .content { 72 | padding: 10px; 73 | background: #323437; 74 | 75 | input { 76 | background: #3a3c3f; 77 | } 78 | 79 | .text { 80 | min-height: 32px; 81 | } 82 | 83 | .t1 { 84 | margin-bottom: 10px; 85 | } 86 | 87 | .t2 { 88 | color: #afafaf; 89 | margin-bottom: 5px; 90 | line-height: 24px; 91 | display: flex; 92 | 93 | .t2-text { 94 | flex-shrink: 0; 95 | } 96 | 97 | &:last-child { 98 | margin-bottom: 0; 99 | } 100 | } 101 | } 102 | 103 | .footer { 104 | margin-top: 10px; 105 | text-align: right; 106 | 107 | button { 108 | margin-left: 10px; 109 | } 110 | } -------------------------------------------------------------------------------- /src/app/pages/lib-manager/components/compatible-dialog/compatible-dialog.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, inject } from '@angular/core'; 2 | import { NZ_MODAL_DATA, NzModalRef } from 'ng-zorro-antd/modal'; 3 | import { CommonModule } from '@angular/common'; 4 | import { NzButtonModule } from 'ng-zorro-antd/button'; 5 | import { NzTagModule } from 'ng-zorro-antd/tag'; 6 | import { TranslateModule } from '@ngx-translate/core'; 7 | 8 | @Component({ 9 | selector: 'app-compatible-dialog', 10 | imports: [NzButtonModule, NzTagModule, CommonModule, TranslateModule], 11 | templateUrl: './compatible-dialog.component.html', 12 | styleUrl: './compatible-dialog.component.scss' 13 | }) 14 | export class CompatibleDialogComponent { 15 | 16 | readonly modal = inject(NzModalRef); 17 | readonly data: { libCompatibility: string[]; boardCore: string } = inject(NZ_MODAL_DATA); 18 | 19 | get libCompatibility(): string[] { 20 | return this.data.libCompatibility; 21 | } 22 | 23 | get boardCore(): string { 24 | return this.data.boardCore; 25 | } 26 | 27 | constructor( 28 | ) { 29 | } 30 | 31 | ngOnInit(): void { 32 | } 33 | 34 | cancel(): void { 35 | this.modal.close({ result: 'cancel' }); 36 | } 37 | 38 | continue(): void { 39 | this.modal.close({ result: 'continue' }); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/app/pages/playground/data.ts: -------------------------------------------------------------------------------- 1 | export const SUBJECT_LIST = [ 2 | { 3 | "name": "Arduino开发入门", 4 | "description": "针对Arduino初学者的基础教程,包含Arduino IDE安装配置、基本电路连接、常用组件介绍(LED、按钮、传感器等)、基础编程语法、简单项目实践。适合零基础学习者快速掌握Arduino开发环境和基本技能。", 5 | "target_audience": "初学者,无编程经验者", 6 | "difficulty_level": "入门级" 7 | }, 8 | { 9 | "name": "Arduino创意项目", 10 | "description": "展示各种有趣且实用的Arduino创意项目,包括智能家居小装置、互动艺术装置、音乐与灯光控制、可穿戴设备等。每个项目提供详细的材料清单、电路图、代码和组装步骤,激发学习者创造力。", 11 | "target_audience": "具备基础Arduino知识的爱好者", 12 | "difficulty_level": "初级到中级" 13 | }, 14 | { 15 | "name": "Arduino机器人开发", 16 | "description": "专注于使用Arduino进行机器人设计与开发,涵盖电机控制、舵机应用、传感器集成、运动规划、简易AI算法实现等内容。包含多个渐进式机器人项目,如循线小车、避障机器人、机械臂等。", 17 | "target_audience": "对机器人和自动化感兴趣的学习者", 18 | "difficulty_level": "中级到高级" 19 | }, 20 | { 21 | "name": "Arduino物联网开发", 22 | "description": "探索Arduino在物联网领域的应用,内容包括WiFi/蓝牙模块使用、云平台连接、远程数据监控、手机App交互、MQTT协议应用等。通过实际项目讲解如何将Arduino设备接入互联网并实现智能控制与数据分析。", 23 | "target_audience": "想要学习IoT开发的Arduino使用者", 24 | "difficulty_level": "中级到高级" 25 | } 26 | ] 27 | 28 | export const ITEM_LIST = [ 29 | { 30 | "name": "LED闪烁(Blink)", 31 | "description": "学习如何控制一个LED灯的开关,这是Arduino的第一个入门项目。" 32 | }, 33 | { 34 | "name": "按钮控制LED", 35 | "description": "使用按钮控制LED的开关状态,学习数字输入的基础操作。" 36 | }, 37 | { 38 | "name": "PWM调光(AnalogWrite)", 39 | "description": "通过脉宽调制(PWM)技术调节LED的亮度,理解模拟输出的概念。" 40 | }, 41 | { 42 | "name": "读取模拟输入(AnalogRead)", 43 | "description": "从光敏电阻或电位器等模拟传感器读取数据,学习模拟输入读取。" 44 | }, 45 | { 46 | "name": "温度传感器读取", 47 | "description": "使用温度传感器(如LM35或DHT11)读取环境温度数据。" 48 | }, 49 | { 50 | "name": "超声波距离测量", 51 | "description": "使用HC-SR04超声波传感器测量距离,学习传感器的应用。" 52 | }, 53 | { 54 | "name": "控制直流电机", 55 | "description": "通过H桥模块或晶体管控制直流电机的转动方向和速度。" 56 | }, 57 | { 58 | "name": "蜂鸣器播放音调", 59 | "description": "通过蜂鸣器播放简单的音调或音乐,学习产生声音的基础。" 60 | }, 61 | { 62 | "name": "I2C LCD显示屏", 63 | "description": "使用I2C协议控制LCD显示屏,显示文字或数据。" 64 | }, 65 | { 66 | "name": "简单的红外遥控接收", 67 | "description": "使用红外接收模块读取遥控器的信号进行控制。" 68 | } 69 | ] -------------------------------------------------------------------------------- /src/app/pages/playground/playground.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |
6 | 7 | 9 | 10 | 11 | @if(keyword.length > 0){ 12 | 13 | } @else { 14 | 15 | } 16 | 17 |
18 | @for (tag of tagList; track tag) { 19 | {{ tag }} 20 | } 21 |
22 |
23 | 24 |
-------------------------------------------------------------------------------- /src/app/pages/playground/playground.component.scss: -------------------------------------------------------------------------------- 1 | @use "../lib-manager/lib-manager.component.scss"; 2 | -------------------------------------------------------------------------------- /src/app/pages/playground/playground.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Output } from '@angular/core'; 2 | import { FormsModule } from '@angular/forms'; 3 | import { TranslateModule, TranslateService } from '@ngx-translate/core'; 4 | import { NzButtonModule } from 'ng-zorro-antd/button'; 5 | import { NzInputModule } from 'ng-zorro-antd/input'; 6 | import { NzTagModule } from 'ng-zorro-antd/tag'; 7 | import { NzToolTipModule } from 'ng-zorro-antd/tooltip'; 8 | import { Location } from '@angular/common'; 9 | import { SUBJECT_LIST } from './data'; 10 | import { Router, RouterModule } from '@angular/router'; 11 | 12 | @Component({ 13 | selector: 'app-playground', 14 | imports: [ 15 | FormsModule, 16 | NzButtonModule, 17 | NzTagModule, 18 | NzInputModule, 19 | NzToolTipModule, 20 | TranslateModule, 21 | RouterModule 22 | ], 23 | templateUrl: './playground.component.html', 24 | styleUrl: './playground.component.scss' 25 | }) 26 | export class PlaygroundComponent { 27 | @Output() close = new EventEmitter(); 28 | tagList: string[] = []; 29 | // exampleList = [] 30 | 31 | constructor( 32 | private router: Router, 33 | private location: Location, 34 | private translate: TranslateService 35 | ) { 36 | 37 | } 38 | 39 | ngOnInit() { 40 | // 使用翻译初始化标签列表 41 | this.tagList = [ 42 | this.translate.instant('显示全部'), 43 | this.translate.instant('入门课程'), 44 | this.translate.instant('库示例'), 45 | this.translate.instant('精选项目'), 46 | this.translate.instant('物联网'), 47 | this.translate.instant('机器人'), 48 | ]; 49 | 50 | // this.exampleList = SUBJECT_LIST 51 | } 52 | 53 | keyword: string = ''; 54 | search(keyword = this.keyword) { 55 | if (keyword) { 56 | keyword = keyword.replace(/\s/g, '').toLowerCase(); 57 | this.router.navigate(['/main/playground/list'], { 58 | queryParams: { keyword } 59 | }); 60 | } else { 61 | 62 | } 63 | } 64 | 65 | back() { 66 | this.location.back(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/app/pages/playground/subject-item/subject-item.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
{{ exampleItem?.nickname }}
5 |
6 | 更新时间:2024/6/16 7 | {{ exampleItem?.author }} 8 | 9 |
10 |
11 |
12 |
13 |
14 | {{ exampleItem?.description }} 15 |
16 |
17 | @for( item of exampleItem?.examples;track $index) { 18 |
19 |
{{item?.nickname}}
20 |
{{item?.description}}
21 | 22 | 23 | 26 | 29 | 30 |
31 | } 32 |
33 |
34 |
35 |
36 | 44 |
45 |
46 |
47 |
48 |
49 |
50 |
-------------------------------------------------------------------------------- /src/app/pages/playground/subject-list/subject-list.component.html: -------------------------------------------------------------------------------- 1 |
2 | @for( subject of subjectList;track $index) { 3 |
4 |
5 | 6 |
7 |
8 |
{{ subject.nickname }}
9 |
{{ subject.description }}
10 |
11 | 24 |
25 | } 26 |
-------------------------------------------------------------------------------- /src/app/pages/playground/subject-list/subject-list.component.scss: -------------------------------------------------------------------------------- 1 | .example-list { 2 | display: flex; 3 | flex-wrap: wrap; 4 | gap: 15px; 5 | width: 100%; 6 | padding: 15px; 7 | 8 | .example-item { 9 | width: 280px; 10 | height: 280px; 11 | background: #2b2b2b; 12 | border-radius: 5px; 13 | padding: 10px; 14 | position: relative; 15 | display: flex; 16 | flex-direction: column; 17 | overflow: hidden; 18 | cursor: pointer; 19 | 20 | &:hover{ 21 | background: #303a4d; 22 | } 23 | 24 | .img-box { 25 | border-radius: 5px; 26 | position: relative; 27 | width: 260px; 28 | height: 130px; 29 | overflow: hidden; 30 | 31 | img { 32 | width: 260px; 33 | height: 130px; 34 | } 35 | } 36 | 37 | .name { 38 | font-size: 16px; 39 | font-weight: 600; 40 | color: #fff; 41 | margin: 7px 0 7px 0; 42 | } 43 | 44 | .text { 45 | font-size: 14px; 46 | color: #ccc; 47 | height: 62px; 48 | white-space: normal; 49 | overflow: hidden; 50 | line-clamp: 3; 51 | display: -webkit-box; 52 | -webkit-box-orient: vertical; 53 | -webkit-line-clamp: 3; 54 | } 55 | 56 | .footer { 57 | display: flex; 58 | justify-content: space-between; 59 | align-items: center; 60 | margin-top: 10px; 61 | font-size: 12px; 62 | color: #aaa; 63 | 64 | .author { 65 | display: flex; 66 | align-items: center; 67 | color: rgb(79, 123, 228); 68 | } 69 | 70 | .state { 71 | display: flex; 72 | 73 | >div { 74 | width: 60px; 75 | } 76 | 77 | i { 78 | font-size: 16px; 79 | color: #aaa; 80 | } 81 | } 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /src/app/services/electron.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | @Injectable({ 4 | providedIn: 'root', 5 | }) 6 | export class ElectronService { 7 | isElectron = false; 8 | electron: any = window['electronAPI']; 9 | 10 | constructor() { } 11 | 12 | async init() { 13 | if (this.electron && typeof this.electron.versions() == 'object') { 14 | console.log('Running in electron', this.electron.versions()); 15 | this.isElectron = true; 16 | // 在这里把 相关nodejs内容 挂载到 window 上 17 | // 调用前先判断isElectron 18 | for (let key in this.electron) { 19 | console.log('load ' + key); 20 | window[key] = this.electron[key]; 21 | } 22 | } else { 23 | console.log('Running in browser'); 24 | } 25 | } 26 | 27 | /** 28 | * 读取文件内容 29 | */ 30 | readFile(filePath: string) { 31 | return window['fs'].readFileSync(filePath, 'utf8'); 32 | } 33 | 34 | /** 35 | * 读取目录内容 36 | */ 37 | readDir(dirPath: string) { 38 | return window['fs'].readDirSync(dirPath); 39 | } 40 | 41 | /** 42 | * 写文件 43 | */ 44 | writeFile(filePath: string, content: string) { 45 | window['fs'].writeFileSync(filePath, content); 46 | } 47 | 48 | /** 49 | * 判断路径是否存在 50 | */ 51 | exists(path: string): boolean { 52 | return window['fs'].existsSync(path) 53 | } 54 | 55 | /** 56 | * 判断是否为目录 57 | */ 58 | isDirectory(path: string) { 59 | return window['fs'].isDirectory(path); 60 | } 61 | 62 | isFile(path: string) { 63 | return window['fs'].isFile(path); 64 | } 65 | 66 | openUrl(url) { 67 | window['other'].openByBrowser(url); 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/app/services/iwindow.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | @Injectable({ 4 | providedIn: 'root', 5 | }) 6 | export class IwindowService { 7 | windows: IWindowOpts[] = []; 8 | 9 | // bounds: HTMLElement; 10 | 11 | constructor() {} 12 | 13 | openWindow(opts: IWindowOpts) { 14 | opts.zindex = opts.zindex == 0 ? opts.zindex : this.getMaxZindex() + 1; 15 | opts.size = opts.size || { width: 400, height: 600 }; 16 | opts.position = opts.position || { 17 | x: window.innerWidth - opts.size.width, 18 | y: 65, 19 | }; 20 | this.windows.push(opts); 21 | } 22 | 23 | closeWindow(opts: IWindowOpts) { 24 | let index = this.windows.indexOf(opts); 25 | this.windows.splice(index, 1); 26 | } 27 | 28 | getMaxZindex() { 29 | if (this.windows.length === 0) return 0; 30 | return this.windows.reduce((prev, curr) => { 31 | return prev.zindex > curr.zindex ? prev : curr; 32 | }).zindex; 33 | } 34 | } 35 | 36 | export interface IWindowOpts { 37 | position?: { 38 | x: number; 39 | y: number; 40 | }; 41 | size?: { 42 | width: number; 43 | height: number; 44 | minWidth?: number; 45 | minHeight?: number; 46 | }; 47 | type?: string; 48 | title: string; 49 | zindex?: number; 50 | } 51 | -------------------------------------------------------------------------------- /src/app/services/log.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Subject } from 'rxjs'; 3 | 4 | @Injectable({ 5 | providedIn: 'root' 6 | }) 7 | export class LogService { 8 | 9 | list: LogOptions[] = []; 10 | 11 | stateSubject = new Subject(); 12 | 13 | constructor() { } 14 | 15 | /** 16 | * 使用提供的选项更新日志状态。 17 | * @param opts - 要更新和发送的日志选项。 18 | */ 19 | update(opts: LogOptions) { 20 | opts['timestamp'] = Date.now(); 21 | // opts['showDetail'] = false; 22 | this.list.push(opts); 23 | this.stateSubject.next(opts); 24 | } 25 | 26 | clear() { 27 | this.list = []; 28 | } 29 | } 30 | 31 | export interface LogOptions { 32 | title?: string, 33 | detail?: string, 34 | state?: string, 35 | timestamp?: number, 36 | } -------------------------------------------------------------------------------- /src/app/services/notice.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Subject } from 'rxjs'; 3 | 4 | @Injectable({ 5 | providedIn: 'root' 6 | }) 7 | export class NoticeService { 8 | 9 | data: NoticeOptions; 10 | 11 | stateSubject = new Subject(); 12 | 13 | noticeList: NoticeOptions[] = []; 14 | 15 | constructor() { } 16 | 17 | update(opts: NoticeOptions) { 18 | opts['timestamp'] = Date.now(); 19 | opts['showDetail'] = false; 20 | this.stateSubject.next(opts); 21 | if (opts.state === 'error') { 22 | this.noticeList.push(opts) 23 | } 24 | } 25 | 26 | clear() { 27 | this.stateSubject.next(null); 28 | } 29 | } 30 | 31 | export interface NoticeOptions { 32 | title?: string, 33 | text?: string, 34 | state?: string, 35 | progress?: number, 36 | setTimeout?: number, 37 | stop?: Function, 38 | detail?: string, 39 | showDetail?: boolean, 40 | timestamp?: number, 41 | } -------------------------------------------------------------------------------- /src/app/services/serial.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { ElectronService } from './electron.service'; 3 | 4 | @Injectable({ 5 | providedIn: 'root' 6 | }) 7 | export class SerialService { 8 | 9 | // 编译上传时,通过这里获取串口 10 | currentPort; 11 | 12 | constructor( 13 | private electronService: ElectronService 14 | ) { } 15 | 16 | // 此处还未考虑linux、macos适配 17 | async getSerialPorts(): Promise { 18 | if (this.electronService.isElectron) { 19 | let serialList = (await window['SerialPort'].list()).map((item) => { 20 | let friendlyName: string = item.friendlyName.replace(/ \(COM\d+\)$/, ''); 21 | let keywords = ["蓝牙", "ble", "bluetooth"]; 22 | let icon: string = keywords.some(keyword => item.friendlyName.toLowerCase().includes(keyword.toLowerCase())) ? "fa-light fa-bluetooth" : 'fa-light fa-usb-drive'; 23 | return { 24 | name: item.path, 25 | text: friendlyName, 26 | type: 'serial', 27 | icon: icon, 28 | } 29 | }); 30 | return serialList; 31 | } else { 32 | const port = await navigator['serial'].requestPort(); 33 | return [{ port: port, name: '' }]; 34 | } 35 | } 36 | } 37 | 38 | 39 | export interface PortItem { 40 | port?: string, 41 | name?: string, 42 | text?: string, 43 | type?: string, 44 | icon?: string, 45 | disabled?: boolean 46 | } 47 | -------------------------------------------------------------------------------- /src/app/tools/aily-chat/aily-chat.component.scss: -------------------------------------------------------------------------------- 1 | @use "../serial-monitor/serial-monitor.component.scss"; 2 | 3 | .window-box { 4 | position: relative; 5 | display: flex; 6 | flex-direction: column; 7 | height: 100%; 8 | } 9 | 10 | .dialog-list { 11 | padding-left: 10px; 12 | height: calc(100% - 180px); 13 | overflow-y: auto; 14 | 15 | .dialogs { 16 | padding: 0 10px 10px 0; 17 | } 18 | } 19 | 20 | .sender { 21 | position: relative; 22 | flex-shrink: 0; 23 | 24 | .settings { 25 | height: 40px; 26 | position: relative; 27 | } 28 | 29 | .input-box { 30 | margin-top: 10px; 31 | height: calc(100% - 48px) !important; 32 | 33 | textarea { 34 | font-size: 13px; 35 | } 36 | } 37 | } 38 | 39 | nz-resize-handle { 40 | .line { 41 | margin-top: 5px; 42 | height: 1px; 43 | background: rgba(255, 255, 255, 0.1); 44 | } 45 | } 46 | 47 | .historyList { 48 | background: #555; 49 | position: absolute; 50 | top: 40px; 51 | right: 0; 52 | } 53 | 54 | .guide-box { 55 | height: 100%; 56 | flex-direction: column; 57 | 58 | i { 59 | font-size: 60px; 60 | margin-bottom: 6px; 61 | } 62 | 63 | .title { 64 | font-size: 20px; 65 | } 66 | 67 | .text { 68 | color: #999; 69 | text-align: center; 70 | } 71 | } -------------------------------------------------------------------------------- /src/app/tools/aily-chat/components/aily-board-viewer/aily-board-viewer.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |
6 |
7 |
{{boardInfo.nickname}}
8 |
该开发板带wifi、蓝牙和丰富的接口,适合物联网项目开发
9 |
10 |
11 |
12 |
13 |
14 |
15 |
-------------------------------------------------------------------------------- /src/app/tools/aily-chat/components/aily-board-viewer/aily-board-viewer.component.scss: -------------------------------------------------------------------------------- 1 | .aily-board-viewer { 2 | border-radius: 5px; 3 | padding: 5px 10px; 4 | background-color: #3a3a3a; 5 | color: #ccc; 6 | container-type: inline-size; // 启用容器查询 7 | 8 | &:hover { 9 | background-color: #3f3f3f; 10 | } 11 | } 12 | 13 | .board-card { 14 | display: flex; 15 | position: relative; 16 | 17 | .img-box { 18 | width: 50px; 19 | height: 50px; 20 | margin-right: 15px; 21 | flex-shrink: 0; 22 | } 23 | 24 | .text-box { 25 | padding: 5px; 26 | width: calc(100% - 125px); 27 | 28 | .title { 29 | font-size: 14px; 30 | font-weight: bold; 31 | } 32 | 33 | .text{ 34 | color: #999; 35 | } 36 | } 37 | 38 | .action-btns { 39 | font-size: 18px; 40 | position: absolute; 41 | right: 0px; 42 | top: 0px; 43 | 44 | .btn{ 45 | width: 30px; 46 | height: 50px; 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /src/app/tools/aily-chat/components/aily-button-viewer/aily-button-viewer.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | @for (button of buttons; track button.action || $index) { 4 | 8 | } 9 | 10 |
-------------------------------------------------------------------------------- /src/app/tools/aily-chat/components/aily-button-viewer/aily-button-viewer.component.scss: -------------------------------------------------------------------------------- 1 | .aily-button-viewer { 2 | .button-container { 3 | display: flex; 4 | flex-wrap: wrap; 5 | gap: 8px; 6 | padding: 0; 7 | margin: 0; 8 | 9 | .aily-button { 10 | min-width: 80px; 11 | height: 32px; 12 | border-radius: 6px; 13 | font-size: 14px; 14 | transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); 15 | 16 | .button-icon { 17 | margin-right: 6px; 18 | } 19 | 20 | &:hover { 21 | transform: translateY(-1px); 22 | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); 23 | } 24 | 25 | &:active { 26 | transform: translateY(0); 27 | } 28 | 29 | &[disabled] { 30 | cursor: not-allowed; 31 | opacity: 0.6; 32 | 33 | &:hover { 34 | transform: none; 35 | box-shadow: none; 36 | } 37 | } 38 | } 39 | } 40 | 41 | // 响应式设计 42 | @media (max-width: 576px) { 43 | .button-container { 44 | flex-direction: column; 45 | 46 | .aily-button { 47 | width: 100%; 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/app/tools/aily-chat/components/aily-library-viewer/aily-library-viewer.component.scss: -------------------------------------------------------------------------------- 1 | @use "../aily-board-viewer/aily-board-viewer.component.scss"; 2 | 3 | .aily-library-viewer { 4 | border-radius: 5px; 5 | padding: 5px 10px; 6 | background-color: #3a3a3a; 7 | color: #ccc; 8 | 9 | &:hover { 10 | background-color: #3f3f3f; 11 | } 12 | } 13 | 14 | 15 | .img-box { 16 | i { 17 | font-size: 24px; 18 | } 19 | } -------------------------------------------------------------------------------- /src/app/tools/aily-chat/components/aily-state-viewer/aily-state-viewer.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | @switch(stateInfo.state){ 5 | @case('doing'){ 6 |
7 | 8 |
9 | } 10 | @case('error'){ 11 |
12 | 13 |
14 | } 15 | @case('warn'){ 16 |
17 | 18 |
19 | } 20 | @case('done'){ 21 |
22 | 23 |
24 | } 25 | } 26 |
27 |
28 | {{ getDisplayText() }} 29 |
30 |
31 |
-------------------------------------------------------------------------------- /src/app/tools/aily-chat/components/aily-state-viewer/aily-state-viewer.component.scss: -------------------------------------------------------------------------------- 1 | .aily-state-viewer { 2 | border-radius: 5px; 3 | padding: 5px 10px; 4 | background-color: #3a3a3a; 5 | color: #ccc; 6 | } 7 | 8 | .state-icon { 9 | flex-shrink: 0; 10 | font-size: 14px; 11 | margin-right: 5px; 12 | 13 | .lloading { 14 | color: #1890ff; 15 | } 16 | 17 | .done{ 18 | color: #52c41a; 19 | } 20 | 21 | .error { 22 | color: #ff4d4f; 23 | } 24 | 25 | .warn { 26 | color: #faad14; 27 | } 28 | 29 | .info { 30 | color: #1890ff; 31 | } 32 | } 33 | 34 | .state-text { 35 | flex: 1; 36 | font-size: 13px; 37 | } -------------------------------------------------------------------------------- /src/app/tools/aily-chat/components/dialog/dialog.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | @if(data.role=='user'){ 4 | 奈何col 5 | }@else { 6 | aily 7 | } 8 |
9 | @if(data.state=='thinking'){ 10 |
Thinking...
11 | }@else if(data.state=='retrieving'){ 12 |
Retrieving...
13 | }@else{ 14 |
15 | } 16 |
-------------------------------------------------------------------------------- /src/app/tools/aily-chat/readme.md: -------------------------------------------------------------------------------- 1 | ## MCP工具调用设计 2 | [MCP:TOOLNAME] 3 | ## 文件引用 4 | [FILE:FILEPATH] 5 | ## -------------------------------------------------------------------------------- /src/app/tools/aily-chat/services/speech.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | @Injectable({ 4 | providedIn: 'root' 5 | }) 6 | export class SpeechService { 7 | 8 | constructor() { } 9 | } 10 | -------------------------------------------------------------------------------- /src/app/tools/app-store/app-store.component.html: -------------------------------------------------------------------------------- 1 | @if (currentUrl == "/code-viewer") { 2 | 3 | 4 | 5 | } @else { 6 | 7 | 8 | 9 | } 10 | 11 | -------------------------------------------------------------------------------- /src/app/tools/app-store/app-store.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/src/app/tools/app-store/app-store.component.scss -------------------------------------------------------------------------------- /src/app/tools/app-store/app-store.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | // import { InnerWindowComponent } from '../../components/inner-window/inner-window.component'; 3 | import { ToolContainerComponent } from '../../components/tool-container/tool-container.component'; 4 | import { SubWindowComponent } from '../../components/sub-window/sub-window.component'; 5 | import { CommonModule } from '@angular/common'; 6 | import { UiService } from '../../services/ui.service'; 7 | import { Router } from '@angular/router'; 8 | 9 | @Component({ 10 | selector: 'app-app-store', 11 | imports: [ 12 | // InnerWindowComponent, 13 | ToolContainerComponent, 14 | SubWindowComponent, 15 | CommonModule 16 | ], 17 | templateUrl: './app-store.component.html', 18 | styleUrl: './app-store.component.scss' 19 | }) 20 | export class AppStoreComponent { 21 | currentUrl; 22 | 23 | windowInfo = '应用商店'; 24 | 25 | constructor( 26 | private uiService: UiService, 27 | private router: Router, 28 | ) { } 29 | 30 | ngOnInit() { 31 | this.currentUrl = this.router.url; 32 | } 33 | 34 | close() { 35 | this.uiService.closeTool('app-store'); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/app/tools/code-viewer/code-viewer.component.html: -------------------------------------------------------------------------------- 1 | @if (currentUrl == "/code-viewer") { 2 | 6 | 7 | 8 | } @else { 9 | 13 | 14 | 15 | } 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/app/tools/code-viewer/code-viewer.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/src/app/tools/code-viewer/code-viewer.component.scss -------------------------------------------------------------------------------- /src/app/tools/code-viewer/code-viewer.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { InnerWindowComponent } from '../../components/inner-window/inner-window.component'; 3 | import { MonacoEditorComponent } from '../../components/monaco-editor/monaco-editor.component'; 4 | import { BlocklyService } from '../../blockly/blockly.service'; 5 | import { ToolContainerComponent } from '../../components/tool-container/tool-container.component'; 6 | import { UiService } from '../../services/ui.service'; 7 | import { SubWindowComponent } from '../../components/sub-window/sub-window.component'; 8 | import { Router } from '@angular/router'; 9 | import { CommonModule } from '@angular/common'; 10 | 11 | @Component({ 12 | selector: 'app-code-viewer', 13 | imports: [ 14 | MonacoEditorComponent, 15 | InnerWindowComponent, 16 | ToolContainerComponent, 17 | SubWindowComponent, 18 | CommonModule 19 | ], 20 | templateUrl: './code-viewer.component.html', 21 | styleUrl: './code-viewer.component.scss', 22 | }) 23 | export class CodeViewerComponent { 24 | code = ''; 25 | 26 | currentUrl; 27 | 28 | windowInfo = '代码查看'; 29 | 30 | constructor( 31 | private blocklyService: BlocklyService, 32 | private uiService: UiService, 33 | private router: Router, 34 | ) {} 35 | 36 | ngOnInit() { 37 | this.currentUrl = this.router.url; 38 | } 39 | 40 | ngAfterViewInit(): void { 41 | this.blocklyService.codeSubject.subscribe((code) => { 42 | setTimeout(() => { 43 | this.code = code; 44 | }, 100); 45 | }); 46 | } 47 | 48 | close() { 49 | this.uiService.closeTool('code-viewer'); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/app/tools/data-chart/data-chart.component.html: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /src/app/tools/data-chart/data-chart.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/src/app/tools/data-chart/data-chart.component.scss -------------------------------------------------------------------------------- /src/app/tools/serial-monitor/components/data-item/add-newline.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; 3 | 4 | @Pipe({ 5 | name: 'addNewLine', 6 | standalone: true 7 | }) 8 | export class AddNewLinePipe implements PipeTransform { 9 | constructor(private sanitizer: DomSanitizer) { } 10 | 11 | transform(value: Uint8Array | string): SafeHtml { 12 | let stringValue: string; 13 | if (value instanceof Uint8Array) { 14 | stringValue = new TextDecoder().decode(value); 15 | } else { 16 | stringValue = value; 17 | } 18 | 19 | // 在每个换行符后添加
标签 20 | const htmlValue = stringValue.replace(/\n/g, '\n
'); 21 | 22 | // 使用 DomSanitizer 将结果标记为安全的 HTML 23 | return this.sanitizer.bypassSecurityTrustHtml(htmlValue); 24 | } 25 | } -------------------------------------------------------------------------------- /src/app/tools/serial-monitor/components/data-item/data-item.component.html: -------------------------------------------------------------------------------- 1 | @if (viewMode.showTimestamp) { 2 |
3 | 4 |
{{ data.time }}
5 |
6 | {{data.dir}} 7 |
8 |
11 |
12 |
13 | }@else{ 14 | 17 | 18 | } 19 | 20 | @if (showMenu) { 21 | 23 | } -------------------------------------------------------------------------------- /src/app/tools/serial-monitor/components/data-item/data-item.component.scss: -------------------------------------------------------------------------------- 1 | .item { 2 | position: relative; 3 | display: flex; 4 | line-height: 24px; 5 | padding: 1px 3px; 6 | border-radius: 5px; 7 | margin-bottom: 1px; 8 | user-select: text; 9 | 10 | &.highlight { 11 | background: rgba(57, 135, 252, 0.5); 12 | 13 | &:hover { 14 | background: rgba(57, 135, 252, 0.6); 15 | } 16 | } 17 | 18 | &:hover { 19 | background: rgba(255, 255, 255, 0.1); 20 | } 21 | 22 | .time { 23 | flex-shrink: 0; 24 | color: #888; 25 | } 26 | 27 | .dir { 28 | color: #888; 29 | background-color: rgba(51, 51, 51, 0.6); 30 | display: flex; 31 | justify-content: center; 32 | border-radius: 5px; 33 | margin: 0 5px 0 5px; 34 | font-size: 9px; 35 | width: 20px; 36 | flex-grow: 0; 37 | flex-shrink: 0; 38 | 39 | &.tx { 40 | background-color: #4e6694 !important; 41 | color: #999; 42 | } 43 | 44 | &.sys { 45 | color: #0b8800; 46 | } 47 | } 48 | } 49 | 50 | .data { 51 | word-wrap: break-word; 52 | overflow-wrap: break-word; 53 | word-break: break-word; 54 | max-width: 100%; 55 | 56 | &.nowrap { 57 | white-space: nowrap; 58 | } 59 | 60 | &.sys { 61 | color: #0b8800; 62 | } 63 | 64 | &.tx { 65 | color: rgb(130, 182, 225); 66 | } 67 | 68 | &.error { 69 | color: rgb(182, 0, 0); 70 | } 71 | 72 | ::ng-deep { 73 | .zf { 74 | width: 14px; 75 | height: 20px; 76 | background: rgba(255, 140, 0, 0.3); 77 | color: #fff; 78 | margin: 0 2px; 79 | border-radius: 5px; 80 | } 81 | 82 | .hex { 83 | margin-right: 7px; 84 | } 85 | } 86 | } 87 | 88 | // 添加搜索高亮相关样式 89 | 90 | .search-highlight { 91 | background-color: #ffeb3b; 92 | color: #000; 93 | padding: 0 2px; 94 | border-radius: 2px; 95 | } 96 | 97 | .search-active { 98 | background-color: rgba(255, 235, 59, 0.15); 99 | border-left: 3px solid #ffeb3b; 100 | } -------------------------------------------------------------------------------- /src/app/tools/serial-monitor/components/data-item/show-hex.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; 3 | 4 | @Pipe({ 5 | name: 'showHex', 6 | standalone: true 7 | }) 8 | export class ShowHexPipe implements PipeTransform { 9 | constructor(private sanitizer: DomSanitizer) {} 10 | 11 | transform(value: Uint8Array | string): SafeHtml { 12 | if (!value) return ''; 13 | let bytes: Uint8Array; 14 | 15 | // 如果输入是字符串,将其转换为Uint8Array 16 | if (typeof value === 'string') { 17 | bytes = new TextEncoder().encode(value); 18 | } else { 19 | bytes = value; 20 | } 21 | 22 | // 转换为十六进制并添加HTML格式 23 | const hexArray = Array.from(bytes).map(byte => 24 | `${byte.toString(16).padStart(2, '0').toUpperCase()}` 25 | ); 26 | 27 | // 使用DomSanitizer标记HTML为安全 28 | return this.sanitizer.bypassSecurityTrustHtml(hexArray.join('')); 29 | } 30 | } -------------------------------------------------------------------------------- /src/app/tools/serial-monitor/components/data-item/show-nr.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; 3 | 4 | @Pipe({ 5 | name: 'showNR', 6 | standalone: true 7 | }) 8 | export class ShowNRPipe implements PipeTransform { 9 | constructor(private sanitizer: DomSanitizer) { } 10 | 11 | transform(value: Uint8Array | string): SafeHtml { 12 | if (!value) return ''; 13 | 14 | // 如果输入是 Uint8Array,先转换为字符串 15 | let strValue = ''; 16 | if (value instanceof Uint8Array) { 17 | // 使用 TextDecoder 将 Uint8Array 转换为字符串 18 | strValue = new TextDecoder('utf-8').decode(value); 19 | } else { 20 | strValue = value; 21 | } 22 | 23 | // 转义HTML特殊字符,防止XSS攻击 24 | strValue = this.escapeHtml(strValue); 25 | 26 | // 替换换行符 27 | strValue = strValue.replace(/\n/g, '\\n
'); 28 | 29 | // 替换回车符 30 | strValue = strValue.replace(/\r/g, '\\r'); 31 | 32 | // 替换制表符 33 | strValue = strValue.replace(/\t/g, '\\t'); 34 | 35 | // 替换其他控制字符和不可见字符 36 | strValue = strValue.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, (char) => { 37 | return `\\x${char.charCodeAt(0).toString(16).padStart(2, '0')}`; 38 | }); 39 | 40 | // 使用DomSanitizer标记HTML为安全 41 | return this.sanitizer.bypassSecurityTrustHtml(strValue); 42 | } 43 | 44 | // 转义HTML特殊字符 45 | private escapeHtml(text: string): string { 46 | return text 47 | .replace(/&/g, '&') 48 | .replace(//g, '>') 50 | .replace(/"/g, '"') 51 | .replace(/'/g, '''); 52 | } 53 | } -------------------------------------------------------------------------------- /src/app/tools/serial-monitor/components/history-message-list/history-message-list.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
发送历史记录
4 |
5 | 6 |
7 |
8 |
9 | 10 | @if(sendHistoryList.length === 0) { 11 |
暂无历史记录
12 | } @else { 13 | @for(item of sendHistoryList; track $index) { 14 |
15 |
{{ item }}
16 |
17 | 20 | 23 |
24 |
25 | } 26 | } 27 |
28 |
29 |
-------------------------------------------------------------------------------- /src/app/tools/serial-monitor/components/history-message-list/history-message-list.component.scss: -------------------------------------------------------------------------------- 1 | .history-list-container { 2 | position: absolute; 3 | top: 2px; 4 | left: 0; 5 | width: 100%; 6 | height: calc(100% - 2px); 7 | display: flex; 8 | flex-direction: column; 9 | background-color: #323437; 10 | z-index: 1000; 11 | } 12 | 13 | .history-header { 14 | height: 30px; 15 | 16 | .history-title { 17 | // font-weight: 500; 18 | padding-left: 10px; 19 | } 20 | 21 | .history-close { 22 | position: absolute; 23 | right: 10px; 24 | top: 0; 25 | cursor: pointer; 26 | width: 30px; 27 | height: 30px; 28 | } 29 | } 30 | 31 | 32 | 33 | 34 | .history-content { 35 | padding: 10px; 36 | } 37 | 38 | .history-item { 39 | display: flex; 40 | justify-content: space-between; 41 | align-items: center; 42 | padding: 8px 10px; 43 | border-bottom: 1px solid var(--border-color-light); 44 | } 45 | 46 | .history-text { 47 | flex: 1; 48 | overflow: hidden; 49 | text-overflow: ellipsis; 50 | white-space: nowrap; 51 | word-break: break-all; 52 | color: var(--text-color); 53 | } 54 | 55 | .history-actions { 56 | display: flex; 57 | gap: 5px; 58 | } 59 | 60 | .history-btn { 61 | padding: 4px 8px; 62 | background: transparent; 63 | border: none; 64 | cursor: pointer; 65 | color: var(--text-color-secondary); 66 | border-radius: 4px; 67 | } 68 | 69 | .history-btn:hover { 70 | background-color: var(--hover-bg); 71 | } 72 | 73 | .empty-history { 74 | text-align: center; 75 | padding: 20px; 76 | color: var(--text-color-secondary); 77 | } -------------------------------------------------------------------------------- /src/app/tools/serial-monitor/components/history-message-list/history-message-list.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Output } from '@angular/core'; 2 | import { SerialMonitorService } from '../../serial-monitor.service'; 3 | import { SimplebarAngularModule } from 'simplebar-angular'; 4 | 5 | @Component({ 6 | selector: 'app-history-message-list', 7 | imports: [SimplebarAngularModule], 8 | templateUrl: './history-message-list.component.html', 9 | styleUrl: './history-message-list.component.scss' 10 | }) 11 | export class HistoryMessageListComponent { 12 | 13 | @Output() value = new EventEmitter(); 14 | @Output() send = new EventEmitter(); 15 | @Output() close = new EventEmitter(); 16 | 17 | constructor(private serialMonitorService: SerialMonitorService) { } 18 | 19 | 20 | get sendHistoryList() { 21 | return this.serialMonitorService.sendHistoryList; 22 | } 23 | 24 | editHistory(content: string) { 25 | this.value.emit(content); 26 | } 27 | 28 | resendHistory(content: string) { 29 | this.send.emit(content); 30 | } 31 | 32 | onClose() { 33 | this.close.emit(); 34 | console.log('close'); 35 | 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/app/tools/serial-monitor/components/quick-send-editor/quick-send-editor.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 | 9 |
10 |
-------------------------------------------------------------------------------- /src/app/tools/serial-monitor/components/quick-send-editor/quick-send-editor.component.scss: -------------------------------------------------------------------------------- 1 | .quick-send-editor { 2 | background: #323437; 3 | padding-top: 7px; 4 | border-radius: 5px; 5 | height: calc(100% - 50px); 6 | width: 100%; 7 | position: absolute; 8 | bottom: 0; 9 | 10 | .content { 11 | margin: -10px 10px 0 10px; 12 | background: #292929; 13 | height: calc(100% - 28px); 14 | border-radius: 0 0 5px 5px; 15 | overflow: hidden; 16 | } 17 | 18 | .btns { 19 | padding: 0 10px; 20 | height: 28px; 21 | display: flex; 22 | align-items: center; 23 | justify-content: flex-end; 24 | position: absolute; 25 | bottom: 5px; 26 | width: 100%; 27 | button { 28 | height: 28px; 29 | line-height: 16px; 30 | border-radius: 5px; 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /src/app/tools/serial-monitor/components/quick-send-list/quick-send-list.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
快捷发送
5 | @for(item of quickSendList; track $index) { 6 |
7 | {{item.name}} 8 |
9 | } 10 | 11 |
12 |
13 |
14 | @if(showMore){ 15 | 16 | }@else{ 17 | 18 | } 19 |
20 |
-------------------------------------------------------------------------------- /src/app/tools/serial-monitor/components/quick-send-list/quick-send-list.component.scss: -------------------------------------------------------------------------------- 1 | .quick-send-list { 2 | position: relative; 3 | width: 100%; 4 | font-size: 12px; 5 | 6 | &:hover .setting { 7 | visibility: visible; 8 | } 9 | } 10 | 11 | .btns { 12 | display: flex; 13 | align-items: center; 14 | height: 100%; 15 | } 16 | 17 | .title { 18 | padding: 0 10px; 19 | flex-shrink: 0; 20 | } 21 | 22 | .item-btn { 23 | height: 24px; 24 | min-width: 15px; 25 | flex-shrink: 0; 26 | background-color: #333; 27 | padding: 0 6px; 28 | margin-right: 10px; 29 | border-radius: 5px; 30 | cursor: pointer; 31 | 32 | &.signal { 33 | background-color: rgba(221, 77, 0, 0.5); 34 | 35 | &:hover { 36 | background-color: rgba(221, 77, 0, 0.8); 37 | } 38 | } 39 | 40 | &.text { 41 | background-color: rgba(4, 110, 197, 0.5); 42 | 43 | &:hover { 44 | background-color: rgba(4, 110, 197, 0.8); 45 | } 46 | } 47 | 48 | &.hex { 49 | background-color: rgba(35, 118, 0, 0.5); 50 | 51 | &:hover { 52 | background-color: rgba(35, 118, 0, 0.8); 53 | } 54 | } 55 | 56 | &:hover { 57 | background-color: #444; 58 | } 59 | } 60 | 61 | .setting { 62 | position: absolute; 63 | top: 0; 64 | right: 0; 65 | font-size: 18px; 66 | width: 38px; 67 | height: 38px; 68 | padding-top: 2px; 69 | 70 | &:hover { 71 | color: #fff; 72 | } 73 | } 74 | 75 | ngx-simplebar { 76 | height: 40px; 77 | width: calc(100% - 40px); 78 | 79 | ::ng-deep { 80 | .simplebar-content-wrapper { 81 | height: 100%; 82 | } 83 | 84 | .simplebar-scrollbar:before { 85 | height: 5px; 86 | } 87 | 88 | .simplebar-horizontal { 89 | height: 5px; 90 | } 91 | 92 | .simplebar-content { 93 | height: 100%; 94 | } 95 | } 96 | } -------------------------------------------------------------------------------- /src/app/tools/serial-monitor/components/quick-send-list/quick-send-list.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ElementRef, EventEmitter, Output, ViewChild } from '@angular/core'; 2 | import { SimplebarAngularComponent, SimplebarAngularModule } from 'simplebar-angular'; 3 | import { SerialMonitorService } from '../../serial-monitor.service'; 4 | import { CommonModule } from '@angular/common'; 5 | 6 | @Component({ 7 | selector: 'app-quick-send-list', 8 | imports: [SimplebarAngularModule, CommonModule], 9 | templateUrl: './quick-send-list.component.html', 10 | styleUrl: './quick-send-list.component.scss' 11 | }) 12 | export class QuickSendListComponent { 13 | 14 | @ViewChild(SimplebarAngularComponent) simplebar: SimplebarAngularComponent; 15 | 16 | options2 = { 17 | autoHide: true, 18 | clickOnTrack: true, 19 | scrollbarMinSize: 10 20 | }; 21 | 22 | get quickSendList() { 23 | return this.serialMonitorService.quickSendList; 24 | } 25 | 26 | constructor(private serialMonitorService: SerialMonitorService) { } 27 | 28 | ngAfterViewInit() { 29 | const simplebarElement = this.simplebar.SimpleBar.getScrollElement() 30 | 31 | simplebarElement.addEventListener('wheel', (event: WheelEvent) => { 32 | // console.log('wheel', event); 33 | event.preventDefault(); 34 | const scrollAmount = event.deltaY || event.deltaX; 35 | simplebarElement.scrollLeft += scrollAmount * 0.2; 36 | }, { passive: false }); 37 | } 38 | 39 | send(item) { 40 | switch (item.type) { 41 | case 'text': 42 | this.serialMonitorService.sendData(item.data, 'text', true); 43 | break; 44 | case 'hex': 45 | this.serialMonitorService.sendData(item.data, 'hex'); 46 | break; 47 | case 'signal': 48 | this.serialMonitorService.sendSignal(item.data); 49 | break; 50 | default: 51 | break; 52 | } 53 | } 54 | 55 | @Output() openMore = new EventEmitter(); 56 | showMore = false; 57 | edit() { 58 | this.showMore = !this.showMore; 59 | this.openMore.emit(this.showMore); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/app/tools/serial-monitor/components/search-box/search-box.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/tools/serial-monitor/components/search-box/search-box.component.scss: -------------------------------------------------------------------------------- 1 | .search-box { 2 | position: absolute; 3 | bottom: 0; 4 | right: 0; 5 | background: #202020; 6 | border-radius: 5px 0 5px 0; 7 | width: 240px; 8 | 9 | nz-input-group { 10 | border: none; 11 | } 12 | 13 | i { 14 | cursor: pointer; 15 | 16 | &:hover { 17 | color: #1890ff; 18 | } 19 | } 20 | 21 | .result-count { 22 | margin-left: 8px; 23 | font-size: 12px; 24 | color: #888; 25 | } 26 | } -------------------------------------------------------------------------------- /src/app/tools/serial-monitor/components/search-box/search-box.component.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from '@angular/common'; 2 | import { Component, EventEmitter, Input, Output } from '@angular/core'; 3 | import { FormsModule } from '@angular/forms'; 4 | import { NzInputModule } from 'ng-zorro-antd/input'; 5 | 6 | @Component({ 7 | selector: 'app-search-box', 8 | imports: [NzInputModule, CommonModule, FormsModule], 9 | templateUrl: './search-box.component.html', 10 | styleUrl: './search-box.component.scss' 11 | }) 12 | export class SearchBoxComponent { 13 | @Output() keywordChange = new EventEmitter(); 14 | @Output() prevResult = new EventEmitter(); 15 | @Output() nextResult = new EventEmitter(); 16 | @Input() resultsCount = 0; 17 | @Input() currentIndex = -1; 18 | 19 | keyword; 20 | 21 | onKeywordChange(e) { 22 | this.keywordChange.emit(this.keyword); 23 | } 24 | 25 | onPrevClick() { 26 | this.prevResult.emit(); 27 | } 28 | 29 | onNextClick() { 30 | this.nextResult.emit(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/app/tools/serial-monitor/components/setting-more/setting-more.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
数据位
5 |
7 |
{{selectedDataBits.name}}
8 |
9 | 10 |
11 |
12 |
13 |
14 |
停止位
15 |
17 |
{{selectedStopBits.name}}
18 |
19 | 20 |
21 |
22 |
23 |
24 |
校验位
25 |
27 |
{{selectedParity.name}}
28 |
29 | 30 |
31 |
32 |
33 |
34 |
流控制
35 |
37 |
{{selectedFlowControl.name}}
38 |
39 | 40 |
41 |
42 |
43 |
44 |
45 | 46 | @if (showMenu) { 47 | 49 | } -------------------------------------------------------------------------------- /src/app/tools/serial-monitor/components/setting-more/setting-more.component.scss: -------------------------------------------------------------------------------- 1 | .settings { 2 | background: #292929; 3 | margin: -17px 10px 10px 10px; 4 | padding-top: 7px; 5 | border-radius: 5px; 6 | 7 | .line { 8 | display: flex; 9 | align-items: center; 10 | position: relative; 11 | flex-wrap: wrap; 12 | } 13 | } 14 | 15 | .item { 16 | display: flex; 17 | align-items: center; 18 | color: #eee; 19 | position: relative; 20 | height: 40px; 21 | font-size: 12px; 22 | 23 | .title { 24 | padding: 0 10px; 25 | flex-shrink: 0; 26 | } 27 | 28 | .item-inner { 29 | height: 24px; 30 | width: 90px; 31 | background-color: #333; 32 | border-radius: 2px; 33 | position: relative; 34 | display: flex; 35 | justify-content: flex-start; 36 | padding-left: 10px; 37 | color: #eee; 38 | font-size: 14px; 39 | cursor: pointer; 40 | 41 | &.selected { 42 | .arrow { 43 | transform: rotate(90deg); 44 | } 45 | } 46 | 47 | .btn { 48 | width: 20px; 49 | height: 24px; 50 | position: absolute; 51 | right: 0; 52 | color: rgb(0, 96, 160); 53 | 54 | &:nth-child(2) { 55 | right: 20px; 56 | } 57 | 58 | &:hover { 59 | color: rgb(0, 140, 233); 60 | } 61 | } 62 | 63 | .value { 64 | pointer-events: none; 65 | } 66 | 67 | .arrow-box { 68 | pointer-events: none; 69 | position: absolute; 70 | right: 8px; 71 | 72 | .arrow { 73 | transition: transform 0.3s; 74 | } 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /src/app/tools/serial-monitor/components/widget-data/widget-data.component.html: -------------------------------------------------------------------------------- 1 |

widget-data works!

2 | -------------------------------------------------------------------------------- /src/app/tools/serial-monitor/components/widget-data/widget-data.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/src/app/tools/serial-monitor/components/widget-data/widget-data.component.scss -------------------------------------------------------------------------------- /src/app/tools/serial-monitor/components/widget-data/widget-data.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-widget-data', 5 | imports: [], 6 | templateUrl: './widget-data.component.html', 7 | styleUrl: './widget-data.component.scss' 8 | }) 9 | export class WidgetDataComponent { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/app/tools/serial-monitor/config.ts: -------------------------------------------------------------------------------- 1 | export const BAUDRATE_LIST = [ 2 | { name: '4800', value: 4800 }, 3 | { name: '9600', value: 9600 }, 4 | { name: '19200', value: 19200 }, 5 | { name: '38400', value: 38400 }, 6 | { name: '57600', value: 57600 }, 7 | { name: '115200', value: 115200 }, 8 | { name: '230400', value: 230400 }, 9 | { name: '460800', value: 460800 }, 10 | { name: '921600', value: 921600 }, 11 | { name: '1000000', value: 1000000 }, 12 | { name: '2000000', value: 2000000 }, 13 | { name: '3000000', value: 3000000 }, 14 | { name: '4000000', value: 4000000 }, 15 | ] 16 | 17 | // 数据位配置选项 18 | export const DATA_BITS_LIST = [ 19 | { name: '5', value: 5 }, 20 | { name: '6', value: 6 }, 21 | { name: '7', value: 7 }, 22 | { name: '8', value: 8, isDefault: true } 23 | ] 24 | 25 | // 停止位配置选项 26 | export const STOP_BITS_LIST = [ 27 | { name: '1', value: 1, isDefault: true }, 28 | { name: '1.5', value: 1.5 }, 29 | { name: '2', value: 2 } 30 | ] 31 | 32 | // 校验位配置选项 33 | export const PARITY_LIST = [ 34 | { name: '无', value: 'none', isDefault: true }, 35 | { name: '奇校验', value: 'odd' }, 36 | { name: '偶校验', value: 'even' }, 37 | { name: '标记', value: 'mark' }, 38 | { name: '空格', value: 'space' } 39 | ] 40 | 41 | // 流控制配置选项 42 | export const FLOW_CONTROL_LIST = [ 43 | { name: '无', value: 'none', isDefault: true }, 44 | { name: 'RTS/CTS', value: 'hardware' }, 45 | { name: 'XON/XOFF', value: 'software' } 46 | ] 47 | -------------------------------------------------------------------------------- /src/app/tools/serial-monitor/right-menu.config.ts: -------------------------------------------------------------------------------- 1 | export let RIGHT_MENU = [ 2 | { 3 | name: '复制文本', 4 | data: { action: 'copy' }, 5 | icon: 'fa-light fa-copy', 6 | }, 7 | { 8 | name: 'Hex显示', 9 | data: { action: 'hex' }, 10 | icon: 'fa-light fa-square-code', 11 | }, 12 | { 13 | name: '高亮标记', 14 | data: { action: 'highlight' }, 15 | icon: 'fa-light fa-highlighter', 16 | } 17 | ]; 18 | -------------------------------------------------------------------------------- /src/app/tools/simulator/readme.md: -------------------------------------------------------------------------------- 1 | # todo 2 | 使用项目: 3 | https://github.com/wokwi/wokwi-elements 4 | https://github.com/wokwi/wokwi-gdbserver 5 | https://github.com/wokwi/avr8js 6 | 7 | 8 | ## ESP32 9 | 对于EPS32由于wokwi没有提供esp32版本模拟器。 10 | 可以直接运行[qemu](https://github.com/espressif/qemu)实现模拟,然后通过qemu提供的监视器接口获取硬件状态。 11 | 12 | -------------------------------------------------------------------------------- /src/app/tools/simulator/simulator-editor/simulator-editor.component.html: -------------------------------------------------------------------------------- 1 | 10 |
-------------------------------------------------------------------------------- /src/app/tools/simulator/simulator-editor/simulator-editor.component.scss: -------------------------------------------------------------------------------- 1 | /* 允许用户在自定义元素内选择文本 */ 2 | g[data-type="example.ArduinoElement"] { 3 | -webkit-user-select: text; 4 | user-select: text; 5 | } 6 | 7 | g[data-type="example.ArduinoElement"] div { 8 | cursor: auto; 9 | } 10 | 11 | /* 确保元素显示占满容器 */ 12 | :host { 13 | display: block; 14 | width: 100%; 15 | height: 100%; 16 | } 17 | -------------------------------------------------------------------------------- /src/app/tools/simulator/simulator.component.html: -------------------------------------------------------------------------------- 1 | @if (currentUrl == "/simulator") { 2 | 3 | 4 | 5 | } @else { 6 | 7 | 8 | 9 | } 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/app/tools/simulator/simulator.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/src/app/tools/simulator/simulator.component.scss -------------------------------------------------------------------------------- /src/app/tools/simulator/simulator.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { ToolContainerComponent } from '../../components/tool-container/tool-container.component'; 3 | import { SubWindowComponent } from '../../components/sub-window/sub-window.component'; 4 | import { Router } from '@angular/router'; 5 | import { SimulatorEditorComponent } from './simulator-editor/simulator-editor.component'; 6 | import { CommonModule } from '@angular/common'; 7 | 8 | @Component({ 9 | selector: 'app-simulator', 10 | imports: [ 11 | CommonModule, 12 | ToolContainerComponent, 13 | SubWindowComponent, 14 | SimulatorEditorComponent 15 | ], 16 | templateUrl: './simulator.component.html', 17 | styleUrl: './simulator.component.scss' 18 | }) 19 | export class SimulatorComponent { 20 | 21 | currentUrl; 22 | 23 | constructor( 24 | private router: Router 25 | ) { 26 | 27 | } 28 | 29 | ngOnInit() { 30 | this.currentUrl = this.router.url; 31 | } 32 | 33 | close() { 34 | 35 | } 36 | 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/app/tools/terminal/ansi.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; 3 | import { FancyAnsi } from 'fancy-ansi'; 4 | 5 | @Pipe({ 6 | name: 'ansi', 7 | standalone: true 8 | }) 9 | export class AnsiPipe implements PipeTransform { 10 | private fancyAnsi = new FancyAnsi(); 11 | 12 | constructor(private sanitizer: DomSanitizer) {} 13 | 14 | transform(value: string | null | undefined): SafeHtml { 15 | if (!value) { 16 | return ''; 17 | } 18 | 19 | const htmlString = this.fancyAnsi.toHtml(value); 20 | return this.sanitizer.bypassSecurityTrustHtml(htmlString); 21 | } 22 | } -------------------------------------------------------------------------------- /src/app/tools/terminal/readme.md: -------------------------------------------------------------------------------- 1 | # 终端调用 2 | 3 | ## 前端调用 4 | ```javascript 5 | // 打开终端 6 | UiService.openTool('terminal'); 7 | // 关闭终端 8 | UiService.closeTool('terminal'); 9 | // 更新终端 10 | UiService.updateTerminal(); 11 | // 清空终端 12 | UiService.clearTerminal(); 13 | ``` 14 | 15 | ## electron调用 -------------------------------------------------------------------------------- /src/app/tools/terminal/terminal.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 |
11 | 12 |
13 |
14 | 15 |
16 |
17 |
18 |
19 |
20 |
21 | 22 | @for(item of logList;track $index) { 23 |
24 |
{{item.timestamp | date:'yyyy-MM-dd HH:mm:ss'}}
25 |
{{item.title}}
26 |
27 |
28 | } 29 |
30 |
31 |
32 |
33 |
-------------------------------------------------------------------------------- /src/app/windows/about/about.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 |
6 |
-------------------------------------------------------------------------------- /src/app/windows/about/about.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/src/app/windows/about/about.component.scss -------------------------------------------------------------------------------- /src/app/windows/about/about.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { SubWindowComponent } from '../../components/sub-window/sub-window.component'; 3 | 4 | @Component({ 5 | selector: 'app-about', 6 | imports: [ 7 | SubWindowComponent 8 | ], 9 | templateUrl: './about.component.html', 10 | styleUrl: './about.component.scss' 11 | }) 12 | export class AboutComponent { 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/app/windows/settings/readme.md: -------------------------------------------------------------------------------- 1 | # 可用配置项目 2 | 3 | 4 | ## 项目配置 5 | 项目默认存放路径 6 | 7 | ## npm仓库配置 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Aily Blockly 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
Loading...
33 |
34 |
35 | 36 | 37 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { bootstrapApplication } from '@angular/platform-browser'; 2 | import { appConfig } from './app/app.config'; 3 | import { AppComponent } from './app/app.component'; 4 | 5 | bootstrapApplication(AppComponent, appConfig) 6 | .catch((err) => console.error(err)); 7 | -------------------------------------------------------------------------------- /test-blocks.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailyProject/aily-blockly/107e8619af783da7c02e90b62ceddf067c0ad41d/test-blocks.json -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | /* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ 2 | /* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ 3 | { 4 | "extends": "./tsconfig.json", 5 | "compilerOptions": { 6 | "outDir": "./out-tsc/app", 7 | "types": ["node"] 8 | }, 9 | "files": [ 10 | "src/main.ts" 11 | ], 12 | "include": [ 13 | "src/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ 2 | /* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ 3 | { 4 | "compileOnSave": false, 5 | "compilerOptions": { 6 | "outDir": "./dist/out-tsc", 7 | "strict": false, 8 | "noImplicitOverride": true, 9 | "noPropertyAccessFromIndexSignature": true, 10 | "noImplicitReturns": false, 11 | "noFallthroughCasesInSwitch": true, 12 | "skipLibCheck": true, 13 | "isolatedModules": true, 14 | "resolveJsonModule": true, 15 | "esModuleInterop": true, 16 | "experimentalDecorators": true, 17 | "moduleResolution": "bundler", 18 | "importHelpers": true, 19 | "target": "ES2022", 20 | "module": "ES2022", 21 | "allowJs": true, 22 | "types": [ 23 | "node" 24 | ] 25 | }, 26 | "angularCompilerOptions": { 27 | "enableI18nLegacyMessageIdFormat": false, 28 | "strictInjectionParameters": true, 29 | "strictInputAccessModifiers": true, 30 | "strictTemplates": true 31 | }, 32 | "exclude": [ 33 | "child", 34 | "electron", 35 | "dist", 36 | "node_modules", 37 | "**/*.spec.ts", 38 | "**/*.test.ts", 39 | "src/environments/*.ts", 40 | "temp/**/*" 41 | ] 42 | } -------------------------------------------------------------------------------- /tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ 2 | /* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ 3 | { 4 | "extends": "./tsconfig.json", 5 | "compilerOptions": { 6 | "outDir": "./out-tsc/spec", 7 | "types": [ 8 | "jasmine" 9 | ] 10 | }, 11 | "include": [ 12 | "src/**/*.spec.ts", 13 | "src/**/*.d.ts" 14 | ] 15 | } 16 | --------------------------------------------------------------------------------