├── .editorconfig ├── .eslintrc.cjs ├── .gitattributes ├── .github ├── FUNDING.yml ├── logo.svg ├── pixi-active.svg ├── pixi-inactive.svg └── workflows │ ├── deploy-docs.yaml │ └── main.yml ├── .gitignore ├── .husky └── pre-commit ├── .prettierrc ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── packages ├── api │ ├── README.md │ ├── global.d.ts │ ├── package.json │ ├── src │ │ ├── extensions │ │ │ ├── ext.ts │ │ │ ├── overlay.ts │ │ │ ├── properties.ts │ │ │ ├── stats.ts │ │ │ └── tree.ts │ │ ├── index.ts │ │ └── types.ts │ ├── tsconfig.json │ └── vite.config.ts ├── backend │ ├── package.json │ ├── src │ │ ├── assets │ │ │ └── gpuTextures │ │ │ │ └── textures.ts │ │ ├── extensions │ │ │ ├── Extensions.ts │ │ │ └── getExtension.ts │ │ ├── handler.ts │ │ ├── pixi.ts │ │ ├── rendering │ │ │ ├── instructions.ts │ │ │ ├── program.ts │ │ │ ├── readPixels.ts │ │ │ ├── renderableData.ts │ │ │ ├── rendering.ts │ │ │ └── stats.ts │ │ ├── scene │ │ │ ├── overlay │ │ │ │ ├── overlay.ts │ │ │ │ └── overlayExtension.ts │ │ │ ├── scene.ts │ │ │ ├── stats │ │ │ │ ├── stats.ts │ │ │ │ └── statsExtension.ts │ │ │ └── tree │ │ │ │ ├── extensions │ │ │ │ ├── animatedSprite │ │ │ │ │ ├── animatedSpritePropertyExtension.ts │ │ │ │ │ └── sharedAnimatedSpriteProps.ts │ │ │ │ ├── container │ │ │ │ │ ├── containerPropertyExtension.ts │ │ │ │ │ ├── sharedContainerProps.ts │ │ │ │ │ ├── v7ContainerProps.ts │ │ │ │ │ └── v8ContainerProps.ts │ │ │ │ ├── ninesliceSprite │ │ │ │ │ ├── ninesliceSpritePropertyExtension.ts │ │ │ │ │ ├── sharedNinesliceSpriteProps.ts │ │ │ │ │ ├── v7NinesliceSpriteProps.ts │ │ │ │ │ └── v8NinesliceSpriteProps.ts │ │ │ │ ├── text │ │ │ │ │ ├── textPropertyExtension.ts │ │ │ │ │ └── textProps.ts │ │ │ │ ├── tilingSprite │ │ │ │ │ ├── sharedTilingSpriteProps.ts │ │ │ │ │ ├── tilingSpritePropertyExtension.ts │ │ │ │ │ ├── v7TilingSpriteProps.ts │ │ │ │ │ └── v8TilingSpriteProps.ts │ │ │ │ ├── utils │ │ │ │ │ ├── convertProp.ts │ │ │ │ │ └── getProps.ts │ │ │ │ └── view │ │ │ │ │ ├── viewPropertyExtension.ts │ │ │ │ │ └── viewProps.ts │ │ │ │ ├── properties.ts │ │ │ │ └── tree.ts │ │ └── utils │ │ │ ├── getPixiType.ts │ │ │ ├── loop.ts │ │ │ ├── poller.ts │ │ │ └── throttle.ts │ └── tsconfig.json ├── devtool-chrome │ ├── assets │ │ ├── contentStyle.css │ │ ├── pixi-icon-active-128.png │ │ ├── pixi-icon-active-16.png │ │ ├── pixi-icon-active-48.png │ │ ├── pixi-icon-inactive-128.png │ │ ├── pixi-icon-inactive-16.png │ │ └── pixi-icon-inactive-48.png │ ├── manifest.dev.json │ ├── manifest.json │ ├── package.json │ ├── postcss.config.js │ ├── src │ │ ├── background │ │ │ └── index.ts │ │ ├── content │ │ │ └── index.ts │ │ ├── devtools │ │ │ ├── devtools.ts │ │ │ ├── panel │ │ │ │ ├── panel.html │ │ │ │ └── panel.tsx │ │ │ └── pixi-index.html │ │ ├── inject │ │ │ ├── close.ts │ │ │ └── index.ts │ │ └── messageUtils.ts │ ├── tailwind.config.js │ ├── vite.chrome.config.ts │ └── vite.inject.config.ts ├── devtool-local │ ├── index.html │ ├── package.json │ ├── postcss.config.js │ ├── src │ │ ├── main.tsx │ │ └── scene.ts │ ├── tailwind.config.js │ └── vite.config.ts ├── docs │ ├── .gitignore │ ├── README.md │ ├── babel.config.js │ ├── docs │ │ ├── guide │ │ │ ├── faq.md │ │ │ ├── features │ │ │ │ ├── _category_.yml │ │ │ │ ├── assets.mdx │ │ │ │ └── scene.mdx │ │ │ └── installation.mdx │ │ └── plugin │ │ │ ├── index.md │ │ │ ├── overlay.md │ │ │ ├── properties.md │ │ │ ├── stats.md │ │ │ └── tree.md │ ├── docusaurus.config.ts │ ├── package.json │ ├── sidebars.ts │ ├── src │ │ ├── css │ │ │ └── custom.css │ │ └── pages │ │ │ ├── index.module.css │ │ │ └── index.tsx │ ├── static │ │ ├── .nojekyll │ │ ├── gif │ │ │ ├── devtool-asset-search.gif │ │ │ ├── devtool-asset-selection.gif │ │ │ ├── devtool-asset-sorting.gif │ │ │ ├── devtool-copy.gif │ │ │ ├── devtool-delete.gif │ │ │ ├── devtool-highlight.gif │ │ │ ├── devtool-lock.gif │ │ │ ├── devtool-properties.gif │ │ │ ├── devtool-rename.gif │ │ │ ├── devtool-reparent.gif │ │ │ ├── devtool-right-click.gif │ │ │ ├── devtool-search.gif │ │ │ ├── devtool-selection.gif │ │ │ └── devtool-stats.gif │ │ ├── img │ │ │ ├── devtool-screenshot.png │ │ │ ├── docusaurus.png │ │ │ ├── favicon.png │ │ │ ├── logo-chrome.svg │ │ │ ├── logo-light.svg │ │ │ ├── logo-main.svg │ │ │ ├── logo.svg │ │ │ ├── ogimage.png │ │ │ ├── undraw_docusaurus_mountain.svg │ │ │ ├── undraw_docusaurus_react.svg │ │ │ └── undraw_docusaurus_tree.svg │ │ └── social │ │ │ ├── discord.svg │ │ │ ├── github.svg │ │ │ ├── open-col-icon.svg │ │ │ └── twitter.svg │ └── tsconfig.json ├── example │ ├── index.html │ ├── package.json │ ├── src │ │ └── index.ts │ └── vite.config.ts └── frontend │ ├── components.json │ ├── package.json │ ├── src │ ├── App.tsx │ ├── assets │ │ ├── icon-active-48.png │ │ ├── react.svg │ │ ├── transparent-light.svg │ │ └── transparent.svg │ ├── components │ │ ├── collapsible │ │ │ ├── collapsible-section.tsx │ │ │ └── collapsible-split.tsx │ │ ├── mode-toggle.tsx │ │ ├── navbar │ │ │ └── navbar.tsx │ │ ├── properties │ │ │ ├── boolean-property.tsx │ │ │ ├── button-property.tsx │ │ │ ├── color-property.tsx │ │ │ ├── multi-property.tsx │ │ │ ├── number-property.tsx │ │ │ ├── propertyEntry.tsx │ │ │ ├── propertyTypes.tsx │ │ │ ├── range-property.tsx │ │ │ ├── select-property.tsx │ │ │ ├── text-property.tsx │ │ │ └── vector-property.tsx │ │ ├── smooth-charts │ │ │ └── stat.tsx │ │ ├── theme-provider.tsx │ │ └── ui │ │ │ ├── button.tsx │ │ │ ├── checkbox.tsx │ │ │ ├── clipboard.tsx │ │ │ ├── codearea.tsx │ │ │ ├── color.tsx │ │ │ ├── context-menu.tsx │ │ │ ├── dropdown-menu.tsx │ │ │ ├── input.tsx │ │ │ ├── popover.tsx │ │ │ ├── radio-group.tsx │ │ │ ├── select.tsx │ │ │ ├── separator.tsx │ │ │ ├── slider.tsx │ │ │ ├── svg-texture.tsx │ │ │ ├── switch.tsx │ │ │ ├── textarea.tsx │ │ │ ├── toggle-group.tsx │ │ │ ├── toggle.tsx │ │ │ ├── tooltip.tsx │ │ │ └── vector2.tsx │ ├── globals.css │ ├── lib │ │ ├── interval.tsx │ │ ├── localStorage.tsx │ │ └── utils.ts │ ├── pages │ │ ├── assets │ │ │ ├── AssetsPanel.tsx │ │ │ ├── assets.ts │ │ │ └── gpuTextures │ │ │ │ ├── GPUTextures.tsx │ │ │ │ ├── TextureProperties.tsx │ │ │ │ ├── TextureStats.tsx │ │ │ │ └── TextureViewer.tsx │ │ ├── rendering │ │ │ ├── CanvasPanel.tsx │ │ │ ├── InstructionsPanel.tsx │ │ │ ├── RenderingPanel.tsx │ │ │ ├── RenderingStats.tsx │ │ │ ├── instructions │ │ │ │ ├── Batch.tsx │ │ │ │ ├── CustomRender.tsx │ │ │ │ ├── Filter.tsx │ │ │ │ ├── Graphics.tsx │ │ │ │ ├── Instructions.tsx │ │ │ │ ├── Mask.tsx │ │ │ │ ├── Mesh.tsx │ │ │ │ ├── NineSliceSprite.tsx │ │ │ │ ├── RenderGroup.tsx │ │ │ │ ├── TilingSprite.tsx │ │ │ │ ├── shared │ │ │ │ │ ├── InstructionBubble.tsx │ │ │ │ │ ├── InstructionSection.tsx │ │ │ │ │ ├── PropertyDisplay.tsx │ │ │ │ │ ├── Shader.tsx │ │ │ │ │ ├── State.tsx │ │ │ │ │ └── Texture.tsx │ │ │ │ └── test.tsx │ │ │ └── rendering.ts │ │ └── scene │ │ │ ├── ScenePanel.tsx │ │ │ ├── SceneStats.tsx │ │ │ ├── graph │ │ │ ├── SceneProperties.tsx │ │ │ ├── SceneTree.tsx │ │ │ └── tree │ │ │ │ ├── context-menu-button.tsx │ │ │ │ ├── cursor.tsx │ │ │ │ ├── node-button.tsx │ │ │ │ ├── node-trigger.tsx │ │ │ │ ├── node.tsx │ │ │ │ └── simple-tree.tsx │ │ │ └── scene.ts │ ├── types.ts │ └── vite-env.d.ts │ ├── tailwind.config.js │ └── tsconfig.json ├── scripts ├── release.mts └── utils │ ├── bump.mts │ ├── json.mts │ └── spawn.mts └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # Top-most EditorConfig file 2 | root = true 3 | 4 | # All files 5 | [*] 6 | charset = utf-8 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:@typescript-eslint/recommended', 7 | 'plugin:react-hooks/recommended', 8 | 'prettier', 9 | 'plugin:prettier/recommended', 10 | ], 11 | ignorePatterns: ['dist', '.eslintrc.cjs'], 12 | parser: '@typescript-eslint/parser', 13 | plugins: ['react-refresh', 'import'], 14 | rules: { 15 | '@typescript-eslint/no-explicit-any': 'off', 16 | '@typescript-eslint/consistent-type-imports': ['error', { disallowTypeAnnotations: false }], 17 | 'import/consistent-type-specifier-style': ['error', 'prefer-top-level'], 18 | 'import/no-duplicates': ['error'], 19 | '@typescript-eslint/no-unused-vars': [ 20 | 'error', 21 | { 22 | args: 'all', 23 | argsIgnorePattern: '^_', 24 | caughtErrors: 'none', 25 | caughtErrorsIgnorePattern: '^_', 26 | destructuredArrayIgnorePattern: '^_', 27 | varsIgnorePattern: '^_', 28 | ignoreRestSiblings: true, 29 | }, 30 | ], 31 | }, 32 | }; 33 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.js text eol=lf 2 | *.ts text eol=lf 3 | *.json text eol=lf 4 | *.yml text eol=lf 5 | *.md text eol=lf 6 | *.txt text eol=lf 7 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | open_collective: pixijs 2 | -------------------------------------------------------------------------------- /.github/workflows/deploy-docs.yaml: -------------------------------------------------------------------------------- 1 | name: Reusable Deploy 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - docs 8 | 9 | concurrency: 10 | group: ${{ github.workflow }}-${{ github.ref || github.run_id }} 11 | cancel-in-progress: true 12 | 13 | jobs: 14 | deploy: 15 | name: Deploy 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v4 19 | 20 | - uses: actions/setup-node@v4 21 | with: 22 | node-version: '20' 23 | cache: 'npm' 24 | 25 | - run: npm ci 26 | - run: | 27 | git config --global user.name "$GITHUB_ACTOR" 28 | git config --global user.email "$GITHUB_ACTOR@users.noreply.github.com" 29 | git remote set-url origin https://git:${GIT_PASS}@github.com/pixijs/devtools.git 30 | npm run deploy 31 | env: 32 | GIT_USER: $GITHUB_ACTOR 33 | GIT_PASS: ${{ secrets.GITHUB_TOKEN }} 34 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Automation 2 | on: 3 | push: 4 | branches: ['**'] 5 | release: 6 | types: [published] 7 | pull_request: 8 | branches: ['**'] 9 | jobs: 10 | build: 11 | name: Build 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | - name: Install xvfb 16 | run: sudo apt-get install xvfb 17 | - name: Use Node.js 20.x 18 | uses: actions/setup-node@v4 19 | with: 20 | node-version: 20 21 | - name: Install Dependencies 22 | run: npm ci 23 | 24 | - name: Test Lint 25 | run: npm run lint 26 | 27 | - name: Test Types 28 | run: npm run types 29 | 30 | - name: Build for Distribution 31 | run: xvfb-run --auto-servernum npm run build 32 | 33 | - name: Upload chrome extension 34 | if: github.event_name != 'release' 35 | uses: actions/upload-artifact@v4 36 | with: 37 | name: upload 38 | path: .upload/ 39 | include-hidden-files: true 40 | 41 | # Automatically attach files to release 42 | - name: Upload to Release 43 | if: github.event_name == 'release' 44 | uses: softprops/action-gh-release@v2 45 | with: 46 | files: .upload/* 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | 26 | .upload 27 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | npx lint-staged 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "singleQuote": true, 4 | "trailingComma": "all", 5 | "tabWidth": 2, 6 | "semi": true, 7 | "plugins": ["prettier-plugin-tailwindcss"] 8 | } 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 PixiJS 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PixiJS DevTools 2 | 3 |
4 | 5 | Logo 6 |

A chrome extension for debugging PixiJS applications.

7 | 8 |

9 | 10 | 11 | # Getting Started 12 | 13 | Please follow the documentation at [pixijs.io/devtools](https://pixijs.io/devtools) 14 | 15 | ## License 16 | 17 | MIT License. 18 | -------------------------------------------------------------------------------- /packages/api/README.md: -------------------------------------------------------------------------------- 1 | # PixiJS DevTools 2 | 3 |
4 | 5 | Logo 6 |

A chrome extension for debugging PixiJS applications.

7 | 8 |

9 | 10 | 11 | # Getting Started 12 | 13 | Please follow the documentation at [pixijs.io/devtools](https://pixijs.io/devtools) 14 | 15 | ## License 16 | 17 | MIT License. 18 | -------------------------------------------------------------------------------- /packages/api/global.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-var */ 2 | import type { Application, Container, Renderer } from 'pixi.js'; 3 | import type { OverlayExtension } from './dist/extensions/overlay'; 4 | import type { StatsExtension } from './dist/extensions/stats'; 5 | import type { TreeExtension } from './dist/extensions/tree'; 6 | import type { PropertiesExtension } from './dist/extensions/properties'; 7 | import type { NodeExtension } from './dist/extensions/node'; 8 | import type { PixiDevtools } from './dist/pixi'; 9 | 10 | declare global { 11 | var __PIXI_APP_INIT__: (arg: Application | Renderer, version?: string) => void; 12 | var __PIXI_RENDERER_INIT__: (arg: Application | Renderer, version?: string) => void; 13 | 14 | interface Window { 15 | __PIXI_APP__: Application | undefined; 16 | __PIXI_STAGE__: Container | undefined; 17 | __PIXI_RENDERER__: Renderer | undefined; 18 | __PIXI__: typeof import('pixi.js'); 19 | PIXI: typeof import('pixi.js'); 20 | __PIXI_DEVTOOLS_WRAPPER__: PixiDevtools; 21 | __PIXI_DEVTOOLS__: { 22 | version?: string; 23 | pixi?: typeof import('pixi.js'); 24 | app?: Application | undefined; 25 | stage?: Container | undefined; 26 | renderer?: Renderer | undefined; 27 | /** @deprecated since 2.0.0 */ 28 | plugins?: any; 29 | extensions?: (OverlayExtension | StatsExtension | TreeExtension | PropertiesExtension | NodeExtension)[]; 30 | }; 31 | $pixi: Container | null; 32 | } 33 | 34 | namespace PixiMixins { 35 | interface Container { 36 | __devtoolIgnore?: boolean; 37 | __devtoolIgnoreChildren?: string; 38 | __devtoolLocked?: boolean; 39 | } 40 | 41 | interface WebGLSystems { 42 | __devtoolInjected?: boolean; 43 | } 44 | 45 | interface WebGPUSystems { 46 | __devtoolInjected?: boolean; 47 | } 48 | } 49 | 50 | namespace GlobalMixins { 51 | interface Container { 52 | __devtoolIgnore?: boolean; 53 | __devtoolIgnoreChildren?: string; 54 | __devtoolLocked?: boolean; 55 | } 56 | } 57 | } 58 | 59 | export {}; 60 | -------------------------------------------------------------------------------- /packages/api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pixi/devtools", 3 | "publishConfig": { 4 | "access": "public" 5 | }, 6 | "homepage": "https://pixijs.io/devtools/", 7 | "bugs": "https://github.com/pixijs/devtools/issues", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/pixijs/devtools.git" 11 | }, 12 | "license": "MIT", 13 | "version": "2.2.1", 14 | "type": "module", 15 | "main": "dist/index.cjs", 16 | "module": "dist/index.js", 17 | "types": "dist/index.d.ts", 18 | "exports": { 19 | ".": { 20 | "types": "./dist/index.d.ts", 21 | "import": "./dist/index.js", 22 | "require": "./dist/index.cjs" 23 | } 24 | }, 25 | "files": [ 26 | "dist", 27 | "global.d.ts" 28 | ], 29 | "scripts": { 30 | "build": "vite build && tsc", 31 | "watch": "vite build --watch", 32 | "start": "npm run watch" 33 | }, 34 | "devDependencies": { 35 | "pixi.js": "^8.2.2" 36 | }, 37 | "peerDependencies": { 38 | "pixi.js": "^7 || ^8" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packages/api/src/extensions/ext.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Collection of valid extension types. 3 | */ 4 | export type ExtensionType = 'overlay' | 'stats' | 'sceneTree' | 'sceneProperties'; 5 | 6 | export enum ExtensionPriority { 7 | Low = -1, 8 | Normal = 0, 9 | High = 1, 10 | } 11 | 12 | /** 13 | * The metadata for an extension. 14 | * @ignore 15 | */ 16 | export interface ExtensionMetadataDetails { 17 | /** The extension type, can be multiple types */ 18 | type: ExtensionType | ExtensionType[]; 19 | /** Optional. Some extensions provide an API name/property, to make them more easily accessible */ 20 | name?: string; 21 | /** Optional, used for sorting the extensions in a particular order */ 22 | priority?: number; 23 | } 24 | 25 | /** 26 | * The metadata for an extension. 27 | */ 28 | export type ExtensionMetadata = ExtensionType | ExtensionMetadataDetails; 29 | -------------------------------------------------------------------------------- /packages/api/src/extensions/overlay.ts: -------------------------------------------------------------------------------- 1 | import type { Container } from 'pixi.js'; 2 | import type { ExtensionMetadata } from './ext'; 3 | 4 | export interface OverlayExtension { 5 | extension: ExtensionMetadata; 6 | /** 7 | * Get the css style for the selected highlight overlay. 8 | * @param node The selected node. 9 | */ 10 | getSelectedStyle?(node: Container | null): Partial; 11 | /** 12 | * Get the css style for the hover highlight overlay. 13 | * @param node The hovered node. 14 | */ 15 | getHoverStyle?(node: Container | null): Partial; 16 | /** 17 | * Get the global position of the node. 18 | * @param node The currently selected node to get the global position of. 19 | */ 20 | getGlobalBounds?(node: Container): { x: number; y: number; width: number; height: number }; 21 | } 22 | -------------------------------------------------------------------------------- /packages/api/src/extensions/properties.ts: -------------------------------------------------------------------------------- 1 | import type { Container } from 'pixi.js'; 2 | import type { ExtensionMetadata } from './ext'; 3 | 4 | export type MultiProps = { 5 | inputs: Omit[]; 6 | }; 7 | 8 | type PropertyPanelData = { 9 | value: any; 10 | prop: string; 11 | entry: { 12 | section: string; 13 | tooltip?: string; 14 | label?: string; 15 | type: 'boolean' | 'number' | 'range' | 'select' | 'text' | 'button' | 'vector2' | 'vectorX' | 'color' | 'multi'; 16 | options?: any; 17 | }; 18 | }; 19 | 20 | export type PropertiesEntry = PropertyPanelData & { allowUndefined?: boolean; allowCopy?: boolean }; 21 | export type Properties = Omit & { allowUndefined?: boolean; allowCopy?: boolean }; 22 | 23 | export interface PropertiesExtension { 24 | extension: ExtensionMetadata; 25 | /** 26 | * Test if the plugin can handle the container. 27 | * @param container The current node. 28 | */ 29 | testNode(container: Container): boolean; 30 | /** 31 | * Test if the plugin can handle the property. 32 | * @param prop The property name. 33 | */ 34 | testProp(prop: string): boolean; 35 | /** 36 | * Set the property for the container. 37 | * @param container The current node. 38 | * @param prop The property name. 39 | * @param value The new value. This value will be an array if the property has multiple inputs. e.g position. 40 | */ 41 | setProperty(container: Container, prop: string, value: any): void; 42 | /** 43 | * Get the properties for the container. 44 | * @param container The current node. 45 | */ 46 | getProperties(container: Container): PropertiesEntry[]; 47 | } 48 | -------------------------------------------------------------------------------- /packages/api/src/extensions/stats.ts: -------------------------------------------------------------------------------- 1 | import type { Container } from 'pixi.js'; 2 | import type { ExtensionMetadata } from './ext'; 3 | 4 | export interface StatsExtension { 5 | extension: ExtensionMetadata; 6 | /** 7 | * Track some stats for a node. 8 | * This function is called on every node in the scene. 9 | * @param container The current node 10 | * @param state The current stats for the scene. 11 | */ 12 | track: (container: Container, state: Record) => void; 13 | /** 14 | * Get the keys of what ypu are tracking. 15 | * This is used to warn the user if the same key is being tracked by multiple extensions. 16 | */ 17 | getKeys: () => string[]; 18 | } 19 | -------------------------------------------------------------------------------- /packages/api/src/index.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/triple-slash-reference 2 | /// 3 | import type { Devtools } from './types'; 4 | 5 | export * from './extensions/ext'; 6 | export * from './extensions/overlay'; 7 | export * from './extensions/properties'; 8 | export * from './extensions/stats'; 9 | export * from './extensions/tree'; 10 | export * from './types'; 11 | 12 | export async function initDevtools(opts: Devtools) { 13 | const options: Devtools = { 14 | importPixi: false, 15 | ...opts, 16 | }; 17 | 18 | if (options.app) { 19 | options.renderer = options.app.renderer; 20 | options.stage = options.app.stage; 21 | } 22 | 23 | if (options.importPixi && !options.pixi) { 24 | options.pixi = await import('pixi.js'); 25 | } 26 | 27 | window.__PIXI_DEVTOOLS__ = { 28 | ...(window.__PIXI_DEVTOOLS__ || {}), 29 | app: options.app, 30 | stage: options.stage, 31 | renderer: options.renderer, 32 | version: options.version, 33 | extensions: [...(window.__PIXI_DEVTOOLS__?.extensions || []), ...(options.extensions || [])], 34 | plugins: {}, 35 | }; 36 | } 37 | -------------------------------------------------------------------------------- /packages/api/src/types.ts: -------------------------------------------------------------------------------- 1 | import type { Container, Renderer, Application } from 'pixi.js'; 2 | import type { OverlayExtension } from './extensions/overlay'; 3 | 4 | /** @deprecated since 2.0.0 */ 5 | export interface DevtoolApp extends DevtoolPixi { 6 | app: Application; 7 | } 8 | 9 | /** @deprecated since 2.0.0 */ 10 | export interface DevtoolRenderer extends DevtoolPixi { 11 | renderer: Renderer; 12 | stage: Container; 13 | } 14 | 15 | interface DevtoolPixi { 16 | pixi?: typeof import('pixi.js'); 17 | importPixi?: boolean; 18 | extensions?: OverlayExtension[]; 19 | /** @deprecated since 2.0.0 */ 20 | plugins?: any; 21 | } 22 | 23 | /** @deprecated since 2.0.0 */ 24 | export type DevtoolsAPI = DevtoolApp | DevtoolRenderer; 25 | 26 | /** 27 | * The options for the devtools 28 | */ 29 | export interface Devtools extends DevtoolPixi { 30 | app?: Application; 31 | renderer?: Renderer; 32 | stage?: Container; 33 | version?: string; 34 | } 35 | -------------------------------------------------------------------------------- /packages/api/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "declaration": true, 5 | "emitDeclarationOnly": true, 6 | "noEmit": false, 7 | "outDir": "dist" 8 | }, 9 | "include": ["src"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/api/vite.config.ts: -------------------------------------------------------------------------------- 1 | // vite.config.js 2 | import { resolve } from 'node:path'; 3 | import { defineConfig } from 'vite'; 4 | 5 | export default defineConfig({ 6 | build: { 7 | lib: { 8 | // Could also be a dictionary or array of multiple entry points 9 | entry: resolve(__dirname, 'src/index.ts'), 10 | formats: ['es', 'cjs'], 11 | fileName: 'index', 12 | }, 13 | rollupOptions: { 14 | // make sure to externalize deps that shouldn't be bundled 15 | // into your library 16 | external: ['pixi.js'], 17 | }, 18 | }, 19 | }); 20 | -------------------------------------------------------------------------------- /packages/backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@devtool/backend", 3 | "version": "0.0.0", 4 | "private": true, 5 | "type": "module", 6 | "scripts": {}, 7 | "dependencies": {} 8 | } 9 | -------------------------------------------------------------------------------- /packages/backend/src/handler.ts: -------------------------------------------------------------------------------- 1 | import type { Container } from 'pixi.js'; 2 | import type { PixiDevtools } from './pixi'; 3 | 4 | export class PixiHandler { 5 | protected _devtool: typeof PixiDevtools; 6 | constructor(devtool: typeof PixiDevtools) { 7 | this._devtool = devtool; 8 | } 9 | 10 | public init() {} 11 | public reset() {} 12 | public preupdate() {} 13 | public update() {} 14 | public throttledUpdate() {} 15 | public loop(_container: Container) {} 16 | public postupdate() {} 17 | } 18 | -------------------------------------------------------------------------------- /packages/backend/src/rendering/program.ts: -------------------------------------------------------------------------------- 1 | import type { Filter, TextureShader } from 'pixi.js'; 2 | 3 | export function getProgramSource( 4 | filter: Filter | TextureShader, 5 | shader: 'fragment' | 'vertex', 6 | rendererType: 'webgl' | 'webgpu', 7 | ): string { 8 | const type = rendererType === 'webgl' ? 'glProgram' : 'gpuProgram'; 9 | const program = filter[type]; 10 | const source = program[shader]; 11 | 12 | if (!source) { 13 | return ''; 14 | } 15 | 16 | if (typeof source === 'string') { 17 | return source; 18 | } 19 | 20 | return source.source; 21 | } 22 | -------------------------------------------------------------------------------- /packages/backend/src/rendering/readPixels.ts: -------------------------------------------------------------------------------- 1 | import type { GlRenderTarget, WebGLRenderer, WebGPURenderer } from 'pixi.js'; 2 | 3 | export function readGlPixels( 4 | gl: WebGLRenderingContext, 5 | renderer: WebGLRenderer, 6 | canvasTextures: string[], 7 | width: number, 8 | height: number, 9 | ) { 10 | // Create a buffer to hold the pixel data 11 | // Uint8Array for 8-bit per channel (RGBA), adjust as needed 12 | const pixels = new Uint8Array(width * height * 4); 13 | 14 | const renterTarget = renderer.renderTarget.getRenderTarget(renderer.renderTarget.renderTarget); 15 | const glRenterTarget = renderer.renderTarget.getGpuRenderTarget(renterTarget) as GlRenderTarget; 16 | // Bind the framebuffer you want to read from (null for default framebuffer) 17 | gl.bindFramebuffer(gl.FRAMEBUFFER, glRenterTarget.resolveTargetFramebuffer); 18 | 19 | // Read the pixels 20 | gl.readPixels( 21 | 0, 22 | 0, // Start reading from the bottom left of the framebuffer 23 | width, 24 | height, // The dimensions of the area you want to read 25 | gl.RGBA, // Format of the pixel data 26 | gl.UNSIGNED_BYTE, // Type of the pixel data 27 | pixels, // The buffer to read the pixels into 28 | ); 29 | 30 | // Create a 2D canvas to draw the pixels on 31 | const canvas2d = document.createElement('canvas'); 32 | canvas2d.width = width; 33 | canvas2d.height = height; 34 | const ctx = canvas2d.getContext('2d')!; 35 | 36 | // Create an ImageData object 37 | const imageData = new ImageData(new Uint8ClampedArray(pixels), width, height); 38 | 39 | // Draw the ImageData object to the canvas 40 | ctx.putImageData(imageData, 0, 0); 41 | 42 | // Convert the canvas to a data URL and set it as the src of an image element 43 | const dataUrl = canvas2d.toDataURL('image/webp', 0.5); 44 | canvasTextures.push(dataUrl); 45 | } 46 | 47 | export function readGPUPixels(renderer: WebGPURenderer, canvasTextures: string[]) { 48 | const webGPUCanvas = renderer.view.canvas as HTMLCanvasElement; 49 | 50 | const canvas = document.createElement('canvas'); 51 | canvas.width = webGPUCanvas.width; 52 | canvas.height = webGPUCanvas.height; 53 | 54 | const context = canvas.getContext('2d')!; 55 | 56 | context.drawImage(webGPUCanvas, 0, 0); 57 | 58 | // const { width, height } = webGPUCanvas; 59 | 60 | // context.getImageData(0, 0, width, height); 61 | 62 | // Convert the canvas to a data URL and set it as the src of an image element 63 | const dataUrl = canvas.toDataURL('image/webp', 0.5); 64 | canvasTextures.push(dataUrl); 65 | 66 | // const pixels = new Uint8ClampedArray(imageData.data.buffer); 67 | 68 | // return { pixels, width, height }; 69 | } 70 | -------------------------------------------------------------------------------- /packages/backend/src/rendering/stats.ts: -------------------------------------------------------------------------------- 1 | export class Stats { 2 | private frames: number = 0; 3 | private prevTime: number = 0; 4 | public fps: number = 0; 5 | public memory: number = 0; 6 | public maxMemory: number = 0; 7 | public drawCalls: number = 0; 8 | 9 | public update(): void { 10 | this.frames++; 11 | 12 | const time = (performance || Date).now(); 13 | 14 | if (time >= this.prevTime + 1000) { 15 | this.fps = (this.frames * 1000) / (time - this.prevTime); 16 | this.prevTime = time; 17 | this.frames = 0; 18 | 19 | // @ts-expect-error it does exist in chrome 20 | const memory = performance.memory; 21 | this.memory = memory.usedJSHeapSize / 1048576; 22 | this.maxMemory = memory.jsHeapSizeLimit / 1048576; 23 | } 24 | } 25 | 26 | public reset(): void { 27 | this.frames = 0; 28 | this.prevTime = 0; 29 | this.fps = 0; 30 | this.memory = 0; 31 | this.maxMemory = 0; 32 | this.drawCalls = 0; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/backend/src/scene/overlay/overlayExtension.ts: -------------------------------------------------------------------------------- 1 | import type { OverlayExtension } from '@pixi/devtools'; 2 | import type { Container } from 'pixi.js'; 3 | import { isParticleContainer } from '../../utils/getPixiType'; 4 | 5 | export const overlayExtension: OverlayExtension = { 6 | extension: { 7 | type: 'overlay', 8 | priority: -1, 9 | name: 'default-overlay', 10 | }, 11 | 12 | getSelectedStyle() { 13 | return { 14 | backgroundColor: 'hsla(340 70% 44% / 35%)', 15 | border: '1px solid hsla(0, 0%, 100%, 0.5)', 16 | }; 17 | }, 18 | 19 | getHoverStyle() { 20 | return { 21 | backgroundColor: 'hsla(192 84% 40% / 40%)', 22 | border: '1px solid hsla(0, 0%, 100%, 0.5)', 23 | }; 24 | }, 25 | 26 | getGlobalBounds(node: Container) { 27 | if (isParticleContainer(node)) { 28 | if (node.boundsArea) return node.boundsArea; 29 | // unknown bounds 30 | return { x: 0, y: 0, width: 0, height: 0 }; 31 | } 32 | return node.getBounds(); 33 | }, 34 | }; 35 | -------------------------------------------------------------------------------- /packages/backend/src/scene/scene.ts: -------------------------------------------------------------------------------- 1 | import type { Container } from 'pixi.js'; 2 | import { PixiHandler } from '../handler'; 3 | import { Stats } from './stats/stats'; 4 | import type { PixiDevtools } from '../pixi'; 5 | import { Overlay } from './overlay/overlay'; 6 | import { Tree } from './tree/tree'; 7 | import { Properties } from './tree/properties'; 8 | 9 | export class Scene extends PixiHandler { 10 | public stats: Stats; 11 | public overlay: Overlay; 12 | public tree: Tree; 13 | public properties: Properties; 14 | 15 | // scene state 16 | // public get stats(){ 17 | // return this.stats.stats; 18 | // } 19 | 20 | constructor(devtool: typeof PixiDevtools) { 21 | super(devtool); 22 | this.stats = new Stats(devtool); 23 | this.overlay = new Overlay(devtool); 24 | this.tree = new Tree(devtool); 25 | this.properties = new Properties(devtool); 26 | } 27 | 28 | public override init() { 29 | this.stats.init(); 30 | this.overlay.init(); 31 | this.tree.init(); 32 | this.properties.init(); 33 | } 34 | public override reset() { 35 | this.stats.reset(); 36 | this.overlay.reset(); 37 | this.tree.reset(); 38 | this.properties.reset(); 39 | } 40 | public override preupdate() { 41 | this.stats.preupdate(); 42 | this.overlay.preupdate(); 43 | this.tree.preupdate(); 44 | this.properties.preupdate(); 45 | } 46 | public override loop(container: Container) { 47 | this.stats.loop(container); 48 | this.tree.loop(container); 49 | } 50 | public override postupdate() { 51 | this.stats.postupdate(); 52 | this.overlay.postupdate(); 53 | this.tree.postupdate(); 54 | this.properties.postupdate(); 55 | } 56 | 57 | public override throttledUpdate() { 58 | this.stats.throttledUpdate(); 59 | this.overlay.throttledUpdate(); 60 | this.tree.throttledUpdate(); 61 | this.properties.throttledUpdate(); 62 | } 63 | public override update() { 64 | this.stats.update(); 65 | this.overlay.update(); 66 | this.tree.update(); 67 | this.properties.update(); 68 | } 69 | 70 | public getStats() { 71 | return this.stats.stats; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /packages/backend/src/scene/stats/stats.ts: -------------------------------------------------------------------------------- 1 | import type { StatsExtension } from '@pixi/devtools'; 2 | import type { Container } from 'pixi.js'; 3 | import { extensions } from '../../extensions/Extensions'; 4 | import { getExtensionsProp } from '../../extensions/getExtension'; 5 | import { PixiHandler } from '../../handler'; 6 | 7 | export class Stats extends PixiHandler { 8 | public static extensions: StatsExtension[] = []; 9 | private _extensions: Required[] = []; 10 | 11 | public stats: Record = {}; 12 | 13 | public override init() { 14 | this._extensions = getExtensionsProp(Stats.extensions, 'track'); 15 | const allKeys: string[] = []; 16 | for (const plugin of this._extensions) { 17 | allKeys.push(...plugin.getKeys()); 18 | } 19 | 20 | // check for duplicates 21 | const duplicates = allKeys.filter((item, index) => allKeys.indexOf(item) !== index); 22 | 23 | if (duplicates.length > 0) { 24 | console.warn(`[PixiJS Devtools] Stats: Duplicate keys found: ${duplicates.join(', ')}`); 25 | } 26 | } 27 | 28 | public override reset() { 29 | this.stats = {}; 30 | } 31 | 32 | public override throttledUpdate(): void { 33 | this.stats = {}; 34 | } 35 | 36 | public override loop(container: Container) { 37 | for (const plugin of this._extensions) { 38 | plugin.track(container, this.stats); 39 | } 40 | } 41 | 42 | public override postupdate() { 43 | for (const key in this.stats) { 44 | // also format the keys to be more readable 45 | const formattedKey = this.formatCamelCase(key); 46 | this.stats[formattedKey] = this.stats[key]; 47 | delete this.stats[key]; 48 | } 49 | } 50 | 51 | private formatCamelCase(string: string) { 52 | let result = string.replace(/([A-Z])/g, ' $1'); 53 | result = result.charAt(0).toUpperCase() + result.slice(1); 54 | return result; 55 | } 56 | } 57 | 58 | extensions.handleByList('stats', Stats.extensions); 59 | -------------------------------------------------------------------------------- /packages/backend/src/scene/stats/statsExtension.ts: -------------------------------------------------------------------------------- 1 | import type { DevtoolState } from '@devtool/frontend/types'; 2 | import type { StatsExtension } from '@pixi/devtools'; 3 | import type { Container } from 'pixi.js'; 4 | import { getPixiType } from '../../utils/getPixiType'; 5 | 6 | export const totalStatsExtension: StatsExtension = { 7 | extension: { 8 | type: 'stats', 9 | name: 'default-stats-total', 10 | }, 11 | 12 | track: (_container: Container, state: NonNullable) => { 13 | state.total = (state.total || 0) + 1; 14 | }, 15 | 16 | getKeys: () => { 17 | return ['total']; 18 | }, 19 | }; 20 | 21 | const typeMap = { 22 | Container: 'container', 23 | Sprite: 'sprite', 24 | Graphics: 'graphics', 25 | Mesh: 'mesh', 26 | Text: 'text', 27 | BitmapText: 'bitmapText', 28 | HTMLText: 'htmlText', 29 | AnimatedSprite: 'animatedSprite', 30 | NineSliceSprite: 'nineSliceSprite', 31 | TilingSprite: 'tilingSprite', 32 | ParticleContainer: 'particleContainer', 33 | }; 34 | 35 | export const pixiStatsExtension = { 36 | extension: { 37 | type: 'stats', 38 | name: 'default-stats-pixi', 39 | }, 40 | track(container: Container, state: NonNullable) { 41 | const type = getPixiType(container) as keyof typeof typeMap; 42 | const mappedType = typeMap[type]; 43 | 44 | if (mappedType) { 45 | state[mappedType] = (state[mappedType] || 0) + 1; 46 | } 47 | }, 48 | getKeys: () => { 49 | return Object.values(typeMap); 50 | }, 51 | } satisfies StatsExtension; 52 | -------------------------------------------------------------------------------- /packages/backend/src/scene/tree/extensions/animatedSprite/animatedSpritePropertyExtension.ts: -------------------------------------------------------------------------------- 1 | import type { PropertiesEntry } from '@pixi/devtools'; 2 | import type { AnimatedSprite } from 'pixi.js'; 3 | import { PixiDevtools } from '../../../../pixi'; 4 | import { isAnimatedSprite } from '../../../../utils/getPixiType'; 5 | import type { DefaultPropertyExtension } from '../container/containerPropertyExtension'; 6 | import { sharedAnimatedSpriteProps } from './sharedAnimatedSpriteProps'; 7 | 8 | export const animatedSpritePropertyExtension: DefaultPropertyExtension = { 9 | extension: { 10 | type: 'sceneProperties', 11 | name: 'default-animated-sprite-properties', 12 | priority: 0, 13 | }, 14 | properties() { 15 | return sharedAnimatedSpriteProps; 16 | }, 17 | testNode(container: AnimatedSprite) { 18 | return isAnimatedSprite(container, PixiDevtools.pixi); 19 | }, 20 | testProp(prop: string) { 21 | // test if the property is in the list 22 | return this.properties().some((p) => prop.startsWith(p.prop)); 23 | }, 24 | getProperties(container: AnimatedSprite) { 25 | // get the properties from the container 26 | const activeProps = this.properties().reduce((result, property) => { 27 | const prop = property.prop; 28 | let value = container[prop as keyof AnimatedSprite]; 29 | 30 | if (value != null && (prop === 'gotoAndPlay' || prop === 'stop' || prop === 'play')) { 31 | value = true; 32 | } 33 | 34 | if (value == null && property.allowUndefined !== true) { 35 | return result; 36 | } 37 | 38 | result.push({ 39 | ...property, 40 | value, 41 | }); 42 | 43 | return result; 44 | }, [] as PropertiesEntry[]); 45 | 46 | return activeProps; 47 | }, 48 | setProperty(container: AnimatedSprite, prop, value) { 49 | if (prop === 'gotoAndPlay') { 50 | container.gotoAndPlay(0); 51 | } else if (prop === 'stop') { 52 | container.stop(); 53 | } else if (prop === 'play') { 54 | container.play(); 55 | } else { 56 | (container as any)[prop] = value; 57 | } 58 | }, 59 | }; 60 | -------------------------------------------------------------------------------- /packages/backend/src/scene/tree/extensions/animatedSprite/sharedAnimatedSpriteProps.ts: -------------------------------------------------------------------------------- 1 | import type { Properties } from '@pixi/devtools'; 2 | 3 | export const sharedAnimatedSpriteProps = [ 4 | { prop: 'animationSpeed', entry: { section: 'Animated Sprite', type: 'number' } }, 5 | { prop: 'loop', entry: { section: 'Animated Sprite', type: 'boolean' } }, 6 | { prop: 'updateAnchor', entry: { section: 'Animated Sprite', type: 'boolean' } }, 7 | { 8 | prop: 'totalFrames', 9 | entry: { section: 'Animated Sprite', type: 'number', options: { disabled: true } }, 10 | }, 11 | { prop: 'currentFrame', entry: { section: 'Animated Sprite', type: 'number' } }, 12 | { prop: 'autoUpdate', entry: { section: 'Animated Sprite', type: 'boolean' } }, 13 | { prop: 'playing', entry: { section: 'Animated Sprite', type: 'boolean' } }, 14 | { 15 | prop: 'gotoAndPlay', 16 | entry: { section: 'Animated Sprite', type: 'button', label: 'start' }, 17 | }, 18 | { prop: 'stop', entry: { section: 'Animated Sprite', type: 'button', label: 'stop' } }, 19 | { prop: 'play', entry: { section: 'Animated Sprite', type: 'button', label: 'play' } }, 20 | ] as Properties[]; 21 | -------------------------------------------------------------------------------- /packages/backend/src/scene/tree/extensions/container/v7ContainerProps.ts: -------------------------------------------------------------------------------- 1 | import type { Properties } from '@pixi/devtools'; 2 | import { sharedContainerProps } from './sharedContainerProps'; 3 | 4 | export const v7BlendModeMap = { 5 | normal: 0, 6 | add: 1, 7 | multiply: 2, 8 | screen: 3, 9 | overlay: 4, 10 | darken: 5, 11 | lighten: 6, 12 | colorDodge: 7, 13 | colorBurn: 8, 14 | hardLight: 9, 15 | softLight: 10, 16 | difference: 11, 17 | exclusion: 12, 18 | hue: 13, 19 | saturation: 14, 20 | color: 15, 21 | luminosity: 16, 22 | none: 20, 23 | }; 24 | 25 | export const v7ContainerProps = [ 26 | ...sharedContainerProps, 27 | { prop: 'name', entry: { section: 'Info', type: 'text' } }, 28 | { 29 | prop: 'blendMode', 30 | entry: { 31 | section: 'Appearance', 32 | options: { 33 | options: [ 34 | 'normal', 35 | 'add', 36 | 'multiply', 37 | 'screen', 38 | 'overlay', 39 | 'darken', 40 | 'lighten', 41 | 'color-dodge', 42 | 'color-burn', 43 | 'hard-light', 44 | 'soft-light', 45 | 'difference', 46 | 'exclusion', 47 | 'hue', 48 | 'saturation', 49 | 'color', 50 | 'luminosity', 51 | 'none', 52 | ], 53 | }, 54 | type: 'select', 55 | }, 56 | }, 57 | ] as Properties[]; 58 | -------------------------------------------------------------------------------- /packages/backend/src/scene/tree/extensions/container/v8ContainerProps.ts: -------------------------------------------------------------------------------- 1 | import type { Properties } from '@pixi/devtools'; 2 | import { sharedContainerProps } from './sharedContainerProps'; 3 | 4 | export const v8ContainerProps = [ 5 | ...sharedContainerProps, 6 | { prop: 'label', entry: { section: 'Info', type: 'text' } }, 7 | { 8 | prop: 'blendMode', 9 | entry: { 10 | section: 'Appearance', 11 | tooltip: `To use certain modes, enable them as follows:\n{{import 'pixi.js/advanced-blend-modes';}}`, 12 | options: { 13 | options: [ 14 | 'inherit', 15 | 'normal', 16 | 'add', 17 | 'multiply', 18 | 'screen', 19 | 'overlay', 20 | 'darken', 21 | 'lighten', 22 | 'color-dodge', 23 | 'color-burn', 24 | 'hard-light', 25 | 'soft-light', 26 | 'difference', 27 | 'exclusion', 28 | 'hue', 29 | 'saturation', 30 | 'color', 31 | 'luminosity', 32 | 'none', 33 | 'erase', 34 | 'subtract', 35 | 'linear-burn', 36 | 'linear-dodge', 37 | 'linear-light', 38 | 'pin-light', 39 | 'divide', 40 | 'vivid-light', 41 | 'hard-mix', 42 | 'negation', 43 | ], 44 | }, 45 | type: 'select', 46 | }, 47 | }, 48 | { 49 | prop: 'boundsArea', 50 | entry: { 51 | section: 'Rendering', 52 | options: { 53 | inputs: [{ label: 'x' }, { label: 'y' }, { label: 'width' }, { label: 'height' }], 54 | }, 55 | type: 'vectorX', 56 | }, 57 | }, 58 | { prop: 'isRenderGroup', entry: { section: 'Rendering', type: 'boolean' } }, 59 | 60 | { prop: 'cullable', entry: { section: 'Culling', type: 'boolean' } }, 61 | { 62 | prop: 'cullArea', 63 | entry: { 64 | section: 'Culling', 65 | options: { 66 | inputs: [{ label: 'x' }, { label: 'y' }, { label: 'width' }, { label: 'height' }], 67 | }, 68 | type: 'vectorX', 69 | }, 70 | }, 71 | { prop: 'cullableChildren', entry: { section: 'Culling', type: 'boolean' } }, 72 | ] as Properties[]; 73 | -------------------------------------------------------------------------------- /packages/backend/src/scene/tree/extensions/ninesliceSprite/ninesliceSpritePropertyExtension.ts: -------------------------------------------------------------------------------- 1 | import type { AnimatedSprite } from 'pixi.js'; 2 | import { PixiDevtools } from '../../../../pixi'; 3 | import { isNineSliceSprite } from '../../../../utils/getPixiType'; 4 | import type { DefaultPropertyExtension } from '../container/containerPropertyExtension'; 5 | import { getProps } from '../utils/getProps'; 6 | import { v7NineSliceProps } from './v7NinesliceSpriteProps'; 7 | import { v8NinesSliceProps } from './v8NinesliceSpriteProps'; 8 | 9 | export const nineSlicePropertyExtension: DefaultPropertyExtension = { 10 | extension: { 11 | type: 'sceneProperties', 12 | name: 'default-nineslice-properties', 13 | priority: 0, 14 | }, 15 | properties() { 16 | return getProps(v7NineSliceProps, v8NinesSliceProps); 17 | }, 18 | testNode(container: AnimatedSprite) { 19 | return isNineSliceSprite(container, PixiDevtools.pixi); 20 | }, 21 | testProp(prop: string) { 22 | // test if the property is in the list 23 | return this.properties().some((p) => prop.startsWith(p.prop)); 24 | }, 25 | getProperties(container: AnimatedSprite) { 26 | // get the properties from the container 27 | const activeProps = this.properties().reduce((result, property) => { 28 | const prop = property.prop; 29 | const value = container[prop as keyof AnimatedSprite]; 30 | 31 | if (value == null && property.allowUndefined !== true) { 32 | return result; 33 | } 34 | 35 | result.push({ 36 | ...property, 37 | value, 38 | }); 39 | 40 | return result; 41 | }, [] as any[]); 42 | 43 | return activeProps; 44 | }, 45 | setProperty(container: AnimatedSprite, prop, value) { 46 | (container as any)[prop] = value; 47 | }, 48 | }; 49 | -------------------------------------------------------------------------------- /packages/backend/src/scene/tree/extensions/ninesliceSprite/sharedNinesliceSpriteProps.ts: -------------------------------------------------------------------------------- 1 | import type { Properties } from '@pixi/devtools'; 2 | 3 | export const sharedNineSliceProps = [ 4 | { prop: 'leftWidth', entry: { section: 'NineSlice Sprite', type: 'number' } }, 5 | { prop: 'rightWidth', entry: { section: 'NineSlice Sprite', type: 'number' } }, 6 | { prop: 'topHeight', entry: { section: 'NineSlice Sprite', type: 'number' } }, 7 | { 8 | prop: 'bottomHeight', 9 | entry: { 10 | section: 'NineSlice Sprite', 11 | type: 'number', 12 | }, 13 | }, 14 | ] as Properties[]; 15 | -------------------------------------------------------------------------------- /packages/backend/src/scene/tree/extensions/ninesliceSprite/v7NinesliceSpriteProps.ts: -------------------------------------------------------------------------------- 1 | import type { Properties } from '@pixi/devtools'; 2 | import { sharedViewProps } from '../view/viewProps'; 3 | 4 | export const v7NineSliceProps = [...sharedViewProps] as Properties[]; 5 | -------------------------------------------------------------------------------- /packages/backend/src/scene/tree/extensions/ninesliceSprite/v8NinesliceSpriteProps.ts: -------------------------------------------------------------------------------- 1 | import type { Properties } from '@pixi/devtools'; 2 | import { sharedNineSliceProps } from './sharedNinesliceSpriteProps'; 3 | 4 | export const v8NinesSliceProps = [ 5 | ...sharedNineSliceProps, 6 | { 7 | prop: 'originalWidth', 8 | entry: { 9 | section: 'NineSlice Sprite', 10 | type: 'number', 11 | }, 12 | }, 13 | { 14 | prop: 'originalHeight', 15 | entry: { 16 | section: 'NineSlice Sprite', 17 | type: 'number', 18 | }, 19 | }, 20 | ] as Properties[]; 21 | -------------------------------------------------------------------------------- /packages/backend/src/scene/tree/extensions/text/textPropertyExtension.ts: -------------------------------------------------------------------------------- 1 | import type { PropertiesEntry } from '@pixi/devtools'; 2 | import type { Text } from 'pixi.js'; 3 | import { PixiDevtools } from '../../../../pixi'; 4 | import { isText } from '../../../../utils/getPixiType'; 5 | import type { DefaultPropertyExtension } from '../container/containerPropertyExtension'; 6 | import { textProps } from './textProps'; 7 | 8 | export const textPropertyExtension: DefaultPropertyExtension = { 9 | extension: { 10 | type: 'sceneProperties', 11 | name: 'default-text-properties', 12 | priority: 0, 13 | }, 14 | properties() { 15 | return textProps; 16 | }, 17 | testNode(container: Text) { 18 | return isText(container, PixiDevtools.pixi); 19 | }, 20 | testProp(prop: string) { 21 | // test if the property is in the list 22 | return this.properties().some((p) => prop.startsWith(p.prop)); 23 | }, 24 | getProperties(container: Text) { 25 | // get the properties from the container 26 | const activeProps = this.properties().reduce((result, property) => { 27 | const prop = property.prop; 28 | const value = container[prop as keyof Text]; 29 | 30 | if (value == null && property.allowUndefined !== true) { 31 | return result; 32 | } 33 | 34 | result.push({ 35 | ...property, 36 | value, 37 | }); 38 | 39 | return result; 40 | }, [] as PropertiesEntry[]); 41 | 42 | return activeProps; 43 | }, 44 | setProperty(container: Text, prop, value) { 45 | (container as any)[prop] = value; 46 | }, 47 | }; 48 | -------------------------------------------------------------------------------- /packages/backend/src/scene/tree/extensions/text/textProps.ts: -------------------------------------------------------------------------------- 1 | import type { Properties } from '@pixi/devtools'; 2 | 3 | export const textProps = [ 4 | { prop: 'text', entry: { section: 'Text', type: 'text' } }, 5 | // { section: 'Text', property: 'style', propertyProps: { label: 'Style' }, type: 'text' }, 6 | { prop: 'resolution', entry: { section: 'Text', type: 'number' } }, 7 | ] as Properties[]; 8 | -------------------------------------------------------------------------------- /packages/backend/src/scene/tree/extensions/tilingSprite/sharedTilingSpriteProps.ts: -------------------------------------------------------------------------------- 1 | import type { Properties } from '@pixi/devtools'; 2 | 3 | export const sharedTilingSpriteProps = [ 4 | { 5 | prop: 'tilePosition', 6 | entry: { 7 | section: 'Tiling Sprite', 8 | options: { x: { label: 'x' }, y: { label: 'y' } }, 9 | type: 'vector2', 10 | }, 11 | }, 12 | { 13 | prop: 'tileScale', 14 | entry: { 15 | section: 'Tiling Sprite', 16 | options: { x: { label: 'x' }, y: { label: 'y' } }, 17 | type: 'vector2', 18 | }, 19 | }, 20 | { prop: 'tileRotation', entry: { section: 'Tiling Sprite', type: 'number' } }, 21 | { prop: 'clampMargin', entry: { section: 'Tiling Sprite', type: 'number' } }, 22 | ] as Properties[]; 23 | -------------------------------------------------------------------------------- /packages/backend/src/scene/tree/extensions/tilingSprite/tilingSpritePropertyExtension.ts: -------------------------------------------------------------------------------- 1 | import type { PropertiesEntry } from '@pixi/devtools'; 2 | import type { Container } from 'pixi.js'; 3 | import { PixiDevtools } from '../../../../pixi'; 4 | import { isTilingSprite } from '../../../../utils/getPixiType'; 5 | import type { DefaultPropertyExtension } from '../container/containerPropertyExtension'; 6 | import { arrayToPoint, pointToArray } from '../utils/convertProp'; 7 | import { getProps } from '../utils/getProps'; 8 | import { v7TilingSpriteProps } from './v7TilingSpriteProps'; 9 | import { v8TilingSpriteProps } from './v8TilingSpriteProps'; 10 | 11 | const propertyValueExtractors = { 12 | tilePosition: pointToArray, 13 | tileScale: pointToArray, 14 | } as const; 15 | 16 | type PropertyValueExtractors = keyof typeof propertyValueExtractors; 17 | 18 | const propertyValueSetters: Record = { 19 | tilePosition: arrayToPoint, 20 | tileScale: arrayToPoint, 21 | }; 22 | 23 | export const tilingSpritePropertyExtension: DefaultPropertyExtension = { 24 | extension: { 25 | type: 'sceneProperties', 26 | name: 'default-tiling-sprite-properties', 27 | priority: 0, 28 | }, 29 | properties() { 30 | return getProps(v7TilingSpriteProps, v8TilingSpriteProps); 31 | }, 32 | testNode(container) { 33 | return isTilingSprite(container, PixiDevtools.pixi); 34 | }, 35 | testProp(prop: string) { 36 | return this.properties().some((p) => prop.startsWith(p.prop)); 37 | }, 38 | getProperties(container) { 39 | const activeProps = this.properties().reduce((result, property) => { 40 | const prop = property.prop; 41 | let value = container[prop as keyof Container]; 42 | 43 | if (value != null && propertyValueExtractors[prop as PropertyValueExtractors]) { 44 | value = propertyValueExtractors[prop as PropertyValueExtractors](value); 45 | } 46 | 47 | if (value == null && property.allowUndefined !== true) { 48 | return result; 49 | } 50 | 51 | result.push({ 52 | ...property, 53 | value, 54 | }); 55 | 56 | return result; 57 | }, [] as PropertiesEntry[]); 58 | 59 | return activeProps; 60 | }, 61 | setProperty(container, prop, value) { 62 | // set the property on the container 63 | if (propertyValueSetters[prop as PropertyValueExtractors]) { 64 | propertyValueSetters[prop as PropertyValueExtractors](container, prop, value); 65 | } else { 66 | (container as any)[prop] = value; 67 | } 68 | }, 69 | }; 70 | -------------------------------------------------------------------------------- /packages/backend/src/scene/tree/extensions/tilingSprite/v7TilingSpriteProps.ts: -------------------------------------------------------------------------------- 1 | import type { Properties } from '@pixi/devtools'; 2 | import { sharedTilingSpriteProps } from './sharedTilingSpriteProps'; 3 | 4 | export const v7TilingSpriteProps = [ 5 | ...sharedTilingSpriteProps, 6 | { value: null, prop: 'uvRespectAnchor', entry: { section: 'Transform', type: 'boolean' } }, 7 | ] as Properties[]; 8 | -------------------------------------------------------------------------------- /packages/backend/src/scene/tree/extensions/tilingSprite/v8TilingSpriteProps.ts: -------------------------------------------------------------------------------- 1 | import type { Properties } from '@pixi/devtools'; 2 | import { sharedTilingSpriteProps } from './sharedTilingSpriteProps'; 3 | 4 | export const v8TilingSpriteProps = [...sharedTilingSpriteProps] as Properties[]; 5 | -------------------------------------------------------------------------------- /packages/backend/src/scene/tree/extensions/utils/convertProp.ts: -------------------------------------------------------------------------------- 1 | export const pointToArray = (value: any) => [value.x, value.y]; 2 | export const boundsToArray = (value: any) => [value.x, value.y, value.width, value.height]; 3 | export const matrixToArray = (value: any) => [value.a, value.b, value.c, value.d, value.tx, value.ty]; 4 | 5 | export const arrayToPoint = (container: any, prop: string, value: any) => container[prop].set(value[0], value[1]); 6 | export const arrayToBounds = (container: any, prop: string, value: any) => { 7 | container[prop].x = value[0]; 8 | container[prop].y = value[1]; 9 | container[prop].width = value[2]; 10 | container[prop].height = value[3]; 11 | }; 12 | export const arrayToMatrix = (container: any, prop: string, value: any) => container[prop].fromArray(value); 13 | -------------------------------------------------------------------------------- /packages/backend/src/scene/tree/extensions/utils/getProps.ts: -------------------------------------------------------------------------------- 1 | import { PixiDevtools } from '../../../../pixi'; 2 | 3 | export function getProps(v7: T, v8: K) { 4 | if (PixiDevtools.majorVersion === '7') { 5 | return v7; 6 | } 7 | 8 | return v8; 9 | } 10 | -------------------------------------------------------------------------------- /packages/backend/src/scene/tree/extensions/view/viewPropertyExtension.ts: -------------------------------------------------------------------------------- 1 | import type { PropertiesEntry } from '@pixi/devtools'; 2 | import type { AnimatedSprite } from 'pixi.js'; 3 | import type { DefaultPropertyExtension } from '../container/containerPropertyExtension'; 4 | import { arrayToPoint, pointToArray } from '../utils/convertProp'; 5 | import { sharedViewProps } from './viewProps'; 6 | 7 | export const viewPropertyExtension: DefaultPropertyExtension = { 8 | extension: { 9 | type: 'sceneProperties', 10 | name: 'default-view-properties', 11 | priority: 0, 12 | }, 13 | properties() { 14 | return sharedViewProps; 15 | }, 16 | testNode() { 17 | // any node could have these properties, so might as well always check 18 | return true; 19 | }, 20 | testProp(prop: string) { 21 | // test if the property is in the list 22 | return this.properties().some((p) => prop.startsWith(p.prop)); 23 | }, 24 | getProperties(container: AnimatedSprite) { 25 | // get the properties from the container 26 | const activeProps = this.properties().reduce((result, property) => { 27 | const prop = property.prop; 28 | let value = container[prop as keyof AnimatedSprite]; 29 | 30 | if (value != null && prop === 'anchor') { 31 | value = pointToArray(value); 32 | } 33 | 34 | if (value == null && property.allowUndefined !== true) { 35 | return result; 36 | } 37 | 38 | result.push({ 39 | ...property, 40 | value, 41 | }); 42 | 43 | return result; 44 | }, [] as PropertiesEntry[]); 45 | 46 | return activeProps; 47 | }, 48 | setProperty(container: AnimatedSprite, prop, value) { 49 | if (prop === 'anchor') { 50 | arrayToPoint(container, prop, value); 51 | } else { 52 | (container as any)[prop] = value; 53 | } 54 | }, 55 | }; 56 | -------------------------------------------------------------------------------- /packages/backend/src/scene/tree/extensions/view/viewProps.ts: -------------------------------------------------------------------------------- 1 | import type { Properties } from '@pixi/devtools'; 2 | 3 | export const sharedViewProps = [ 4 | { 5 | prop: 'anchor', 6 | entry: { section: 'Transform', options: { x: { label: 'x' }, y: { label: 'y' } }, type: 'vector2' }, 7 | }, 8 | { prop: 'roundPixels', entry: { section: 'Transform', type: 'boolean' } }, 9 | { 10 | prop: 'batched', 11 | entry: { 12 | section: 'Rendering', 13 | options: { label: 'Batched', disabled: true }, 14 | type: 'boolean', 15 | }, 16 | }, 17 | ] as Properties[]; 18 | -------------------------------------------------------------------------------- /packages/backend/src/scene/tree/properties.ts: -------------------------------------------------------------------------------- 1 | import type { PropertiesExtension } from '@pixi/devtools'; 2 | import { extensions } from '../../extensions/Extensions'; 3 | import { PixiHandler } from '../../handler'; 4 | 5 | export class Properties extends PixiHandler { 6 | public static extensions: PropertiesExtension[] = []; 7 | 8 | private _extensions!: PropertiesExtension[]; 9 | 10 | public override init() { 11 | this._extensions = Properties.extensions; 12 | } 13 | 14 | public setValue(prop: string, value: any) { 15 | const selectedNode = this._devtool.scene.tree.selectedNode; 16 | 17 | if (!selectedNode) return; 18 | 19 | this._extensions.forEach((plugin) => { 20 | if (plugin.testNode(selectedNode) && plugin.testProp(prop)) { 21 | plugin.setProperty(selectedNode, prop, value); 22 | } 23 | }); 24 | } 25 | 26 | public getActiveProps() { 27 | const selectedNode = this._devtool.scene.tree.selectedNode; 28 | if (!selectedNode) return; 29 | 30 | if (selectedNode.__devtoolLocked) return; 31 | 32 | const activeProps = this._extensions.reduce( 33 | (result, plugin) => { 34 | if (plugin.testNode(selectedNode)) { 35 | result.push(...plugin.getProperties(selectedNode)); 36 | } 37 | return result; 38 | }, 39 | [] as ReturnType, 40 | ); 41 | 42 | return activeProps || []; 43 | } 44 | } 45 | 46 | extensions.handleByList('sceneProperties', Properties.extensions); 47 | -------------------------------------------------------------------------------- /packages/backend/src/utils/loop.ts: -------------------------------------------------------------------------------- 1 | import type { Container } from 'pixi.js'; 2 | 3 | interface LoopOptions { 4 | loop: (container: Container, parent: Container) => void; 5 | test?: (container: Container, parent: Container) => boolean | 'children'; 6 | container: Container; 7 | } 8 | 9 | export function loop(options: LoopOptions) { 10 | const { container, ...rest } = options; 11 | 12 | loopRecursive(container, rest); 13 | } 14 | 15 | function loopRecursive(container: Container, opts: Omit) { 16 | const { loop, test } = opts; 17 | 18 | if (!container) { 19 | return; 20 | } 21 | 22 | const testResult = test?.(container, container.parent) ?? true; 23 | 24 | if (testResult === false) { 25 | return; 26 | } 27 | 28 | loop(container, container.parent); 29 | 30 | if (container.children.length === 0 || testResult === 'children') { 31 | return; 32 | } 33 | 34 | for (let i = 0; i < container.children.length; i++) { 35 | loopRecursive(container.children[i], opts); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/backend/src/utils/poller.ts: -------------------------------------------------------------------------------- 1 | import { DevtoolMessage } from '@devtool/frontend/types'; 2 | import { PixiDevtools } from '../pixi'; 3 | 4 | // Constants 5 | const MAX_TRIES = 10; 6 | const POLLING_INTERVAL_MS = 1000; 7 | 8 | let pixiPollingInterval: number | undefined; 9 | let tryCount = 0; 10 | 11 | // Function to start polling for PixiJS 12 | export function pollPixi(foundCallback: () => void, loopCallback?: () => void, stopCallback?: () => void) { 13 | // Start a new interval 14 | pixiPollingInterval = window.setInterval(() => { 15 | // If we've tried more than the max tries, stop polling 16 | if (tryCount > MAX_TRIES) { 17 | stopPolling(); 18 | if (stopCallback) { 19 | stopCallback(); 20 | } 21 | return; 22 | } 23 | 24 | // If a loop callback is provided, call it 25 | if (loopCallback) { 26 | loopCallback(); 27 | } 28 | // Try to detect Pixi 29 | tryDetectPixi(foundCallback); 30 | 31 | // Increment the try count 32 | tryCount += 1; 33 | }, POLLING_INTERVAL_MS); 34 | 35 | // Return the interval ID 36 | return pixiPollingInterval; 37 | } 38 | 39 | // Function to stop polling 40 | function stopPolling() { 41 | if (pixiPollingInterval) { 42 | clearInterval(pixiPollingInterval); 43 | } 44 | } 45 | 46 | // Function to try to detect Pixi 47 | function tryDetectPixi(foundCallback: () => void) { 48 | try { 49 | const pixiDetectionResult = PixiDevtools.isPixiActive; 50 | 51 | // If Pixi is active, stop polling and post a message 52 | if (pixiDetectionResult === DevtoolMessage.active) { 53 | stopPolling(); 54 | foundCallback(); 55 | } else { 56 | window.postMessage({ method: pixiDetectionResult, data: JSON.stringify({}) }, '*'); 57 | } 58 | } catch (error) { 59 | // If an error occurred, stop polling 60 | stopPolling(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /packages/backend/src/utils/throttle.ts: -------------------------------------------------------------------------------- 1 | export class Throttle { 2 | private lastUpdateTime: number; 3 | 4 | constructor() { 5 | this.lastUpdateTime = 0; 6 | } 7 | 8 | shouldExecute(interval: number): boolean { 9 | const currentTime = Date.now(); 10 | if (currentTime - this.lastUpdateTime >= interval) { 11 | this.lastUpdateTime = currentTime; 12 | return true; 13 | } 14 | return false; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/backend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "paths": { 5 | "@devtool/frontend/*": ["packages/frontend/src/*"], 6 | "@pixi/devtools": ["packages/api/src/index.ts"] 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/devtool-chrome/assets/contentStyle.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixijs/devtools/4cc27578299d8deb8f868c9eafd008ce807ff13b/packages/devtool-chrome/assets/contentStyle.css -------------------------------------------------------------------------------- /packages/devtool-chrome/assets/pixi-icon-active-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixijs/devtools/4cc27578299d8deb8f868c9eafd008ce807ff13b/packages/devtool-chrome/assets/pixi-icon-active-128.png -------------------------------------------------------------------------------- /packages/devtool-chrome/assets/pixi-icon-active-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixijs/devtools/4cc27578299d8deb8f868c9eafd008ce807ff13b/packages/devtool-chrome/assets/pixi-icon-active-16.png -------------------------------------------------------------------------------- /packages/devtool-chrome/assets/pixi-icon-active-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixijs/devtools/4cc27578299d8deb8f868c9eafd008ce807ff13b/packages/devtool-chrome/assets/pixi-icon-active-48.png -------------------------------------------------------------------------------- /packages/devtool-chrome/assets/pixi-icon-inactive-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixijs/devtools/4cc27578299d8deb8f868c9eafd008ce807ff13b/packages/devtool-chrome/assets/pixi-icon-inactive-128.png -------------------------------------------------------------------------------- /packages/devtool-chrome/assets/pixi-icon-inactive-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixijs/devtools/4cc27578299d8deb8f868c9eafd008ce807ff13b/packages/devtool-chrome/assets/pixi-icon-inactive-16.png -------------------------------------------------------------------------------- /packages/devtool-chrome/assets/pixi-icon-inactive-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixijs/devtools/4cc27578299d8deb8f868c9eafd008ce807ff13b/packages/devtool-chrome/assets/pixi-icon-inactive-48.png -------------------------------------------------------------------------------- /packages/devtool-chrome/manifest.dev.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "DEV: PixiJS DevTools", 3 | "action": { 4 | "default_icon": { 5 | "16": "pixi-icon-inactive-16.png", 6 | "48": "pixi-icon-inactive-48.png", 7 | "128": "pixi-icon-inactive-128.png" 8 | } 9 | }, 10 | "content_scripts": [ 11 | { 12 | "matches": ["http://*/*", "https://*/*", ""], 13 | "js": ["content/index.ts"], 14 | "css": ["contentStyle.css"], 15 | "run_at": "document_start" 16 | } 17 | ], 18 | "background": { 19 | "service_worker": "background/index.ts", 20 | "type": "module" 21 | }, 22 | "icons": { 23 | "16": "pixi-icon-active-16.png", 24 | "48": "pixi-icon-active-48.png", 25 | "128": "pixi-icon-active-128.png" 26 | }, 27 | "permissions": ["activeTab"], 28 | "devtools_page": "devtools/pixi-index.html", 29 | "web_accessible_resources": [ 30 | { 31 | "resources": ["contentStyle.css", "pixi-icon-active-128.png", "pixi-icon-inactive-48.png"], 32 | "matches": [""] 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /packages/devtool-chrome/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "PixiJS DevTools", 4 | "description": "DevTools for PixiJS", 5 | "action": { 6 | "default_icon": { 7 | "16": "pixi-icon-inactive-16.png", 8 | "48": "pixi-icon-inactive-48.png", 9 | "128": "pixi-icon-inactive-128.png" 10 | } 11 | }, 12 | "content_scripts": [ 13 | { 14 | "matches": ["http://*/*", "https://*/*", ""], 15 | "js": ["content/index.ts"], 16 | "css": ["contentStyle.css"], 17 | "run_at": "document_start" 18 | } 19 | ], 20 | "background": { 21 | "service_worker": "background/index.ts", 22 | "type": "module" 23 | }, 24 | "icons": { 25 | "16": "pixi-icon-active-16.png", 26 | "48": "pixi-icon-active-48.png", 27 | "128": "pixi-icon-active-128.png" 28 | }, 29 | "permissions": ["activeTab"], 30 | "devtools_page": "devtools/pixi-index.html" 31 | } 32 | -------------------------------------------------------------------------------- /packages/devtool-chrome/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@devtool/chrome", 3 | "private": true, 4 | "version": "2.2.1", 5 | "type": "module", 6 | "scripts": { 7 | "build": "run-s build:*", 8 | "build:chrome": "vite build --config vite.chrome.config.ts", 9 | "build:inject": "vite build --config vite.inject.config.ts", 10 | "watch": "run-p watch:*", 11 | "watch:chrome": "vite dev --config vite.chrome.config.ts", 12 | "watch:inject": "node -e \"setTimeout(() => process.exit(0), 5000)\" && vite build --config vite.inject.config.ts --watch --mode development", 13 | "start": "npm run watch" 14 | }, 15 | "dependencies": { 16 | "@devtool/frontend": "*", 17 | "@devtool/backend": "*" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/devtool-chrome/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /packages/devtool-chrome/src/content/index.ts: -------------------------------------------------------------------------------- 1 | import { convertPostMessage, convertPostMessageData } from '../messageUtils'; 2 | 3 | function injectScript(file_path: string, tag: string) { 4 | const node = document.getElementsByTagName(tag)[0]; 5 | 6 | if (!node) { 7 | console.error('[Pixi Devtool] Failed to inject script, please submit an issue'); 8 | return; 9 | } 10 | 11 | const script = document.createElement('script'); 12 | script.setAttribute('type', 'text/javascript'); 13 | script.setAttribute('src', file_path); 14 | node.appendChild(script); 15 | } 16 | 17 | if (document.readyState === 'loading') { 18 | // Loading hasn't finished yet 19 | document.addEventListener('DOMContentLoaded', function () { 20 | injectScript(chrome.runtime.getURL('inject/index.js'), 'body'); 21 | }); 22 | } else { 23 | // `DOMContentLoaded` has already fired 24 | injectScript(chrome.runtime.getURL('inject/index.js'), 'body'); 25 | } 26 | 27 | window.addEventListener( 28 | 'message', 29 | (event) => { 30 | // We only accept messages from ourselves 31 | if (event.source !== window || !event.data.method) { 32 | return; 33 | } 34 | 35 | const converted = convertPostMessageData(event.data); 36 | 37 | if (!converted.method.startsWith('pixi-')) return; 38 | // pass the message to the background 39 | const message = convertPostMessage(converted.method, converted.data); 40 | chrome.runtime.sendMessage(message); 41 | }, 42 | false, 43 | ); 44 | 45 | chrome.runtime.onMessage.addListener(function (message) { 46 | if (message.method === 'panelClosed') { 47 | injectScript(chrome.runtime.getURL('inject/index2.js'), 'body'); 48 | } 49 | }); 50 | -------------------------------------------------------------------------------- /packages/devtool-chrome/src/devtools/devtools.ts: -------------------------------------------------------------------------------- 1 | import { DevtoolMessage } from '@devtool/frontend/types'; 2 | import { convertPostMessage } from '../messageUtils'; 3 | 4 | chrome.devtools.panels.create( 5 | import.meta.env.DEV ? 'Dev: PixiJS DevTools' : 'PixiJS DevTools', 6 | 'pixi-icon-active-128.png', 7 | 'devtools/panel/panel.html', 8 | (panel) => { 9 | const tabId = chrome.devtools.inspectedWindow.tabId; 10 | 11 | panel.onShown.addListener(() => { 12 | const message = convertPostMessage(DevtoolMessage.panelShown, {}); 13 | chrome.runtime.sendMessage({ ...message, tabId }); 14 | }); 15 | 16 | panel.onHidden.addListener(() => { 17 | const message = convertPostMessage(DevtoolMessage.panelHidden, {}); 18 | chrome.runtime.sendMessage({ ...message, tabId }); 19 | }); 20 | }, 21 | ); 22 | -------------------------------------------------------------------------------- /packages/devtool-chrome/src/devtools/panel/panel.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Dev Tools Panel 7 | 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /packages/devtool-chrome/src/devtools/panel/panel.tsx: -------------------------------------------------------------------------------- 1 | import App from '@devtool/frontend/App'; 2 | import type { BridgeFn } from '@devtool/frontend/lib/utils'; 3 | 4 | import { createRoot } from 'react-dom/client'; 5 | 6 | /** 7 | * Thanks pixi-inspector for this snippet 8 | * https://github.com/bfanger/pixi-inspector 9 | */ 10 | const bridge: BridgeFn = (code: string) => 11 | new Promise((resolve, reject) => { 12 | chrome.devtools.inspectedWindow.eval(code, (result, err) => { 13 | if (err) { 14 | if (err instanceof Error) { 15 | reject(err); 16 | } 17 | reject(new Error(err.value || err.description || err.code) + `\n${code}`); 18 | } 19 | resolve(result as any); 20 | }); 21 | }); 22 | 23 | const container = document.getElementById('app-container'); 24 | const root = createRoot(container!); 25 | root.render(); 26 | 27 | if (import.meta.env.DEV) { 28 | new EventSource('http://localhost:10808').addEventListener('change', () => { 29 | bridge('window.location.reload()'); 30 | window.location.reload(); 31 | }); 32 | } 33 | -------------------------------------------------------------------------------- /packages/devtool-chrome/src/devtools/pixi-index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /packages/devtool-chrome/src/inject/close.ts: -------------------------------------------------------------------------------- 1 | window.__PIXI_DEVTOOLS_WRAPPER__?.scene.overlay.enableHighlight(false); 2 | window.__PIXI_DEVTOOLS_WRAPPER__?.scene.overlay.enablePicker(false); 3 | window.__PIXI_DEVTOOLS_WRAPPER__?.reset(); 4 | -------------------------------------------------------------------------------- /packages/devtool-chrome/src/inject/index.ts: -------------------------------------------------------------------------------- 1 | // Note: this file is compiled to `dist/content-inject/index.js` and is used by the content script 2 | 3 | import { pollPixi } from '@devtool/backend/utils/poller'; 4 | import { DevtoolMessage } from '@devtool/frontend/types'; 5 | import { convertPostMessage } from '../messageUtils'; 6 | import type { Application, Renderer } from 'pixi.js'; 7 | 8 | function attach() { 9 | window.postMessage(convertPostMessage(DevtoolMessage.active, {}), '*'); 10 | } 11 | function injectIFrames() { 12 | if (window.frames) { 13 | for (let i = 0; i < window.frames.length; i += 1) { 14 | try { 15 | const frame = window.frames[i] as Window & typeof globalThis; 16 | frame.__PIXI_APP_INIT__ = init; 17 | frame.__PIXI_RENDERER_INIT__ = init; 18 | } catch (_) { 19 | // access to iframe was denied 20 | } 21 | } 22 | } 23 | window.__PIXI_APP_INIT__ = init; 24 | window.__PIXI_RENDERER_INIT__ = init; 25 | 26 | return null; 27 | } 28 | function init(arg: Application | Renderer, version?: string) { 29 | const stage = (arg as Application).stage; 30 | const renderer = stage ? (arg as Application).renderer : (arg as Renderer); 31 | window.__PIXI_DEVTOOLS__ = { 32 | ...window.__PIXI_DEVTOOLS__, 33 | renderer, 34 | stage, 35 | version, 36 | }; 37 | window.__PIXI_DEVTOOLS_WRAPPER__?.reset(); 38 | } 39 | 40 | // try injecting iframes using requestAnimationFrame until poller is stopped 41 | const inject = () => { 42 | injectIFrames(); 43 | requestAnimationFrame(inject); 44 | }; 45 | 46 | inject(); 47 | 48 | pollPixi(attach); 49 | -------------------------------------------------------------------------------- /packages/devtool-chrome/src/messageUtils.ts: -------------------------------------------------------------------------------- 1 | import type { DevtoolMessage } from '@devtool/frontend/types'; 2 | 3 | /** 4 | * Standard message format 5 | * @param method - The message type 6 | * @param data - The message data 7 | * @returns 8 | */ 9 | export function convertPostMessage(method: DevtoolMessage, data: unknown) { 10 | return { method, data: JSON.stringify(data) }; 11 | } 12 | 13 | /** 14 | * Convert the message data to a standard format 15 | * @param a - The original message 16 | */ 17 | export function convertPostMessageData(a: ReturnType) { 18 | let data: string | object = a.data; 19 | 20 | if (typeof data === 'string') { 21 | try { 22 | data = JSON.parse(data); 23 | } catch (error) { 24 | data = {}; 25 | } 26 | } 27 | return { 28 | method: a.method, 29 | data, 30 | }; 31 | } 32 | -------------------------------------------------------------------------------- /packages/devtool-chrome/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | import theme from '../frontend/tailwind.config.js'; 3 | theme.content = [ 4 | '../frontend/src/pages/**/*.{ts,tsx}', 5 | '../frontend/src/components/**/*.{ts,tsx}', 6 | '../frontend/src/app/**/*.{ts,tsx}', 7 | '../frontend/src/**/*.{ts,tsx}', 8 | ]; 9 | export default theme; 10 | -------------------------------------------------------------------------------- /packages/devtool-chrome/vite.chrome.config.ts: -------------------------------------------------------------------------------- 1 | import type { ManifestV3Export } from '@crxjs/vite-plugin'; 2 | import { crx } from '@crxjs/vite-plugin'; 3 | import react from '@vitejs/plugin-react-swc'; 4 | import { defineConfig } from 'vite'; 5 | import fs from 'fs'; 6 | import path, { resolve } from 'node:path'; 7 | 8 | import pkg from './package.json'; 9 | import devManifest from './manifest.dev.json'; 10 | import manifest from './manifest.json'; 11 | 12 | export default defineConfig((config) => { 13 | const isDev = config.mode === 'development'; 14 | const publicDir = resolve(__dirname, './assets'); 15 | const outDir = resolve(__dirname, isDev ? 'dist/chrome-dev' : 'dist/chrome'); 16 | const extensionManifest = { 17 | ...manifest, 18 | ...(isDev ? devManifest : ({} as ManifestV3Export)), 19 | name: isDev ? `DEV: ${manifest.name}` : manifest.name, 20 | version: pkg.version, 21 | }; 22 | 23 | return { 24 | root: resolve(__dirname, 'src/'), 25 | resolve: { 26 | alias: { 27 | '@devtool/frontend': path.resolve(process.cwd(), '../../packages/frontend/src/'), 28 | '@devtool/backend': path.resolve(process.cwd(), '../../packages/backend/src/'), 29 | }, 30 | }, 31 | plugins: [ 32 | react(), 33 | crx({ 34 | manifest: extensionManifest as ManifestV3Export, 35 | contentScripts: { 36 | injectCss: true, 37 | }, 38 | }), 39 | { 40 | name: 'manifest-plugin', 41 | enforce: 'post', 42 | writeBundle() { 43 | const manifestPath = path.resolve(__dirname, 'dist/chrome', 'manifest.json'); 44 | if (fs.existsSync(manifestPath)) { 45 | const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8')); 46 | const resource = manifest.web_accessible_resources[0]; 47 | resource.resources.push('inject/index.js'); 48 | resource.resources.push('inject/index2.js'); 49 | fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2)); 50 | } 51 | }, 52 | }, 53 | ], 54 | publicDir, 55 | build: { 56 | outDir, 57 | sourcemap: isDev, 58 | rollupOptions: { 59 | input: { 60 | panel: resolve(__dirname, 'src/devtools/panel/panel.html'), 61 | }, 62 | }, 63 | }, 64 | server: { 65 | port: 10808, 66 | }, 67 | }; 68 | }); 69 | -------------------------------------------------------------------------------- /packages/devtool-chrome/vite.inject.config.ts: -------------------------------------------------------------------------------- 1 | import path, { resolve } from 'node:path'; 2 | import { defineConfig } from 'vite'; 3 | 4 | // the crx plugin doesn't seem to work with additional files, so we'll just build the injection library here 5 | export default defineConfig((config) => { 6 | const isDev = config.mode === 'development'; 7 | const outDir = isDev ? 'chrome-dev' : 'chrome'; 8 | return { 9 | resolve: { 10 | alias: { 11 | '@devtool/frontend': path.resolve(process.cwd(), '../../packages/frontend/src/'), 12 | '@devtool/backend': path.resolve(process.cwd(), '../../packages/backend/src/'), 13 | }, 14 | }, 15 | root: resolve(__dirname, 'src/'), 16 | plugins: [ 17 | { 18 | name: 'wrap-in-iife', 19 | generateBundle(_outputOptions, bundle) { 20 | Object.keys(bundle).forEach((fileName) => { 21 | const file = bundle[fileName]; 22 | if (fileName.slice(-3) === '.js' && 'code' in file) { 23 | file.code = `(() => {\n${file.code}})()`; 24 | } 25 | }); 26 | }, 27 | }, 28 | ], 29 | build: { 30 | lib: { 31 | entry: ['inject/index.ts', 'inject/close.ts'], 32 | fileName: 'index', 33 | formats: ['es'], 34 | }, 35 | target: 'es2020', 36 | outDir: resolve(__dirname, `dist/${outDir}/inject`), 37 | }, 38 | }; 39 | }); 40 | -------------------------------------------------------------------------------- /packages/devtool-local/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Pixi Devtool - Local 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /packages/devtool-local/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@devtool/local", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "build": "vite build", 8 | "watch": "vite dev", 9 | "start": "npm run watch" 10 | }, 11 | "dependencies": { 12 | "@devtool/frontend": "*", 13 | "@devtool/backend": "*", 14 | "@pixi/devtools": "*" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/devtool-local/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /packages/devtool-local/src/main.tsx: -------------------------------------------------------------------------------- 1 | import App from '@devtool/frontend/App'; 2 | import type { BridgeFn } from '@devtool/frontend/lib/utils'; 3 | import React from 'react'; 4 | import ReactDOM from 'react-dom/client'; 5 | import { PixiDevtools } from '@devtool/backend/pixi'; 6 | import scene from './scene.ts'; 7 | import * as PIXI from 'pixi.js'; 8 | 9 | (async () => { 10 | // wait for the app to be ready 11 | await new Promise((resolve) => setTimeout(resolve, 1000)); 12 | 13 | const canvas = document.getElementById('canvas') as HTMLCanvasElement; 14 | 15 | const app = await scene(canvas); 16 | 17 | window.__PIXI_DEVTOOLS__ = { 18 | pixi: PIXI, 19 | app, 20 | }; 21 | 22 | const renderer = PixiDevtools.renderer; 23 | 24 | if (renderer) { 25 | renderer.render = new Proxy(renderer.render, { 26 | apply(target, thisArg, ...args) { 27 | PixiDevtools.update(); 28 | messageListeners.forEach((listener) => 29 | listener({ 30 | method: 'pixi-state-update', 31 | data: JSON.stringify(PixiDevtools.state), 32 | }), 33 | ); 34 | // @ts-expect-error - TODO: fix this type 35 | return target.apply(thisArg, ...args); 36 | }, 37 | }); 38 | } 39 | })(); 40 | 41 | const messageListeners: ((message: unknown) => void)[] = []; 42 | const mockChrome = { 43 | runtime: { 44 | connect: () => ({ 45 | postMessage: () => {}, 46 | onMessage: { 47 | addListener: (listner: (message: unknown) => void) => { 48 | messageListeners.push(listner); 49 | }, 50 | }, 51 | }), 52 | }, 53 | devtools: { 54 | inspectedWindow: { 55 | tabId: 0, 56 | eval: (code: string, cb: () => void) => { 57 | eval(code); 58 | cb(); 59 | }, 60 | }, 61 | }, 62 | } as unknown as typeof chrome; 63 | 64 | const mockBridge: BridgeFn = (code): Promise => { 65 | eval(code); 66 | return Promise.resolve(); 67 | }; 68 | 69 | ReactDOM.createRoot(document.getElementById('root')!).render( 70 | 71 |
72 | 73 |
74 | 75 |
76 |
77 |
, 78 | ); 79 | -------------------------------------------------------------------------------- /packages/devtool-local/src/scene.ts: -------------------------------------------------------------------------------- 1 | import { Application, Assets, Container, Sprite, Rectangle } from 'pixi.js'; 2 | 3 | export default async function buildScene(canvas: HTMLCanvasElement) { 4 | // Create a new application 5 | const app = new Application(); 6 | 7 | // Initialize the application 8 | await app.init({ background: '#1099bb', canvas }); 9 | 10 | // Create and add a container to the stage 11 | const container = new Container(); 12 | 13 | app.stage.addChild(container); 14 | 15 | // Load the bunny texture 16 | const texture = await Assets.load('https://pixijs.com/assets/bunny.png'); 17 | 18 | // Create a 5x5 grid of bunnies in the container 19 | for (let i = 0; i < 25; i++) { 20 | const bunny = new Sprite(texture); 21 | 22 | bunny.x = (i % 5) * 40; 23 | bunny.y = Math.floor(i / 5) * 40; 24 | bunny.label = `Bunny ${i}`; 25 | bunny.filterArea = new Rectangle(0, 0, 40, 40); 26 | // bunny.boundsArea = new PIXI.Rectangle(0, 0, 40, 40); 27 | container.addChild(bunny); 28 | } 29 | 30 | // Move the container to the center 31 | container.x = app.screen.width / 2; 32 | container.y = app.screen.height / 2; 33 | 34 | // Center the bunny sprites in local container coordinates 35 | container.pivot.x = container.width / 2; 36 | container.pivot.y = container.height / 2; 37 | 38 | // Listen for animate update 39 | app.ticker.add((time) => { 40 | // Continuously rotate the container! 41 | // * use delta to create frame-independent transform * 42 | container.rotation -= 0.01 * time.deltaTime; 43 | }); 44 | 45 | // add two buttons to add and remove bunnies 46 | const addButton = document.createElement('button'); 47 | addButton.textContent = 'Add Bunny'; 48 | // position over top of everything else 49 | addButton.style.position = 'absolute'; 50 | addButton.style.top = '10px'; 51 | addButton.style.left = '10px'; 52 | 53 | // addButton.onclick = () => { 54 | // const bunny = new Sprite(texture); 55 | // const i = container.children.length; 56 | // bunny.x = (i % 5) * 40; 57 | // bunny.y = Math.floor(i / 5) * 40; 58 | // container.addChild(bunny); 59 | // }; 60 | 61 | // document.body.appendChild(addButton); 62 | 63 | // const removeButton = document.createElement('button'); 64 | // removeButton.textContent = 'Remove Bunny'; 65 | // // position over top of everything else 66 | // removeButton.style.position = 'absolute'; 67 | // removeButton.style.top = '10px'; 68 | // removeButton.style.left = '100px'; 69 | // removeButton.onclick = () => { 70 | // container.removeChild(container.children[container.children.length - 1]); 71 | // }; 72 | 73 | // document.body.appendChild(removeButton); 74 | 75 | return app; 76 | } 77 | -------------------------------------------------------------------------------- /packages/devtool-local/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | import theme from '../frontend/tailwind.config.js'; 3 | theme.content = [ 4 | '../frontend/src/pages/**/*.{ts,tsx}', 5 | '../frontend/src/components/**/*.{ts,tsx}', 6 | '../frontend/src/app/**/*.{ts,tsx}', 7 | '../frontend/src/**/*.{ts,tsx}', 8 | ]; 9 | export default theme; 10 | -------------------------------------------------------------------------------- /packages/devtool-local/vite.config.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import react from '@vitejs/plugin-react'; 3 | import { defineConfig } from 'vite'; 4 | 5 | export default defineConfig({ 6 | plugins: [react()], 7 | resolve: { 8 | alias: { 9 | '@devtool/frontend': path.resolve(process.cwd(), '../../packages/frontend/src/'), 10 | '@devtool/backend': path.resolve(process.cwd(), '../../packages/backend/src/'), 11 | '@pixi/devtool': path.resolve(process.cwd(), '../../packages/api/src/'), 12 | }, 13 | }, 14 | }); 15 | -------------------------------------------------------------------------------- /packages/docs/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | -------------------------------------------------------------------------------- /packages/docs/README.md: -------------------------------------------------------------------------------- 1 | # Website 2 | 3 | This website is built using [Docusaurus](https://docusaurus.io/), a modern static website generator. 4 | 5 | ### Installation 6 | 7 | ``` 8 | $ yarn 9 | ``` 10 | 11 | ### Local Development 12 | 13 | ``` 14 | $ yarn start 15 | ``` 16 | 17 | This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. 18 | 19 | ### Build 20 | 21 | ``` 22 | $ yarn build 23 | ``` 24 | 25 | This command generates static content into the `build` directory and can be served using any static contents hosting service. 26 | 27 | ### Deployment 28 | 29 | Using SSH: 30 | 31 | ``` 32 | $ USE_SSH=true yarn deploy 33 | ``` 34 | 35 | Not using SSH: 36 | 37 | ``` 38 | $ GIT_USER= yarn deploy 39 | ``` 40 | 41 | If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch. 42 | -------------------------------------------------------------------------------- /packages/docs/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /packages/docs/docs/guide/faq.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | --- 4 | 5 | # F.A.Q 6 | 7 | ## The PixiJS Devtools don't show up 8 | 9 | Here are some troubleshooting steps to help you if you don't the PixiJS devtools in your browser: 10 | 11 | * Check if the extension is installed and enabled: 12 | 13 | Open the Chrome extensions page by typing `chrome://extensions` in the address bar. Make sure the PixiJS Devtools extension is installed and enabled. 14 | If it's not installed, please read the [installation guide](/docs/guide/installation). 15 | 16 | * Try closing the devtools pane, refreshing the page and opening the devtools pane again**. 17 | * Try restarting the browser or the computer. 18 | * If you have multiple pages with the PixiJS Devtools open, try closing all of them and opening a new one. 19 | * If you have multiple versions of the devtools installed, it's recommended to disable/remove the others. n 20 | * Look for errors in the browser Console. 21 | 22 | If you see any errors, please report them in the [GitHub issues](https://github.com/pixijs/devtools/issues). 23 | 24 | --- 25 | 26 | ## Scene isn't updating in the Devtools 27 | 28 | If the scene isn't updating in the devtools, try the following steps: 29 | 30 | * Close the devtools pane. 31 | * Refresh the page. 32 | * Open the devtools pane again. 33 | 34 | If the scene still isn't updating, try looking for errors in the console: 35 | 36 | * Open the browser console, you can do this by pressing `ESC` 37 | * Look for errors in the devtools Console. 38 | You can open the devtools console by right-clicking on the devtools pane and selecting "Inspect". 39 | 40 | 41 |
42 | How to open the devtools console 43 | Right click on the devtools pane and select Inspect 44 |
45 | 46 | 47 | 48 | If you see any errors, please report them in the [GitHub issues](https://github.com/pixijs/devtools/issues). 49 | 50 | --- 51 | 52 | ## Property is not displayed 53 | 54 | If a property is not displayed in the devtools, it might be because the property is not set on the object. 55 | 56 | For example, the `filterArea` property is not displayed in the devtool until it's set on the object in your code. 57 | To get the property to display, you can set it on the object like this: 58 | 59 | ```js 60 | const sprite = new Sprite(texture); 61 | sprite.filterArea = new Rectangle(0, 0, 100, 100); 62 | ``` 63 | 64 | If you think a property should be displayed, please report it in the [GitHub issues](https://github.com/pixijs/devtools/issues). 65 | -------------------------------------------------------------------------------- /packages/docs/docs/guide/features/_category_.yml: -------------------------------------------------------------------------------- 1 | label: Features 2 | position: 1 3 | collapsed: false 4 | -------------------------------------------------------------------------------- /packages/docs/docs/guide/features/assets.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | title: Assets 4 | --- 5 | 6 | import React from 'react'; 7 | import { useColorMode } from '@docusaurus/theme-common'; 8 | 9 | export const Gif = ({ src, alt }) => { 10 | return ( 11 |
12 | {alt} 13 |
14 | ); 15 | }; 16 | 17 | # Asset Panel 18 | 19 | The Asset Panel in PixiJS provides a detailed view of all assets currently utilized by your application. This tool is invaluable for monitoring GPU memory usage 20 | and identifying which textures are loaded onto the GPU. 21 | 22 | By visualizing your assets, the Asset Panel becomes a powerful resource for debugging and optimizing your PixiJS application. It helps you detect and remove 23 | unused assets that are still loaded on the GPU, ensuring efficient memory usage and improved performance. 24 | 25 | ## Texture Inspection 26 | 27 | The Asset Panel displays all textures currently loaded onto the GPU. You can view the texture's dimensions, format, and memory usage. The panel also provides 28 | a preview of the texture, making it easy to identify the asset. 29 | 30 | 31 | 32 | ## Texture Search 33 | 34 | The Asset Panel includes a search feature that allows you to quickly find a specific texture. 35 | 36 | 37 | ## Texture Sorting 38 | 39 | You can sort the textures in the Asset Panel by various criteria, such as memory usage or dimensions. This feature helps you identify large textures that may 40 | be impacting your application's performance. 41 | 42 | 43 | -------------------------------------------------------------------------------- /packages/docs/docs/plugin/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 0 3 | --- 4 | 5 | # Getting Started 6 | 7 | This guide will help you create your own extensions for the devtools. 8 | 9 | :::warning 10 | The devtools are still in development and the API may change in the future. 11 | 12 | We are also still improving the documentation and adding more examples. 13 | ::: 14 | 15 | ## Extension Types 16 | 17 | There are several types of extensions that you can create for the devtools that allow you to extend the functionality of the devtool: 18 | 19 | - [Properties](properties.md) - Add properties to be displayed in the tree. 20 | - [Overlay](overlay.md) - Highlight the bounds of a PixiJS object in the scene. 21 | - [Stats](stats.md) - Track stats for a node in the scene. 22 | - [Tree](tree.md) - Customise nodes in the tree. 23 | 24 | ## Adding an Extension 25 | 26 | To add an extension to the devtools, you need to add it to the `extensions` array in the `devtools` options when you initialise the devtools. 27 | 28 | ```ts 29 | import { initDevtools } from '@pixi/devtools'; 30 | 31 | initDevtools({ 32 | ... 33 | extensions: [ 34 | // Add your extension here 35 | ], 36 | }); 37 | ``` 38 | 39 | or adding it directly to the window: 40 | 41 | ```js 42 | window.__PIXI_DEVTOOLS__ = { 43 | ... 44 | extensions: [ 45 | // Add your extension here 46 | ], 47 | }; 48 | ``` 49 | :::note 50 | At this time, the devtool does not support adding extensions after the devtool has been initialised. 51 | ::: 52 | -------------------------------------------------------------------------------- /packages/docs/docs/plugin/overlay.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Overlay 3 | --- 4 | # Overlay Extension 5 | 6 | The overlay extension allows you to highlight the bounds of a PixiJS object in the scene. This is useful for debugging layout and alignment issues. 7 | You can customise the style of the overlay for selected and hovered nodes, as well as define the bounds of the node. 8 | 9 | Since you can change the styling and bounds per node, you can use this extension to create custom overlays for different types of objects. 10 | 11 | ```ts 12 | export interface OverlayExtension { 13 | /** 14 | * The metadata for the extension. 15 | */ 16 | extension: ExtensionMetadata; 17 | /** 18 | * Get the css style for the selected highlight overlay. 19 | * @param node The selected node. 20 | */ 21 | getSelectedStyle?(node: Container | null): Partial; 22 | /** 23 | * Get the css style for the hover highlight overlay. 24 | * @param node The hovered node. 25 | */ 26 | getHoverStyle?(node: Container | null): Partial; 27 | /** 28 | * Get the global position of the node. 29 | * @param node The currently selected node to get the global position of. 30 | */ 31 | getGlobalBounds?(node: Container): { x: number; y: number; width: number; height: number }; 32 | } 33 | ``` 34 | 35 | #### Example 36 | 37 | ```ts 38 | import { Container, Sprite, Text, Rectangle } from 'pixi.js'; 39 | import type { ExtensionMetadata } from '@pixi-spine/devtools'; 40 | 41 | export const overlay: OverlayExtension = { 42 | extension: { 43 | type: 'overlay', 44 | name: 'custom-overlay', 45 | }, 46 | getSelectedStyle(node: Container | null) { 47 | const options = {} as Partial; 48 | 49 | if (node instanceof Sprite) { 50 | return { 51 | border: '2px solid #ff0000', 52 | }; 53 | } else if (node instanceof Container) { 54 | return { 55 | border: '1px solid #00ff00', 56 | }; 57 | } 58 | 59 | return { 60 | border: '2px solid #ff0000', 61 | }; 62 | }, 63 | getHoverStyle(node: Container | null) { 64 | return { 65 | border: '2px solid #00ff00', 66 | }; 67 | }, 68 | getGlobalBounds(node: Container) { 69 | if (node instanceof Text) { 70 | // Custom bounds for text objects 71 | const bounds = node.getBounds(); 72 | return new Rectangle(bounds.x, bounds.y, bounds.width + 10, bounds.height + 10); 73 | } 74 | // Default bounds 75 | return node.getBounds(); 76 | }, 77 | }; 78 | ``` 79 | -------------------------------------------------------------------------------- /packages/docs/docs/plugin/stats.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Stats 3 | --- 4 | # Stats Extension 5 | 6 | The stats extension allows you to provided valuable insights into the current scene by displaying various metrics, such as the number of specific nodes or any other data you wish to track. This tool enhances your ability to monitor and optimize your PixiJS application. 7 | 8 | ```ts 9 | export interface StatsExtension { 10 | /** 11 | * The metadata for the extension. 12 | */ 13 | extension: ExtensionMetadata; 14 | /** 15 | * Track some stats for a node. 16 | * This function is called on every node in the scene. 17 | * @param container The current node 18 | * @param state The current stats for the scene. 19 | */ 20 | track: (container: Container, state: Record) => void; 21 | /** 22 | * Get the keys of what ypu are tracking. 23 | * This is used to warn the user if the same key is being tracked by multiple extensions. 24 | */ 25 | getKeys: () => string[]; 26 | } 27 | ``` 28 | 29 | #### Example 30 | 31 | ```ts 32 | import { Sprite } from 'pixi.js'; 33 | import type { Container } from 'pixi.js'; 34 | import type { ExtensionMetadata } from '@pixi-spine/devtools'; 35 | 36 | export const stats: StatsExtension = { 37 | extension: { 38 | type: 'stats', 39 | name: 'custom-stats', 40 | }, 41 | track(container: Container, state: Record) { 42 | if (container instanceof Sprite) { 43 | state.spriteCount = (state.spriteCount || 0) + 1; 44 | } 45 | }, 46 | getKeys() { 47 | return ['spriteCount']; 48 | }, 49 | }; 50 | ``` 51 | -------------------------------------------------------------------------------- /packages/docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@devtool/docs", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "docusaurus": "docusaurus", 7 | "start": "docusaurus start", 8 | "build": "docusaurus build", 9 | "swizzle": "docusaurus swizzle", 10 | "deploy": "docusaurus deploy", 11 | "clear": "docusaurus clear", 12 | "serve": "docusaurus serve", 13 | "write-translations": "docusaurus write-translations", 14 | "write-heading-ids": "docusaurus write-heading-ids", 15 | "typecheck": "tsc" 16 | }, 17 | "dependencies": { 18 | "@docusaurus/core": "3.4.0", 19 | "@docusaurus/preset-classic": "3.4.0", 20 | "@mdx-js/react": "^3.0.0", 21 | "clsx": "^2.0.0", 22 | "prism-react-renderer": "^2.3.0", 23 | "react": "^18.0.0", 24 | "react-dom": "^18.0.0" 25 | }, 26 | "devDependencies": { 27 | "@docusaurus/module-type-aliases": "3.4.0", 28 | "@docusaurus/tsconfig": "3.4.0", 29 | "@docusaurus/types": "3.4.0", 30 | "typescript": "~5.2.2" 31 | }, 32 | "browserslist": { 33 | "production": [ 34 | ">0.5%", 35 | "not dead", 36 | "not op_mini all" 37 | ], 38 | "development": [ 39 | "last 3 chrome version", 40 | "last 3 firefox version", 41 | "last 5 safari version" 42 | ] 43 | }, 44 | "engines": { 45 | "node": ">=18.0" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /packages/docs/sidebars.ts: -------------------------------------------------------------------------------- 1 | import type { SidebarsConfig } from '@docusaurus/plugin-content-docs'; 2 | 3 | /** 4 | * Creating a sidebar enables you to: 5 | - create an ordered group of docs 6 | - render a sidebar for each doc of that group 7 | - provide next/previous navigation 8 | 9 | The sidebars can be generated from the filesystem, or explicitly defined here. 10 | 11 | Create as many sidebars as you want. 12 | */ 13 | const sidebars: SidebarsConfig = { 14 | // By default, Docusaurus generates a sidebar from the docs folder structure 15 | guide: [{ type: 'autogenerated', dirName: 'guide' }], 16 | plugin: [ 17 | 'plugin/index', 18 | { 19 | type: 'category', 20 | label: 'Scene Extensions', 21 | items: ['plugin/properties', 'plugin/overlay', 'plugin/stats', 'plugin/tree'], 22 | collapsed: false, 23 | collapsible: false, 24 | }, 25 | ], 26 | 27 | // But you can create a sidebar manually 28 | /* 29 | tutorialSidebar: [ 30 | 'intro', 31 | 'hello', 32 | { 33 | type: 'category', 34 | label: 'Tutorial', 35 | items: ['tutorial-basics/create-a-document'], 36 | }, 37 | ], 38 | */ 39 | }; 40 | 41 | export default sidebars; 42 | -------------------------------------------------------------------------------- /packages/docs/src/css/custom.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Outfit:wght@600&family=Roboto+Mono&family=Roboto:wght@400;500;700&display=swap'); 2 | /** 3 | * Any CSS included here will be global. The classic template 4 | * bundles Infima by default. Infima is a CSS framework designed to 5 | * work well for content-centric websites. 6 | */ 7 | 8 | /* You can override the default Infima variables here. */ 9 | :root { 10 | --ifm-font-family-base: 'Roboto'; 11 | --ifm-color-primary: #e91e63; 12 | --ifm-color-primary-dark: #d81557; 13 | --ifm-color-primary-darker: #cc1452; 14 | --ifm-color-primary-darkest: #a81044; 15 | --ifm-color-primary-light: #eb3674; 16 | --ifm-color-primary-lighter: #ed427c; 17 | --ifm-color-primary-lightest: #f06695; 18 | --ifm-code-font-size: 95%; 19 | --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1); 20 | --ifm-hr-background-color: var(--ifm-color-gray-700); 21 | } 22 | 23 | /* For readability concerns, you should choose a lighter palette in dark mode. */ 24 | /* [data-theme='dark'] { 25 | --ifm-color-primary: #25c2a0; 26 | --ifm-color-primary-dark: #21af90; 27 | --ifm-color-primary-darker: #1fa588; 28 | --ifm-color-primary-darkest: #1a8870; 29 | --ifm-color-primary-light: #29d5b0; 30 | --ifm-color-primary-lighter: #32d8b4; 31 | --ifm-color-primary-lightest: #4fddbf; 32 | --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3); 33 | } */ 34 | 35 | .header-twitter-link::before { 36 | width: 26px; 37 | height: 24px; 38 | background: url('/social/twitter.svg') no-repeat center; 39 | } 40 | 41 | .header-discord-link::before { 42 | width: 30px; 43 | height: 24px; 44 | background: url('/social/discord.svg') no-repeat center; 45 | } 46 | 47 | .header-github-link::before { 48 | width: 24px; 49 | height: 24px; 50 | background: url('/social/github.svg') no-repeat center; 51 | } 52 | 53 | .header-open-col-link::before { 54 | width: 24px; 55 | height: 24px; 56 | background: url('/social/open-col-icon.svg') no-repeat center; 57 | /* set icons to white when dark mode enabled and black when light */ 58 | [data-theme='dark'] &::before { 59 | filter: unset; 60 | } 61 | } 62 | 63 | .header-link { 64 | &::before { 65 | content: ''; 66 | display: flex; 67 | background-size: contain; 68 | } 69 | 70 | /* set icons to white when dark mode enabled and black when light */ 71 | [data-theme='dark'] &::before { 72 | filter: invert(1); 73 | } 74 | 75 | &:hover { 76 | opacity: 0.6; 77 | } 78 | } 79 | 80 | /* hr { 81 | 82 | }5f5f5f */ 83 | -------------------------------------------------------------------------------- /packages/docs/src/pages/index.module.css: -------------------------------------------------------------------------------- 1 | /** 2 | * CSS files with the .module.css suffix will be treated as CSS modules 3 | * and scoped locally. 4 | */ 5 | 6 | .heroBanner { 7 | padding: 4rem 0; 8 | text-align: center; 9 | position: relative; 10 | overflow: hidden; 11 | } 12 | 13 | @media screen and (max-width: 996px) { 14 | .heroBanner { 15 | padding: 2rem; 16 | } 17 | } 18 | 19 | .buttons { 20 | display: flex; 21 | align-items: center; 22 | justify-content: center; 23 | gap: 2rem; 24 | } 25 | -------------------------------------------------------------------------------- /packages/docs/src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx'; 2 | import Link from '@docusaurus/Link'; 3 | import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; 4 | import Layout from '@theme/Layout'; 5 | import Heading from '@theme/Heading'; 6 | 7 | import styles from './index.module.css'; 8 | 9 | function HomepageHeader() { 10 | const { siteConfig } = useDocusaurusContext(); 11 | return ( 12 |
13 |
14 | 15 | Logo 16 | 17 |

18 | {siteConfig.tagline} 19 |

20 |
21 | 22 | Get Started 23 | 24 | 29 | Features 30 | 31 |
32 |
33 |
34 | ); 35 | } 36 | 37 | export default function Home(): JSX.Element { 38 | const { siteConfig } = useDocusaurusContext(); 39 | return ( 40 | 41 | 42 |
45 | Hero 54 |
55 |
56 | ); 57 | } 58 | -------------------------------------------------------------------------------- /packages/docs/static/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixijs/devtools/4cc27578299d8deb8f868c9eafd008ce807ff13b/packages/docs/static/.nojekyll -------------------------------------------------------------------------------- /packages/docs/static/gif/devtool-asset-search.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixijs/devtools/4cc27578299d8deb8f868c9eafd008ce807ff13b/packages/docs/static/gif/devtool-asset-search.gif -------------------------------------------------------------------------------- /packages/docs/static/gif/devtool-asset-selection.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixijs/devtools/4cc27578299d8deb8f868c9eafd008ce807ff13b/packages/docs/static/gif/devtool-asset-selection.gif -------------------------------------------------------------------------------- /packages/docs/static/gif/devtool-asset-sorting.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixijs/devtools/4cc27578299d8deb8f868c9eafd008ce807ff13b/packages/docs/static/gif/devtool-asset-sorting.gif -------------------------------------------------------------------------------- /packages/docs/static/gif/devtool-copy.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixijs/devtools/4cc27578299d8deb8f868c9eafd008ce807ff13b/packages/docs/static/gif/devtool-copy.gif -------------------------------------------------------------------------------- /packages/docs/static/gif/devtool-delete.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixijs/devtools/4cc27578299d8deb8f868c9eafd008ce807ff13b/packages/docs/static/gif/devtool-delete.gif -------------------------------------------------------------------------------- /packages/docs/static/gif/devtool-highlight.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixijs/devtools/4cc27578299d8deb8f868c9eafd008ce807ff13b/packages/docs/static/gif/devtool-highlight.gif -------------------------------------------------------------------------------- /packages/docs/static/gif/devtool-lock.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixijs/devtools/4cc27578299d8deb8f868c9eafd008ce807ff13b/packages/docs/static/gif/devtool-lock.gif -------------------------------------------------------------------------------- /packages/docs/static/gif/devtool-properties.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixijs/devtools/4cc27578299d8deb8f868c9eafd008ce807ff13b/packages/docs/static/gif/devtool-properties.gif -------------------------------------------------------------------------------- /packages/docs/static/gif/devtool-rename.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixijs/devtools/4cc27578299d8deb8f868c9eafd008ce807ff13b/packages/docs/static/gif/devtool-rename.gif -------------------------------------------------------------------------------- /packages/docs/static/gif/devtool-reparent.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixijs/devtools/4cc27578299d8deb8f868c9eafd008ce807ff13b/packages/docs/static/gif/devtool-reparent.gif -------------------------------------------------------------------------------- /packages/docs/static/gif/devtool-right-click.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixijs/devtools/4cc27578299d8deb8f868c9eafd008ce807ff13b/packages/docs/static/gif/devtool-right-click.gif -------------------------------------------------------------------------------- /packages/docs/static/gif/devtool-search.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixijs/devtools/4cc27578299d8deb8f868c9eafd008ce807ff13b/packages/docs/static/gif/devtool-search.gif -------------------------------------------------------------------------------- /packages/docs/static/gif/devtool-selection.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixijs/devtools/4cc27578299d8deb8f868c9eafd008ce807ff13b/packages/docs/static/gif/devtool-selection.gif -------------------------------------------------------------------------------- /packages/docs/static/gif/devtool-stats.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixijs/devtools/4cc27578299d8deb8f868c9eafd008ce807ff13b/packages/docs/static/gif/devtool-stats.gif -------------------------------------------------------------------------------- /packages/docs/static/img/devtool-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixijs/devtools/4cc27578299d8deb8f868c9eafd008ce807ff13b/packages/docs/static/img/devtool-screenshot.png -------------------------------------------------------------------------------- /packages/docs/static/img/docusaurus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixijs/devtools/4cc27578299d8deb8f868c9eafd008ce807ff13b/packages/docs/static/img/docusaurus.png -------------------------------------------------------------------------------- /packages/docs/static/img/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixijs/devtools/4cc27578299d8deb8f868c9eafd008ce807ff13b/packages/docs/static/img/favicon.png -------------------------------------------------------------------------------- /packages/docs/static/img/ogimage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixijs/devtools/4cc27578299d8deb8f868c9eafd008ce807ff13b/packages/docs/static/img/ogimage.png -------------------------------------------------------------------------------- /packages/docs/static/social/discord.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/docs/static/social/github.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/docs/static/social/open-col-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 12 | 13 | 14 | 15 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /packages/docs/static/social/twitter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | -------------------------------------------------------------------------------- /packages/docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@docusaurus/tsconfig", 3 | "compilerOptions": { 4 | "baseUrl": "." 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Pixi Example 8 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /packages/example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@devtool/example", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "build": "vite build", 8 | "watch": "vite dev", 9 | "start": "npm run watch" 10 | }, 11 | "dependencies": { 12 | "pixi.js": "^8.2.2", 13 | "@pixi/devtools": "*" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/example/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | 3 | export default defineConfig({ 4 | build: { 5 | sourcemap: true, 6 | }, 7 | server: { 8 | port: 3000, 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /packages/frontend/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": false, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "src/globals.css", 9 | "baseColor": "zinc", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@devtool/frontend", 3 | "version": "0.0.0", 4 | "private": true, 5 | "type": "module", 6 | "scripts": {}, 7 | "dependencies": { 8 | "@radix-ui/react-checkbox": "^1.1.1", 9 | "@radix-ui/react-collapsible": "^1.0.3", 10 | "@radix-ui/react-context-menu": "^2.2.1", 11 | "@radix-ui/react-dropdown-menu": "^2.0.6", 12 | "@radix-ui/react-icons": "^1.3.0", 13 | "@radix-ui/react-popover": "^1.0.7", 14 | "@radix-ui/react-scroll-area": "^1.1.0", 15 | "@radix-ui/react-select": "^2.1.1", 16 | "@radix-ui/react-separator": "^1.0.3", 17 | "@radix-ui/react-slider": "^1.2.0", 18 | "@radix-ui/react-slot": "^1.0.2", 19 | "@radix-ui/react-radio-group": "^1.2.0", 20 | "@radix-ui/react-switch": "^1.1.0", 21 | "@radix-ui/react-tabs": "^1.0.4", 22 | "@radix-ui/react-toggle": "^1.1.0", 23 | "@radix-ui/react-toggle-group": "^1.0.4", 24 | "@radix-ui/react-tooltip": "^1.1.2", 25 | "class-variance-authority": "^0.7.0", 26 | "clsx": "^2.1.1", 27 | "fuse.js": "^7.0.0", 28 | "react": "^18.3.1", 29 | "react-arborist": "^3.4.0", 30 | "react-color": "^2.19.3", 31 | "react-dom": "^18.3.1", 32 | "react-icons": "^5.2.1", 33 | "react-resizable-panels": "^2.0.20", 34 | "react-virtualized-auto-sizer": "^1.0.24", 35 | "react-syntax-highlighter": "npm:@fengkx/react-syntax-highlighter@15.6.1", 36 | "smoothie": "^1.36.1", 37 | "tailwind-merge": "^2.4.0", 38 | "tailwindcss-animate": "^1.0.7", 39 | "zustand": "^4.5.4" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/frontend/src/assets/icon-active-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixijs/devtools/4cc27578299d8deb8f868c9eafd008ce807ff13b/packages/frontend/src/assets/icon-active-48.png -------------------------------------------------------------------------------- /packages/frontend/src/assets/transparent-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /packages/frontend/src/assets/transparent.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /packages/frontend/src/components/collapsible/collapsible-section.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from '../../lib/utils'; 2 | import { useState } from 'react'; 3 | import { FaAngleDown, FaCopy as CopyIcon } from 'react-icons/fa6'; 4 | import { useLocalStorage } from '../../lib/localStorage'; 5 | 6 | export const SaveCollapsibleSection: React.FC = ({ 7 | storageKey, 8 | ...props 9 | }) => { 10 | const [defaultCollapsed, setDefaultCollapsed] = useLocalStorage(storageKey, props.defaultCollapsed); 11 | 12 | return ; 13 | }; 14 | 15 | interface CollapsibleSectionProps extends React.HTMLAttributes { 16 | children: React.ReactNode; 17 | title: string; 18 | onCopy?: () => void; 19 | defaultCollapsed?: boolean; 20 | onCollapse?: (collapsed: boolean) => void; 21 | } 22 | export const CollapsibleSection = ({ 23 | className, 24 | children, 25 | title, 26 | onCopy, 27 | defaultCollapsed, 28 | onCollapse, 29 | }: CollapsibleSectionProps) => { 30 | const [collapsed, setCollapsed] = useState(defaultCollapsed ?? false); 31 | 32 | return ( 33 | <> 34 |
{ 40 | setCollapsed(!collapsed); 41 | onCollapse && onCollapse(!collapsed); 42 | }} 43 | > 44 |
45 | {onCopy && ( 46 | { 48 | onCopy(); 49 | e.stopPropagation(); 50 | }} 51 | className="hover:fill-primary cursor-pointer opacity-45 hover:opacity-100" 52 | /> 53 | )} 54 |
{title}
55 |
56 | 57 |
58 | {!collapsed && children} 59 | 60 | ); 61 | }; 62 | -------------------------------------------------------------------------------- /packages/frontend/src/components/collapsible/collapsible-split.tsx: -------------------------------------------------------------------------------- 1 | import type { PanelProps } from 'react-resizable-panels'; 2 | import { PanelGroup, Panel, PanelResizeHandle } from 'react-resizable-panels'; 3 | import { CollapsibleSection } from './collapsible-section'; 4 | 5 | interface CollapsibleSectionProps { 6 | left: React.ReactNode; 7 | right: React.ReactNode; 8 | rightOptions?: PanelProps; 9 | leftOptions?: PanelProps; 10 | title?: string; 11 | } 12 | export const CollapsibleSplit: React.FC = ({ 13 | title, 14 | left, 15 | right, 16 | rightOptions, 17 | leftOptions, 18 | }) => { 19 | return ( 20 | 21 |
22 |
23 | 24 | 25 | {left} 26 | 27 | 28 | 29 | {right} 30 | 31 | 32 |
33 |
34 |
35 | ); 36 | }; 37 | -------------------------------------------------------------------------------- /packages/frontend/src/components/mode-toggle.tsx: -------------------------------------------------------------------------------- 1 | import { FaMoon, FaSun } from 'react-icons/fa6'; 2 | 3 | import { Button } from './ui/button'; 4 | import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from './ui/dropdown-menu'; 5 | import { useTheme } from './theme-provider'; 6 | 7 | export function ModeToggle() { 8 | const { setTheme } = useTheme(); 9 | 10 | return ( 11 | 12 | 13 | 18 | 19 | 20 | setTheme('light')}>Light 21 | setTheme('dark')}>Dark 22 | setTheme('system')}>System 23 | 24 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /packages/frontend/src/components/properties/boolean-property.tsx: -------------------------------------------------------------------------------- 1 | import type { SwitchProps } from '@radix-ui/react-switch'; 2 | import { Switch } from '../ui/switch'; 3 | import type { PropertyPanelData } from './propertyTypes'; 4 | 5 | export const BooleanProperty: React.FC = ({ value, entry }) => { 6 | return ( 7 | 13 | ); 14 | }; 15 | -------------------------------------------------------------------------------- /packages/frontend/src/components/properties/button-property.tsx: -------------------------------------------------------------------------------- 1 | import type { ButtonProps } from '../ui/button'; 2 | import { Button } from '../ui/button'; 3 | import type { PropertyPanelData } from './propertyTypes'; 4 | 5 | export type ButtonFnProps = { label: string } & ButtonProps; 6 | export const ButtonProperty: React.FC = ({ entry }) => { 7 | return ( 8 | 17 | ); 18 | }; 19 | -------------------------------------------------------------------------------- /packages/frontend/src/components/properties/color-property.tsx: -------------------------------------------------------------------------------- 1 | import type { ColorProps } from '../ui/color'; 2 | import { ColorInput } from '../ui/color'; 3 | import type { PropertyPanelData } from './propertyTypes'; 4 | 5 | export const ColorProperty: React.FC = ({ value, entry }) => { 6 | return ; 7 | }; 8 | -------------------------------------------------------------------------------- /packages/frontend/src/components/properties/multi-property.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import type { PropertyOptions, PropertyPanelData, PropertyTypes } from './propertyTypes'; 3 | import { propertyMap } from './propertyTypes'; 4 | 5 | export interface MultiProps { 6 | inputs: { 7 | value: any; 8 | prop: string; 9 | entry: { 10 | label?: string; 11 | type: PropertyTypes; 12 | options?: PropertyOptions; 13 | }; 14 | }[]; 15 | } 16 | 17 | export const MultiProperty: React.FC = ({ entry }) => { 18 | const options = entry.options as MultiProps; 19 | const initializeValues = () => options.inputs.map((input) => input.value); 20 | const [currentValues, setCurrentValues] = useState(initializeValues); 21 | const inputsDependency = 22 | options.inputs.length + options.inputs.map((input) => `${input.prop}${input.value}`).join(','); 23 | 24 | useEffect(() => { 25 | setCurrentValues(initializeValues()); 26 | // eslint-disable-next-line react-hooks/exhaustive-deps 27 | }, [inputsDependency]); 28 | 29 | const comps = options.inputs.map((input, i) => { 30 | const PropertyComponent = propertyMap[input.entry.type]; 31 | const onChange = (value: string) => { 32 | setCurrentValues((prevValues) => { 33 | const newValues = [...prevValues]; 34 | newValues[i] = JSON.parse(value); 35 | entry.onChange(JSON.stringify([input.prop, newValues])); 36 | return newValues; 37 | }); 38 | }; 39 | const props = { ...input, entry: { ...input.entry, onChange }, value: currentValues[i] } as PropertyPanelData; 40 | return ( 41 |
42 |
43 |
{input.entry.label}
44 | 45 |
46 |
47 | ); 48 | }); 49 | 50 | return
{comps}
; 51 | }; 52 | -------------------------------------------------------------------------------- /packages/frontend/src/components/properties/number-property.tsx: -------------------------------------------------------------------------------- 1 | import type { InputProps } from '../ui/input'; 2 | import { Input } from '../ui/input'; 3 | import type { PropertyPanelData } from './propertyTypes'; 4 | 5 | export const NumberProperty: React.FC = ({ value, entry }) => { 6 | return ( 7 | entry.onChange(Number(e.target.value))} 12 | className="border-border hover:border-secondary focus:border-secondary h-6 w-full rounded text-xs outline-none" 13 | /> 14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /packages/frontend/src/components/properties/propertyEntry.tsx: -------------------------------------------------------------------------------- 1 | import { memo } from 'react'; 2 | import { cn } from '../../lib/utils'; 3 | import { Separator } from '../ui/separator'; 4 | import { TooltipWrapper } from '../ui/tooltip'; 5 | 6 | interface PropertyEntryProps { 7 | title: string; 8 | input: React.ReactNode; 9 | className?: string; 10 | tooltip?: string; 11 | isLast: boolean; 12 | } 13 | export const PropertyEntry: React.FC = memo(({ title, input, className, tooltip, isLast }) => { 14 | return ( 15 | <> 16 |
17 | {tooltip ? ( 18 |
19 | {title}
} tip={tooltip} /> 20 |
21 | ) : ( 22 |
{title}
23 | )} 24 |
{input}
25 | 26 | {!isLast && } 27 | 28 | ); 29 | }); 30 | -------------------------------------------------------------------------------- /packages/frontend/src/components/properties/propertyTypes.tsx: -------------------------------------------------------------------------------- 1 | import type { SliderProps } from '@radix-ui/react-slider'; 2 | import type { SwitchProps } from '@radix-ui/react-switch'; 3 | import { memo } from 'react'; 4 | import { isDifferent } from '../../lib/utils'; 5 | import type { ColorProps } from '../ui/color'; 6 | import type { InputProps } from '../ui/input'; 7 | import type { Vector2Props, VectorXProps } from '../ui/vector2'; 8 | import { BooleanProperty } from './boolean-property'; 9 | import type { ButtonFnProps } from './button-property'; 10 | import { ButtonProperty } from './button-property'; 11 | import { ColorProperty } from './color-property'; 12 | import { NumberProperty } from './number-property'; 13 | import { RangeProperty } from './range-property'; 14 | import { SelectProperty } from './select-property'; 15 | import { TextProperty } from './text-property'; 16 | import { Vector2Property, VectorXProperty } from './vector-property'; 17 | import { MultiProperty } from './multi-property'; 18 | 19 | export type PropertyTypes = 20 | | 'boolean' 21 | | 'number' 22 | | 'range' 23 | | 'select' 24 | | 'text' 25 | | 'button' 26 | | 'vector2' 27 | | 'vectorX' 28 | | 'color' 29 | | 'multi'; 30 | 31 | export type SelectionProps = { options: string[] }; 32 | 33 | export type PropertyOptions = 34 | | InputProps 35 | | SwitchProps 36 | | Vector2Props 37 | | SliderProps 38 | | SelectionProps 39 | | VectorXProps 40 | | ColorProps 41 | | ButtonFnProps; 42 | 43 | export type PropertyPanelData = { 44 | value: any; 45 | prop: string; 46 | entry: { 47 | section: string; 48 | tooltip?: string; 49 | label?: string; 50 | type: PropertyTypes; 51 | options?: PropertyOptions; 52 | onChange: (value: string | number | boolean) => void; 53 | }; 54 | }; 55 | 56 | const memoTest = (props: any, nextProps: any) => { 57 | return !isDifferent(props, nextProps); 58 | }; 59 | 60 | export const propertyMap: Record> = { 61 | boolean: memo(BooleanProperty, memoTest), 62 | number: memo(NumberProperty, memoTest), 63 | vector2: memo(Vector2Property, memoTest), 64 | range: memo(RangeProperty, memoTest), 65 | select: memo(SelectProperty, memoTest), 66 | text: memo(TextProperty, memoTest), 67 | button: memo(ButtonProperty, memoTest), 68 | vectorX: memo(VectorXProperty, memoTest), 69 | color: memo(ColorProperty, memoTest), 70 | multi: memo(MultiProperty, memoTest), 71 | }; 72 | -------------------------------------------------------------------------------- /packages/frontend/src/components/properties/range-property.tsx: -------------------------------------------------------------------------------- 1 | import type { SliderProps } from '@radix-ui/react-slider'; 2 | import { Slider } from '../ui/slider'; 3 | import type { PropertyPanelData } from './propertyTypes'; 4 | 5 | export const RangeProperty: React.FC = ({ value, entry }) => { 6 | return ( 7 | entry.onChange(JSON.stringify(value))} 11 | className="border-border hover:border-secondary focus:border-secondary h-6 w-full rounded outline-none" 12 | /> 13 | ); 14 | }; 15 | -------------------------------------------------------------------------------- /packages/frontend/src/components/properties/select-property.tsx: -------------------------------------------------------------------------------- 1 | import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '../ui/select'; 2 | import type { PropertyPanelData, SelectionProps } from './propertyTypes'; 3 | 4 | export const SelectProperty: React.FC = ({ value, entry }) => { 5 | return ( 6 | 20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /packages/frontend/src/components/properties/text-property.tsx: -------------------------------------------------------------------------------- 1 | import type { InputProps } from '../ui/input'; 2 | import { Input } from '../ui/input'; 3 | import type { PropertyPanelData } from './propertyTypes'; 4 | 5 | export const TextProperty: React.FC = ({ value, entry }) => { 6 | return ( 7 | entry.onChange(JSON.stringify(e.target.value))} 12 | className="border-border hover:border-secondary focus:border-secondary h-6 w-full rounded text-xs outline-none" 13 | /> 14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /packages/frontend/src/components/properties/vector-property.tsx: -------------------------------------------------------------------------------- 1 | import type { Vector2Props, VectorXProps } from '../ui/vector2'; 2 | import { Vector2, VectorX } from '../ui/vector2'; 3 | import type { PropertyPanelData } from './propertyTypes'; 4 | 5 | export const Vector2Property: React.FC = ({ value, entry }) => { 6 | return ; 7 | }; 8 | 9 | export const VectorXProperty: React.FC = ({ value, entry }) => { 10 | return ; 11 | }; 12 | -------------------------------------------------------------------------------- /packages/frontend/src/components/theme-provider.tsx: -------------------------------------------------------------------------------- 1 | import { createContext, useContext, useEffect, useState } from 'react'; 2 | 3 | type Theme = 'dark' | 'light' | 'system'; 4 | 5 | type ThemeProviderProps = { 6 | children: React.ReactNode; 7 | defaultTheme?: Theme; 8 | storageKey?: string; 9 | }; 10 | 11 | type ThemeProviderState = { 12 | theme: Theme; 13 | setTheme: (theme: Theme) => void; 14 | }; 15 | 16 | const initialState: ThemeProviderState = { 17 | theme: 'system', 18 | setTheme: () => null, 19 | }; 20 | 21 | const ThemeProviderContext = createContext(initialState); 22 | 23 | export function ThemeProvider({ 24 | children, 25 | defaultTheme = 'system', 26 | storageKey = 'vite-ui-theme', 27 | ...props 28 | }: ThemeProviderProps) { 29 | const [theme, setTheme] = useState(() => (localStorage.getItem(storageKey) as Theme) || defaultTheme); 30 | 31 | useEffect(() => { 32 | const root = window.document.documentElement; 33 | 34 | root.classList.remove('light', 'dark'); 35 | 36 | if (theme === 'system') { 37 | const systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; 38 | 39 | root.classList.add(systemTheme); 40 | return; 41 | } 42 | 43 | root.classList.add(theme); 44 | }, [theme]); 45 | 46 | const value = { 47 | theme, 48 | setTheme: (theme: Theme) => { 49 | localStorage.setItem(storageKey, theme); 50 | setTheme(theme); 51 | }, 52 | }; 53 | 54 | return ( 55 | 56 | {children} 57 | 58 | ); 59 | } 60 | 61 | export const useTheme = () => { 62 | const context = useContext(ThemeProviderContext); 63 | 64 | if (context === undefined) throw new Error('useTheme must be used within a ThemeProvider'); 65 | 66 | return context; 67 | }; 68 | -------------------------------------------------------------------------------- /packages/frontend/src/components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Slot } from '@radix-ui/react-slot'; 3 | import { cva } from 'class-variance-authority'; 4 | import type { VariantProps } from 'class-variance-authority'; 5 | 6 | import { cn } from '../../lib/utils'; 7 | 8 | const buttonVariants = cva( 9 | 'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50', 10 | { 11 | variants: { 12 | variant: { 13 | default: 'bg-primary text-primary-foreground shadow hover:bg-primary/90', 14 | destructive: 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90', 15 | outline: 'border border-primary bg-background shadow-sm hover:bg-primary/90 hover:text-accent-foreground', 16 | secondary: 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80', 17 | ghost: 'hover:bg-accent hover:text-accent-foreground', 18 | link: 'text-primary underline-offset-4 hover:underline', 19 | }, 20 | size: { 21 | default: 'h-9 px-4 py-2', 22 | sm: 'h-8 rounded-md px-3 text-xs', 23 | xs: 'h-4 rounded-md px-2 text-xs', 24 | lg: 'h-10 rounded-md px-8', 25 | icon: 'h-9 w-9', 26 | }, 27 | }, 28 | defaultVariants: { 29 | variant: 'default', 30 | size: 'default', 31 | }, 32 | }, 33 | ); 34 | 35 | export interface ButtonProps 36 | extends React.ButtonHTMLAttributes, 37 | VariantProps { 38 | asChild?: boolean; 39 | } 40 | 41 | const Button = React.forwardRef( 42 | ({ className, variant, size, asChild = false, ...props }, ref) => { 43 | const Comp = asChild ? Slot : 'button'; 44 | return ; 45 | }, 46 | ); 47 | Button.displayName = 'Button'; 48 | 49 | export { Button }; 50 | -------------------------------------------------------------------------------- /packages/frontend/src/components/ui/checkbox.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as CheckboxPrimitive from '@radix-ui/react-checkbox'; 3 | import { CheckIcon } from '@radix-ui/react-icons'; 4 | 5 | import { cn } from '../../lib/utils'; 6 | 7 | const Checkbox = React.forwardRef< 8 | React.ElementRef, 9 | React.ComponentPropsWithoutRef 10 | >(({ className, ...props }, ref) => ( 11 | 19 | 20 | 21 | 22 | 23 | )); 24 | Checkbox.displayName = CheckboxPrimitive.Root.displayName; 25 | 26 | export { Checkbox }; 27 | -------------------------------------------------------------------------------- /packages/frontend/src/components/ui/clipboard.tsx: -------------------------------------------------------------------------------- 1 | import { FaRegCopy as ClipboardIcon, FaCheck as CheckIcon } from 'react-icons/fa6'; 2 | import { useState, useEffect } from 'react'; 3 | import { Button } from './button'; 4 | import { copyToClipboard } from '@devtool/frontend/lib/utils'; 5 | 6 | export const CopyToClipboardButton: React.FC<{ data: string }> = ({ data }) => { 7 | const [copied, setCopied] = useState(false); 8 | 9 | const onClick = (data: string) => { 10 | copyToClipboard(data); 11 | setCopied(true); 12 | }; 13 | 14 | useEffect(() => { 15 | if (copied) { 16 | const timer = setTimeout(() => { 17 | setCopied(false); 18 | }, 2000); // Change back to ClipboardIcon after 2 seconds 19 | 20 | return () => clearTimeout(timer); // Clean up on unmount 21 | } 22 | }, [copied]); 23 | 24 | return ( 25 | 33 | ); 34 | }; 35 | -------------------------------------------------------------------------------- /packages/frontend/src/components/ui/codearea.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; 3 | import { dracula } from 'react-syntax-highlighter/dist/esm/styles/prism'; 4 | 5 | export const CodeArea: React.FC<{ codeString: string }> = ({ codeString }) => { 6 | return ( 7 |
8 | 9 | {codeString} 10 | 11 |
12 | ); 13 | }; 14 | -------------------------------------------------------------------------------- /packages/frontend/src/components/ui/color.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import type { ChromePickerProps } from 'react-color'; 3 | import { ChromePicker } from 'react-color'; 4 | import { Button } from './button'; 5 | 6 | export interface ColorProps extends Omit { 7 | value: number; 8 | onChange: (color: string) => void; 9 | } 10 | 11 | export const ColorInput: React.FC = ({ value, onChange, ...rest }) => { 12 | const [displayColorPicker, setDisplayColorPicker] = useState(false); 13 | 14 | const handleClick = () => { 15 | setDisplayColorPicker(!displayColorPicker); 16 | }; 17 | 18 | const handleClose = () => { 19 | setDisplayColorPicker(false); 20 | }; 21 | 22 | // convert number ot hex 23 | const hexString = value?.toString(16) ?? 'ffffff'; 24 | 25 | const hex = `#${'000000'.substring(0, 6 - hexString.length) + hexString}`; 26 | 27 | return ( 28 |
29 | {displayColorPicker ? ( 30 |
31 | 39 |
40 | onChange(JSON.stringify(updatedColor.hex))} 43 | className="relative top-2" 44 | {...rest} 45 | /> 46 |
47 |
48 | ) : ( 49 | 57 | // 58 | )} 59 |
60 | ); 61 | }; 62 | -------------------------------------------------------------------------------- /packages/frontend/src/components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { cn } from '../../lib/utils'; 4 | 5 | export interface InputProps extends React.InputHTMLAttributes {} 6 | 7 | const Input = React.forwardRef(({ className, type, ...props }, ref) => { 8 | if (!props.defaultValue) { 9 | props.value = props.value ?? ''; 10 | } 11 | return ( 12 | 21 | ); 22 | }); 23 | Input.displayName = 'Input'; 24 | 25 | export { Input }; 26 | -------------------------------------------------------------------------------- /packages/frontend/src/components/ui/popover.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as PopoverPrimitive from '@radix-ui/react-popover'; 3 | 4 | import { cn } from '../../lib/utils'; 5 | 6 | const Popover = PopoverPrimitive.Root; 7 | 8 | const PopoverTrigger = PopoverPrimitive.Trigger; 9 | 10 | const PopoverAnchor = PopoverPrimitive.Anchor; 11 | 12 | const PopoverContent = React.forwardRef< 13 | React.ElementRef, 14 | React.ComponentPropsWithoutRef 15 | >(({ className, align = 'center', sideOffset = 4, ...props }, ref) => ( 16 | 17 | 27 | 28 | )); 29 | PopoverContent.displayName = PopoverPrimitive.Content.displayName; 30 | 31 | export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }; 32 | -------------------------------------------------------------------------------- /packages/frontend/src/components/ui/radio-group.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | // import { CheckIcon } from '@radix-ui/react-icons'; 3 | import { FaCircle as CheckIcon } from 'react-icons/fa6'; 4 | 5 | import * as RadioGroupPrimitive from '@radix-ui/react-radio-group'; 6 | 7 | import { cn } from '../../lib/utils'; 8 | 9 | const RadioGroup = React.forwardRef< 10 | React.ElementRef, 11 | React.ComponentPropsWithoutRef 12 | >(({ className, ...props }, ref) => { 13 | return ; 14 | }); 15 | RadioGroup.displayName = RadioGroupPrimitive.Root.displayName; 16 | 17 | const RadioGroupItem = React.forwardRef< 18 | React.ElementRef, 19 | React.ComponentPropsWithoutRef 20 | >(({ className, ...props }, ref) => { 21 | return ( 22 | 30 | 31 | 32 | 33 | 34 | ); 35 | }); 36 | RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName; 37 | 38 | export { RadioGroup, RadioGroupItem }; 39 | -------------------------------------------------------------------------------- /packages/frontend/src/components/ui/separator.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as SeparatorPrimitive from '@radix-ui/react-separator'; 3 | 4 | import { cn } from '../../lib/utils'; 5 | 6 | const Separator = React.forwardRef< 7 | React.ElementRef, 8 | React.ComponentPropsWithoutRef 9 | >(({ className, orientation = 'horizontal', decorative = true, ...props }, ref) => ( 10 | 17 | )); 18 | Separator.displayName = SeparatorPrimitive.Root.displayName; 19 | 20 | export { Separator }; 21 | -------------------------------------------------------------------------------- /packages/frontend/src/components/ui/slider.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as SliderPrimitive from '@radix-ui/react-slider'; 3 | 4 | import { cn } from '../../lib/utils'; 5 | 6 | const Slider = React.forwardRef< 7 | React.ElementRef, 8 | React.ComponentPropsWithoutRef 9 | >(({ className, ...props }, ref) => ( 10 | 15 | 16 | 17 | 18 | 19 | 20 | )); 21 | Slider.displayName = SliderPrimitive.Root.displayName; 22 | 23 | export { Slider }; 24 | -------------------------------------------------------------------------------- /packages/frontend/src/components/ui/svg-texture.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useTheme } from '../theme-provider'; 3 | 4 | interface SVGTextureProps { 5 | color: string; 6 | } 7 | 8 | export const SVGTexture: React.FC = ({ color }) => { 9 | const { theme } = useTheme(); 10 | 11 | const color1 = theme === 'dark' ? 'hsla(0,0%,15%,0.75)' : 'hsla(0,0%,94%,0.75)'; 12 | 13 | return ( 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | ); 26 | }; 27 | -------------------------------------------------------------------------------- /packages/frontend/src/components/ui/switch.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as SwitchPrimitives from '@radix-ui/react-switch'; 3 | 4 | import { cn } from '../../lib/utils'; 5 | 6 | const Switch = React.forwardRef< 7 | React.ElementRef, 8 | React.ComponentPropsWithoutRef 9 | >(({ className, ...props }, ref) => ( 10 | 18 | 23 | 24 | )); 25 | Switch.displayName = SwitchPrimitives.Root.displayName; 26 | 27 | export { Switch }; 28 | -------------------------------------------------------------------------------- /packages/frontend/src/components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { cn } from '../../lib/utils'; 4 | 5 | export interface TextareaProps extends React.TextareaHTMLAttributes {} 6 | 7 | const Textarea = React.forwardRef(({ className, ...props }, ref) => { 8 | return ( 9 |