├── .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 |
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 [](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 | 
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 |
9 |
10 |
11 |
个人赞助
12 |
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 |
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 |
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 |
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 |
21 | }
22 | @case ("error") {
23 |
28 | }
29 | @case ("warn") {
30 |
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 |
51 |
52 |
53 |
54 | }
--------------------------------------------------------------------------------
/src/app/components/project-manager/project-manager.component.html:
--------------------------------------------------------------------------------
1 |
2 |
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 |
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 |
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 |
6 |
7 |
8 |
9 |
10 |
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 |
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 |
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 |
35 | }
36 | @case('warn'){
37 |
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 |
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 |
--------------------------------------------------------------------------------
/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 |
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 |
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 |
10 | }
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
2 |
3 |
5 |
6 |
7 | 0">{{ currentIndex + 1 }}/{{ resultsCount }}
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/app/windows/about/about.component.html:
--------------------------------------------------------------------------------
1 |
2 |
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 |
--------------------------------------------------------------------------------