From d563a87ebe5353c80515ae5c85829f02a7e48064 Mon Sep 17 00:00:00 2001 From: Zyie <24736175+Zyie@users.noreply.github.com> Date: Mon, 12 Feb 2024 15:29:08 +0000 Subject: [PATCH] tidy --- .github/workflows/main.yml | 126 +- README.md | 23 +- global.d.ts | 9 + package-lock.json | 16 + package.json | 4 +- src/chrome/src/background/index.ts | 1 - src/chrome/src/content/index.ts | 2 +- src/chrome/src/devtools/index.html | 2 +- src/chrome/src/devtools/panel/panel.html | 2 +- src/chrome/src/inject/index.ts | 2 +- src/chrome/src/popup/index.html | 2 +- src/example/index.html | 2 +- src/example/interactivity.html | 2 +- src/example/src/main.ts | 6 +- src/example/text.html | 2 +- src/lib/src/components/Container.tsx | 9 +- .../components/collapsible/Collapsible.css | 61 - .../components/collapsible/Collapsible.tsx | 36 +- src/lib/src/components/collapsible/styles.tsx | 51 + .../components/properties/BaseProperty.tsx | 9 +- .../src/components/properties/Properties.css | 4 - .../src/components/properties/Properties.tsx | 54 +- .../components/properties/number/Number.tsx | 6 +- .../components/properties/number/Vector2.tsx | 8 +- .../components/properties/slider/Slider.tsx | 4 +- .../components/properties/switch/Switch.tsx | 15 +- .../src/components/properties/text/Text.tsx | 12 +- .../src/components/scene/SceneComponent.css | 0 .../src/components/scene/SceneComponent.tsx | 158 +- src/lib/src/components/scene/inspector.tsx | 22 + src/lib/src/components/scene/stats.tsx | 155 ++ .../src/components/smooth-charts/smoothie.ts | 1948 +++++++++-------- .../src/components/tree/TreeViewComponent.css | 67 - .../src/components/tree/TreeViewComponent.tsx | 56 +- src/lib/src/components/tree/styles.tsx | 69 + src/lib/src/detection/devtool.ts | 33 +- .../properties/AnimatedSpritePropsPlugin.ts | 25 + .../properties/ContainerPropsPlugin.ts | 102 +- .../properties/GraphicsPropsPlugin.ts | 17 + .../detection/properties/MeshPropsPlugin.ts | 18 + .../properties/NineSlicePropsPlugin.ts | 44 + .../detection/properties/PropertyPlugins.ts | 77 + .../detection/properties/SpritePropsPlugin.ts | 10 +- .../detection/properties/TextPropsPlugin.ts | 20 + .../properties/TilingSpritePropsPlugin.ts | 31 + .../src/detection/properties/properties.ts | 35 +- src/lib/src/detection/updateSceneGraph.ts | 3 +- src/lib/src/detection/utils/getPixiType.ts | 3 +- src/lib/src/detection/utils/loop.ts | 30 +- src/lib/src/detection/utils/poll.ts | 4 +- src/lib/src/detection/utils/utils.ts | 13 - src/lib/src/pages/index.css | 78 +- src/lib/src/pages/index.tsx | 9 +- src/lib/src/pages/styles.tsx | 41 + src/lib/src/utils/Diff.ts | 2 +- src/lib/src/utils/DiffChar.ts | 4 +- src/lib/src/utils/checkDiff.ts | 1 - tsconfig.json | 51 +- vite.inject.config.ts | 10 +- 59 files changed, 1979 insertions(+), 1627 deletions(-) delete mode 100644 src/lib/src/components/collapsible/Collapsible.css create mode 100644 src/lib/src/components/collapsible/styles.tsx delete mode 100644 src/lib/src/components/properties/Properties.css delete mode 100644 src/lib/src/components/scene/SceneComponent.css create mode 100644 src/lib/src/components/scene/inspector.tsx create mode 100644 src/lib/src/components/scene/stats.tsx delete mode 100644 src/lib/src/components/tree/TreeViewComponent.css create mode 100644 src/lib/src/components/tree/styles.tsx create mode 100644 src/lib/src/detection/properties/AnimatedSpritePropsPlugin.ts create mode 100644 src/lib/src/detection/properties/GraphicsPropsPlugin.ts create mode 100644 src/lib/src/detection/properties/MeshPropsPlugin.ts create mode 100644 src/lib/src/detection/properties/NineSlicePropsPlugin.ts create mode 100644 src/lib/src/detection/properties/PropertyPlugins.ts create mode 100644 src/lib/src/detection/properties/TextPropsPlugin.ts create mode 100644 src/lib/src/detection/properties/TilingSpritePropsPlugin.ts delete mode 100644 src/lib/src/detection/utils/utils.ts create mode 100644 src/lib/src/pages/styles.tsx diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7f3a63a..b1dcb64 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,11 +1,11 @@ name: Automation on: push: - branches: [ '**' ] + branches: ['**'] release: - types: [ published ] + types: [published] pull_request: - branches: [ '**' ] + branches: ['**'] jobs: build: name: Build @@ -18,70 +18,70 @@ jobs: SOURCE_DIR: 'deploy' runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v3 - - name: Install xvfb - run: sudo apt-get install xvfb - - name: Use Node.js 18.x - uses: actions/setup-node@v3 - with: - node-version: 18 - - name: Install Dependencies - run: npm ci + - uses: actions/checkout@v3 + - name: Install xvfb + run: sudo apt-get install xvfb + - name: Use Node.js 18.x + uses: actions/setup-node@v3 + with: + node-version: 18 + - name: Install Dependencies + run: npm ci - - name: Build for Distribution - run: xvfb-run --auto-servernum npm run dist + - name: Build for Distribution + run: xvfb-run --auto-servernum npm run dist - # All the below are deploy-related steps - - name: Extract Branch Name - id: branch_name - if: github.event_name == 'push' && !contains(github.ref, 'refs/tags') - run: echo BRANCH_NAME=${GITHUB_REF/refs\/heads\//} >> $GITHUB_OUTPUT + # All the below are deploy-related steps + - name: Extract Branch Name + id: branch_name + if: github.event_name == 'push' && !contains(github.ref, 'refs/tags') + run: echo BRANCH_NAME=${GITHUB_REF/refs\/heads\//} >> $GITHUB_OUTPUT - # Examples: - # 1) PR feature/acme merged into dev - # 2) branch A merged into branch B - # 3) branch A pushed directly to git - - name: Deploy Non-Tag Branches - uses: jakejarvis/s3-sync-action@master - if: github.event_name == 'push' && env.AWS_ACCESS_KEY_ID != '' - with: - args: --acl public-read --follow-symlinks --delete --cache-control "max-age=60" - env: - DEST_DIR: ${{ steps.branch_name.outputs.BRANCH_NAME }} + # Examples: + # 1) PR feature/acme merged into dev + # 2) branch A merged into branch B + # 3) branch A pushed directly to git + - name: Deploy Non-Tag Branches + uses: jakejarvis/s3-sync-action@master + if: github.event_name == 'push' && env.AWS_ACCESS_KEY_ID != '' + with: + args: --acl public-read --follow-symlinks --delete --cache-control "max-age=60" + env: + DEST_DIR: ${{ steps.branch_name.outputs.BRANCH_NAME }} - # Release is published and deployed into s3://bucket-name/v5.22/ - - name: Deploy Released Branches - uses: jakejarvis/s3-sync-action@master - if: github.event_name == 'release' && env.AWS_ACCESS_KEY_ID != '' - with: - args: --acl public-read --follow-symlinks --delete --cache-control "max-age=2592000" - env: - DEST_DIR: ${{ github.event.release.tag_name }} + # Release is published and deployed into s3://bucket-name/v5.22/ + - name: Deploy Released Branches + uses: jakejarvis/s3-sync-action@master + if: github.event_name == 'release' && env.AWS_ACCESS_KEY_ID != '' + with: + args: --acl public-read --follow-symlinks --delete --cache-control "max-age=2592000" + env: + DEST_DIR: ${{ github.event.release.tag_name }} - # Same release from previous deployed into s3://bucket-name/release/ - - name: Deploy Latest Release - uses: jakejarvis/s3-sync-action@master - if: github.event_name == 'release' && github.event.release.prerelease == false && env.AWS_ACCESS_KEY_ID != '' - with: - args: --acl public-read --follow-symlinks --delete --cache-control "max-age=1209600" - env: - DEST_DIR: 'latest' + # Same release from previous deployed into s3://bucket-name/release/ + - name: Deploy Latest Release + uses: jakejarvis/s3-sync-action@master + if: github.event_name == 'release' && github.event.release.prerelease == false && env.AWS_ACCESS_KEY_ID != '' + with: + args: --acl public-read --follow-symlinks --delete --cache-control "max-age=1209600" + env: + DEST_DIR: 'latest' - # Publish to NPM - - name: Publish Latest Release - if: github.event_name == 'release' && github.event.release.prerelease == false && env.NODE_AUTH_TOKEN != '' - run: npm run publish-ci + # Publish to NPM + - name: Publish Latest Release + if: github.event_name == 'release' && github.event.release.prerelease == false && env.NODE_AUTH_TOKEN != '' + run: npm run publish-ci - # Publish to NPM with prerelease dist-tag - - name: Publish Latest Prerelease - if: github.event_name == 'release' && github.event.release.prerelease && env.NODE_AUTH_TOKEN != '' - run: npm run publish-ci - env: - XS_PUBLISH_TAG: prerelease - - # Automatically attach browser files to release - - name: Upload to Release - if: github.event_name == 'release' - uses: softprops/action-gh-release@v1 - with: - files: dist/* \ No newline at end of file + # Publish to NPM with prerelease dist-tag + - name: Publish Latest Prerelease + if: github.event_name == 'release' && github.event.release.prerelease && env.NODE_AUTH_TOKEN != '' + run: npm run publish-ci + env: + XS_PUBLISH_TAG: prerelease + + # Automatically attach browser files to release + - name: Upload to Release + if: github.event_name == 'release' + uses: softprops/action-gh-release@v1 + with: + files: dist/* diff --git a/README.md b/README.md index 3999d58..5f5aaef 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Depending on your version of PixiJS, you'll need to figure out which major version of PixiJS Filters to use. | PixiJS | PixiJS Filters | -|-------------|----------------| +| ----------- | -------------- | | v5.x | v4.x | | v6.x - v7.x | v5.x | | v8.x | v6.x | @@ -34,8 +34,8 @@ If all else failes, you can manually download the bundled file from the [release ## Filters -| Filter | Preview | -|----------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------| +| Filter | Preview | +| -------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | --- | | **AdjustmentFilter**
_pixi-filters/adjustment_
[View demo][Adjustment_demo] | ![adjustment](https://filters.pixijs.download/main/screenshots/adjustment.png?v=3) | | **AdvancedBloomFilter**
_pixi-filters/advanced-bloom_
[View demo][AdvancedBloom_demo] | ![advanced-bloom](https://filters.pixijs.download/main/screenshots/advanced-bloom.png?v=3) | | **AsciiFilter**
_pixi-filters/ascii_
[View demo][Ascii_demo] | ![ascii](https://filters.pixijs.download/main/screenshots/ascii.png?v=3) | @@ -65,8 +65,8 @@ If all else failes, you can manually download the bundled file from the [release | **PixelateFilter**
_pixi-filters/pixelate_
[View demo][Pixelate_demo] | ![pixelate](https://filters.pixijs.download/main/screenshots/pixelate.png?v=3) | | **RadialBlurFilter**
_pixi-filters/radial-blur_
[View demo][RadialBlur_demo] | ![radial-blur](https://filters.pixijs.download/main/screenshots/radial-blur.png?v=3) | | **ReflectionFilter**
_pixi-filters/reflection_
[View demo][Reflection_demo] | ![reflection](https://filters.pixijs.download/main/screenshots/reflection.png?v=3) | -| **RGBSplitFilter**
_pixi-filters/rgb-split_
[View demo][RGBSplit_demo] | ![rgb split](https://filters.pixijs.download/main/screenshots/rgb.png?v=3) | | -| **ShockwaveFilter**
_pixi-filters/shockwave_
[View demo][Shockwave_demo] | ![shockwave](https://filters.pixijs.download/main/screenshots/shockwave.gif?v=3) | +| **RGBSplitFilter**
_pixi-filters/rgb-split_
[View demo][RGBSplit_demo] | ![rgb split](https://filters.pixijs.download/main/screenshots/rgb.png?v=3) | | +| **ShockwaveFilter**
_pixi-filters/shockwave_
[View demo][Shockwave_demo] | ![shockwave](https://filters.pixijs.download/main/screenshots/shockwave.gif?v=3) | | **SimpleLightmapFilter**
_pixi-filters/simple-lightmap_
[View demo][SimpleLightmap_demo] | ![simple-lightmap](https://filters.pixijs.download/main/screenshots/simple-lightmap.png?v=3) | | **TiltShiftFilter**
_pixi-filters/tilt-shift_
[View demo][TiltShift_demo] | ![tilt-shift](https://filters.pixijs.download/main/screenshots/tilt-shift.png?v=3) | | **TwistFilter**
_pixi-filters/twist_
[View demo][Twist_demo] | ![twist](https://filters.pixijs.download/main/screenshots/twist.png?v=3) | @@ -76,10 +76,10 @@ If all else failes, you can manually download the bundled file from the [release PixiJS has a handful of core filters that are built-in to the PixiJS library. -| Filter | Preview | -|-----------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------| -| **AlphaFilter**
[View demo][Alpha_demo] | ![alpha](https://filters.pixijs.download/main/screenshots/alpha.png?v=3) | -| **BlurFilter**
[View demo][Blur_demo] | ![blur](https://filters.pixijs.download/main/screenshots/blur.png?v=3) | +| Filter | Preview | +| ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | +| **AlphaFilter**
[View demo][Alpha_demo] | ![alpha](https://filters.pixijs.download/main/screenshots/alpha.png?v=3) | +| **BlurFilter**
[View demo][Blur_demo] | ![blur](https://filters.pixijs.download/main/screenshots/blur.png?v=3) | | **ColorMatrixFilter** (contrast)
[View demo][ColorMatrix_demo] | ![color-matrix-contrast](https://filters.pixijs.download/main/screenshots/color-matrix-contrast.png?v=3) | | **ColorMatrixFilter** (desaturate)
[View demo][ColorMatrix_demo] | ![color-matrix-desaturate](https://filters.pixijs.download/main/screenshots/color-matrix-desaturate.png?v=3) | | **ColorMatrixFilter** (kodachrome)
[View demo][ColorMatrix_demo] | ![color-matrix-kodachrome](https://filters.pixijs.download/main/screenshots/color-matrix-kodachrome.png?v=3) | @@ -89,8 +89,8 @@ PixiJS has a handful of core filters that are built-in to the PixiJS library. | **ColorMatrixFilter** (predator)
[View demo][ColorMatrix_demo] | ![color-matrix-predator](https://filters.pixijs.download/main/screenshots/color-matrix-predator.png?v=3) | | **ColorMatrixFilter** (saturate)
[View demo][ColorMatrix_demo] | ![color-matrix-saturate](https://filters.pixijs.download/main/screenshots/color-matrix-saturate.png?v=3) | | **ColorMatrixFilter** (sepia)
[View demo][ColorMatrix_demo] | ![color-matrix-sepia](https://filters.pixijs.download/main/screenshots/color-matrix-sepia.png?v=3) | -| **DisplacementFilter**
[View demo][Displacement_demo] | ![displacement](https://filters.pixijs.download/main/screenshots/displacement.png?v=3) | -| **NoiseFilter**
[View demo][Noise_demo] | ![noise](https://filters.pixijs.download/main/screenshots/noise.png?v=3) | +| **DisplacementFilter**
[View demo][Displacement_demo] | ![displacement](https://filters.pixijs.download/main/screenshots/displacement.png?v=3) | +| **NoiseFilter**
[View demo][Noise_demo] | ![noise](https://filters.pixijs.download/main/screenshots/noise.png?v=3) | ## Building @@ -117,6 +117,7 @@ npm run watch API documention can be found [here](http://pixijs.io/filters/docs/). + [Adjustment_demo]: https://filters.pixijs.download/main/examples/index.html?enabled=AdjustmentFilter [AdvancedBloom_demo]: https://filters.pixijs.download/main/examples/index.html?enabled=AdvancedBloomFilter [Ascii_demo]: https://filters.pixijs.download/main/examples/index.html?enabled=AsciiFilter diff --git a/global.d.ts b/global.d.ts index 7b07860..390564c 100644 --- a/global.d.ts +++ b/global.d.ts @@ -5,6 +5,15 @@ declare global { __PIXI_RENDERER__: import('pixi.js').Renderer | undefined; __PIXI__: import('pixi.js'); __PIXI_DEVTOOL_WRAPPER__: any; + __PIXI__DEVTOOLS__: { + pixi: typeof import('pixi.js'); + app: import('pixi.js').Application | undefined; + stage?: import('pixi.js').Container | undefined; + renderer?: import('pixi.js').Renderer | undefined; + scenePanel?: { + propertyPlugins?: any[]; + }; + }; } } diff --git a/package-lock.json b/package-lock.json index 2a2648c..7e2219c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -58,6 +58,7 @@ "patch-package": "^8.0.0", "pixi.js": "^8.0.0-rc.8", "pre-commit": "^1.2.2", + "prettier": "^3.2.5", "rimraf": "^4.4.1", "ts-node": "^10.9.1", "typescript": "^5.3.3", @@ -15954,6 +15955,21 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/pretty-format": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", diff --git a/package.json b/package.json index 5a22dfa..95a4763 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,8 @@ "copy:assets": "copyfiles \"{dist,docs,examples}/**\" deploy", "deploy": "xs clean,build,docs && xs upload", "release": "xs bump,test && run-s deploy && xs git-push", - "publish-ci": "xs publish" + "publish-ci": "xs publish", + "prettier": "prettier . --write" }, "pre-commit": [ "lint", @@ -84,6 +85,7 @@ "patch-package": "^8.0.0", "pixi.js": "^8.0.0-rc.8", "pre-commit": "^1.2.2", + "prettier": "^3.2.5", "rimraf": "^4.4.1", "ts-node": "^10.9.1", "typescript": "^5.3.3", diff --git a/src/chrome/src/background/index.ts b/src/chrome/src/background/index.ts index 6c65659..f2a9f8e 100644 --- a/src/chrome/src/background/index.ts +++ b/src/chrome/src/background/index.ts @@ -52,7 +52,6 @@ chrome.runtime.onMessage.addListener((request: Message, sender: chrome.runtime.M } }); - chrome.runtime.onConnect.addListener(function (port) { if (port.name !== 'devtools-connection') return; diff --git a/src/chrome/src/content/index.ts b/src/chrome/src/content/index.ts index d00cf21..c535ae9 100644 --- a/src/chrome/src/content/index.ts +++ b/src/chrome/src/content/index.ts @@ -29,5 +29,5 @@ window.addEventListener( chrome.runtime.sendMessage(message); } }, - false + false, ); diff --git a/src/chrome/src/devtools/index.html b/src/chrome/src/devtools/index.html index 687359c..7fc23cd 100644 --- a/src/chrome/src/devtools/index.html +++ b/src/chrome/src/devtools/index.html @@ -1,4 +1,4 @@ - + diff --git a/src/chrome/src/devtools/panel/panel.html b/src/chrome/src/devtools/panel/panel.html index 375262e..4334723 100644 --- a/src/chrome/src/devtools/panel/panel.html +++ b/src/chrome/src/devtools/panel/panel.html @@ -1,4 +1,4 @@ - + diff --git a/src/chrome/src/inject/index.ts b/src/chrome/src/inject/index.ts index 375218d..f363503 100644 --- a/src/chrome/src/inject/index.ts +++ b/src/chrome/src/inject/index.ts @@ -1 +1 @@ -import '@lib/src/detection' \ No newline at end of file +import '@lib/src/detection'; diff --git a/src/chrome/src/popup/index.html b/src/chrome/src/popup/index.html index 8d33c0a..969f919 100644 --- a/src/chrome/src/popup/index.html +++ b/src/chrome/src/popup/index.html @@ -1,4 +1,4 @@ - + diff --git a/src/example/index.html b/src/example/index.html index 1336536..ff34768 100644 --- a/src/example/index.html +++ b/src/example/index.html @@ -1,4 +1,4 @@ - + diff --git a/src/example/interactivity.html b/src/example/interactivity.html index 82ead10..dae4efc 100644 --- a/src/example/interactivity.html +++ b/src/example/interactivity.html @@ -1,4 +1,4 @@ - + diff --git a/src/example/src/main.ts b/src/example/src/main.ts index ecc4cca..4557b72 100644 --- a/src/example/src/main.ts +++ b/src/example/src/main.ts @@ -8,8 +8,10 @@ import * as PIXI from 'pixi.js'; // Initialize the application await app.init({ background: '#1099bb', resizeTo: window }); - window.__PIXI_APP__ = app; - window.__PIXI__ = PIXI; + window.__PIXI__DEVTOOLS__ = { + app: app, + pixi: PIXI, + } // Append the application canvas to the document body document.body.appendChild(app.canvas); diff --git a/src/example/text.html b/src/example/text.html index 1d663b1..c8dd216 100644 --- a/src/example/text.html +++ b/src/example/text.html @@ -1,4 +1,4 @@ - + diff --git a/src/lib/src/components/Container.tsx b/src/lib/src/components/Container.tsx index 2a7f643..410c541 100644 --- a/src/lib/src/components/Container.tsx +++ b/src/lib/src/components/Container.tsx @@ -2,14 +2,12 @@ import styled from 'styled-components'; export const Container = styled.div` background-color: #292929; - padding: 10px; - width: calc(100% - 20px); + width: calc(100%); height: 100vh; `; export const SectionContainer = styled.div` margin-bottom: 5px; - `; export const SectionHeader = styled.div` @@ -20,11 +18,10 @@ export const TitleGroup = styled.div` display: flex; flex: 1; justify-content: space-between; - /*margin-bottom: 10px;*/ `; export const Title = styled.span` - font-size: 1.3em; + font-size: 12px; color: #ccc; font-weight: light; `; @@ -32,4 +29,4 @@ export const Title = styled.span` export const OptionsGroup = styled.div` display: flex; margin: 5px 0; -`; \ No newline at end of file +`; diff --git a/src/lib/src/components/collapsible/Collapsible.css b/src/lib/src/components/collapsible/Collapsible.css deleted file mode 100644 index 2557c87..0000000 --- a/src/lib/src/components/collapsible/Collapsible.css +++ /dev/null @@ -1,61 +0,0 @@ -/* reset */ -button { - all: unset; -} - -.CollapsibleRoot { - width: 100%; - margin-top: 16px; -} - -.collapsible-header { - display: flex; - align-items: center; - justify-content: space-between; - background-color: var(--active-bg-color); - height: 36px; - padding: 0 24px; - border-radius: 4px; - margin-bottom: 8px; - cursor: pointer; -} - -.CollapsibleContent { - padding: 0 24px; - border-radius: 4px; - background-color: var(--panel-background-color); - padding-top: 8px; - display: flex; - flex-wrap: wrap; -} - -.IconButton { - display: inline-flex; - align-items: center; - color: white; - justify-content: center; -} - -.IconButton svg { - width: 20px; - height: 20px; -} - -/* .IconButton[data-state='closed'] { - background-color: white; -} -.IconButton[data-state='open'] { - background-color: var(--violet-3); -} -.IconButton:hover { - background-color: var(--violet-3); -} -.IconButton:focus { - box-shadow: 0 0 0 2px black; -} */ - -.Text { - font-size: 16px; - font-weight: bold; - line-height: 25px; -} diff --git a/src/lib/src/components/collapsible/Collapsible.tsx b/src/lib/src/components/collapsible/Collapsible.tsx index efc09ab..6dad49a 100644 --- a/src/lib/src/components/collapsible/Collapsible.tsx +++ b/src/lib/src/components/collapsible/Collapsible.tsx @@ -1,28 +1,30 @@ import React from 'react'; -import * as Collapsible from '@radix-ui/react-collapsible'; -// import './styles.css'; -import './Collapsible.css'; -import { FaCircleMinus, FaCirclePlus } from 'react-icons/fa6'; +import { FaAngleDown, FaAngleUp } from 'react-icons/fa6'; +import { + CollapsibleContent, + CollapsibleHeader, + CollapsibleHeaderIcon, + CollapsibleRoot, + CollapsibleTrigger, +} from './styles'; interface CollapsibleProps { title: string; children?: React.ReactNode; + contentWrap?: 'wrap' | 'nowrap'; } -const CollapsibleComponent: React.FC = ({ children, title }) => { +const CollapsibleComponent: React.FC = ({ children, title, contentWrap }) => { const [open, setOpen] = React.useState(true); return ( - - -
- - {title} - - - {/* */} -
-
- {children} -
+ + + + {title} + {open ? : } + + + {children} + ); }; diff --git a/src/lib/src/components/collapsible/styles.tsx b/src/lib/src/components/collapsible/styles.tsx new file mode 100644 index 0000000..21c2f45 --- /dev/null +++ b/src/lib/src/components/collapsible/styles.tsx @@ -0,0 +1,51 @@ +import styled from 'styled-components'; +import * as Collapsible from '@radix-ui/react-collapsible'; + +export const CollapsibleRoot = styled(Collapsible.Root)` + background-color: var(--header); + width: calc(100%); +`; + +export const CollapsibleTrigger = styled(Collapsible.Trigger)``; + +export const CollapsibleHeader = styled.div` + display: flex; + align-items: center; + justify-content: space-between; + height: 30px; + padding: 0 14px; + cursor: pointer; + border-bottom: 1px solid var(--line); + color: var(--text); + font-size: 14px; + font-weight: bold; + + &:hover { + border-bottom: 1px solid var(--primary-color); + } + + &[data-state='closed']:hover { + border-bottom: 1px solid var(--secondary-color); + } +`; + +export const CollapsibleHeaderIcon = styled.button` + all: unset; + display: inline-flex; + align-items: center; + color: var(--text); + justify-content: center; + + svg { + width: 14px; + height: 14px; + } +`; + +export const CollapsibleContent = styled(Collapsible.Content)<{ wrap?: 'wrap' | 'nowrap' }>` + background-color: var(--bg); + padding-top: 8px; + display: flex; + flex-wrap: ${({ wrap = 'wrap' }) => wrap}; + flex-direction: ${({ wrap = 'wrap' }) => (wrap === 'wrap' ? 'row' : 'column')}; +`; diff --git a/src/lib/src/components/properties/BaseProperty.tsx b/src/lib/src/components/properties/BaseProperty.tsx index 99b7839..371729d 100644 --- a/src/lib/src/components/properties/BaseProperty.tsx +++ b/src/lib/src/components/properties/BaseProperty.tsx @@ -9,17 +9,16 @@ export interface BasePropertyProps { const Wrapper = styled.div` display: flex; - padding: 8px 20px; - flex-wrap: wrap; - gap: 15px; + padding: 2px 16px; + gap: 8px; align-items: center; `; const StyledLabelRoot = styled(Label.Root)` - font-size: 15px; + font-size: 12px; font-weight: 500; line-height: 35px; - color: white; + color: var(--text); `; export const BaseProperty: React.FC = ({ label, children }) => { diff --git a/src/lib/src/components/properties/Properties.css b/src/lib/src/components/properties/Properties.css deleted file mode 100644 index 873b851..0000000 --- a/src/lib/src/components/properties/Properties.css +++ /dev/null @@ -1,4 +0,0 @@ -/* reset */ -input { - all: unset; -} diff --git a/src/lib/src/components/properties/Properties.tsx b/src/lib/src/components/properties/Properties.tsx index af9cc04..3b57021 100644 --- a/src/lib/src/components/properties/Properties.tsx +++ b/src/lib/src/components/properties/Properties.tsx @@ -2,13 +2,11 @@ import { usePixiStore } from '@lib/src/pages'; import React from 'react'; import { SectionContainer, SectionHeader, Title, TitleGroup } from '../Container'; import CollapsibleComponent from '../collapsible/Collapsible'; -import './Properties.css'; import { NumberInput, NumberProps } from './number/Number'; import { Slider, SliderProps } from './slider/Slider'; import { SwitchProps, Switcher } from './switch/Switch'; import { TextInput, TextProps } from './text/Text'; import { Vector2, Vector2Props } from './number/Vector2'; - export interface PropertyProps { type: | 'boolean' @@ -107,31 +105,33 @@ const PropertiesComponent: React.FC = () => { }); return ( - - - - Properties - - - {Object.keys(sections).map((section) => ( - - - <> - {sections[section].map((property) => ( - - ))} - - - - ))} - +
+ + + + Properties + + + {Object.keys(sections).map((section) => ( + + + <> + {sections[section].map((property) => ( + + ))} + + + + ))} + +
); }; diff --git a/src/lib/src/components/properties/number/Number.tsx b/src/lib/src/components/properties/number/Number.tsx index 6473fd7..8f8f354 100644 --- a/src/lib/src/components/properties/number/Number.tsx +++ b/src/lib/src/components/properties/number/Number.tsx @@ -17,10 +17,10 @@ const StyledNumberRoot = styled.input` justify-content: center; border-radius: 4px; padding: 0 10px; - height: 35px; - font-size: 15px; + height: 24px; + font-size: 10px; line-height: 1; - color: white; + color: var(--text); background-color: var(--darkest-color); box-shadow: 0 0 0 1px black; diff --git a/src/lib/src/components/properties/number/Vector2.tsx b/src/lib/src/components/properties/number/Vector2.tsx index 59d81d2..7b5087f 100644 --- a/src/lib/src/components/properties/number/Vector2.tsx +++ b/src/lib/src/components/properties/number/Vector2.tsx @@ -19,11 +19,11 @@ const Vector2Container = styled.div` export const Vector2: React.FC = ({ x, y, label, onChange, value }) => { x.onChange = (value: number) => { console.log('x.onChange', value, y.value); - onChange(JSON.stringify([value, y.value])) - } + onChange(JSON.stringify([value, y.value])); + }; y.onChange = (value: number) => { - onChange(JSON.stringify([x.value, value])) - } + onChange(JSON.stringify([x.value, value])); + }; x.value = value[0]; y.value = value[1]; diff --git a/src/lib/src/components/properties/slider/Slider.tsx b/src/lib/src/components/properties/slider/Slider.tsx index 8b9a7ec..07ab4d2 100644 --- a/src/lib/src/components/properties/slider/Slider.tsx +++ b/src/lib/src/components/properties/slider/Slider.tsx @@ -31,8 +31,8 @@ const StyledSliderTrack = styled(Sliderer.Track)` const StyledSliderThumb = styled(Sliderer.Thumb)` display: block; - width: 20px; - height: 20px; + width: 15px; + height: 15px; background-color: white; box-shadow: 0 2px 10px black; border-radius: 10px; diff --git a/src/lib/src/components/properties/switch/Switch.tsx b/src/lib/src/components/properties/switch/Switch.tsx index fd8dcc1..9eba597 100644 --- a/src/lib/src/components/properties/switch/Switch.tsx +++ b/src/lib/src/components/properties/switch/Switch.tsx @@ -6,11 +6,12 @@ import { BaseProperty, BasePropertyProps } from '../BaseProperty'; export interface SwitchProps extends BasePropertyProps { value: boolean; onChange: (value: boolean) => void; + disabled?: boolean; } const StyledSwitchRoot = styled(Switch.Root)` - width: 42px; - height: 25px; + width: 26px; + height: 17px; background-color: var(--darkest-color); border-radius: 9999px; position: relative; @@ -28,8 +29,8 @@ const StyledSwitchRoot = styled(Switch.Root)` const StyledSwitchThumb = styled(Switch.Thumb)` display: block; - width: 21px; - height: 21px; + width: 15px; + height: 15px; background-color: white; border-radius: 9999px; box-shadow: 0 2px 2px black; @@ -38,14 +39,14 @@ const StyledSwitchThumb = styled(Switch.Thumb)` will-change: transform; &[data-state='checked'] { - transform: translateX(19px); + transform: translateX(10px); } `; -export const Switcher: React.FC = ({ value, onChange, label }) => { +export const Switcher: React.FC = ({ value, onChange, label, disabled = false }) => { return ( - + diff --git a/src/lib/src/components/properties/text/Text.tsx b/src/lib/src/components/properties/text/Text.tsx index eda4955..5891e72 100644 --- a/src/lib/src/components/properties/text/Text.tsx +++ b/src/lib/src/components/properties/text/Text.tsx @@ -9,16 +9,16 @@ export interface TextProps extends BasePropertyProps { } const StyledNumberRoot = styled.input` - width: 200px; + width: 100%; display: inline-flex; align-items: center; justify-content: center; border-radius: 4px; padding: 0 10px; - height: 35px; - font-size: 15px; + height: 24px; + font-size: 10px; line-height: 1; - color: white; + color: var(--text); background-color: var(--darkest-color); box-shadow: 0 0 0 1px black; @@ -36,10 +36,10 @@ export const TextInput: React.FC = ({ value, onChange, label, ...rest onChange(JSON.stringify(event.target.value))} {...rest} /> ); -}; \ No newline at end of file +}; diff --git a/src/lib/src/components/scene/SceneComponent.css b/src/lib/src/components/scene/SceneComponent.css deleted file mode 100644 index e69de29..0000000 diff --git a/src/lib/src/components/scene/SceneComponent.tsx b/src/lib/src/components/scene/SceneComponent.tsx index 47cf2d0..756face 100644 --- a/src/lib/src/components/scene/SceneComponent.tsx +++ b/src/lib/src/components/scene/SceneComponent.tsx @@ -1,168 +1,20 @@ -import { usePixiStore } from '@lib/src/pages/index'; -import React, { useEffect } from 'react'; -import { TimeSeries } from 'smoothie'; -import { SectionContainer, SectionHeader, Title, TitleGroup } from '../Container'; -import SmoothieComponent from '../smooth-charts/SmoothieComponent'; -import './SceneComponent.css'; -import TreeViewComponent from '../tree/TreeViewComponent'; +import React from 'react'; import CollapsibleComponent from '../collapsible/Collapsible'; -import PropertiesComponent from '../properties/Properties'; - -const updateGraph = (timeSeries: TimeSeries, numContainers: number) => { - timeSeries.append(new Date().getTime(), numContainers); -}; - -const defaultSmoothieOptions = { - width: 250, - grid: { - strokeStyle: 'transparent', - fillStyle: 'transparent', - }, - labels: { - fillStyle: 'rgb(255, 255, 255)', - precision: 0, - }, - millisPerPixel: 60, - height: 30, -}; - -const data = [ - { - title: 'Total', - statName: 'totalSceneObjects', - color: '#E72264', - timeSeries: new TimeSeries({ - resetBounds: true, - resetBoundsInterval: 3000, - }), - }, - { - title: 'Container', - statName: 'container', - color: '#E72264', - timeSeries: new TimeSeries({ - resetBounds: true, - resetBoundsInterval: 3000, - }), - }, - { - title: 'Sprite', - statName: 'sprite', - color: '#E72264', - timeSeries: new TimeSeries({ - resetBounds: true, - resetBoundsInterval: 3000, - }), - }, - { - title: 'Graphics', - statName: 'graphics', - color: '#E72264', - timeSeries: new TimeSeries({ - resetBounds: true, - resetBoundsInterval: 3000, - }), - }, - { - title: 'Filters', - statName: 'filter', - color: '#E72264', - timeSeries: new TimeSeries({ - resetBounds: true, - resetBoundsInterval: 3000, - }), - }, - { - title: 'Meshes', - statName: 'mesh', - color: '#E72264', - timeSeries: new TimeSeries({ - resetBounds: true, - resetBoundsInterval: 3000, - }), - }, - { - title: 'Texts', - statName: 'text', - color: '#E72264', - timeSeries: new TimeSeries({ - resetBounds: true, - resetBoundsInterval: 3000, - }), - }, - { - title: 'BitmapTexts', - statName: 'bitmapText', - color: '#E72264', - timeSeries: new TimeSeries({ - resetBounds: true, - resetBoundsInterval: 3000, - }), - }, - { - title: 'HTMLTexts', - statName: 'htmlText', - color: '#E72264', - timeSeries: new TimeSeries({ - resetBounds: true, - resetBoundsInterval: 3000, - }), - }, -] as const; +import { Inspector } from './inspector'; +import { Stats } from './stats'; // eslint-disable-next-line @typescript-eslint/no-empty-interface interface SceneComponentProps {} export const SceneComponent: React.FC = () => { - const sceneStats = usePixiStore((state) => state.sceneStats); - console.log('rendering scene'); - - useEffect(() => { - const intervalIds = data.map((item) => { - return setInterval(() => { - const sceneStats = usePixiStore.getState().sceneStats; - updateGraph(item.timeSeries, sceneStats[item.statName]); - }, 100); - }); - // Clean up on unmount - return () => { - intervalIds.forEach((intervalId) => clearInterval(intervalId)); - }; - }, [sceneStats]); - return ( <> - {data.map((item, index) => ( - - - - - {item.title} ({sceneStats[item.statName]}) - - - - - - ))} + -
- - -
+
); diff --git a/src/lib/src/components/scene/inspector.tsx b/src/lib/src/components/scene/inspector.tsx new file mode 100644 index 0000000..568b175 --- /dev/null +++ b/src/lib/src/components/scene/inspector.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import PropertiesComponent from '../properties/Properties'; +import TreeViewComponent from '../tree/TreeViewComponent'; +import styled from 'styled-components'; + +const InspectorWrapper = styled.div` + display: flex; + flex-direction: row; + width: 100%; + height: 100%; +`; + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +interface InspectorProps {} +export const Inspector: React.FC = () => { + return ( + + + + + ); +}; diff --git a/src/lib/src/components/scene/stats.tsx b/src/lib/src/components/scene/stats.tsx new file mode 100644 index 0000000..55785bd --- /dev/null +++ b/src/lib/src/components/scene/stats.tsx @@ -0,0 +1,155 @@ +import { usePixiStore } from '@lib/src/pages'; +import React, { useEffect } from 'react'; +import { SectionContainer, SectionHeader, Title, TitleGroup } from '../Container'; +import SmoothieComponent, { TimeSeries } from '../smooth-charts/SmoothieComponent'; + +const defaultSmoothieOptions = { + width: 250, + grid: { + strokeStyle: 'transparent', + fillStyle: 'transparent', + }, + labels: { + fillStyle: 'rgb(255, 255, 255)', + precision: 0, + }, + millisPerPixel: 60, + height: 30, +}; + +const data = [ + { + title: 'Total', + statName: 'totalSceneObjects', + color: '#E72264', + timeSeries: new TimeSeries({ + resetBounds: true, + resetBoundsInterval: 3000, + }), + }, + { + title: 'Container', + statName: 'container', + color: '#E72264', + timeSeries: new TimeSeries({ + resetBounds: true, + resetBoundsInterval: 3000, + }), + }, + { + title: 'Sprite', + statName: 'sprite', + color: '#E72264', + timeSeries: new TimeSeries({ + resetBounds: true, + resetBoundsInterval: 3000, + }), + }, + { + title: 'Graphics', + statName: 'graphics', + color: '#E72264', + timeSeries: new TimeSeries({ + resetBounds: true, + resetBoundsInterval: 3000, + }), + }, + { + title: 'Filters', + statName: 'filter', + color: '#E72264', + timeSeries: new TimeSeries({ + resetBounds: true, + resetBoundsInterval: 3000, + }), + }, + { + title: 'Meshes', + statName: 'mesh', + color: '#E72264', + timeSeries: new TimeSeries({ + resetBounds: true, + resetBoundsInterval: 3000, + }), + }, + { + title: 'Texts', + statName: 'text', + color: '#E72264', + timeSeries: new TimeSeries({ + resetBounds: true, + resetBoundsInterval: 3000, + }), + }, + { + title: 'BitmapTexts', + statName: 'bitmapText', + color: '#E72264', + timeSeries: new TimeSeries({ + resetBounds: true, + resetBoundsInterval: 3000, + }), + }, + { + title: 'HTMLTexts', + statName: 'htmlText', + color: '#E72264', + timeSeries: new TimeSeries({ + resetBounds: true, + resetBoundsInterval: 3000, + }), + }, +] as const; + +const updateGraph = (timeSeries: TimeSeries, numContainers: number) => { + timeSeries.append(new Date().getTime(), numContainers); +}; + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +interface StatsProps {} +export const Stats: React.FC = () => { + const sceneStats = usePixiStore((state) => state.sceneStats); + + console.log('rendering stats'); + + useEffect(() => { + const intervalIds = data.map((item) => { + return setInterval(() => { + const sceneStats = usePixiStore.getState().sceneStats; + updateGraph(item.timeSeries, sceneStats[item.statName]); + }, 100); + }); + // Clean up on unmount + return () => { + intervalIds.forEach((intervalId) => clearInterval(intervalId)); + }; + }, [sceneStats]); + + return ( + <> + {data.map((item, index) => ( + + + + + {item.title} ({sceneStats[item.statName]}) + + + + + + ))} + + ); +}; diff --git a/src/lib/src/components/smooth-charts/smoothie.ts b/src/lib/src/components/smooth-charts/smoothie.ts index 1d5afae..6451ecf 100644 --- a/src/lib/src/components/smooth-charts/smoothie.ts +++ b/src/lib/src/components/smooth-charts/smoothie.ts @@ -95,1022 +95,1052 @@ * Add title option, by @mesca */ -;(function(exports) { - - // Date.now polyfill - Date.now = Date.now || function() { return new Date().getTime(); }; - - var Util = { - extend: function() { - arguments[0] = arguments[0] || {}; - for (var i = 1; i < arguments.length; i++) - { - for (var key in arguments[i]) - { - if (arguments[i].hasOwnProperty(key)) - { - if (typeof(arguments[i][key]) === 'object') { - if (arguments[i][key] instanceof Array) { - arguments[0][key] = arguments[i][key]; - } else { - arguments[0][key] = Util.extend(arguments[0][key], arguments[i][key]); - } - } else { +(function (exports) { + // Date.now polyfill + Date.now = + Date.now || + function () { + return new Date().getTime(); + }; + + var Util = { + extend: function () { + arguments[0] = arguments[0] || {}; + for (var i = 1; i < arguments.length; i++) { + for (var key in arguments[i]) { + if (arguments[i].hasOwnProperty(key)) { + if (typeof arguments[i][key] === 'object') { + if (arguments[i][key] instanceof Array) { arguments[0][key] = arguments[i][key]; + } else { + arguments[0][key] = Util.extend(arguments[0][key], arguments[i][key]); } + } else { + arguments[0][key] = arguments[i][key]; } } } - return arguments[0]; - }, - binarySearch: function(data, value) { - var low = 0, - high = data.length; - while (low < high) { - var mid = (low + high) >> 1; - if (value < data[mid][0]) - high = mid; - else - low = mid + 1; - } - return low; - } - }; - - /** - * Initialises a new TimeSeries with optional data options. - * - * Options are of the form (defaults shown): - * - *
-     * {
-     *   resetBounds: true,        // enables/disables automatic scaling of the y-axis
-     *   resetBoundsInterval: 3000 // the period between scaling calculations, in millis
-     * }
-     * 
- * - * Presentation options for TimeSeries are specified as an argument to SmoothieChart.addTimeSeries. - * - * @constructor - */ - function TimeSeries(options) { - this.options = Util.extend({}, TimeSeries.defaultOptions, options); - this.disabled = false; - this.clear(); - } - - TimeSeries.defaultOptions = { - resetBoundsInterval: 3000, - resetBounds: true - }; - - /** - * Clears all data and state from this TimeSeries object. - */ - TimeSeries.prototype.clear = function() { - this.data = []; - this.maxValue = Number.NaN; // The maximum value ever seen in this TimeSeries. - this.minValue = Number.NaN; // The minimum value ever seen in this TimeSeries. - }; - - /** - * Recalculate the min/max values for this TimeSeries object. - * - * This causes the graph to scale itself in the y-axis. - */ - TimeSeries.prototype.resetBounds = function() { - if (this.data.length) { - // Walk through all data points, finding the min/max value - this.maxValue = this.data[0][1]; - this.minValue = this.data[0][1]; - for (var i = 1; i < this.data.length; i++) { - var value = this.data[i][1]; - if (value > this.maxValue) { - this.maxValue = value; - } - if (value < this.minValue) { - this.minValue = value; - } - } - } else { - // No data exists, so set min/max to NaN - this.maxValue = Number.NaN; - this.minValue = Number.NaN; } - }; - - /** - * Adds a new data point to the TimeSeries, preserving chronological order. - * - * @param timestamp the position, in time, of this data point - * @param value the value of this data point - * @param sumRepeatedTimeStampValues if timestamp has an exact match in the series, this flag controls - * whether it is replaced, or the values summed (defaults to false.) - */ - TimeSeries.prototype.append = function(timestamp, value, sumRepeatedTimeStampValues) { - // Rewind until we hit an older timestamp - var i = this.data.length - 1; - while (i >= 0 && this.data[i][0] > timestamp) { - i--; + return arguments[0]; + }, + binarySearch: function (data, value) { + var low = 0, + high = data.length; + while (low < high) { + var mid = (low + high) >> 1; + if (value < data[mid][0]) high = mid; + else low = mid + 1; } - - if (i === -1) { - // This new item is the oldest data - this.data.splice(0, 0, [timestamp, value]); - } else if (this.data.length > 0 && this.data[i][0] === timestamp) { - // Update existing values in the array - if (sumRepeatedTimeStampValues) { - // Sum this value into the existing 'bucket' - this.data[i][1] += value; - value = this.data[i][1]; - } else { - // Replace the previous value - this.data[i][1] = value; + return low; + }, + }; + + /** + * Initialises a new TimeSeries with optional data options. + * + * Options are of the form (defaults shown): + * + *
+   * {
+   *   resetBounds: true,        // enables/disables automatic scaling of the y-axis
+   *   resetBoundsInterval: 3000 // the period between scaling calculations, in millis
+   * }
+   * 
+ * + * Presentation options for TimeSeries are specified as an argument to SmoothieChart.addTimeSeries. + * + * @constructor + */ + function TimeSeries(options) { + this.options = Util.extend({}, TimeSeries.defaultOptions, options); + this.disabled = false; + this.clear(); + } + + TimeSeries.defaultOptions = { + resetBoundsInterval: 3000, + resetBounds: true, + }; + + /** + * Clears all data and state from this TimeSeries object. + */ + TimeSeries.prototype.clear = function () { + this.data = []; + this.maxValue = Number.NaN; // The maximum value ever seen in this TimeSeries. + this.minValue = Number.NaN; // The minimum value ever seen in this TimeSeries. + }; + + /** + * Recalculate the min/max values for this TimeSeries object. + * + * This causes the graph to scale itself in the y-axis. + */ + TimeSeries.prototype.resetBounds = function () { + if (this.data.length) { + // Walk through all data points, finding the min/max value + this.maxValue = this.data[0][1]; + this.minValue = this.data[0][1]; + for (var i = 1; i < this.data.length; i++) { + var value = this.data[i][1]; + if (value > this.maxValue) { + this.maxValue = value; + } + if (value < this.minValue) { + this.minValue = value; } - } else if (i < this.data.length - 1) { - // Splice into the correct position to keep timestamps in order - this.data.splice(i + 1, 0, [timestamp, value]); - } else { - // Add to the end of the array - this.data.push([timestamp, value]); - } - - this.maxValue = isNaN(this.maxValue) ? value : Math.max(this.maxValue, value); - this.minValue = isNaN(this.minValue) ? value : Math.min(this.minValue, value); - }; - - TimeSeries.prototype.dropOldData = function(oldestValidTime, maxDataSetLength) { - // We must always keep one expired data point as we need this to draw the - // line that comes into the chart from the left, but any points prior to that can be removed. - var removeCount = 0; - while (this.data.length - removeCount >= maxDataSetLength && this.data[removeCount + 1][0] < oldestValidTime) { - removeCount++; } - if (removeCount !== 0) { - this.data.splice(0, removeCount); + } else { + // No data exists, so set min/max to NaN + this.maxValue = Number.NaN; + this.minValue = Number.NaN; + } + }; + + /** + * Adds a new data point to the TimeSeries, preserving chronological order. + * + * @param timestamp the position, in time, of this data point + * @param value the value of this data point + * @param sumRepeatedTimeStampValues if timestamp has an exact match in the series, this flag controls + * whether it is replaced, or the values summed (defaults to false.) + */ + TimeSeries.prototype.append = function (timestamp, value, sumRepeatedTimeStampValues) { + // Rewind until we hit an older timestamp + var i = this.data.length - 1; + while (i >= 0 && this.data[i][0] > timestamp) { + i--; + } + + if (i === -1) { + // This new item is the oldest data + this.data.splice(0, 0, [timestamp, value]); + } else if (this.data.length > 0 && this.data[i][0] === timestamp) { + // Update existing values in the array + if (sumRepeatedTimeStampValues) { + // Sum this value into the existing 'bucket' + this.data[i][1] += value; + value = this.data[i][1]; + } else { + // Replace the previous value + this.data[i][1] = value; } - }; - - /** - * Initialises a new SmoothieChart. - * - * Options are optional, and should be of the form below. Just specify the values you - * need and the rest will be given sensible defaults as shown: - * - *
-     * {
-     *   minValue: undefined,                      // specify to clamp the lower y-axis to a given value
-     *   maxValue: undefined,                      // specify to clamp the upper y-axis to a given value
-     *   maxValueScale: 1,                         // allows proportional padding to be added above the chart. for 10% padding, specify 1.1.
-     *   minValueScale: 1,                         // allows proportional padding to be added below the chart. for 10% padding, specify 1.1.
-     *   yRangeFunction: undefined,                // function({min: , max: }) { return {min: , max: }; }
-     *   scaleSmoothing: 0.125,                    // controls the rate at which y-value zoom animation occurs
-     *   millisPerPixel: 20,                       // sets the speed at which the chart pans by
-     *   enableDpiScaling: true,                   // support rendering at different DPI depending on the device
-     *   yMinFormatter: function(min, precision) { // callback function that formats the min y value label
-     *     return parseFloat(min).toFixed(precision);
-     *   },
-     *   yMaxFormatter: function(max, precision) { // callback function that formats the max y value label
-     *     return parseFloat(max).toFixed(precision);
-     *   },
-     *   yIntermediateFormatter: function(intermediate, precision) { // callback function that formats the intermediate y value labels
-     *     return parseFloat(intermediate).toFixed(precision);
-     *   },
-     *   maxDataSetLength: 2,
-     *   interpolation: 'bezier'                   // one of 'bezier', 'linear', or 'step'
-     *   timestampFormatter: null,                 // optional function to format time stamps for bottom of chart
-     *                                             // you may use SmoothieChart.timeFormatter, or your own: function(date) { return ''; }
-     *   scrollBackwards: false,                   // reverse the scroll direction of the chart
-     *   horizontalLines: [],                      // [ { value: 0, color: '#ffffff', lineWidth: 1 } ]
-     *   grid:
-     *   {
-     *     fillStyle: '#000000',                   // the background colour of the chart
-     *     lineWidth: 1,                           // the pixel width of grid lines
-     *     strokeStyle: '#777777',                 // colour of grid lines
-     *     millisPerLine: 1000,                    // distance between vertical grid lines
-     *     sharpLines: false,                      // controls whether grid lines are 1px sharp, or softened
-     *     verticalSections: 2,                    // number of vertical sections marked out by horizontal grid lines
-     *     borderVisible: true                     // whether the grid lines trace the border of the chart or not
-     *   },
-     *   labels
-     *   {
-     *     disabled: false,                        // enables/disables labels showing the min/max values
-     *     fillStyle: '#ffffff',                   // colour for text of labels,
-     *     fontSize: 15,
-     *     fontFamily: 'sans-serif',
-     *     precision: 2,
-     *     showIntermediateLabels: false,          // shows intermediate labels between min and max values along y axis
-     *     intermediateLabelSameAxis: true,
-     *   },
-     *   title
-     *   {
-     *     text: '',                               // the text to display on the left side of the chart
-     *     fillStyle: '#ffffff',                   // colour for text
-     *     fontSize: 15,
-     *     fontFamily: 'sans-serif',
-     *     verticalAlign: 'middle'                 // one of 'top', 'middle', or 'bottom'
-     *   },
-     *   tooltip: false                            // show tooltip when mouse is over the chart
-     *   tooltipLine: {                            // properties for a vertical line at the cursor position
-     *     lineWidth: 1,
-     *     strokeStyle: '#BBBBBB'
-     *   },
-     *   tooltipFormatter: SmoothieChart.tooltipFormatter, // formatter function for tooltip text
-     *   nonRealtimeData: false,                   // use time of latest data as current time
-     *   displayDataFromPercentile: 1,             // display not latest data, but data from the given percentile
-     *                                             // useful when trying to see old data saved by setting a high value for maxDataSetLength
-     *                                             // should be a value between 0 and 1
-     *   responsive: false,                        // whether the chart should adapt to the size of the canvas
-     *   limitFPS: 0                               // maximum frame rate the chart will render at, in FPS (zero means no limit)
-     * }
-     * 
- * - * @constructor - */ - function SmoothieChart(options) { - this.options = Util.extend({}, SmoothieChart.defaultChartOptions, options); - this.seriesSet = []; - this.currentValueRange = 1; - this.currentVisMinValue = 0; - this.lastRenderTimeMillis = 0; - this.lastChartTimestamp = 0; - - this.mousemove = this.mousemove.bind(this); - this.mouseout = this.mouseout.bind(this); + } else if (i < this.data.length - 1) { + // Splice into the correct position to keep timestamps in order + this.data.splice(i + 1, 0, [timestamp, value]); + } else { + // Add to the end of the array + this.data.push([timestamp, value]); } - - /** Formats the HTML string content of the tooltip. */ - SmoothieChart.tooltipFormatter = function (timestamp, data) { - var timestampFormatter = this.options.timestampFormatter || SmoothieChart.timeFormatter, - lines = [timestampFormatter(new Date(timestamp))], - label; - - for (var i = 0; i < data.length; ++i) { - label = data[i].series.options.tooltipLabel || '' - if (label !== ''){ - label = label + ' '; - } - lines.push('' + + + this.maxValue = isNaN(this.maxValue) ? value : Math.max(this.maxValue, value); + this.minValue = isNaN(this.minValue) ? value : Math.min(this.minValue, value); + }; + + TimeSeries.prototype.dropOldData = function (oldestValidTime, maxDataSetLength) { + // We must always keep one expired data point as we need this to draw the + // line that comes into the chart from the left, but any points prior to that can be removed. + var removeCount = 0; + while (this.data.length - removeCount >= maxDataSetLength && this.data[removeCount + 1][0] < oldestValidTime) { + removeCount++; + } + if (removeCount !== 0) { + this.data.splice(0, removeCount); + } + }; + + /** + * Initialises a new SmoothieChart. + * + * Options are optional, and should be of the form below. Just specify the values you + * need and the rest will be given sensible defaults as shown: + * + *
+   * {
+   *   minValue: undefined,                      // specify to clamp the lower y-axis to a given value
+   *   maxValue: undefined,                      // specify to clamp the upper y-axis to a given value
+   *   maxValueScale: 1,                         // allows proportional padding to be added above the chart. for 10% padding, specify 1.1.
+   *   minValueScale: 1,                         // allows proportional padding to be added below the chart. for 10% padding, specify 1.1.
+   *   yRangeFunction: undefined,                // function({min: , max: }) { return {min: , max: }; }
+   *   scaleSmoothing: 0.125,                    // controls the rate at which y-value zoom animation occurs
+   *   millisPerPixel: 20,                       // sets the speed at which the chart pans by
+   *   enableDpiScaling: true,                   // support rendering at different DPI depending on the device
+   *   yMinFormatter: function(min, precision) { // callback function that formats the min y value label
+   *     return parseFloat(min).toFixed(precision);
+   *   },
+   *   yMaxFormatter: function(max, precision) { // callback function that formats the max y value label
+   *     return parseFloat(max).toFixed(precision);
+   *   },
+   *   yIntermediateFormatter: function(intermediate, precision) { // callback function that formats the intermediate y value labels
+   *     return parseFloat(intermediate).toFixed(precision);
+   *   },
+   *   maxDataSetLength: 2,
+   *   interpolation: 'bezier'                   // one of 'bezier', 'linear', or 'step'
+   *   timestampFormatter: null,                 // optional function to format time stamps for bottom of chart
+   *                                             // you may use SmoothieChart.timeFormatter, or your own: function(date) { return ''; }
+   *   scrollBackwards: false,                   // reverse the scroll direction of the chart
+   *   horizontalLines: [],                      // [ { value: 0, color: '#ffffff', lineWidth: 1 } ]
+   *   grid:
+   *   {
+   *     fillStyle: '#000000',                   // the background colour of the chart
+   *     lineWidth: 1,                           // the pixel width of grid lines
+   *     strokeStyle: '#777777',                 // colour of grid lines
+   *     millisPerLine: 1000,                    // distance between vertical grid lines
+   *     sharpLines: false,                      // controls whether grid lines are 1px sharp, or softened
+   *     verticalSections: 2,                    // number of vertical sections marked out by horizontal grid lines
+   *     borderVisible: true                     // whether the grid lines trace the border of the chart or not
+   *   },
+   *   labels
+   *   {
+   *     disabled: false,                        // enables/disables labels showing the min/max values
+   *     fillStyle: '#ffffff',                   // colour for text of labels,
+   *     fontSize: 15,
+   *     fontFamily: 'sans-serif',
+   *     precision: 2,
+   *     showIntermediateLabels: false,          // shows intermediate labels between min and max values along y axis
+   *     intermediateLabelSameAxis: true,
+   *   },
+   *   title
+   *   {
+   *     text: '',                               // the text to display on the left side of the chart
+   *     fillStyle: '#ffffff',                   // colour for text
+   *     fontSize: 15,
+   *     fontFamily: 'sans-serif',
+   *     verticalAlign: 'middle'                 // one of 'top', 'middle', or 'bottom'
+   *   },
+   *   tooltip: false                            // show tooltip when mouse is over the chart
+   *   tooltipLine: {                            // properties for a vertical line at the cursor position
+   *     lineWidth: 1,
+   *     strokeStyle: '#BBBBBB'
+   *   },
+   *   tooltipFormatter: SmoothieChart.tooltipFormatter, // formatter function for tooltip text
+   *   nonRealtimeData: false,                   // use time of latest data as current time
+   *   displayDataFromPercentile: 1,             // display not latest data, but data from the given percentile
+   *                                             // useful when trying to see old data saved by setting a high value for maxDataSetLength
+   *                                             // should be a value between 0 and 1
+   *   responsive: false,                        // whether the chart should adapt to the size of the canvas
+   *   limitFPS: 0                               // maximum frame rate the chart will render at, in FPS (zero means no limit)
+   * }
+   * 
+ * + * @constructor + */ + function SmoothieChart(options) { + this.options = Util.extend({}, SmoothieChart.defaultChartOptions, options); + this.seriesSet = []; + this.currentValueRange = 1; + this.currentVisMinValue = 0; + this.lastRenderTimeMillis = 0; + this.lastChartTimestamp = 0; + + this.mousemove = this.mousemove.bind(this); + this.mouseout = this.mouseout.bind(this); + } + + /** Formats the HTML string content of the tooltip. */ + SmoothieChart.tooltipFormatter = function (timestamp, data) { + var timestampFormatter = this.options.timestampFormatter || SmoothieChart.timeFormatter, + lines = [timestampFormatter(new Date(timestamp))], + label; + + for (var i = 0; i < data.length; ++i) { + label = data[i].series.options.tooltipLabel || ''; + if (label !== '') { + label = label + ' '; + } + lines.push( + '' + label + - this.options.yMaxFormatter(data[i].value, this.options.labels.precision) + ''); - } - - return lines.join('
'); - }; - - SmoothieChart.defaultChartOptions = { - millisPerPixel: 20, - enableDpiScaling: true, - yMinFormatter: function(min, precision) { - return parseFloat(min).toFixed(precision); - }, - yMaxFormatter: function(max, precision) { - return parseFloat(max).toFixed(precision); - }, - yIntermediateFormatter: function(intermediate, precision) { - return parseFloat(intermediate).toFixed(precision); - }, - maxValueScale: 1, - minValueScale: 1, - interpolation: 'bezier', - scaleSmoothing: 0.125, - maxDataSetLength: 2, - scrollBackwards: false, - displayDataFromPercentile: 1, - grid: { - fillStyle: '#000000', - strokeStyle: '#777777', - lineWidth: 1, - sharpLines: false, - millisPerLine: 1000, - verticalSections: 2, - borderVisible: true - }, - labels: { - fillStyle: '#ffffff', - disabled: false, - fontSize: 10, - fontFamily: 'monospace', - precision: 2, - showIntermediateLabels: false, - intermediateLabelSameAxis: true, - }, - title: { - text: '', - fillStyle: '#ffffff', - fontSize: 15, - fontFamily: 'monospace', - verticalAlign: 'middle' - }, - horizontalLines: [], - tooltip: false, - tooltipLine: { - lineWidth: 1, - strokeStyle: '#BBBBBB' + this.options.yMaxFormatter(data[i].value, this.options.labels.precision) + + '
', + ); + } + + return lines.join('
'); + }; + + SmoothieChart.defaultChartOptions = { + millisPerPixel: 20, + enableDpiScaling: true, + yMinFormatter: function (min, precision) { + return parseFloat(min).toFixed(precision); + }, + yMaxFormatter: function (max, precision) { + return parseFloat(max).toFixed(precision); + }, + yIntermediateFormatter: function (intermediate, precision) { + return parseFloat(intermediate).toFixed(precision); + }, + maxValueScale: 1, + minValueScale: 1, + interpolation: 'bezier', + scaleSmoothing: 0.125, + maxDataSetLength: 2, + scrollBackwards: false, + displayDataFromPercentile: 1, + grid: { + fillStyle: '#000000', + strokeStyle: '#777777', + lineWidth: 1, + sharpLines: false, + millisPerLine: 1000, + verticalSections: 2, + borderVisible: true, + }, + labels: { + fillStyle: '#ffffff', + disabled: false, + fontSize: 10, + fontFamily: 'monospace', + precision: 2, + showIntermediateLabels: false, + intermediateLabelSameAxis: true, + }, + title: { + text: '', + fillStyle: '#ffffff', + fontSize: 15, + fontFamily: 'monospace', + verticalAlign: 'middle', + }, + horizontalLines: [], + tooltip: false, + tooltipLine: { + lineWidth: 1, + strokeStyle: '#BBBBBB', + }, + tooltipFormatter: SmoothieChart.tooltipFormatter, + nonRealtimeData: false, + responsive: false, + limitFPS: 0, + }; + + // Based on http://inspirit.github.com/jsfeat/js/compatibility.js + SmoothieChart.AnimateCompatibility = (function () { + var requestAnimationFrame = function (callback, element) { + var requestAnimationFrame = + window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame || + function (callback) { + return window.setTimeout(function () { + callback(Date.now()); + }, 16); + }; + return requestAnimationFrame.call(window, callback, element); }, - tooltipFormatter: SmoothieChart.tooltipFormatter, - nonRealtimeData: false, - responsive: false, - limitFPS: 0 - }; - - // Based on http://inspirit.github.com/jsfeat/js/compatibility.js - SmoothieChart.AnimateCompatibility = (function() { - var requestAnimationFrame = function(callback, element) { - var requestAnimationFrame = - window.requestAnimationFrame || - window.webkitRequestAnimationFrame || - window.mozRequestAnimationFrame || - window.oRequestAnimationFrame || - window.msRequestAnimationFrame || - function(callback) { - return window.setTimeout(function() { - callback(Date.now()); - }, 16); - }; - return requestAnimationFrame.call(window, callback, element); - }, - cancelAnimationFrame = function(id) { - var cancelAnimationFrame = - window.cancelAnimationFrame || - function(id) { - clearTimeout(id); - }; - return cancelAnimationFrame.call(window, id); + cancelAnimationFrame = function (id) { + var cancelAnimationFrame = + window.cancelAnimationFrame || + function (id) { + clearTimeout(id); }; - - return { - requestAnimationFrame: requestAnimationFrame, - cancelAnimationFrame: cancelAnimationFrame + return cancelAnimationFrame.call(window, id); }; - })(); - - SmoothieChart.defaultSeriesPresentationOptions = { - lineWidth: 1, - strokeStyle: '#ffffff' - }; - - /** - * Adds a TimeSeries to this chart, with optional presentation options. - * - * Presentation options should be of the form (defaults shown): - * - *
-     * {
-     *   lineWidth: 1,
-     *   strokeStyle: '#ffffff',
-     *   fillStyle: undefined,
-     *   tooltipLabel: undefined
-     * }
-     * 
- */ - SmoothieChart.prototype.addTimeSeries = function(timeSeries, options) { - this.seriesSet.push({timeSeries: timeSeries, options: Util.extend({}, SmoothieChart.defaultSeriesPresentationOptions, options)}); - if (timeSeries.options.resetBounds && timeSeries.options.resetBoundsInterval > 0) { - timeSeries.resetBoundsTimerId = setInterval( - function() { - timeSeries.resetBounds(); - }, - timeSeries.options.resetBoundsInterval - ); - } - }; - - /** - * Removes the specified TimeSeries from the chart. - */ - SmoothieChart.prototype.removeTimeSeries = function(timeSeries) { - // Find the correct timeseries to remove, and remove it - var numSeries = this.seriesSet.length; - for (var i = 0; i < numSeries; i++) { - if (this.seriesSet[i].timeSeries === timeSeries) { - this.seriesSet.splice(i, 1); - break; - } - } - // If a timer was operating for that timeseries, remove it - if (timeSeries.resetBoundsTimerId) { - // Stop resetting the bounds, if we were - clearInterval(timeSeries.resetBoundsTimerId); - } - }; - - /** - * Gets render options for the specified TimeSeries. - * - * As you may use a single TimeSeries in multiple charts with different formatting in each usage, - * these settings are stored in the chart. - */ - SmoothieChart.prototype.getTimeSeriesOptions = function(timeSeries) { - // Find the correct timeseries to remove, and remove it - var numSeries = this.seriesSet.length; - for (var i = 0; i < numSeries; i++) { - if (this.seriesSet[i].timeSeries === timeSeries) { - return this.seriesSet[i].options; - } - } + + return { + requestAnimationFrame: requestAnimationFrame, + cancelAnimationFrame: cancelAnimationFrame, }; - - /** - * Brings the specified TimeSeries to the top of the chart. It will be rendered last. - */ - SmoothieChart.prototype.bringToFront = function(timeSeries) { - // Find the correct timeseries to remove, and remove it - var numSeries = this.seriesSet.length; - for (var i = 0; i < numSeries; i++) { - if (this.seriesSet[i].timeSeries === timeSeries) { - var set = this.seriesSet.splice(i, 1); - this.seriesSet.push(set[0]); - break; - } + })(); + + SmoothieChart.defaultSeriesPresentationOptions = { + lineWidth: 1, + strokeStyle: '#ffffff', + }; + + /** + * Adds a TimeSeries to this chart, with optional presentation options. + * + * Presentation options should be of the form (defaults shown): + * + *
+   * {
+   *   lineWidth: 1,
+   *   strokeStyle: '#ffffff',
+   *   fillStyle: undefined,
+   *   tooltipLabel: undefined
+   * }
+   * 
+ */ + SmoothieChart.prototype.addTimeSeries = function (timeSeries, options) { + this.seriesSet.push({ + timeSeries: timeSeries, + options: Util.extend({}, SmoothieChart.defaultSeriesPresentationOptions, options), + }); + if (timeSeries.options.resetBounds && timeSeries.options.resetBoundsInterval > 0) { + timeSeries.resetBoundsTimerId = setInterval(function () { + timeSeries.resetBounds(); + }, timeSeries.options.resetBoundsInterval); + } + }; + + /** + * Removes the specified TimeSeries from the chart. + */ + SmoothieChart.prototype.removeTimeSeries = function (timeSeries) { + // Find the correct timeseries to remove, and remove it + var numSeries = this.seriesSet.length; + for (var i = 0; i < numSeries; i++) { + if (this.seriesSet[i].timeSeries === timeSeries) { + this.seriesSet.splice(i, 1); + break; } - }; - - /** - * Instructs the SmoothieChart to start rendering to the provided canvas, with specified delay. - * - * @param canvas the target canvas element - * @param delayMillis an amount of time to wait before a data point is shown. This can prevent the end of the series - * from appearing on screen, with new values flashing into view, at the expense of some latency. - */ - SmoothieChart.prototype.streamTo = function(canvas, delayMillis) { - this.canvas = canvas; - this.delay = delayMillis; - this.start(); - }; - - SmoothieChart.prototype.getTooltipEl = function () { - // Create the tool tip element lazily - if (!this.tooltipEl) { - this.tooltipEl = document.createElement('div'); - this.tooltipEl.className = 'smoothie-chart-tooltip'; - this.tooltipEl.style.position = 'absolute'; - this.tooltipEl.style.display = 'none'; - document.body.appendChild(this.tooltipEl); + } + // If a timer was operating for that timeseries, remove it + if (timeSeries.resetBoundsTimerId) { + // Stop resetting the bounds, if we were + clearInterval(timeSeries.resetBoundsTimerId); + } + }; + + /** + * Gets render options for the specified TimeSeries. + * + * As you may use a single TimeSeries in multiple charts with different formatting in each usage, + * these settings are stored in the chart. + */ + SmoothieChart.prototype.getTimeSeriesOptions = function (timeSeries) { + // Find the correct timeseries to remove, and remove it + var numSeries = this.seriesSet.length; + for (var i = 0; i < numSeries; i++) { + if (this.seriesSet[i].timeSeries === timeSeries) { + return this.seriesSet[i].options; } - return this.tooltipEl; - }; - - SmoothieChart.prototype.updateTooltip = function () { - if(!this.options.tooltip){ - return; + } + }; + + /** + * Brings the specified TimeSeries to the top of the chart. It will be rendered last. + */ + SmoothieChart.prototype.bringToFront = function (timeSeries) { + // Find the correct timeseries to remove, and remove it + var numSeries = this.seriesSet.length; + for (var i = 0; i < numSeries; i++) { + if (this.seriesSet[i].timeSeries === timeSeries) { + var set = this.seriesSet.splice(i, 1); + this.seriesSet.push(set[0]); + break; } - var el = this.getTooltipEl(); - - if (!this.mouseover || !this.options.tooltip) { - el.style.display = 'none'; - return; + } + }; + + /** + * Instructs the SmoothieChart to start rendering to the provided canvas, with specified delay. + * + * @param canvas the target canvas element + * @param delayMillis an amount of time to wait before a data point is shown. This can prevent the end of the series + * from appearing on screen, with new values flashing into view, at the expense of some latency. + */ + SmoothieChart.prototype.streamTo = function (canvas, delayMillis) { + this.canvas = canvas; + this.delay = delayMillis; + this.start(); + }; + + SmoothieChart.prototype.getTooltipEl = function () { + // Create the tool tip element lazily + if (!this.tooltipEl) { + this.tooltipEl = document.createElement('div'); + this.tooltipEl.className = 'smoothie-chart-tooltip'; + this.tooltipEl.style.position = 'absolute'; + this.tooltipEl.style.display = 'none'; + document.body.appendChild(this.tooltipEl); + } + return this.tooltipEl; + }; + + SmoothieChart.prototype.updateTooltip = function () { + if (!this.options.tooltip) { + return; + } + var el = this.getTooltipEl(); + + if (!this.mouseover || !this.options.tooltip) { + el.style.display = 'none'; + return; + } + + var time = this.lastChartTimestamp; + + // x pixel to time + var t = this.options.scrollBackwards + ? time - this.mouseX * this.options.millisPerPixel + : time - (this.canvas.offsetWidth - this.mouseX) * this.options.millisPerPixel; + + var data = []; + + // For each data set... + for (var d = 0; d < this.seriesSet.length; d++) { + var timeSeries = this.seriesSet[d].timeSeries; + if (timeSeries.disabled) { + continue; } - - var time = this.lastChartTimestamp; - - // x pixel to time - var t = this.options.scrollBackwards - ? time - this.mouseX * this.options.millisPerPixel - : time - (this.canvas.offsetWidth - this.mouseX) * this.options.millisPerPixel; - - var data = []; - - // For each data set... - for (var d = 0; d < this.seriesSet.length; d++) { - var timeSeries = this.seriesSet[d].timeSeries; - if (timeSeries.disabled) { - continue; - } - - // find datapoint closest to time 't' - var closeIdx = Util.binarySearch(timeSeries.data, t); - if (closeIdx > 0 && closeIdx < timeSeries.data.length) { - data.push({ series: this.seriesSet[d], index: closeIdx, value: timeSeries.data[closeIdx][1] }); - } + + // find datapoint closest to time 't' + var closeIdx = Util.binarySearch(timeSeries.data, t); + if (closeIdx > 0 && closeIdx < timeSeries.data.length) { + data.push({ series: this.seriesSet[d], index: closeIdx, value: timeSeries.data[closeIdx][1] }); } - - if (data.length) { - el.innerHTML = this.options.tooltipFormatter.call(this, t, data); - el.style.display = 'block'; - } else { - el.style.display = 'none'; + } + + if (data.length) { + el.innerHTML = this.options.tooltipFormatter.call(this, t, data); + el.style.display = 'block'; + } else { + el.style.display = 'none'; + } + }; + + SmoothieChart.prototype.mousemove = function (evt) { + this.mouseover = true; + this.mouseX = evt.offsetX; + this.mouseY = evt.offsetY; + this.mousePageX = evt.pageX; + this.mousePageY = evt.pageY; + if (!this.options.tooltip) { + return; + } + var el = this.getTooltipEl(); + el.style.top = Math.round(this.mousePageY) + 'px'; + el.style.left = Math.round(this.mousePageX) + 'px'; + this.updateTooltip(); + }; + + SmoothieChart.prototype.mouseout = function () { + this.mouseover = false; + this.mouseX = this.mouseY = -1; + if (this.tooltipEl) this.tooltipEl.style.display = 'none'; + }; + + /** + * Make sure the canvas has the optimal resolution for the device's pixel ratio. + */ + SmoothieChart.prototype.resize = function () { + var dpr = !this.options.enableDpiScaling || !window ? 1 : window.devicePixelRatio, + width, + height; + if (this.options.responsive) { + // Newer behaviour: Use the canvas's size in the layout, and set the internal + // resolution according to that size and the device pixel ratio (eg: high DPI) + width = this.canvas.offsetWidth; + height = this.canvas.offsetHeight; + + if (width !== this.lastWidth) { + this.lastWidth = width; + this.canvas.setAttribute('width', Math.floor(width * dpr).toString()); + this.canvas.getContext('2d').scale(dpr, dpr); } - }; - - SmoothieChart.prototype.mousemove = function (evt) { - this.mouseover = true; - this.mouseX = evt.offsetX; - this.mouseY = evt.offsetY; - this.mousePageX = evt.pageX; - this.mousePageY = evt.pageY; - if(!this.options.tooltip){ - return; + if (height !== this.lastHeight) { + this.lastHeight = height; + this.canvas.setAttribute('height', Math.floor(height * dpr).toString()); + this.canvas.getContext('2d').scale(dpr, dpr); } - var el = this.getTooltipEl(); - el.style.top = Math.round(this.mousePageY) + 'px'; - el.style.left = Math.round(this.mousePageX) + 'px'; - this.updateTooltip(); - }; - - SmoothieChart.prototype.mouseout = function () { - this.mouseover = false; - this.mouseX = this.mouseY = -1; - if (this.tooltipEl) - this.tooltipEl.style.display = 'none'; - }; - - /** - * Make sure the canvas has the optimal resolution for the device's pixel ratio. - */ - SmoothieChart.prototype.resize = function () { - var dpr = !this.options.enableDpiScaling || !window ? 1 : window.devicePixelRatio, - width, height; - if (this.options.responsive) { - // Newer behaviour: Use the canvas's size in the layout, and set the internal - // resolution according to that size and the device pixel ratio (eg: high DPI) - width = this.canvas.offsetWidth; - height = this.canvas.offsetHeight; - - if (width !== this.lastWidth) { - - this.lastWidth = width; - this.canvas.setAttribute('width', (Math.floor(width * dpr)).toString()); - this.canvas.getContext('2d').scale(dpr, dpr); - } - if (height !== this.lastHeight) { - this.lastHeight = height; - this.canvas.setAttribute('height', (Math.floor(height * dpr)).toString()); - this.canvas.getContext('2d').scale(dpr, dpr); - } - } else if (dpr !== 1) { - // Older behaviour: use the canvas's inner dimensions and scale the element's size - // according to that size and the device pixel ratio (eg: high DPI) - width = parseInt(this.canvas.getAttribute('width')); - height = parseInt(this.canvas.getAttribute('height')); - - if (!this.originalWidth || (Math.floor(this.originalWidth * dpr) !== width)) { - this.originalWidth = width; - this.canvas.setAttribute('width', (Math.floor(width * dpr)).toString()); - this.canvas.style.width = width + 'px'; - this.canvas.getContext('2d').scale(dpr, dpr); - } - - if (!this.originalHeight || (Math.floor(this.originalHeight * dpr) !== height)) { - this.originalHeight = height; - this.canvas.setAttribute('height', (Math.floor(height * dpr)).toString()); - this.canvas.style.height = height + 'px'; - this.canvas.getContext('2d').scale(dpr, dpr); - } + } else if (dpr !== 1) { + // Older behaviour: use the canvas's inner dimensions and scale the element's size + // according to that size and the device pixel ratio (eg: high DPI) + width = parseInt(this.canvas.getAttribute('width')); + height = parseInt(this.canvas.getAttribute('height')); + + if (!this.originalWidth || Math.floor(this.originalWidth * dpr) !== width) { + this.originalWidth = width; + this.canvas.setAttribute('width', Math.floor(width * dpr).toString()); + this.canvas.style.width = width + 'px'; + this.canvas.getContext('2d').scale(dpr, dpr); } - }; - - /** - * Starts the animation of this chart. - */ - SmoothieChart.prototype.start = function() { - if (this.frame) { - // We're already running, so just return - return; + + if (!this.originalHeight || Math.floor(this.originalHeight * dpr) !== height) { + this.originalHeight = height; + this.canvas.setAttribute('height', Math.floor(height * dpr).toString()); + this.canvas.style.height = height + 'px'; + this.canvas.getContext('2d').scale(dpr, dpr); } - - this.canvas.addEventListener('mousemove', this.mousemove); - this.canvas.addEventListener('mouseout', this.mouseout); - - // Renders a frame, and queues the next frame for later rendering - var animate = function() { - this.frame = SmoothieChart.AnimateCompatibility.requestAnimationFrame(function() { - if(this.options.nonRealtimeData){ - var dateZero = new Date(0); - // find the data point with the latest timestamp - var maxTimeStamp = this.seriesSet.reduce(function(max, series){ - var dataSet = series.timeSeries.data; - var indexToCheck = Math.round(this.options.displayDataFromPercentile * dataSet.length) - 1; - indexToCheck = indexToCheck >= 0 ? indexToCheck : 0; - indexToCheck = indexToCheck <= dataSet.length -1 ? indexToCheck : dataSet.length -1; - if(dataSet && dataSet.length > 0) - { - // timestamp corresponds to element 0 of the data point - var lastDataTimeStamp = dataSet[indexToCheck][0]; - max = max > lastDataTimeStamp ? max : lastDataTimeStamp; - } - return max; - }.bind(this), dateZero); + } + }; + + /** + * Starts the animation of this chart. + */ + SmoothieChart.prototype.start = function () { + if (this.frame) { + // We're already running, so just return + return; + } + + this.canvas.addEventListener('mousemove', this.mousemove); + this.canvas.addEventListener('mouseout', this.mouseout); + + // Renders a frame, and queues the next frame for later rendering + var animate = function () { + this.frame = SmoothieChart.AnimateCompatibility.requestAnimationFrame( + function () { + if (this.options.nonRealtimeData) { + var dateZero = new Date(0); + // find the data point with the latest timestamp + var maxTimeStamp = this.seriesSet.reduce( + function (max, series) { + var dataSet = series.timeSeries.data; + var indexToCheck = Math.round(this.options.displayDataFromPercentile * dataSet.length) - 1; + indexToCheck = indexToCheck >= 0 ? indexToCheck : 0; + indexToCheck = indexToCheck <= dataSet.length - 1 ? indexToCheck : dataSet.length - 1; + if (dataSet && dataSet.length > 0) { + // timestamp corresponds to element 0 of the data point + var lastDataTimeStamp = dataSet[indexToCheck][0]; + max = max > lastDataTimeStamp ? max : lastDataTimeStamp; + } + return max; + }.bind(this), + dateZero, + ); // use the max timestamp as current time this.render(this.canvas, maxTimeStamp > dateZero ? maxTimeStamp : null); } else { this.render(); } animate(); - }.bind(this)); - }.bind(this); - - animate(); - }; - - /** - * Stops the animation of this chart. - */ - SmoothieChart.prototype.stop = function() { - if (this.frame) { - SmoothieChart.AnimateCompatibility.cancelAnimationFrame(this.frame); - delete this.frame; - this.canvas.removeEventListener('mousemove', this.mousemove); - this.canvas.removeEventListener('mouseout', this.mouseout); - } - }; - - SmoothieChart.prototype.updateValueRange = function() { - // Calculate the current scale of the chart, from all time series. - var chartOptions = this.options, - chartMaxValue = Number.NaN, - chartMinValue = Number.NaN; - - for (var d = 0; d < this.seriesSet.length; d++) { - // TODO(ndunn): We could calculate / track these values as they stream in. - var timeSeries = this.seriesSet[d].timeSeries; - if (timeSeries.disabled) { - continue; - } - - if (!isNaN(timeSeries.maxValue)) { - chartMaxValue = !isNaN(chartMaxValue) ? Math.max(chartMaxValue, timeSeries.maxValue) : timeSeries.maxValue; - } - - if (!isNaN(timeSeries.minValue)) { - chartMinValue = !isNaN(chartMinValue) ? Math.min(chartMinValue, timeSeries.minValue) : timeSeries.minValue; - } + }.bind(this), + ); + }.bind(this); + + animate(); + }; + + /** + * Stops the animation of this chart. + */ + SmoothieChart.prototype.stop = function () { + if (this.frame) { + SmoothieChart.AnimateCompatibility.cancelAnimationFrame(this.frame); + delete this.frame; + this.canvas.removeEventListener('mousemove', this.mousemove); + this.canvas.removeEventListener('mouseout', this.mouseout); + } + }; + + SmoothieChart.prototype.updateValueRange = function () { + // Calculate the current scale of the chart, from all time series. + var chartOptions = this.options, + chartMaxValue = Number.NaN, + chartMinValue = Number.NaN; + + for (var d = 0; d < this.seriesSet.length; d++) { + // TODO(ndunn): We could calculate / track these values as they stream in. + var timeSeries = this.seriesSet[d].timeSeries; + if (timeSeries.disabled) { + continue; } - - // Scale the chartMaxValue to add padding at the top if required - if (chartOptions.maxValue != null) { - chartMaxValue = chartOptions.maxValue; - } else { - chartMaxValue *= chartOptions.maxValueScale; + + if (!isNaN(timeSeries.maxValue)) { + chartMaxValue = !isNaN(chartMaxValue) ? Math.max(chartMaxValue, timeSeries.maxValue) : timeSeries.maxValue; } - - // Set the minimum if we've specified one - if (chartOptions.minValue != null) { - chartMinValue = chartOptions.minValue; - } else { - chartMinValue -= Math.abs(chartMinValue * chartOptions.minValueScale - chartMinValue); + + if (!isNaN(timeSeries.minValue)) { + chartMinValue = !isNaN(chartMinValue) ? Math.min(chartMinValue, timeSeries.minValue) : timeSeries.minValue; } - - // If a custom range function is set, call it - if (this.options.yRangeFunction) { - var range = this.options.yRangeFunction({min: chartMinValue, max: chartMaxValue}); - chartMinValue = range.min; - chartMaxValue = range.max; + } + + // Scale the chartMaxValue to add padding at the top if required + if (chartOptions.maxValue != null) { + chartMaxValue = chartOptions.maxValue; + } else { + chartMaxValue *= chartOptions.maxValueScale; + } + + // Set the minimum if we've specified one + if (chartOptions.minValue != null) { + chartMinValue = chartOptions.minValue; + } else { + chartMinValue -= Math.abs(chartMinValue * chartOptions.minValueScale - chartMinValue); + } + + // If a custom range function is set, call it + if (this.options.yRangeFunction) { + var range = this.options.yRangeFunction({ min: chartMinValue, max: chartMaxValue }); + chartMinValue = range.min; + chartMaxValue = range.max; + } + + if (!isNaN(chartMaxValue) && !isNaN(chartMinValue)) { + if (chartMinValue === chartMaxValue) { + chartMinValue -= 1; + //chartMaxValue += 1; } - - if (!isNaN(chartMaxValue) && !isNaN(chartMinValue)) { - if (chartMinValue === chartMaxValue) { - chartMinValue -= 1; - //chartMaxValue += 1; - } - - var targetValueRange = chartMaxValue - chartMinValue; - var valueRangeDiff = (targetValueRange - this.currentValueRange); - var minValueDiff = (chartMinValue - this.currentVisMinValue); - this.isAnimatingScale = Math.abs(valueRangeDiff) > 0.1 || Math.abs(minValueDiff) > 0.1; - const t = 0.000001; - if (Math.abs(valueRangeDiff) < t) { - valueRangeDiff = t; - } - this.currentValueRange += chartOptions.scaleSmoothing * valueRangeDiff; - this.currentVisMinValue += chartOptions.scaleSmoothing * minValueDiff; + + var targetValueRange = chartMaxValue - chartMinValue; + var valueRangeDiff = targetValueRange - this.currentValueRange; + var minValueDiff = chartMinValue - this.currentVisMinValue; + this.isAnimatingScale = Math.abs(valueRangeDiff) > 0.1 || Math.abs(minValueDiff) > 0.1; + const t = 0.000001; + if (Math.abs(valueRangeDiff) < t) { + valueRangeDiff = t; } - - this.valueRange = { min: chartMinValue, max: chartMaxValue }; - }; - - SmoothieChart.prototype.render = function(canvas, time) { - var nowMillis = Date.now(); - - // Respect any frame rate limit. - if (this.options.limitFPS > 0 && nowMillis - this.lastRenderTimeMillis < (1000/this.options.limitFPS)) + this.currentValueRange += chartOptions.scaleSmoothing * valueRangeDiff; + this.currentVisMinValue += chartOptions.scaleSmoothing * minValueDiff; + } + + this.valueRange = { min: chartMinValue, max: chartMaxValue }; + }; + + SmoothieChart.prototype.render = function (canvas, time) { + var nowMillis = Date.now(); + + // Respect any frame rate limit. + if (this.options.limitFPS > 0 && nowMillis - this.lastRenderTimeMillis < 1000 / this.options.limitFPS) return; + + if (!this.isAnimatingScale) { + // We're not animating. We can use the last render time and the scroll speed to work out whether + // we actually need to paint anything yet. If not, we can return immediately. + + // Render at least every 1/6th of a second. The canvas may be resized, which there is + // no reliable way to detect. + var maxIdleMillis = Math.min(1000 / 6, this.options.millisPerPixel); + + if (nowMillis - this.lastRenderTimeMillis < maxIdleMillis) { return; - - if (!this.isAnimatingScale) { - // We're not animating. We can use the last render time and the scroll speed to work out whether - // we actually need to paint anything yet. If not, we can return immediately. - - // Render at least every 1/6th of a second. The canvas may be resized, which there is - // no reliable way to detect. - var maxIdleMillis = Math.min(1000/6, this.options.millisPerPixel); - - if (nowMillis - this.lastRenderTimeMillis < maxIdleMillis) { - return; - } } - - this.resize(); - this.updateTooltip(); - - this.lastRenderTimeMillis = nowMillis; - - canvas = canvas || this.canvas; - time = time || nowMillis - (this.delay || 0); - - // Round time down to pixel granularity, so motion appears smoother. - time -= time % this.options.millisPerPixel; - - this.lastChartTimestamp = time; - - var context = canvas.getContext('2d'), - chartOptions = this.options, - dimensions = { top: 0, left: 0, width: canvas.clientWidth, height: canvas.clientHeight }, - // Calculate the threshold time for the oldest data points. - oldestValidTime = time - (dimensions.width * chartOptions.millisPerPixel), - valueToYPixel = function(value) { - var offset = value - this.currentVisMinValue; - return this.currentValueRange === 0 - ? dimensions.height - : dimensions.height - (Math.round((offset / this.currentValueRange) * dimensions.height)); - }.bind(this), - timeToXPixel = function(t) { - if(chartOptions.scrollBackwards) { - return Math.round((time - t) / chartOptions.millisPerPixel); - } - return Math.round(dimensions.width - ((time - t) / chartOptions.millisPerPixel)); - }; - - this.updateValueRange(); - - context.font = chartOptions.labels.fontSize + 'px ' + chartOptions.labels.fontFamily; - - // Save the state of the canvas context, any transformations applied in this method - // will get removed from the stack at the end of this method when .restore() is called. - context.save(); - - // Move the origin. - context.translate(dimensions.left, dimensions.top); - - // Create a clipped rectangle - anything we draw will be constrained to this rectangle. - // This prevents the occasional pixels from curves near the edges overrunning and creating - // screen cheese (that phrase should need no explanation). - context.beginPath(); - context.rect(0, 0, dimensions.width, dimensions.height); - context.clip(); - - // Clear the working area. - context.save(); - context.fillStyle = chartOptions.grid.fillStyle; - context.clearRect(0, 0, dimensions.width, dimensions.height); - context.fillRect(0, 0, dimensions.width, dimensions.height); - context.restore(); - - // Grid lines... - context.save(); - context.lineWidth = chartOptions.grid.lineWidth; - context.strokeStyle = chartOptions.grid.strokeStyle; - // Vertical (time) dividers. - if (chartOptions.grid.millisPerLine > 0) { - context.beginPath(); - for (var t = time - (time % chartOptions.grid.millisPerLine); - t >= oldestValidTime; - t -= chartOptions.grid.millisPerLine) { - var gx = timeToXPixel(t); - if (chartOptions.grid.sharpLines) { - gx -= 0.5; - } - context.moveTo(gx, 0); - context.lineTo(gx, dimensions.height); + } + + this.resize(); + this.updateTooltip(); + + this.lastRenderTimeMillis = nowMillis; + + canvas = canvas || this.canvas; + time = time || nowMillis - (this.delay || 0); + + // Round time down to pixel granularity, so motion appears smoother. + time -= time % this.options.millisPerPixel; + + this.lastChartTimestamp = time; + + var context = canvas.getContext('2d'), + chartOptions = this.options, + dimensions = { top: 0, left: 0, width: canvas.clientWidth, height: canvas.clientHeight }, + // Calculate the threshold time for the oldest data points. + oldestValidTime = time - dimensions.width * chartOptions.millisPerPixel, + valueToYPixel = function (value) { + var offset = value - this.currentVisMinValue; + return this.currentValueRange === 0 + ? dimensions.height + : dimensions.height - Math.round((offset / this.currentValueRange) * dimensions.height); + }.bind(this), + timeToXPixel = function (t) { + if (chartOptions.scrollBackwards) { + return Math.round((time - t) / chartOptions.millisPerPixel); } - context.stroke(); - context.closePath(); - } - - // Horizontal (value) dividers. - for (var v = 1; v < chartOptions.grid.verticalSections; v++) { - var gy = Math.round(v * dimensions.height / chartOptions.grid.verticalSections); + return Math.round(dimensions.width - (time - t) / chartOptions.millisPerPixel); + }; + + this.updateValueRange(); + + context.font = chartOptions.labels.fontSize + 'px ' + chartOptions.labels.fontFamily; + + // Save the state of the canvas context, any transformations applied in this method + // will get removed from the stack at the end of this method when .restore() is called. + context.save(); + + // Move the origin. + context.translate(dimensions.left, dimensions.top); + + // Create a clipped rectangle - anything we draw will be constrained to this rectangle. + // This prevents the occasional pixels from curves near the edges overrunning and creating + // screen cheese (that phrase should need no explanation). + context.beginPath(); + context.rect(0, 0, dimensions.width, dimensions.height); + context.clip(); + + // Clear the working area. + context.save(); + context.fillStyle = chartOptions.grid.fillStyle; + context.clearRect(0, 0, dimensions.width, dimensions.height); + context.fillRect(0, 0, dimensions.width, dimensions.height); + context.restore(); + + // Grid lines... + context.save(); + context.lineWidth = chartOptions.grid.lineWidth; + context.strokeStyle = chartOptions.grid.strokeStyle; + // Vertical (time) dividers. + if (chartOptions.grid.millisPerLine > 0) { + context.beginPath(); + for ( + var t = time - (time % chartOptions.grid.millisPerLine); + t >= oldestValidTime; + t -= chartOptions.grid.millisPerLine + ) { + var gx = timeToXPixel(t); if (chartOptions.grid.sharpLines) { - gy -= 0.5; + gx -= 0.5; } + context.moveTo(gx, 0); + context.lineTo(gx, dimensions.height); + } + context.stroke(); + context.closePath(); + } + + // Horizontal (value) dividers. + for (var v = 1; v < chartOptions.grid.verticalSections; v++) { + var gy = Math.round((v * dimensions.height) / chartOptions.grid.verticalSections); + if (chartOptions.grid.sharpLines) { + gy -= 0.5; + } + context.beginPath(); + context.moveTo(0, gy); + context.lineTo(dimensions.width, gy); + context.stroke(); + context.closePath(); + } + // Bounding rectangle. + if (chartOptions.grid.borderVisible) { + context.beginPath(); + context.strokeRect(0, 0, dimensions.width, dimensions.height); + context.closePath(); + } + context.restore(); + + // Draw any horizontal lines... + if (chartOptions.horizontalLines && chartOptions.horizontalLines.length) { + for (var hl = 0; hl < chartOptions.horizontalLines.length; hl++) { + var line = chartOptions.horizontalLines[hl], + hly = Math.round(valueToYPixel(line.value)) - 0.5; + context.strokeStyle = line.color || '#ffffff'; + context.lineWidth = line.lineWidth || 1; context.beginPath(); - context.moveTo(0, gy); - context.lineTo(dimensions.width, gy); + context.moveTo(0, hly); + context.lineTo(dimensions.width, hly); context.stroke(); context.closePath(); } - // Bounding rectangle. - if (chartOptions.grid.borderVisible) { - context.beginPath(); - context.strokeRect(0, 0, dimensions.width, dimensions.height); - context.closePath(); + } + + // For each data set... + for (var d = 0; d < this.seriesSet.length; d++) { + context.save(); + var timeSeries = this.seriesSet[d].timeSeries; + if (timeSeries.disabled) { + continue; } - context.restore(); - - // Draw any horizontal lines... - if (chartOptions.horizontalLines && chartOptions.horizontalLines.length) { - for (var hl = 0; hl < chartOptions.horizontalLines.length; hl++) { - var line = chartOptions.horizontalLines[hl], - hly = Math.round(valueToYPixel(line.value)) - 0.5; - context.strokeStyle = line.color || '#ffffff'; - context.lineWidth = line.lineWidth || 1; - context.beginPath(); - context.moveTo(0, hly); - context.lineTo(dimensions.width, hly); - context.stroke(); - context.closePath(); + + var dataSet = timeSeries.data, + seriesOptions = this.seriesSet[d].options; + + // Delete old data that's moved off the left of the chart. + timeSeries.dropOldData(oldestValidTime, chartOptions.maxDataSetLength); + + // Set style for this dataSet. + context.lineWidth = seriesOptions.lineWidth; + context.strokeStyle = seriesOptions.strokeStyle; + // Draw the line... + context.beginPath(); + // Retain lastX, lastY for calculating the control points of bezier curves. + var firstX = 0, + firstY = 0, + lastX = 0, + lastY = 0; + for (var i = 0; i < dataSet.length && dataSet.length !== 1; i++) { + var x = timeToXPixel(dataSet[i][0]), + y = valueToYPixel(dataSet[i][1]); + + if (i === 0) { + firstX = x; + firstY = y; + context.moveTo(x, y); + } else { + switch (chartOptions.interpolation) { + case 'linear': + case 'line': { + context.lineTo(x, y); + break; + } + case 'bezier': + default: { + // Great explanation of Bezier curves: http://en.wikipedia.org/wiki/Bezier_curve#Quadratic_curves + // + // Assuming A was the last point in the line plotted and B is the new point, + // we draw a curve with control points P and Q as below. + // + // A---P + // | + // | + // | + // Q---B + // + // Importantly, A and P are at the same y coordinate, as are B and Q. This is + // so adjacent curves appear to flow as one. + // + context.bezierCurveTo( + // startPoint (A) is implicit from last iteration of loop + Math.round((lastX + x) / 2), + lastY, // controlPoint1 (P) + Math.round(lastX + x) / 2, + y, // controlPoint2 (Q) + x, + y, + ); // endPoint (B) + break; + } + case 'step': { + context.lineTo(x, lastY); + context.lineTo(x, y); + break; + } + } } + + lastX = x; + lastY = y; } - - // For each data set... - for (var d = 0; d < this.seriesSet.length; d++) { - context.save(); - var timeSeries = this.seriesSet[d].timeSeries; - if (timeSeries.disabled) { - continue; - } - - var dataSet = timeSeries.data, - seriesOptions = this.seriesSet[d].options; - - // Delete old data that's moved off the left of the chart. - timeSeries.dropOldData(oldestValidTime, chartOptions.maxDataSetLength); - - // Set style for this dataSet. - context.lineWidth = seriesOptions.lineWidth; - context.strokeStyle = seriesOptions.strokeStyle; - // Draw the line... - context.beginPath(); - // Retain lastX, lastY for calculating the control points of bezier curves. - var firstX = 0, firstY = 0, lastX = 0, lastY = 0; - for (var i = 0; i < dataSet.length && dataSet.length !== 1; i++) { - var x = timeToXPixel(dataSet[i][0]), - y = valueToYPixel(dataSet[i][1]); - - if (i === 0) { - firstX = x; - firstY = y; - context.moveTo(x, y); + + if (dataSet.length > 1) { + if (seriesOptions.fillStyle) { + // Close up the fill region. + if (chartOptions.scrollBackwards) { + context.lineTo(lastX, dimensions.height + seriesOptions.lineWidth); + context.lineTo(firstX, dimensions.height + seriesOptions.lineWidth); + context.lineTo(firstX, firstY); } else { - switch (chartOptions.interpolation) { - case "linear": - case "line": { - context.lineTo(x,y); - break; - } - case "bezier": - default: { - // Great explanation of Bezier curves: http://en.wikipedia.org/wiki/Bezier_curve#Quadratic_curves - // - // Assuming A was the last point in the line plotted and B is the new point, - // we draw a curve with control points P and Q as below. - // - // A---P - // | - // | - // | - // Q---B - // - // Importantly, A and P are at the same y coordinate, as are B and Q. This is - // so adjacent curves appear to flow as one. - // - context.bezierCurveTo( // startPoint (A) is implicit from last iteration of loop - Math.round((lastX + x) / 2), lastY, // controlPoint1 (P) - Math.round((lastX + x)) / 2, y, // controlPoint2 (Q) - x, y); // endPoint (B) - break; - } - case "step": { - context.lineTo(x,lastY); - context.lineTo(x,y); - break; - } - } + context.lineTo(dimensions.width + seriesOptions.lineWidth + 1, lastY); + context.lineTo( + dimensions.width + seriesOptions.lineWidth + 1, + dimensions.height + seriesOptions.lineWidth + 1, + ); + context.lineTo(firstX, dimensions.height + seriesOptions.lineWidth); } - - lastX = x; lastY = y; + context.fillStyle = seriesOptions.fillStyle; + context.fill(); } - - if (dataSet.length > 1) { - if (seriesOptions.fillStyle) { - // Close up the fill region. - if (chartOptions.scrollBackwards) { - context.lineTo(lastX, dimensions.height + seriesOptions.lineWidth); - context.lineTo(firstX, dimensions.height + seriesOptions.lineWidth); - context.lineTo(firstX, firstY); - } else { - context.lineTo(dimensions.width + seriesOptions.lineWidth + 1, lastY); - context.lineTo(dimensions.width + seriesOptions.lineWidth + 1, dimensions.height + seriesOptions.lineWidth + 1); - context.lineTo(firstX, dimensions.height + seriesOptions.lineWidth); - } - context.fillStyle = seriesOptions.fillStyle; - context.fill(); - } - - if (seriesOptions.strokeStyle && seriesOptions.strokeStyle !== 'none') { - context.stroke(); - } - context.closePath(); + + if (seriesOptions.strokeStyle && seriesOptions.strokeStyle !== 'none') { + context.stroke(); } - context.restore(); - } - - if (chartOptions.tooltip && this.mouseX >= 0) { - // Draw vertical bar to show tooltip position - context.lineWidth = chartOptions.tooltipLine.lineWidth; - context.strokeStyle = chartOptions.tooltipLine.strokeStyle; - context.beginPath(); - context.moveTo(this.mouseX, 0); - context.lineTo(this.mouseX, dimensions.height); context.closePath(); - context.stroke(); - this.updateTooltip(); - } - - // Draw the axis values on the chart. - if (!chartOptions.labels.disabled && !isNaN(this.valueRange.min) && !isNaN(this.valueRange.max)) { - var maxValueString = chartOptions.yMaxFormatter(this.valueRange.max, chartOptions.labels.precision), - minValueString = chartOptions.yMinFormatter(this.valueRange.min, chartOptions.labels.precision), - maxLabelPos = chartOptions.scrollBackwards ? 0 : dimensions.width - context.measureText(maxValueString).width - 2, - minLabelPos = chartOptions.scrollBackwards ? 0 : dimensions.width - context.measureText(minValueString).width - 2; - context.fillStyle = chartOptions.labels.fillStyle; - context.fillText(maxValueString, maxLabelPos, chartOptions.labels.fontSize); - context.fillText(minValueString, minLabelPos, dimensions.height - 2); } - - // Display intermediate y axis labels along y-axis to the left of the chart - if ( chartOptions.labels.showIntermediateLabels - && !isNaN(this.valueRange.min) && !isNaN(this.valueRange.max) - && chartOptions.grid.verticalSections > 0) { - // show a label above every vertical section divider - var step = (this.valueRange.max - this.valueRange.min) / chartOptions.grid.verticalSections; - var stepPixels = dimensions.height / chartOptions.grid.verticalSections; - for (var v = 1; v < chartOptions.grid.verticalSections; v++) { - var gy = dimensions.height - Math.round(v * stepPixels); - if (chartOptions.grid.sharpLines) { - gy -= 0.5; - } - var yValue = chartOptions.yIntermediateFormatter(this.valueRange.min + (v * step), chartOptions.labels.precision); - //left of right axis? - let intermediateLabelPos = - chartOptions.labels.intermediateLabelSameAxis - ? (chartOptions.scrollBackwards ? 0 : dimensions.width - context.measureText(yValue).width - 2) - : (chartOptions.scrollBackwards ? dimensions.width - context.measureText(yValue).width - 2 : 0); - - context.fillText(yValue, intermediateLabelPos, gy - chartOptions.grid.lineWidth); + context.restore(); + } + + if (chartOptions.tooltip && this.mouseX >= 0) { + // Draw vertical bar to show tooltip position + context.lineWidth = chartOptions.tooltipLine.lineWidth; + context.strokeStyle = chartOptions.tooltipLine.strokeStyle; + context.beginPath(); + context.moveTo(this.mouseX, 0); + context.lineTo(this.mouseX, dimensions.height); + context.closePath(); + context.stroke(); + this.updateTooltip(); + } + + // Draw the axis values on the chart. + if (!chartOptions.labels.disabled && !isNaN(this.valueRange.min) && !isNaN(this.valueRange.max)) { + var maxValueString = chartOptions.yMaxFormatter(this.valueRange.max, chartOptions.labels.precision), + minValueString = chartOptions.yMinFormatter(this.valueRange.min, chartOptions.labels.precision), + maxLabelPos = chartOptions.scrollBackwards + ? 0 + : dimensions.width - context.measureText(maxValueString).width - 2, + minLabelPos = chartOptions.scrollBackwards + ? 0 + : dimensions.width - context.measureText(minValueString).width - 2; + context.fillStyle = chartOptions.labels.fillStyle; + context.fillText(maxValueString, maxLabelPos, chartOptions.labels.fontSize); + context.fillText(minValueString, minLabelPos, dimensions.height - 2); + } + + // Display intermediate y axis labels along y-axis to the left of the chart + if ( + chartOptions.labels.showIntermediateLabels && + !isNaN(this.valueRange.min) && + !isNaN(this.valueRange.max) && + chartOptions.grid.verticalSections > 0 + ) { + // show a label above every vertical section divider + var step = (this.valueRange.max - this.valueRange.min) / chartOptions.grid.verticalSections; + var stepPixels = dimensions.height / chartOptions.grid.verticalSections; + for (var v = 1; v < chartOptions.grid.verticalSections; v++) { + var gy = dimensions.height - Math.round(v * stepPixels); + if (chartOptions.grid.sharpLines) { + gy -= 0.5; } + var yValue = chartOptions.yIntermediateFormatter(this.valueRange.min + v * step, chartOptions.labels.precision); + //left of right axis? + let intermediateLabelPos = chartOptions.labels.intermediateLabelSameAxis + ? chartOptions.scrollBackwards + ? 0 + : dimensions.width - context.measureText(yValue).width - 2 + : chartOptions.scrollBackwards + ? dimensions.width - context.measureText(yValue).width - 2 + : 0; + + context.fillText(yValue, intermediateLabelPos, gy - chartOptions.grid.lineWidth); } - - // Display timestamps along x-axis at the bottom of the chart. - if (chartOptions.timestampFormatter && chartOptions.grid.millisPerLine > 0) { - var textUntilX = chartOptions.scrollBackwards - ? context.measureText(minValueString).width - : dimensions.width - context.measureText(minValueString).width + 4; - for (var t = time - (time % chartOptions.grid.millisPerLine); - t >= oldestValidTime; - t -= chartOptions.grid.millisPerLine) { - var gx = timeToXPixel(t); - // Only draw the timestamp if it won't overlap with the previously drawn one. - if ((!chartOptions.scrollBackwards && gx < textUntilX) || (chartOptions.scrollBackwards && gx > textUntilX)) { - // Formats the timestamp based on user specified formatting function - // SmoothieChart.timeFormatter function above is one such formatting option - var tx = new Date(t), - ts = chartOptions.timestampFormatter(tx), - tsWidth = context.measureText(ts).width; - - textUntilX = chartOptions.scrollBackwards - ? gx + tsWidth + 2 - : gx - tsWidth - 2; - - context.fillStyle = chartOptions.labels.fillStyle; - if(chartOptions.scrollBackwards) { - context.fillText(ts, gx, dimensions.height - 2); - } else { - context.fillText(ts, gx - tsWidth, dimensions.height - 2); - } + } + + // Display timestamps along x-axis at the bottom of the chart. + if (chartOptions.timestampFormatter && chartOptions.grid.millisPerLine > 0) { + var textUntilX = chartOptions.scrollBackwards + ? context.measureText(minValueString).width + : dimensions.width - context.measureText(minValueString).width + 4; + for ( + var t = time - (time % chartOptions.grid.millisPerLine); + t >= oldestValidTime; + t -= chartOptions.grid.millisPerLine + ) { + var gx = timeToXPixel(t); + // Only draw the timestamp if it won't overlap with the previously drawn one. + if ((!chartOptions.scrollBackwards && gx < textUntilX) || (chartOptions.scrollBackwards && gx > textUntilX)) { + // Formats the timestamp based on user specified formatting function + // SmoothieChart.timeFormatter function above is one such formatting option + var tx = new Date(t), + ts = chartOptions.timestampFormatter(tx), + tsWidth = context.measureText(ts).width; + + textUntilX = chartOptions.scrollBackwards ? gx + tsWidth + 2 : gx - tsWidth - 2; + + context.fillStyle = chartOptions.labels.fillStyle; + if (chartOptions.scrollBackwards) { + context.fillText(ts, gx, dimensions.height - 2); + } else { + context.fillText(ts, gx - tsWidth, dimensions.height - 2); } } } - - // Display title. - if (chartOptions.title.text !== '') { - context.font = chartOptions.title.fontSize + 'px ' + chartOptions.title.fontFamily; - var titleXPos = chartOptions.scrollBackwards ? dimensions.width - context.measureText(chartOptions.title.text).width - 2 : 2; - if (chartOptions.title.verticalAlign == 'bottom') { - context.textBaseline = 'bottom'; - var titleYPos = dimensions.height; - } else if (chartOptions.title.verticalAlign == 'middle') { - context.textBaseline = 'middle'; - var titleYPos = dimensions.height / 2; - } else { - context.textBaseline = 'top'; - var titleYPos = 0; - } - context.fillStyle = chartOptions.title.fillStyle; - context.fillText(chartOptions.title.text, titleXPos, titleYPos); + } + + // Display title. + if (chartOptions.title.text !== '') { + context.font = chartOptions.title.fontSize + 'px ' + chartOptions.title.fontFamily; + var titleXPos = chartOptions.scrollBackwards + ? dimensions.width - context.measureText(chartOptions.title.text).width - 2 + : 2; + if (chartOptions.title.verticalAlign == 'bottom') { + context.textBaseline = 'bottom'; + var titleYPos = dimensions.height; + } else if (chartOptions.title.verticalAlign == 'middle') { + context.textBaseline = 'middle'; + var titleYPos = dimensions.height / 2; + } else { + context.textBaseline = 'top'; + var titleYPos = 0; } - - context.restore(); // See .save() above. - }; - - // Sample timestamp formatting function - SmoothieChart.timeFormatter = function(date) { - function pad2(number) { return (number < 10 ? '0' : '') + number } - return pad2(date.getHours()) + ':' + pad2(date.getMinutes()) + ':' + pad2(date.getSeconds()); - }; - - exports.TimeSeries = TimeSeries; - exports.SmoothieChart = SmoothieChart; - - })(typeof exports === 'undefined' ? this : exports); - - \ No newline at end of file + context.fillStyle = chartOptions.title.fillStyle; + context.fillText(chartOptions.title.text, titleXPos, titleYPos); + } + + context.restore(); // See .save() above. + }; + + // Sample timestamp formatting function + SmoothieChart.timeFormatter = function (date) { + function pad2(number) { + return (number < 10 ? '0' : '') + number; + } + return pad2(date.getHours()) + ':' + pad2(date.getMinutes()) + ':' + pad2(date.getSeconds()); + }; + + exports.TimeSeries = TimeSeries; + exports.SmoothieChart = SmoothieChart; +})(typeof exports === 'undefined' ? this : exports); diff --git a/src/lib/src/components/tree/TreeViewComponent.css b/src/lib/src/components/tree/TreeViewComponent.css deleted file mode 100644 index 0c3e276..0000000 --- a/src/lib/src/components/tree/TreeViewComponent.css +++ /dev/null @@ -1,67 +0,0 @@ -@import url('../../pages/index.css'); - -.tree { - width: 100%; - height: 100%; -} - -.directory { - font-size: 16px; - color: white; - user-select: none; - padding: 20px; -} - -.directory .tree, -.directory .tree-node, -.directory .tree-node-group { - list-style: none; - margin: 0; - padding: 0; -} - -.directory .tree-branch-wrapper, -.directory .tree-node__leaf { - outline: none; -} - -.directory .tree-node { - cursor: pointer; -} - -.directory .tree-node:hover { - background: rgb(255 255 255 / 10%); -} - -.directory .tree .tree-node--focused { - background: rgb(255 255 255 / 20%); -} - -.directory .tree .tree-node--selected { - background: rgb(231 34 100 / 20%) -} - -.directory .tree-node__branch { - display: block; -} - -.directory .icon { - vertical-align: middle; - padding-right: 5px; -} - -.directory .container.open { - color: var(--active-bg-color); - font-weight: bold; - - /* or use text-shadow for a bolder effect */ - text-shadow: 0 0 1px black; -} - -.directory .container.closed { - color: var(--active-color); - font-weight: bold; - - /* or use text-shadow for a bolder effect */ - text-shadow: 0 0 1px black; -} \ No newline at end of file diff --git a/src/lib/src/components/tree/TreeViewComponent.tsx b/src/lib/src/components/tree/TreeViewComponent.tsx index 4c7ae64..6a23b43 100644 --- a/src/lib/src/components/tree/TreeViewComponent.tsx +++ b/src/lib/src/components/tree/TreeViewComponent.tsx @@ -1,14 +1,32 @@ -import React from 'react'; +import React, { useState } from 'react'; import TreeView, { flattenTree } from 'react-accessible-treeview'; import { FaLayerGroup as ContainerIcon, FaRegObjectGroup as SceneNodeIcon, FaFileImage as SpriteIcon, } from 'react-icons/fa6'; -import { SectionContainer, SectionHeader, Title, TitleGroup } from '../Container'; +import { SectionHeader, Title, TitleGroup } from '../Container'; import { usePixiStore } from '@lib/src/pages'; -import './TreeViewComponent.css'; +import { TreeDirectory, TreeWrapper } from './styles'; + +type FlattenTreeData = ReturnType; + +function findParents(flattenTreeData: FlattenTreeData, selectedNode: FlattenTreeData[0]) { + if (!selectedNode) return []; + const parents = [] as FlattenTreeData; + let currentParentId = selectedNode.parent; + + while (currentParentId) { + const parent = flattenTreeData.find((node) => node.id === currentParentId); + if (!parent) break; + + parents.push(parent); + currentParentId = parent.parent; + } + + return parents; +} // eslint-disable-next-line @typescript-eslint/no-empty-interface interface TreeViewComponentProps {} @@ -18,22 +36,31 @@ const TreeViewComponent: React.FC = () => { const flattenTreeData = flattenTree(sceneGraph as any); const selectedNodeId = usePixiStore((state) => state.selectedNodeId); + // find the node in the flattenTreeData that matches the selectedNodeId + const selectedNode = flattenTreeData.find((node) => node.metadata!.uid === selectedNodeId); + const parents = findParents(flattenTreeData, selectedNode!); + const [treeId, setTreeId] = useState( + selectedNode ? [selectedNode.id as number, ...parents.map((node) => node.id as number)] : null, + ); + console.log('rendering tree', selectedNodeId); return ( - - + + - Tree + Tree
-
+ ( -
+
{isBranch ? : } {element.name}
@@ -44,15 +71,20 @@ const TreeViewComponent: React.FC = () => { const selectedNodes = flattenTreeData.filter((node) => selectedNodeIds.has(node.id)); bridge( `window.__PIXI_DEVTOOL_WRAPPER__.properties.setSelectedNodeIds(${JSON.stringify( - selectedNodes.map((node) => node.metadata!.uid)[0] - )})` + selectedNodes.map((node) => node.metadata!.uid)[0], + )})`, ); + + const id = [...selectedNodeIds][0]; + if (!treeId || treeId[0] !== id) { + setTreeId([id as number]); + } }} /> -
+
- + ); }; diff --git a/src/lib/src/components/tree/styles.tsx b/src/lib/src/components/tree/styles.tsx new file mode 100644 index 0000000..e3b2be4 --- /dev/null +++ b/src/lib/src/components/tree/styles.tsx @@ -0,0 +1,69 @@ +import styled from 'styled-components'; +import { SectionContainer } from '../Container'; + +export const TreeWrapper = styled(SectionContainer)` + width: 100%; + height: 100%; + min-width: 50%; +`; + +export const TreeDirectory = styled.div` + font-size: 14px; + color: var(--text); + user-select: none; + padding: 20px 0 0 12px; + + .tree, + .tree-node, + .tree-node-group { + list-style: none; + margin: 0; + padding: 0; + } + + .tree-branch-wrapper, + .tree-node__leaf { + outline: none; + } + + .tree-node { + cursor: pointer; + } + + .tree-node:hover { + background: rgb(255 255 255 / 10%); + } + + .tree .tree-node--focused { + background: rgb(255 255 255 / 20%); + } + + .tree .tree-node--selected { + background: rgb(231 34 100 / 20%); + } + + .tree-node__branch { + display: block; + } + + .icon { + vertical-align: middle; + padding-right: 5px; + } + + .container.open { + color: var(--active-bg-color); + font-weight: bold; + + /* or use text-shadow for a bolder effect */ + text-shadow: 0 0 1px black; + } + + .container.closed { + color: var(--active-color); + font-weight: bold; + + /* or use text-shadow for a bolder effect */ + text-shadow: 0 0 1px black; + } +`; diff --git a/src/lib/src/detection/devtool.ts b/src/lib/src/detection/devtool.ts index 1ff010d..f4db395 100644 --- a/src/lib/src/detection/devtool.ts +++ b/src/lib/src/detection/devtool.ts @@ -3,14 +3,16 @@ import type { flattenTree } from 'react-accessible-treeview'; import { PixiSceneObjectType } from './utils/getPixiType'; import { properties } from './properties/properties'; import { Prop } from './properties/propertyTypes'; +import { MessageType } from '@shared/index'; export interface Pixi { + pixi: () => typeof import('pixi.js'); + detectPixi: () => MessageType; app: () => Application | undefined; stage: () => Container | undefined; renderer: () => Renderer | undefined; canvas: () => HTMLCanvasElement | undefined; state: () => PixiState; - pixi: () => typeof import('pixi.js'); version: () => string; properties: ReturnType; } @@ -21,21 +23,28 @@ export function getPixiWrapper(): Pixi { if (!pixiWrapper) { pixiWrapper = { app: () => { + if (window.__PIXI__DEVTOOLS__?.app) { + return window.__PIXI__DEVTOOLS__.app; + } return window.__PIXI_APP__; }, stage: () => { - if (!window.__PIXI_STAGE__) { - return pixiWrapper!.app()?.stage; + if (window.__PIXI__DEVTOOLS__?.stage) { + return window.__PIXI__DEVTOOLS__.stage; + } else if (window.__PIXI_STAGE__) { + return window.__PIXI_STAGE__; } - return window.__PIXI_STAGE__; + return pixiWrapper!.app()?.stage; }, renderer: () => { - if (!window.__PIXI_RENDERER__) { - return pixiWrapper!.app()?.renderer; + if (window.__PIXI__DEVTOOLS__?.renderer) { + return window.__PIXI__DEVTOOLS__.renderer; + } else if (window.__PIXI_RENDERER__) { + return window.__PIXI_RENDERER__; } - return window.__PIXI_RENDERER__; + return pixiWrapper!.app()?.renderer; }, canvas: () => { const renderer = pixiWrapper!.renderer()!; @@ -50,10 +59,18 @@ export function getPixiWrapper(): Pixi { return getPixiState(); }, pixi: () => { + if (window.__PIXI__DEVTOOLS__?.pixi) { + return window.__PIXI__DEVTOOLS__.pixi; + } return window.__PIXI__; }, - version: () => window.__PIXI__.VERSION, + version: () => pixiWrapper!.pixi().VERSION, properties: properties(), + detectPixi: () => { + return pixiWrapper!.app() || pixiWrapper!.stage() || pixiWrapper!.renderer() + ? MessageType.Active + : MessageType.Inactive; + }, }; window.__PIXI_DEVTOOL_WRAPPER__ = pixiWrapper; diff --git a/src/lib/src/detection/properties/AnimatedSpritePropsPlugin.ts b/src/lib/src/detection/properties/AnimatedSpritePropsPlugin.ts new file mode 100644 index 0000000..315fb30 --- /dev/null +++ b/src/lib/src/detection/properties/AnimatedSpritePropsPlugin.ts @@ -0,0 +1,25 @@ +import { AnimatedSprite } from 'pixi.js'; +import { ContainerPropsPlugin } from './ContainerPropsPlugin'; +import { PropertyPlugin } from './propertyTypes'; + +export const AnimatedSpritePropsPlugin: PropertyPlugin = { + ...ContainerPropsPlugin, + getPropValue: function (container, prop) { + if (this.getPropKeys().indexOf(prop) === -1) { + return null; + } + + return { value: container[prop as keyof AnimatedSprite], prop }; + }, + props: [ + { section: 'Transform', property: 'roundPixels', propertyProps: { label: 'Round Pixels' }, type: 'boolean' }, + { section: 'Animated Sprite', property: 'textures', propertyProps: { label: 'Textures' }, type: 'text' }, + { section: 'Animated Sprite', property: 'totalFrames', propertyProps: { label: 'Total Frames' }, type: 'number' }, + { section: 'Animated Sprite', property: 'currentFrame', propertyProps: { label: 'Current Frame' }, type: 'number' }, + { section: 'Animated Sprite', property: 'autoUpdate', propertyProps: { label: 'Auto Update' }, type: 'boolean' }, + { section: 'Animated Sprite', property: 'playing', propertyProps: { label: 'Playing' }, type: 'boolean' }, + { section: 'Animated Sprite', property: 'start', propertyProps: { label: 'Start' }, type: 'button' }, + { section: 'Animated Sprite', property: 'stop', propertyProps: { label: 'Stop' }, type: 'button' }, + { section: 'Animated Sprite', property: 'play', propertyProps: { label: 'Play' }, type: 'button' }, + ], +}; diff --git a/src/lib/src/detection/properties/ContainerPropsPlugin.ts b/src/lib/src/detection/properties/ContainerPropsPlugin.ts index 0a9ab38..2bbdf79 100644 --- a/src/lib/src/detection/properties/ContainerPropsPlugin.ts +++ b/src/lib/src/detection/properties/ContainerPropsPlugin.ts @@ -3,6 +3,7 @@ import { PropertyPlugin } from './propertyTypes'; import { SliderProps } from '@lib/src/components/properties/slider/Slider'; import { getPixiWrapper } from '../devtool'; import { Vector2Props } from '@lib/src/components/properties/number/Vector2'; +import { TextProps } from '@lib/src/components/properties/text/Text'; export const ContainerPropsPlugin: PropertyPlugin = { getPropValue: function (container, prop) { @@ -60,7 +61,7 @@ export const ContainerPropsPlugin: PropertyPlugin = { }); }, props: [ - { property: 'type', section: 'Info', propertyProps: { label: 'Type', disabled: true }, type: 'text' }, // not editable + { property: 'type', section: 'Info', propertyProps: { label: 'Type', disabled: true } as TextProps, type: 'text' }, // not editable { property: 'label', section: 'Info', propertyProps: { label: 'Label' }, type: 'text' }, { section: 'Transform', @@ -68,16 +69,31 @@ export const ContainerPropsPlugin: PropertyPlugin = { propertyProps: { label: 'Position', x: { label: 'x' }, y: { label: 'y' } } as Vector2Props, type: 'vector2', }, - // { section: 'Transform', property: 'width', propertyProps: { label: 'Width' }, type: 'number' }, - // { section: 'Transform', property: 'height', propertyProps: { label: 'Height' }, type: 'number' }, - // { section: 'Transform', property: 'scale', propertyProps: { label: 'Scale' }, type: 'vector2' }, - // { section: 'Transform', property: 'rotation', propertyProps: { label: 'Rotation' }, type: 'number' }, - // { section: 'Transform', property: 'angle', propertyProps: { label: 'Angle' }, type: 'number' }, - // { section: 'Transform', property: 'pivot', propertyProps: { label: 'Pivot' }, type: 'vector2' }, - // { section: 'Transform', property: 'skew', propertyProps: { label: 'Skew' }, type: 'vector2' }, - // // { section: 'Transform', property: 'worldTransform', propertyProps: { label: 'World Transform' }, type: 'matrix' }, - // { property: 'visible', section: 'Appearance', propertyProps: { label: 'Visible' }, type: 'boolean' }, - // { property: 'renderable', section: 'Appearance', propertyProps: { label: 'Renderable' }, type: 'boolean' }, + { section: 'Transform', property: 'width', propertyProps: { label: 'Width' }, type: 'number' }, + { section: 'Transform', property: 'height', propertyProps: { label: 'Height' }, type: 'number' }, + { + section: 'Transform', + property: 'scale', + propertyProps: { label: 'Scale', x: { label: 'x' }, y: { label: 'y' } } as Vector2Props, + type: 'vector2', + }, + { section: 'Transform', property: 'rotation', propertyProps: { label: 'Rotation' }, type: 'number' }, + { section: 'Transform', property: 'angle', propertyProps: { label: 'Angle' }, type: 'number' }, + { + section: 'Transform', + property: 'pivot', + propertyProps: { label: 'Pivot', x: { label: 'x' }, y: { label: 'y' } } as Vector2Props, + type: 'vector2', + }, + { + section: 'Transform', + property: 'skew', + propertyProps: { label: 'Skew', x: { label: 'x' }, y: { label: 'y' } } as Vector2Props, + type: 'vector2', + }, + // { section: 'Transform', property: 'worldTransform', propertyProps: { label: 'World Transform' }, type: 'matrix' }, + { property: 'visible', section: 'Appearance', propertyProps: { label: 'Visible' }, type: 'boolean' }, + { property: 'renderable', section: 'Appearance', propertyProps: { label: 'Renderable' }, type: 'boolean' }, { property: 'alpha', section: 'Appearance', @@ -104,47 +120,47 @@ export const ContainerPropsPlugin: PropertyPlugin = { // propertyProps: { label: 'Bounds Area' }, // type: 'rect', // }, - // { - // section: 'Rendering', - // property: 'isRenderGroup', - // propertyProps: { label: 'Is Render Group' }, - // type: 'boolean', - // }, + { + section: 'Rendering', + property: 'isRenderGroup', + propertyProps: { label: 'Is Render Group' }, + type: 'boolean', + }, - // { - // section: 'Culling', - // property: 'cullable', - // propertyProps: { label: 'Cullable' }, - // type: 'boolean', - // }, + { + section: 'Culling', + property: 'cullable', + propertyProps: { label: 'Cullable' }, + type: 'boolean', + }, // { // section: 'Culling', // property: 'cullArea', // propertyProps: { label: 'Cull Area' }, // type: 'rect', // }, - // { - // section: 'Culling', - // property: 'cullableChildren', - // propertyProps: { label: 'Cullable Children' }, - // type: 'boolean', - // }, + { + section: 'Culling', + property: 'cullableChildren', + propertyProps: { label: 'Cullable Children' }, + type: 'boolean', + }, - // { - // section: 'Sorting', - // property: 'sortableChildren', - // propertyProps: { label: 'Sortable Children' }, - // type: 'boolean', - // }, - // { section: 'Sorting', property: 'zIndex', propertyProps: { label: 'Z Index' }, type: 'number' }, + { + section: 'Sorting', + property: 'sortableChildren', + propertyProps: { label: 'Sortable Children' }, + type: 'boolean', + }, + { section: 'Sorting', property: 'zIndex', propertyProps: { label: 'Z Index' }, type: 'number' }, - // { section: 'Interaction', property: 'interactive', propertyProps: { label: 'Interactive' }, type: 'boolean' }, - // { - // section: 'Interaction', - // property: 'interactiveChildren', - // propertyProps: { label: 'Interactive Children' }, - // type: 'boolean', - // }, + { section: 'Interaction', property: 'interactive', propertyProps: { label: 'Interactive' }, type: 'boolean' }, + { + section: 'Interaction', + property: 'interactiveChildren', + propertyProps: { label: 'Interactive Children' }, + type: 'boolean', + }, // { section: 'Interaction', property: 'hitArea', propertyProps: { label: 'Hit Area' }, type: 'rect' }, // { section: 'Interaction', property: 'cursor', propertyProps: { label: 'Cursor' }, type: 'select' }, ], diff --git a/src/lib/src/detection/properties/GraphicsPropsPlugin.ts b/src/lib/src/detection/properties/GraphicsPropsPlugin.ts new file mode 100644 index 0000000..5373374 --- /dev/null +++ b/src/lib/src/detection/properties/GraphicsPropsPlugin.ts @@ -0,0 +1,17 @@ +import { Graphics } from 'pixi.js'; +import { ContainerPropsPlugin } from './ContainerPropsPlugin'; +import { PropertyPlugin } from './propertyTypes'; + +export const GraphicsPropsPlugin: PropertyPlugin = { + ...ContainerPropsPlugin, + getPropValue: function (container, prop) { + if (this.getPropKeys().indexOf(prop) === -1) { + return null; + } + + return { value: container[prop as keyof Graphics], prop }; + }, + props: [ + { section: 'Transform', property: 'roundPixels', propertyProps: { label: 'Round Pixels' }, type: 'boolean' }, + ], +}; diff --git a/src/lib/src/detection/properties/MeshPropsPlugin.ts b/src/lib/src/detection/properties/MeshPropsPlugin.ts new file mode 100644 index 0000000..0535f9c --- /dev/null +++ b/src/lib/src/detection/properties/MeshPropsPlugin.ts @@ -0,0 +1,18 @@ +import { Mesh } from 'pixi.js'; +import { ContainerPropsPlugin } from './ContainerPropsPlugin'; +import { PropertyPlugin } from './propertyTypes'; +import { SwitchProps } from '@lib/src/components/properties/switch/Switch'; + +export const MeshPropsPlugin: PropertyPlugin = { + ...ContainerPropsPlugin, + getPropValue: function (container, prop) { + if (this.getPropKeys().indexOf(prop) === -1) { + return null; + } + return { value: container[prop as keyof Mesh], prop }; + }, + props: [ + { section: 'Transform', property: 'roundPixels', propertyProps: { label: 'Round Pixels' }, type: 'boolean' }, + { section: 'Mesh', property: 'batched', propertyProps: { label: 'Batched', disabled: true } as SwitchProps, type: 'boolean' } + ], +}; diff --git a/src/lib/src/detection/properties/NineSlicePropsPlugin.ts b/src/lib/src/detection/properties/NineSlicePropsPlugin.ts new file mode 100644 index 0000000..e1896c6 --- /dev/null +++ b/src/lib/src/detection/properties/NineSlicePropsPlugin.ts @@ -0,0 +1,44 @@ +import { NineSliceSprite } from 'pixi.js'; +import { ContainerPropsPlugin } from './ContainerPropsPlugin'; +import { PropertyPlugin } from './propertyTypes'; + +export const NineSliceSpritePropsPlugin: PropertyPlugin = { + ...ContainerPropsPlugin, + getPropValue: function (container, prop) { + if (this.getPropKeys().indexOf(prop) === -1) { + return null; + } + + return { value: container[prop as keyof NineSliceSprite], prop }; + }, + props: [ + { section: 'Transform', property: 'roundPixels', propertyProps: { label: 'Round Pixels' }, type: 'boolean' }, + { section: 'NineSlice Sprite', property: 'leftWidth', propertyProps: { label: 'Left Width' }, type: 'number' }, + { section: 'NineSlice Sprite', property: 'rightWidth', propertyProps: { label: 'Right Width' }, type: 'number' }, + { section: 'NineSlice Sprite', property: 'topHeight', propertyProps: { label: 'Top Height' }, type: 'number' }, + { + section: 'NineSlice Sprite', + property: 'bottomHeight', + propertyProps: { label: 'Bottom Height' }, + type: 'number', + }, + { + section: 'NineSlice Sprite', + property: 'textureMatrix', + propertyProps: { label: 'Texture Matrix' }, + type: 'matrix', + }, + { + section: 'NineSlice Sprite', + property: 'originalWidth', + propertyProps: { label: 'Original Width' }, + type: 'number', + }, + { + section: 'NineSlice Sprite', + property: 'originalHeight', + propertyProps: { label: 'Original Height' }, + type: 'number', + }, + ], +}; diff --git a/src/lib/src/detection/properties/PropertyPlugins.ts b/src/lib/src/detection/properties/PropertyPlugins.ts new file mode 100644 index 0000000..188924d --- /dev/null +++ b/src/lib/src/detection/properties/PropertyPlugins.ts @@ -0,0 +1,77 @@ +import { Container } from 'pixi.js'; +import { AnimatedSpritePropsPlugin } from './AnimatedSpritePropsPlugin'; +import { ContainerPropsPlugin } from './ContainerPropsPlugin'; +import { GraphicsPropsPlugin } from './GraphicsPropsPlugin'; +import { MeshPropsPlugin } from './MeshPropsPlugin'; +import { NineSliceSpritePropsPlugin } from './NineSlicePropsPlugin'; +import { SpritePropsPlugin } from './SpritePropsPlugin'; +import { TextPropsPlugin } from './TextPropsPlugin'; +import { TilingSpritePropsPlugin } from './TilingSpritePropsPlugin'; +import { PropertyPlugin } from './propertyTypes'; + +export class PropertyPluginsClass { + public defaultPlugins: PropertyPlugin[] = [ + ContainerPropsPlugin, + SpritePropsPlugin, + GraphicsPropsPlugin, + MeshPropsPlugin, + TextPropsPlugin, + NineSliceSpritePropsPlugin, + TilingSpritePropsPlugin, + AnimatedSpritePropsPlugin, + ]; + + public getCurrentValues(node: Container, panels: { keys: string[]; values: any }, out: Record) { + this._getCurrentValues(this.defaultPlugins, node, panels, out); + this._getCurrentValues(this._getUserPlugins(), node, panels, out); + } + + private _getCurrentValues( + plugins: PropertyPlugin[], + node: Container, + panels: { keys: string[]; values: any }, + values: Record, + ) { + plugins.forEach((plugin) => { + panels.keys.forEach((key) => { + const value = plugin.getPropValue(node, key); + + if (!value) return; + + values[value.prop] = value.value; + }); + }); + } + + public setValue(node: Container, property: string, value: any) { + this._setValue(this.defaultPlugins, node, property, value); + this._setValue(this._getUserPlugins(), node, property, value); + } + + private _setValue(plugins: PropertyPlugin[], node: Container, property: string, value: any) { + plugins.forEach((plugin) => { + plugin.setPropValue(node, property, value); + }); + } + + public createPropertyList(allPropertyKeys: Record, node: Container) { + this._createPropertyList(this.defaultPlugins, allPropertyKeys, node); + this._createPropertyList(this._getUserPlugins(), allPropertyKeys, node); + } + + private _createPropertyList(plugins: PropertyPlugin[], allPropertyKeys: Record, node: Container) { + plugins.forEach((plugin) => { + const pluginValues = plugin.getValidProps(node); + + pluginValues.forEach((key) => { + allPropertyKeys[key.property] = key; + }); + }); + } + + private _getUserPlugins() { + return window.__PIXI__DEVTOOLS__?.scenePanel?.propertyPlugins || []; + } +} + +export const PropertyPlugins = new PropertyPluginsClass(); diff --git a/src/lib/src/detection/properties/SpritePropsPlugin.ts b/src/lib/src/detection/properties/SpritePropsPlugin.ts index 33bcfe5..8a8622f 100644 --- a/src/lib/src/detection/properties/SpritePropsPlugin.ts +++ b/src/lib/src/detection/properties/SpritePropsPlugin.ts @@ -1,6 +1,7 @@ import { Sprite } from 'pixi.js'; import { ContainerPropsPlugin } from './ContainerPropsPlugin'; import { PropertyPlugin } from './propertyTypes'; +import { Vector2Props } from '@lib/src/components/properties/number/Vector2'; export const SpritePropsPlugin: PropertyPlugin = { ...ContainerPropsPlugin, @@ -17,8 +18,13 @@ export const SpritePropsPlugin: PropertyPlugin = { return { value: container[prop as keyof Sprite], prop }; }, props: [ - { section: 'Transform', property: 'anchor', propertyProps: { label: 'Anchor' }, type: 'vector2' }, + { + section: 'Transform', + property: 'anchor', + propertyProps: { label: 'Anchor', x: { label: 'x' }, y: { label: 'y' } } as Vector2Props, + type: 'vector2', + }, { section: 'Transform', property: 'roundPixels', propertyProps: { label: 'Round Pixels' }, type: 'boolean' }, - { section: 'Appearance', property: 'texture', propertyProps: { label: 'Texture' }, type: 'text' }, + // { section: 'Appearance', property: 'texture', propertyProps: { label: 'Texture' }, type: 'text' }, ], }; diff --git a/src/lib/src/detection/properties/TextPropsPlugin.ts b/src/lib/src/detection/properties/TextPropsPlugin.ts new file mode 100644 index 0000000..b40dec8 --- /dev/null +++ b/src/lib/src/detection/properties/TextPropsPlugin.ts @@ -0,0 +1,20 @@ +import { Text } from 'pixi.js'; +import { ContainerPropsPlugin } from './ContainerPropsPlugin'; +import { PropertyPlugin } from './propertyTypes'; + +export const TextPropsPlugin: PropertyPlugin = { + ...ContainerPropsPlugin, + getPropValue: function (container, prop) { + if (this.getPropKeys().indexOf(prop) === -1) { + return null; + } + + return { value: container[prop as keyof Text], prop }; + }, + props: [ + { section: 'Transform', property: 'roundPixels', propertyProps: { label: 'Round Pixels' }, type: 'boolean' }, + { section: 'Text', property: 'text', propertyProps: { label: 'Text' }, type: 'text' }, + // { section: 'Text', property: 'style', propertyProps: { label: 'Style' }, type: 'text' }, + { section: 'Text', property: 'resolution', propertyProps: { label: 'Resolution' }, type: 'number' }, + ], +}; diff --git a/src/lib/src/detection/properties/TilingSpritePropsPlugin.ts b/src/lib/src/detection/properties/TilingSpritePropsPlugin.ts new file mode 100644 index 0000000..75850f1 --- /dev/null +++ b/src/lib/src/detection/properties/TilingSpritePropsPlugin.ts @@ -0,0 +1,31 @@ +import { TilingSprite } from 'pixi.js'; +import { ContainerPropsPlugin } from './ContainerPropsPlugin'; +import { PropertyPlugin } from './propertyTypes'; +import { Vector2Props } from '@lib/src/components/properties/number/Vector2'; + +export const TilingSpritePropsPlugin: PropertyPlugin = { + ...ContainerPropsPlugin, + getPropValue: function (container, prop) { + if (this.getPropKeys().indexOf(prop) === -1) { + return null; + } + + return { value: container[prop as keyof TilingSprite], prop }; + }, + props: [ + { section: 'Transform', property: 'roundPixels', propertyProps: { label: 'Round Pixels' }, type: 'boolean' }, + { + section: 'Tiling Sprite', + property: 'tilePosition', + propertyProps: { label: 'Tile Position', x: { label: 'x' }, y: { label: 'y' } } as Vector2Props, + type: 'vector2', + }, + { + section: 'Tiling Sprite', + property: 'tileScale', + propertyProps: { label: 'Tile Scale', x: { label: 'x' }, y: { label: 'y' } } as Vector2Props, + type: 'vector2', + }, + { section: 'Tiling Sprite', property: 'tileRotation', propertyProps: { label: 'Tile Rotation' }, type: 'number' }, + ], +}; diff --git a/src/lib/src/detection/properties/properties.ts b/src/lib/src/detection/properties/properties.ts index 4265fab..e20bd88 100644 --- a/src/lib/src/detection/properties/properties.ts +++ b/src/lib/src/detection/properties/properties.ts @@ -1,8 +1,6 @@ import { getPixiWrapper } from '../devtool'; import { sceneGraphMap } from '../updateSceneGraph'; -import { ContainerPropsPlugin } from './ContainerPropsPlugin'; -import { SpritePropsPlugin } from './SpritePropsPlugin'; -import { PropertyPlugin } from './propertyTypes'; +import { PropertyPlugins } from './PropertyPlugins'; export const properties = () => { let selectedNodeId: string | null = null; @@ -41,21 +39,7 @@ export const properties = () => { this.setProperties(); - const plugins = [ContainerPropsPlugin, SpritePropsPlugin] as PropertyPlugin[]; - - plugins.forEach((plugin) => { - propertyPanel.keys.forEach((key) => { - const value = plugin.getPropValue(node.c, key); - - if (!value) return; - - if (currentValues[value.prop] !== undefined) { - console.warn('Duplicate property', value.prop); - } - - currentValues[value.prop] = value.value; - }); - }); + PropertyPlugins.getCurrentValues(node.c, propertyPanel, currentValues); state.selectedNodeValues = currentValues; state.selectedNodeId = node.u; @@ -64,19 +48,12 @@ export const properties = () => { setProperties: function () { propertyPanel.keys = []; propertyPanel.values = []; - const plugins = [ContainerPropsPlugin, SpritePropsPlugin] as PropertyPlugin[]; const node = this.idToNode(); // find all the common properties const allPropertyKeys = {} as Record; - plugins.forEach((plugin) => { - const pluginValues = plugin.getValidProps(node!.c); - - pluginValues.forEach((key) => { - allPropertyKeys[key.property] = key; - }); - }); + PropertyPlugins.createPropertyList(allPropertyKeys, node!.c); propertyPanel.keys = Object.keys(allPropertyKeys); propertyPanel.values = Object.values(allPropertyKeys); @@ -93,11 +70,7 @@ export const properties = () => { return; } - const plugins = [ContainerPropsPlugin, SpritePropsPlugin] as PropertyPlugin[]; - - plugins.forEach((plugin) => { - plugin.setPropValue(node.c, property, value); - }); + PropertyPlugins.setValue(node.c, property, value); // now need to update the state this.updateSelectedNodes(); diff --git a/src/lib/src/detection/updateSceneGraph.ts b/src/lib/src/detection/updateSceneGraph.ts index d4c1846..65e6ac6 100644 --- a/src/lib/src/detection/updateSceneGraph.ts +++ b/src/lib/src/detection/updateSceneGraph.ts @@ -6,10 +6,9 @@ const uidMap = new WeakMap(); // todo: maybe reset when disconnected let uid = 0; function getId(container: Container): string { - const existing = uidMap.get(container); - if(existing) { + if (existing) { return existing; } diff --git a/src/lib/src/detection/utils/getPixiType.ts b/src/lib/src/detection/utils/getPixiType.ts index 277c7c8..03dfa03 100644 --- a/src/lib/src/detection/utils/getPixiType.ts +++ b/src/lib/src/detection/utils/getPixiType.ts @@ -1,3 +1,4 @@ +import { Container } from 'pixi.js'; import { getPixiWrapper } from '../devtool'; export type PixiSceneObjectType = @@ -12,7 +13,7 @@ export type PixiSceneObjectType = | 'NineSliceSprite' | 'TilingSprite' | 'Unknown'; -export function getPixiType(container: any): PixiSceneObjectType { +export function getPixiType(container: Container): PixiSceneObjectType { const pixiWrapper = getPixiWrapper(); const pixi = pixiWrapper.pixi(); diff --git a/src/lib/src/detection/utils/loop.ts b/src/lib/src/detection/utils/loop.ts index 9684b99..9f88203 100644 --- a/src/lib/src/detection/utils/loop.ts +++ b/src/lib/src/detection/utils/loop.ts @@ -35,21 +35,21 @@ export class Looper { } } -// private _loopRecursive( -// container: Container, -// cb: (container: Container) => void, -// test: (container: Container) => boolean -// ) { -// if (!test(container)) { -// return; -// } - -// cb(container); - -// for (let i = 0; i < container.children.length; i++) { -// this._loopRecursive(container.children[i], cb, test); -// } -// } + // private _loopRecursive( + // container: Container, + // cb: (container: Container) => void, + // test: (container: Container) => boolean + // ) { + // if (!test(container)) { + // return; + // } + + // cb(container); + + // for (let i = 0; i < container.children.length; i++) { + // this._loopRecursive(container.children[i], cb, test); + // } + // } public static shared = new Looper(); } diff --git a/src/lib/src/detection/utils/poll.ts b/src/lib/src/detection/utils/poll.ts index 53f70a4..e9a1923 100644 --- a/src/lib/src/detection/utils/poll.ts +++ b/src/lib/src/detection/utils/poll.ts @@ -1,5 +1,5 @@ import { MessageType, convertPostMessage } from '@shared/index'; -import { detectPixi } from './utils'; +import { getPixiWrapper } from '../devtool'; let pixiPollingInterval: number | undefined; @@ -7,7 +7,7 @@ let pixiPollingInterval: number | undefined; export function pollPixi() { pixiPollingInterval = window.setInterval(() => { try { - const pixiDetectionResult = detectPixi(); + const pixiDetectionResult = getPixiWrapper().detectPixi(); if (pixiDetectionResult === MessageType.Active) { clearInterval(pixiPollingInterval); diff --git a/src/lib/src/detection/utils/utils.ts b/src/lib/src/detection/utils/utils.ts deleted file mode 100644 index 8f3b365..0000000 --- a/src/lib/src/detection/utils/utils.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { MessageType } from '@shared/index'; - -// Function to check if a global variable exists -export function hasGlobal(varname: string) { - return window[varname as keyof Window] || Array.from(window.frames).some((frame) => frame[varname as keyof Window]); -} - -// Function to detect PixiJS -export function detectPixi() { - return hasGlobal('__PIXI_APP__') || hasGlobal('__PIXI_STAGE__') || hasGlobal('__PIXI_RENDERER__') - ? MessageType.Active - : MessageType.Inactive; -} diff --git a/src/lib/src/pages/index.css b/src/lib/src/pages/index.css index 996b644..1525d3c 100644 --- a/src/lib/src/pages/index.css +++ b/src/lib/src/pages/index.css @@ -1,65 +1,35 @@ @import url('https://fonts.cdnfonts.com/css/cascadia-code'); :root { - --background-color: #1e1e1e; - --active-bg-color: #bf2257; - --inactive-disabled-bg-color: #292929; - --active-color: #fff; - --inactive-disabled-color: #6b6b6b; - --panel-background-color: #2d2d30; - --primary-color: #bf2257; - --dark-color: #3e3e3e; - --darker-color: #2d2d30; - --darkest-color: #1e1e1e; - --secondary-color: #1099bb; - } - - body { - font-family: Menlo, Monaco, 'Courier New', monospace; - background-color: var(--background-color); - margin: 0; - } - -.TabsList { - background: var(--background-color); - padding: 8px 8px 0; - width: calc(100% - 16px); - color: #aaa; - justify-content: space-between; - border-bottom: 2px solid #494c50; - border-radius: 4px 4px 0 0; - height: 26px; -} - -.TabsList .TabsTrigger { - cursor: pointer; - margin-right: 4px; - font-size: 16px; - border: none; - padding: 4px 16px; - text-align: center; - border-radius: 4px 4px 0 0; -} - -.TabsTrigger[data-state='active'] { - background-color: var(--active-bg-color); - color: var(--active-color); -} + --background-color: #1e1e1e; + --active-bg-color: #bf2257; + --inactive-disabled-bg-color: #292929; + --active-color: #fff; + --inactive-disabled-color: #6b6b6b; + --panel-background-color: #2d2d30; + --primary-color: #bf2257; + --dark-color: #3e3e3e; + --darker-color: #2d2d30; + --darkest-color: #1e1e1e; + --secondary-color: #1099bb; -.TabsTrigger[data-state='inactive'], -.TabsTrigger[data-disabled] { - background-color: var(--inactive-disabled-bg-color); - color: var(--inactive-disabled-color); + --header: #292a2e; + --bg: #1f2023; + --line: #3d3d3d; + --text: #c5c5c5; + --line-light: #8f8f8f; } -.TabsTrigger[data-state='inactive']:hover { - opacity: 0.7; +body { + font-family: Menlo, Monaco, 'Courier New', monospace; + background-color: var(--bg); + margin: 0; } -.TabsTrigger[data-disabled] { - cursor: not-allowed; +button { + all: unset; } -.TabsTrigger[data-disabled]:hover { - opacity: 1; +input { + all: unset; } diff --git a/src/lib/src/pages/index.tsx b/src/lib/src/pages/index.tsx index 56fe60c..eb36bbc 100644 --- a/src/lib/src/pages/index.tsx +++ b/src/lib/src/pages/index.tsx @@ -1,4 +1,4 @@ -import { Content, List, Root, Trigger } from '@radix-ui/react-tabs'; +// import { Content, List, Root, Trigger } from '@radix-ui/react-tabs'; import React, { useEffect } from 'react'; import { create } from 'zustand'; import { Container } from '../components/Container'; @@ -6,6 +6,7 @@ import { SceneComponent } from '../components/scene/SceneComponent'; import { PixiState } from '../detection/devtool'; import { checkDiff } from '../utils/checkDiff'; import './index.css'; +import { Content, List, Root, Trigger } from './styles'; type ZustandState = { pixiDetected: boolean; @@ -163,9 +164,11 @@ const Panel: React.FC = ({ bridge }) => { ))} ) : ( -
+

PixiJS not detected

-

Please install the PixiJS DevTools extension from the Chrome Web Store to use this panel.

+

+ This page doesn’t appear to be using PixiJS. If this seems wrong, follow the project setup guide{'https://github.com/pixijs/dev-tools#readme'} +

)} diff --git a/src/lib/src/pages/styles.tsx b/src/lib/src/pages/styles.tsx new file mode 100644 index 0000000..3ce6e6e --- /dev/null +++ b/src/lib/src/pages/styles.tsx @@ -0,0 +1,41 @@ +import styled from 'styled-components'; +import { Content as RTContent, List as RTList, Root as RTRoot, Trigger as RTTrigger } from '@radix-ui/react-tabs'; + +export const Root = styled(RTRoot)``; +export const List = styled(RTList)` + background-color: var(--header); + display: flex; + justify-content: space-evenly; +`; +export const Trigger = styled(RTTrigger)` + background-color: var(--header); + color: var(--text); + cursor: pointer; + font-size: 1.2em; + box-sizing: border-box; + width: 100%; + height: 30px; + text-align: center; + border: 1px solid #3d3d3d; + border-right-width: 1px; + border-bottom-width: 1px; + + &:hover { + border-bottom: 2px solid var(--primary-color); + } + + &[data-state='active'] { + border-bottom: 2px solid var(--primary-color); + font-weight: bold; + } + + &[data-state='inactive']:hover { + opacity: 0.7; + border-bottom: 1px solid var(--secondary-color); + } + + &[data-disabled] { + cursor: not-allowed; + } +`; +export const Content = styled(RTContent)``; diff --git a/src/lib/src/utils/Diff.ts b/src/lib/src/utils/Diff.ts index 3a0a17b..11b69e5 100644 --- a/src/lib/src/utils/Diff.ts +++ b/src/lib/src/utils/Diff.ts @@ -260,7 +260,7 @@ function buildValues( lastComponent: Component, newString: string[], oldString: string[], - useLongestToken: boolean + useLongestToken: boolean, ): Component[] { // First we convert our linked list of components in reverse order to an // array in the right order: diff --git a/src/lib/src/utils/DiffChar.ts b/src/lib/src/utils/DiffChar.ts index 89fd9b2..32ba46e 100644 --- a/src/lib/src/utils/DiffChar.ts +++ b/src/lib/src/utils/DiffChar.ts @@ -1,4 +1,6 @@ import Diff, { DiffOptions } from './Diff'; export const characterDiff = new Diff(); -export function diffChars(oldStr: string, newStr: string, options?: DiffOptions) { return characterDiff.diff(oldStr, newStr, options); } \ No newline at end of file +export function diffChars(oldStr: string, newStr: string, options?: DiffOptions) { + return characterDiff.diff(oldStr, newStr, options); +} diff --git a/src/lib/src/utils/checkDiff.ts b/src/lib/src/utils/checkDiff.ts index 77fc0be..78bb22f 100644 --- a/src/lib/src/utils/checkDiff.ts +++ b/src/lib/src/utils/checkDiff.ts @@ -1,4 +1,3 @@ - /** * Check if two values are different * @param a - First value diff --git a/tsconfig.json b/tsconfig.json index 0d2a4c9..7d227f9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,27 +1,26 @@ { - "compilerOptions": { - "target": "esnext", - "types": ["vite/client", "node", "chrome"], - "lib": ["dom", "dom.iterable", "esnext"], - "allowJs": false, - "skipLibCheck": true, - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "noFallthroughCasesInSwitch": true, - "module": "esnext", - "moduleResolution": "node", - "resolveJsonModule": true, - "noEmit": true, - "jsx": "react-jsx", - "baseUrl": ".", - "paths": { - "@chrome/*": ["src/chrome/*"], - "@lib/*": ["src/lib/*"], - "@shared/*": ["src/shared/*"], - } - }, - "include": ["src", "global.d.ts"], - } - \ No newline at end of file + "compilerOptions": { + "target": "esnext", + "types": ["vite/client", "node", "chrome"], + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": false, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "noEmit": true, + "jsx": "react-jsx", + "baseUrl": ".", + "paths": { + "@chrome/*": ["src/chrome/*"], + "@lib/*": ["src/lib/*"], + "@shared/*": ["src/shared/*"] + } + }, + "include": ["src", "global.d.ts"] +} diff --git a/vite.inject.config.ts b/vite.inject.config.ts index 9d983b7..c36631d 100644 --- a/vite.inject.config.ts +++ b/vite.inject.config.ts @@ -16,13 +16,13 @@ export default defineConfig({ name: 'wrap-in-iife', generateBundle(outputOptions, bundle) { Object.keys(bundle).forEach((fileName) => { - const file = bundle[fileName] + const file = bundle[fileName]; if (fileName.slice(-3) === '.js' && 'code' in file) { - file.code = `(() => {\n${file.code}})()` + file.code = `(() => {\n${file.code}})()`; } - }) - } - } + }); + }, + }, ], build: { lib: {