├── .eslintrc ├── .github └── workflows │ ├── php-unit.yml │ ├── test-js.yml │ └── wordpress.yml ├── .gitignore ├── .prettierrc ├── .svnignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── admin └── three-object-viewer-settings │ ├── App.js │ ├── App.test.js.test │ ├── index.js │ └── init.php ├── assets └── wporg │ ├── banner-1544x500.jpg │ ├── banner-772x250.jpg │ ├── icon-128x128.png │ └── icon-256x256.png ├── babel.config.js ├── bin └── install-wp-tests.sh ├── blocks ├── environment │ ├── Edit.js │ ├── Edit.test.js │ ├── Save.js │ ├── block.json │ ├── components │ │ ├── ContextBridgeComponent.js │ │ ├── Controls.js │ │ ├── EditControls.js │ │ ├── EditorPluginProvider.js │ │ ├── EnvironmentFront.js │ │ ├── FrontPluginProvider.js │ │ ├── Networking.js │ │ ├── Player.js │ │ ├── TeleportTravel.js │ │ ├── ThreeObjectEdit.js │ │ ├── core │ │ │ └── front │ │ │ │ ├── ModelObject.js │ │ │ │ ├── NPCObject.js │ │ │ │ ├── Portal.js │ │ │ │ ├── TextObject.js │ │ │ │ ├── ThreeAudio.js │ │ │ │ ├── ThreeImage.js │ │ │ │ ├── ThreeLight.js │ │ │ │ ├── ThreeSky.js │ │ │ │ └── ThreeVideo.js │ │ └── p2pcf │ │ │ └── p2pcf.js │ ├── editor.scss │ ├── frontend.js │ ├── index.js │ ├── init.php │ └── style.scss ├── model-block │ ├── Edit.js │ ├── Edit.test.js │ ├── Save.js │ ├── block.json │ ├── components │ │ └── ModelEdit.js │ ├── editor.scss │ ├── index.js │ ├── init.php │ └── style.scss ├── npc-block │ ├── Edit.js │ ├── Edit.test.js │ ├── Save.js │ ├── block.json │ ├── components │ │ └── ModelEdit.js │ ├── editor.scss │ ├── index.js │ ├── init.php │ └── style.scss ├── sky-block │ ├── Edit.js │ ├── Edit.test.js │ ├── Save.js │ ├── block.json │ ├── editor.scss │ ├── index.js │ ├── init.php │ └── style.scss ├── spawn-point-block │ ├── Edit.js │ ├── Edit.test.js │ ├── Save.js │ ├── block.json │ ├── editor.scss │ ├── index.js │ ├── init.php │ └── style.scss ├── three-audio-block │ ├── Edit.js │ ├── Edit.test.js │ ├── Save.js │ ├── block.json │ ├── editor.scss │ ├── index.js │ ├── init.php │ └── style.scss ├── three-image-block │ ├── Edit.js │ ├── Edit.test.js │ ├── Save.js │ ├── block.json │ ├── editor.scss │ ├── index.js │ ├── init.php │ └── style.scss ├── three-light-block │ ├── Edit.js │ ├── Edit.test.js │ ├── Save.js │ ├── block.json │ ├── editor.scss │ ├── index.js │ ├── init.php │ └── style.scss ├── three-object-block │ ├── Edit.js │ ├── Save.js │ ├── block.json │ ├── components │ │ ├── TeleportTravel.js │ │ ├── ThreeObjectEdit.js │ │ └── ThreeObjectFront.js │ ├── editor.scss │ ├── frontend.js │ ├── index.js │ ├── init.php │ └── style.scss ├── three-portal-block │ ├── Edit.js │ ├── Edit.test.js │ ├── Save.js │ ├── block.json │ ├── editor.scss │ ├── index.js │ ├── init.php │ └── style.scss ├── three-text-block │ ├── Edit.js │ ├── Edit.test.js │ ├── Save.js │ ├── block.json │ ├── editor.scss │ ├── index.js │ ├── init.php │ └── style.scss └── three-video-block │ ├── Edit.js │ ├── Edit.test.js │ ├── Save.js │ ├── block.json │ ├── editor.scss │ ├── index.js │ ├── init.php │ └── style.scss ├── composer.json ├── composer.lock ├── docker-compose.yml ├── inc ├── assets │ ├── audio_icon.png │ ├── default_grid.glb │ └── light_icon.png ├── avatars │ ├── 3ov_default_avatar.vrm │ ├── Idle.fbx │ ├── Running.fbx │ ├── friendly.fbx │ ├── talking.fbx │ └── walking.fbx ├── fonts │ └── roboto.woff ├── functions.php ├── hooks.php ├── threeobjectloaderinit │ ├── index.css │ └── index.js └── utils │ └── draco │ └── draco_decoder.js ├── languages ├── three-object-viewer-es_MX-three-object-viewer-environment-editor-script.json ├── three-object-viewer-es_MX-three-object-viewer-model-block-editor-script.json ├── three-object-viewer-es_MX-three-object-viewer-npc-block-editor-script.json ├── three-object-viewer-es_MX-three-object-viewer-settings.json ├── three-object-viewer-es_MX-three-object-viewer-sky-block-editor-script.json ├── three-object-viewer-es_MX-three-object-viewer-spawn-point-block-editor-script.json ├── three-object-viewer-es_MX-three-object-viewer-three-audio-block-editor-script.json ├── three-object-viewer-es_MX-three-object-viewer-three-image-block-editor-script.json ├── three-object-viewer-es_MX-three-object-viewer-three-light-block-editor-script.json ├── three-object-viewer-es_MX-three-object-viewer-three-portal-block-editor-script.json ├── three-object-viewer-es_MX-three-object-viewer-three-text-block-editor-script.json ├── three-object-viewer-es_MX-three-object-viewer-three-video-block-editor-script.json ├── three-object-viewer-es_MX.mo ├── three-object-viewer-es_MX.po ├── three-object-viewer-ja-three-object-viewer-environment-editor-script.json ├── three-object-viewer-ja-three-object-viewer-model-block-editor-script.json ├── three-object-viewer-ja-three-object-viewer-npc-block-editor-script.json ├── three-object-viewer-ja-three-object-viewer-settings.json ├── three-object-viewer-ja-three-object-viewer-sky-block-editor-script.json ├── three-object-viewer-ja-three-object-viewer-spawn-point-block-editor-script.json ├── three-object-viewer-ja-three-object-viewer-three-audio-block-editor-script.json ├── three-object-viewer-ja-three-object-viewer-three-image-block-editor-script.json ├── three-object-viewer-ja-three-object-viewer-three-light-block-editor-script.json ├── three-object-viewer-ja-three-object-viewer-three-portal-block-editor-script.json ├── three-object-viewer-ja-three-object-viewer-three-text-block-editor-script.json ├── three-object-viewer-ja-three-object-viewer-three-video-block-editor-script.json ├── three-object-viewer-ja.mo ├── three-object-viewer-ja.po ├── three-object-viewer.mo ├── three-object-viewer.po └── three-object-viewer.pot ├── package.js ├── package.json ├── php └── Plugin.php ├── phpcs.xml.dist ├── phpunit-integration.xml ├── phpunit-unit.xml ├── plugin-build ├── .gitignore └── .gitkeep ├── pluginMachine.json ├── readme.txt ├── rename.js ├── tests ├── Integration │ └── EnvironmentTest.php ├── Unit │ ├── EnvironmentTest.php │ └── TestCase.php └── bootstrap.php ├── three-object-viewer.php ├── webpack.config.js └── yarn.lock /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ "plugin:@wordpress/eslint-plugin/recommended" ] 3 | } 4 | 5 | -------------------------------------------------------------------------------- /.github/workflows/php-unit.yml: -------------------------------------------------------------------------------- 1 | name: PHP Unit Tests 2 | 3 | on: push 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | php-versions: [7.2, 7.3, 7.4] 11 | 12 | steps: 13 | - uses: actions/checkout@v2 14 | - name: Setup PHP 15 | uses: shivammathur/setup-php@v2 16 | with: 17 | php-version: ${{ matrix.php-versions }} 18 | # Install composer with cache 19 | - name: Get Composer Cache Directory 20 | id: get-composer-cache-dir # Instead of composer-cache 21 | run: | 22 | echo "::set-output name=dir::$(composer config cache-files-dir)" 23 | 24 | - name: Cache Composer packages 25 | id: composer-cache 26 | uses: actions/cache@v2 27 | with: 28 | path: vendor 29 | key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} 30 | restore-keys: | 31 | ${{ runner.os }}-php- 32 | 33 | - name: Install dependencies 34 | if: steps.composer-cache.outputs.cache-hit != 'true' 35 | run: composer install --prefer-dist --no-progress 36 | 37 | # Run unit tests 38 | - name: Unit Tests 39 | run: composer test:unit 40 | -------------------------------------------------------------------------------- /.github/workflows/test-js.yml: -------------------------------------------------------------------------------- 1 | name: JavaScripts 2 | 3 | on: [push] 4 | 5 | jobs: 6 | buildAndTest: 7 | name: Test 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v1 11 | - uses: actions/setup-node@v1 12 | with: 13 | node-version: '12' 14 | - name: Install dependencies 15 | run: yarn 16 | - name: Test 17 | run: yarn test --ci 18 | -------------------------------------------------------------------------------- /.github/workflows/wordpress.yml: -------------------------------------------------------------------------------- 1 | name: WordPress Tests 2 | 3 | on: [push] 4 | 5 | env: 6 | WP_TESTS_DIR: /github/home/wp-tests/wordpress-tests-lib 7 | WP_CORE_DIR: /github/home/wp-tests/wordpress 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | php-version: [7.2, 7.3, 7.4] 15 | wordpress-version: [latest] 16 | container: 17 | image: junaidbhura/wp-tests:php-${{ matrix.php-version }} 18 | services: 19 | mysql: 20 | image: mysql:5.7.27 21 | options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 22 | env: 23 | MYSQL_ROOT_PASSWORD: root 24 | 25 | steps: 26 | # Setup 27 | - name: Checkout repository 28 | uses: actions/checkout@v1 29 | 30 | ## Install 31 | - name: Get Composer Cache Directory 32 | id: get-composer-cache-dir # Instead of composer-cache 33 | run: | 34 | echo "::set-output name=dir::$(composer config cache-files-dir)" 35 | 36 | - name: Cache Composer packages 37 | id: composer-cache 38 | uses: actions/cache@v2 39 | with: 40 | path: vendor 41 | key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} 42 | restore-keys: | 43 | ${{ runner.os }}-php- 44 | 45 | - name: Install dependencies 46 | if: steps.composer-cache.outputs.cache-hit != 'true' 47 | run: composer install --prefer-dist --no-progress 48 | 49 | ## Install test suite 50 | - name: Install WordPress test suite 51 | run: bash bin/install-wp-tests.sh wordpress_test root root mysql ${{ matrix.wordpress-version }} 52 | 53 | ## Run integration tests 54 | - name: Tests 55 | run: composer test:wordpress 56 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Logs 3 | logs 4 | *.log 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | lerna-debug.log* 9 | .DS_Store 10 | 11 | # Diagnostic reports (https://nodejs.org/api/report.html) 12 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 13 | 14 | # Dependency directories 15 | node_modules/ 16 | vendor/ 17 | 18 | # dotenv environment variables file 19 | .env 20 | .env.test 21 | 22 | vendor 23 | wordpress 24 | build 25 | pro/ 26 | .phpunit.result.cache 27 | plugin-build/pro/three-object-viewer/* 28 | !plugin-build/pro/three-object-viewer/.gitkeep 29 | plugin-build/free/three-object-viewer/* 30 | !plugin-build/free/three-object-viewer/.gitkeep 31 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "singleQuote": false, 4 | "trailingComma": "none" 5 | } 6 | 7 | -------------------------------------------------------------------------------- /.svnignore: -------------------------------------------------------------------------------- 1 | .git 2 | .github 3 | .gitignore 4 | .gitattributes 5 | .DS_Store 6 | .babelrc 7 | .cache 8 | .codeclimate* 9 | .idea 10 | .parcel-cache 11 | .eslintignore 12 | .eslintrc.json 13 | .eslintrc.js 14 | .circleci 15 | .sass-cache 16 | .editorconfig 17 | .husky 18 | .env.testing* 19 | .prettier* 20 | .husky 21 | .phpcs.xml.dist 22 | .svnignore 23 | .zipignore 24 | .docker 25 | docs 26 | node_modules 27 | scripts 28 | tests 29 | *phpunit* 30 | *bin* 31 | *config* 32 | *tests* 33 | *composer.json* 34 | *composer.lock* 35 | webpack.config.js 36 | pluginMachine.json 37 | babel.config.json 38 | phpunit-unit.xml 39 | phpunit-integration.xml 40 | phpcs.xml.dist 41 | yarn.lock 42 | yarn-error.log 43 | jest.config.js 44 | package.json 45 | package-lock.json 46 | docker-compose.yml 47 | docker-composer-phpunit.yml 48 | Makefile 49 | *vendor* -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.defaultFormatter": "rvest.vs-code-prettier-eslint", 3 | "editor.codeActionsOnSave": { 4 | "source.fixAll": true 5 | }, 6 | "[javascript]": { 7 | "editor.defaultFormatter": "vscode.typescript-language-features" 8 | }, 9 | } 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Three Object Viewer 2 | 3 | [![Built With Plugin Machine](https://img.shields.io/badge/Built%20With-Plugin%20Machine-lightgrey)](https://pluginmachine.com) 4 | 5 | ## Installation 6 | 7 | Download the from WordPress or install by searching for "Three Object Viewer" in the plugin repository in wp-admin. 8 | https://wordpress.org/plugins/three-object-viewer/ 9 | 10 | 11 | ## Local Development 12 | 13 | - Git clone: 14 | - `git clone git@github.com:antpb/three-object-viewer.git` 15 | - Install javascript dependencies 16 | - `yarn` 17 | - Install php dependencies 18 | - `composer install` 19 | 20 | ## Working With JavaScript 21 | 22 | - Build JS/CSS 23 | - `yarn build` 24 | - Start JS/CSS for development - currently broken 25 | - `yarn start` 26 | - Test changed files - pending tests 27 | - `yarn test --watch` 28 | - Test all files once 29 | - `yarn test` 30 | - `yarn test --ci` 31 | 32 | 33 | ## Working With PHP 34 | 35 | ### Autoloader 36 | 37 | PHP classes should be located in the "php" directory and follow the [PSR-4 standard](https://www.php-fig.org/psr/psr-4/). 38 | 39 | The root namespace is `threeObjectViewer`. 40 | 41 | 42 | 43 | ### Tests 44 | - Run unit tests 45 | - `composer test:unit` 46 | - Run WordPress tests 47 | - `composer test:wordpress` 48 | - See local development instructions for how to run with Docker. 49 | - Run unit tests and WordPress tests 50 | - `composer test` 51 | 52 | ### Linter 53 | 54 | [PHPCS](https://github.com/squizlabs/PHP_CodeSniffer) is installed for linting and [automatic code fixing](https://github.com/squizlabs/PHP_CodeSniffer/wiki/Fixing-Errors-Automatically). 55 | 56 | - Run linter and autofix 57 | - `composer fixes` 58 | - Run linter to identify issues. 59 | - `compose sniffs` 60 | 61 | ## Local Development Environment 62 | 63 | A [docker-compose](https://docs.docker.com/samples/wordpress/)-based local development environment is provided. 64 | 65 | - Start server 66 | - `docker-compose up -d` 67 | - Acess Site 68 | - [http://localhost:6039](http://localhost:6039) 69 | - WP CLI 70 | - Run any WP CLI command in container: 71 | - `docker-compose run wpcli wp ...` 72 | - Setup site with WP CLI 73 | - `docker-compose run wpcli wp core install --url=http://localhost:6039 --title="Three Object Viewer" --admin_user=admin0 --admin_email=something@example.com` 74 | - `docker-compose run wpcli wp user create admin admin@example.com --role=administrator --user_pass=pass` 75 | 76 | 77 | There is a special phpunit container for running WordPress tests, with WordPress and MySQL configured. 78 | 79 | - Enter container 80 | - `docker-compose run phpunit` 81 | - Composer install 82 | - `composer install` 83 | - Test 84 | - `composer test:wordpress` 85 | 86 | -------------------------------------------------------------------------------- /admin/three-object-viewer-settings/App.test.js.test: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import App from "./App"; 4 | it("renders without crashing", () => { 5 | const div = document.createElement("div"); 6 | ReactDOM.render(, div); 7 | ReactDOM.unmountComponentAtNode(div); 8 | }); 9 | -------------------------------------------------------------------------------- /admin/three-object-viewer-settings/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@wordpress/element'; 3 | import App from './App'; 4 | import apiFetch from '@wordpress/api-fetch'; 5 | 6 | window.addEventListener( 'load', async function () { 7 | //Endpoint URL 8 | const path = '/three-object-viewer/v1/three-object-viewer-settings/'; 9 | 10 | //Get settings from the REST API endpoint 11 | const getSettings = async () => { 12 | let data = await apiFetch( { 13 | path, 14 | method: 'GET', 15 | } ); 16 | return data; 17 | }; 18 | 19 | //Update settings via the REST API endpoint 20 | const updateSettings = async ( data ) => { 21 | let updatedData = apiFetch( { 22 | path, 23 | data, 24 | method: 'POST', 25 | } ); 26 | return updatedData; 27 | }; 28 | 29 | render( 30 | , 31 | document.getElementById( 'three-object-viewer-settings' ) 32 | ); 33 | } ); 34 | -------------------------------------------------------------------------------- /assets/wporg/banner-1544x500.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antpb/three-object-viewer/907f8d6fdf847141c95cec302d153f0decd9428b/assets/wporg/banner-1544x500.jpg -------------------------------------------------------------------------------- /assets/wporg/banner-772x250.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antpb/three-object-viewer/907f8d6fdf847141c95cec302d153f0decd9428b/assets/wporg/banner-772x250.jpg -------------------------------------------------------------------------------- /assets/wporg/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antpb/three-object-viewer/907f8d6fdf847141c95cec302d153f0decd9428b/assets/wporg/icon-128x128.png -------------------------------------------------------------------------------- /assets/wporg/icon-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antpb/three-object-viewer/907f8d6fdf847141c95cec302d153f0decd9428b/assets/wporg/icon-256x256.png -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.cache(true); 3 | const presets = ["@babel/preset-env", "@babel/preset-react"]; 4 | const plugins = ["@babel/plugin-proposal-optional-chaining"]; 5 | return { presets, plugins }; 6 | }; 7 | -------------------------------------------------------------------------------- /blocks/environment/Edit.test.js: -------------------------------------------------------------------------------- 1 | //Import React 2 | import React from "react"; 3 | //Import test renderer 4 | import { render, fireEvent, cleanup } from "@testing-library/react"; 5 | //Import component to test 6 | import { Editor } from "./Edit"; 7 | 8 | describe("Editor componet", () => { 9 | afterEach(cleanup); 10 | it("matches snapshot when selected", () => { 11 | const onChange = jest.fn(); 12 | const { container } = render( 13 | 14 | ); 15 | expect(container).toMatchSnapshot(); 16 | }); 17 | 18 | it("matches snapshot when not selected", () => { 19 | const onChange = jest.fn(); 20 | const { container } = render( 21 | 22 | ); 23 | expect(container).toMatchSnapshot(); 24 | }); 25 | 26 | it("Calls the onchange function", () => { 27 | const onChange = jest.fn(); 28 | const { getByDisplayValue } = render( 29 | 30 | ); 31 | fireEvent.change(getByDisplayValue("Salad"), { 32 | target: { value: "New Value" } 33 | }); 34 | expect(onChange).toHaveBeenCalledTimes(1); 35 | }); 36 | 37 | it("Passes updated value, not event to onChange callback", () => { 38 | const onChange = jest.fn(); 39 | const { getByDisplayValue } = render( 40 | 41 | ); 42 | fireEvent.change(getByDisplayValue("Seltzer"), { 43 | target: { value: "Boring Water" } 44 | }); 45 | expect(onChange).toHaveBeenCalledWith("Boring Water"); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /blocks/environment/Save.js: -------------------------------------------------------------------------------- 1 | import { __ } from "@wordpress/i18n"; 2 | import { useBlockProps, InnerBlocks } from "@wordpress/block-editor"; 3 | 4 | export default function save({ attributes }) { 5 | return ( 6 |
7 | <> 8 |
9 |

10 | {attributes.deviceTarget} 11 |

12 |

13 | {attributes.threeObjectUrl} 14 |

15 |

16 | {attributes.hdr} 17 |

18 |

{attributes.scale}

19 |

20 | {attributes.bg_color} 21 |

22 |

{attributes.zoom}

23 |

24 | {attributes.hasZoom ? 1 : 0} 25 |

26 |

27 | {attributes.hasTip ? 1 : 0} 28 |

29 |

30 | {attributes.positionY} 31 |

32 |

33 | {attributes.rotationY} 34 |

35 |

{attributes.scale}

36 |

37 | {attributes.threePreviewImage} 38 |

39 |

40 | {attributes.animations} 41 |

42 | 43 |
44 | 45 |
46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /blocks/environment/block.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "three-object-viewer/environment", 3 | "attributes": { 4 | "align": { 5 | "type": "string", 6 | "default": "full" 7 | }, 8 | "scale": { 9 | "type": "integer", 10 | "default": 1 11 | }, 12 | "positionX": { 13 | "type": "integer", 14 | "default": 0 15 | }, 16 | "positionY": { 17 | "type": "integer", 18 | "default": 0 19 | }, 20 | "rotationY": { 21 | "type": "integer", 22 | "default": 0 23 | }, 24 | "threeObjectUrl": { 25 | "type": "string", 26 | "default": null 27 | }, 28 | "threePreviewImage": { 29 | "type": "string", 30 | "default": null 31 | }, 32 | "hdr": { 33 | "type": "string", 34 | "default": null 35 | }, 36 | "deviceTarget": { 37 | "type": "string", 38 | "default": "vr" 39 | }, 40 | "animations": { 41 | "type": "string", 42 | "default": "" 43 | } 44 | }, 45 | "category": "design", 46 | "apiVersion": 2, 47 | "supports": { 48 | "html": false, 49 | "multiple": false, 50 | "hasOverlay": false, 51 | "align": ["full"] 52 | }, 53 | "textdomain": "three-object-viewer", 54 | "editorScript": "file:../../build/block-environment.js", 55 | "editorStyle": "file:../../build/block-environment.css", 56 | "style": "file:../../build/block-environment.css" 57 | } 58 | -------------------------------------------------------------------------------- /blocks/environment/components/ContextBridgeComponent.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { useFrontPlugins, FrontPluginContext } from './FrontPluginProvider'; 3 | import { useContextBridge } from "@react-three/drei"; 4 | 5 | //import contextBridgef 6 | // add function for context 7 | export function ContextBridgeComponent(props) { 8 | const { plugins } = useFrontPlugins(); // From your own context 9 | const [registeredThreeovBlocks, setRegisteredThreeovBlocks] = useState([]); 10 | const ContextBridge = useContextBridge(FrontPluginContext); 11 | 12 | useEffect(() => { 13 | if (plugins.length > 0) { 14 | plugins.forEach((plugin) => { 15 | // add the plugin to the registered blocks 16 | setRegisteredThreeovBlocks((registeredThreeovBlocks) => [ 17 | ...registeredThreeovBlocks, 18 | plugin, 19 | ]); 20 | }); 21 | } 22 | }, [plugins]); 23 | 24 | return ( 25 | 26 | { 27 | registeredThreeovBlocks.length > 0 && registeredThreeovBlocks.map((blockElement, index) => { 28 | const BlockComponent = blockElement.type; 29 | return ( 30 | 36 | 37 | 38 | ) 39 | }) 40 | } 41 | 42 | ) 43 | 44 | } 45 | -------------------------------------------------------------------------------- /blocks/environment/components/Controls.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react'; 2 | 3 | export function useKeyboardControls() { 4 | const movement = useRef({ 5 | forward: false, 6 | backward: false, 7 | left: false, 8 | right: false, 9 | shift: false, 10 | space: false 11 | }); 12 | 13 | useEffect(() => { 14 | const handleKeyDown = (e) => { 15 | let element = e.target; 16 | // if the element is an input, dont move 17 | if (element.tagName === 'INPUT' || element.tagName === 'TEXTAREA') return; 18 | if (e.key === 'w' || e.key === 'W' && ! movement.current.forward) movement.current.forward = true; 19 | else if (e.key === 's' || e.key === 'S' && ! movement.current.backward) movement.current.backward = true; 20 | else if (e.key === 'a' || e.key === 'A' && ! movement.current.left) movement.current.left = true; 21 | else if (e.key === 'd' || e.key === 'D' && ! movement.current.right) movement.current.right = true; 22 | else if (e.key === 'space') movement.current.space = true; 23 | else if (e.key === 'Shift') movement.current.shift = true; 24 | else if (e.key === 'r' || e.key === 'R'){ 25 | if (e.metaKey || e.ctrlKey){ 26 | movement.current.respawn = false; 27 | } else { 28 | movement.current.respawn = true; 29 | } 30 | 31 | } 32 | 33 | } 34 | 35 | const handleKeyUp = (e) => { 36 | if (e.key === 'w' || e.key === 'W') movement.current.forward = false; 37 | else if (e.key === 's' || e.key === 'S') movement.current.backward = false; 38 | else if (e.key === 'a' || e.key === 'A') movement.current.left = false; 39 | else if (e.key === 'd' || e.key === 'D') movement.current.right = false; 40 | else if (e.key === 'space') movement.current.space = false; 41 | else if (e.key === 'Shift') movement.current.shift = false; 42 | else if (e.key === 'r' || e.key === 'R') movement.current.respawn = false; 43 | } 44 | 45 | window.addEventListener('keydown', handleKeyDown); 46 | window.addEventListener('keyup', handleKeyUp); 47 | 48 | return () => { 49 | window.removeEventListener('keydown', handleKeyDown); 50 | window.removeEventListener('keyup', handleKeyUp); 51 | } 52 | }, []); 53 | 54 | return movement; 55 | } 56 | -------------------------------------------------------------------------------- /blocks/environment/components/EditControls.js: -------------------------------------------------------------------------------- 1 | import React, { useRef, useState } from "react"; 2 | 3 | import { useFrame, useThree } from "@react-three/fiber"; 4 | import { PointerLockControls, OrbitControls } from "@react-three/drei"; 5 | 6 | const EditControls = (props) => { 7 | const controlsRef = useRef(); 8 | const isLocked = useRef(false); 9 | const [moveForward, setMoveForward] = useState(false); 10 | const [moveBackward, setMoveBackward] = useState(false); 11 | const [moveLeft, setMoveLeft] = useState(false); 12 | const [moveRight, setMoveRight] = useState(false); 13 | const [jump, setJump] = useState(false); 14 | 15 | useFrame(() => { 16 | const velocity = 0.5; 17 | 18 | if (moveForward) { 19 | // playerThing.applyImpulse({x:0, y:0, z:0.1}, true); 20 | controlsRef.current.moveForward(velocity); 21 | } else if (moveLeft) { 22 | controlsRef.current.moveRight(-velocity); 23 | } else if (moveBackward) { 24 | controlsRef.current.moveForward(-velocity); 25 | } else if (moveRight) { 26 | controlsRef.current.moveRight(velocity); 27 | } else if (jump) { 28 | } 29 | }); 30 | 31 | const onKeyDown = function (event, props) { 32 | switch (event.code) { 33 | case "ArrowUp": 34 | case "KeyW": 35 | setMoveForward(true); 36 | break; 37 | 38 | case "ArrowLeft": 39 | case "KeyA": 40 | setMoveLeft(true); 41 | break; 42 | 43 | case "ArrowDown": 44 | case "KeyS": 45 | setMoveBackward(true); 46 | break; 47 | 48 | case "ArrowRight": 49 | case "KeyD": 50 | setMoveRight(true); 51 | break; 52 | case "Space": 53 | window.addEventListener("keydown", (e) => { 54 | if (e.keyCode === 32 && e.target === document.body) { 55 | e.preventDefault(); 56 | } 57 | }); 58 | setJump(true); 59 | break; 60 | default: 61 | } 62 | }; 63 | 64 | const onKeyUp = function (event) { 65 | switch (event.code) { 66 | case "ArrowUp": 67 | case "KeyW": 68 | setMoveForward(false); 69 | break; 70 | 71 | case "ArrowLeft": 72 | case "KeyA": 73 | setMoveLeft(false); 74 | break; 75 | 76 | case "ArrowDown": 77 | case "KeyS": 78 | setMoveBackward(false); 79 | break; 80 | 81 | case "Space": 82 | setJump(false); 83 | break; 84 | 85 | case "ArrowRight": 86 | case "KeyD": 87 | setMoveRight(false); 88 | break; 89 | 90 | default: 91 | } 92 | }; 93 | 94 | document.addEventListener("keydown", onKeyDown); 95 | document.addEventListener("keyup", onKeyUp); 96 | const { gl } = useThree(); 97 | if (gl) { 98 | return ( 99 | { 102 | if (controlsRef.current) { 103 | renderer.addEventListener("lock", () => { 104 | console.log("lock"); 105 | isLocked.current = true; 106 | }); 107 | controlsRef.current.addEventListener("unlock", () => { 108 | console.log("unlock"); 109 | isLocked.current = false; 110 | }); 111 | } 112 | }} 113 | ref={controlsRef} 114 | /> 115 | ); 116 | } 117 | }; 118 | 119 | export default EditControls; 120 | -------------------------------------------------------------------------------- /blocks/environment/components/EditorPluginProvider.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useContext, useEffect, useCallback } from "react"; 2 | 3 | export const EditorPluginContext = React.createContext(); 4 | 5 | export function EditorPluginProvider({ children }) { 6 | 7 | const [plugins, setPlugins] = useState([]); 8 | 9 | const registerEditorPlugin = useCallback((plugin) => { 10 | setPlugins(prevPlugins => [...prevPlugins, plugin]); 11 | }, []); 12 | 13 | useEffect(() => { 14 | // Expose the registerPlugin method globally 15 | window.registerEditorPlugin = registerEditorPlugin; 16 | window.dispatchEvent(new Event('registerEditorPluginReady')); 17 | 18 | return () => { 19 | // Cleanup 20 | window.registerEditorPlugin = null; 21 | }; 22 | }, [registerEditorPlugin]); 23 | 24 | return ( 25 | 26 | {children} 27 | 28 | ); 29 | } 30 | 31 | export const useEditorPlugins = () => useContext(EditorPluginContext); 32 | -------------------------------------------------------------------------------- /blocks/environment/components/FrontPluginProvider.js: -------------------------------------------------------------------------------- 1 | import React, { createContext, useContext, useState, useEffect, useCallback } from "react"; 2 | import { useThree } from '@react-three/fiber'; 3 | 4 | export const FrontPluginContext = React.createContext(); 5 | 6 | export function FrontPluginProvider({ children }) { 7 | 8 | const [plugins, setPlugins] = useState([]); 9 | const { scene, camera } = useThree(); 10 | 11 | const registerFrontPlugin = useCallback((plugin) => { 12 | setPlugins(prevPlugins => [...prevPlugins, plugin]); 13 | }, []); 14 | 15 | useEffect(() => { 16 | // Expose the registerPlugin method globally 17 | window.registerFrontPlugin = registerFrontPlugin; 18 | window.dispatchEvent(new Event('registerFrontPluginReady')); 19 | 20 | return () => { 21 | // Cleanup 22 | window.registerFrontPlugin = null; 23 | }; 24 | }, [registerFrontPlugin]); 25 | 26 | return ( 27 | 28 | {children} 29 | 30 | ); 31 | } 32 | 33 | export const useFrontPlugins = () => useContext(FrontPluginContext); 34 | -------------------------------------------------------------------------------- /blocks/environment/components/Networking.js: -------------------------------------------------------------------------------- 1 | import P2PCF from "./p2pcf/p2pcf.js"; 2 | 3 | const Networking = (props) => { 4 | if (!document.location.hash) { 5 | document.location = 6 | document.location.toString() + `#xpp-${props.postSlug}`; 7 | } 8 | 9 | const userProfileName = 10 | userData.userId === "" 11 | ? Math.floor(Math.random() * 100000) 12 | : userData.userId; 13 | const p2pcf = new P2PCF( 14 | "user-" + userProfileName, 15 | document.location.hash.substring(1), 16 | { 17 | workerUrl: "https://p2pcf.sxpdigital.workers.dev/", 18 | slowPollingRateMs: 5000, 19 | fastPollingRateMs: 1500 20 | } 21 | ); 22 | window.p2pcf = p2pcf; 23 | console.log("client id:", p2pcf.clientId); 24 | 25 | const removePeerUi = (clientId) => { 26 | document.getElementById(clientId)?.remove(); 27 | document.getElementById(`${clientId}-video`)?.remove(); 28 | }; 29 | 30 | const addPeerUi = (sessionId) => { 31 | if (document.getElementById(sessionId)) return; 32 | 33 | const peerEl = document.createElement("div"); 34 | peerEl.style = "display: flex;"; 35 | 36 | const name = document.createElement("div"); 37 | name.innerText = sessionId.substring(0, 5); 38 | 39 | peerEl.id = sessionId; 40 | peerEl.appendChild(name); 41 | 42 | document.getElementById("peers").appendChild(peerEl); 43 | }; 44 | const addMessage = (message) => { 45 | const messageEl = document.createElement("div"); 46 | messageEl.innerText = message; 47 | 48 | document.getElementById("messages").appendChild(messageEl); 49 | }; 50 | let stream; 51 | p2pcf.on("peerconnect", (peer) => { 52 | console.log("Peer connect", peer.id, peer); 53 | console.log(peer.client_id); 54 | 55 | if (stream) { 56 | peer.addStream(stream); 57 | } 58 | peer.on("track", (track, stream) => { 59 | console.log("got track", track); 60 | const video = document.createElement("audio"); 61 | video.id = `${peer.id}-audio`; 62 | video.srcObject = stream; 63 | video.setAttribute("playsinline", true); 64 | document.getElementById("videos").appendChild(video); 65 | video.play(); 66 | }); 67 | addPeerUi(peer.id); 68 | }); 69 | 70 | p2pcf.on("peerclose", (peer) => { 71 | console.log("Peer close", peer.id, peer); 72 | removePeerUi(peer.id); 73 | }); 74 | 75 | p2pcf.on("msg", (peer, data) => { 76 | addMessage( 77 | peer.id.substring(0, 5) + 78 | ": " + 79 | new TextDecoder("utf-8").decode(data) 80 | ); 81 | }); 82 | 83 | const go = () => { 84 | document.getElementById("session-id").innerText = 85 | p2pcf.sessionId.substring(0, 5) + "@" + p2pcf.roomId + ":"; 86 | 87 | // document.getElementById('send-button').addEventListener('click', () => { 88 | // const box = document.getElementById('send-box'); 89 | // addMessage(p2pcf.sessionId.substring(0, 5) + ': ' + box.value); 90 | // p2pcf.broadcast(new TextEncoder().encode(box.value)); 91 | // box.value = ''; 92 | // }) 93 | 94 | document 95 | .getElementById("audio-button") 96 | .addEventListener("click", async () => { 97 | stream = await navigator.mediaDevices.getUserMedia({ 98 | audio: true 99 | }); 100 | 101 | for (const peer of p2pcf.peers.values()) { 102 | peer.addStream(stream); 103 | } 104 | }); 105 | 106 | p2pcf.start(); 107 | }; 108 | if ( 109 | document.readyState === "complete" || 110 | document.readyState === "interactive" 111 | ) { 112 | document 113 | .getElementById("join-button") 114 | .addEventListener("click", async () => { 115 | window.addEventListener("DOMContentLoaded", audio, { 116 | once: true 117 | }); 118 | // window.addEventListener('DOMContentLoaded', go, { once: true }) 119 | }); 120 | } else { 121 | window.addEventListener("DOMContentLoaded", go, { once: true }); 122 | } 123 | 124 | return <>; 125 | }; 126 | 127 | export default Networking; -------------------------------------------------------------------------------- /blocks/environment/components/core/front/TextObject.js: -------------------------------------------------------------------------------- 1 | import React, { useRef } from "react"; 2 | import { 3 | Text, 4 | } from "@react-three/drei"; 5 | 6 | /** 7 | * Represents a text object in a virtual reality scene. 8 | * 9 | * @param {Object} model - The props for the text object. 10 | * 11 | * @return {JSX.Element} The text object. 12 | */ 13 | export function TextObject(model) { 14 | const htmlObj = useRef(); 15 | return ( 16 | <> 17 | 23 | 33 | {model.textContent} 34 | 35 | 36 | 37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /blocks/environment/components/core/front/ThreeAudio.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { useThree } from "@react-three/fiber"; 3 | import { 4 | AudioListener, 5 | PositionalAudio, 6 | AudioLoader, 7 | Audio 8 | } from "three"; 9 | 10 | /** 11 | * Audio component that creates an Audio or PositionalAudio object and attaches it to the Three.js camera. 12 | * 13 | * @param {Object} threeAudio - An object containing audio configuration options. 14 | * @param {string} threeAudio.positional - Indicates if the audio should be positional ("1") or not ("0"). 15 | * @param {string} threeAudio.audioUrl - The URL of the audio file to be loaded and played. 16 | * @param {string} threeAudio.loop - Indicates if the audio should loop ("1") or not ("0"). 17 | * @param {number} threeAudio.volume - The volume level of the audio (0 to 1). 18 | * @param {string} threeAudio.autoPlay - Indicates if the audio should play automatically ("1") or not ("0"). 19 | * @param {number} threeAudio.refDistance - The reference distance for positional audio. 20 | * @param {number} threeAudio.maxDistance - The maximum distance for positional audio. 21 | * @param {number} threeAudio.rolloffFactor - The rolloff factor for positional audio. 22 | * @param {number} threeAudio.coneInnerAngle - The inner cone angle for positional audio (in degrees). 23 | * @param {number} threeAudio.coneOuterAngle - The outer cone angle for positional audio (in degrees). 24 | * @param {number} threeAudio.coneOuterGain - The outer cone gain for positional audio. 25 | * @param {string} threeAudio.distanceModel - The distance model for positional audio. 26 | * @param {number} threeAudio.positionX - The X-coordinate of the audio's position for positional audio. 27 | * @param {number} threeAudio.positionY - The Y-coordinate of the audio's position for positional audio. 28 | * @param {number} threeAudio.positionZ - The Z-coordinate of the audio's position for positional audio. 29 | * @param {number} threeAudio.rotationX - The X-coordinate of the audio's rotation for positional audio (in radians). 30 | * @param {number} threeAudio.rotationY - The Y-coordinate of the audio's rotation for positional audio (in radians). 31 | * @param {number} threeAudio.rotationZ - The Z-coordinate of the audio's rotation for positional audio (in radians). 32 | * 33 | * @returns {JSX.Element} - Returns a JSX element containing a Three.js primitive object (Audio/PositionalAudio). 34 | */ 35 | export function ThreeAudio(threeAudio) { 36 | const { camera } = useThree(); 37 | const [audio, setAudio] = useState(null); 38 | 39 | useEffect(() => { 40 | const listener = new AudioListener(); 41 | camera.add(listener); 42 | 43 | // Create either a PositionalAudio object or a normal Audio object based on the positional attribute 44 | const audio = threeAudio.positional === "1" ? new PositionalAudio(listener) : new Audio(listener); 45 | 46 | if (threeAudio.audioUrl) { 47 | const audioLoader = new AudioLoader(); 48 | audioLoader.load(threeAudio.audioUrl, (buffer) => { 49 | audio.setBuffer(buffer); 50 | audio.setLoop(threeAudio.loop === "1" ? true : false); 51 | audio.setVolume(threeAudio.volume); 52 | if (threeAudio.autoPlay === "1") audio.play(); 53 | }); 54 | } 55 | 56 | if (threeAudio.positional === "1") { 57 | audio.refDistance = threeAudio.refDistance; 58 | audio.maxDistance = threeAudio.maxDistance; 59 | audio.rolloffFactor = threeAudio.rolloffFactor; 60 | audio.coneInnerAngle = threeAudio.coneInnerAngle; 61 | audio.coneOuterAngle = threeAudio.coneOuterAngle; 62 | audio.coneOuterGain = threeAudio.coneOuterGain; 63 | audio.distanceModel = threeAudio.distanceModel; 64 | audio.position.set(threeAudio.positionX, threeAudio.positionY, threeAudio.positionZ); 65 | audio.rotation.set(threeAudio.rotationX, threeAudio.rotationY, threeAudio.rotationZ); 66 | } 67 | 68 | setAudio(audio); 69 | 70 | return () => { 71 | audio.stop(); 72 | camera.remove(listener); 73 | } 74 | }, []); 75 | 76 | return ( 77 | <> 78 | {audio && } 79 | 80 | ); 81 | } 82 | -------------------------------------------------------------------------------- /blocks/environment/components/core/front/ThreeImage.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useLoader, useThree } from "@react-three/fiber"; 3 | import { TextureLoader, DoubleSide } from "three"; 4 | 5 | /** 6 | * Renders an image in a three.js scene. 7 | * 8 | * @param {Object} threeImage - The props for the image. 9 | * 10 | * @return {JSX.Element} The image. 11 | */ 12 | export function ThreeImage(threeImage) { 13 | const texture2 = useLoader(TextureLoader, threeImage.url); 14 | return ( 15 | 29 | 35 | {threeImage.transparent == "1" ? ( 36 | 41 | ) : ( 42 | 43 | )} 44 | 45 | ); 46 | } -------------------------------------------------------------------------------- /blocks/environment/components/core/front/ThreeLight.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { useThree } from "@react-three/fiber"; 3 | import { 4 | DirectionalLight, 5 | AmbientLight, 6 | PointLight, 7 | SpotLight, 8 | Color 9 | } from "three"; 10 | 11 | /** 12 | * Light component that creates various Three.js light objects based on the provided configuration. 13 | * 14 | * @param {Object} threeLight - An object containing light configuration options. 15 | * @param {string} threeLight.type - The type of light: "directional", "ambient", "point", "spot". 16 | * @param {number} threeLight.color - The color of the light. 17 | * @param {number} threeLight.intensity - The intensity of the light. 18 | * @param {number} threeLight.distance - Maximum range of the point light (for PointLight). 19 | * @param {number} threeLight.decay - The amount the light dims along the distance of the point light (for PointLight). 20 | * @param {number} threeLight.positionX - The X-coordinate of the light's position. 21 | * @param {number} threeLight.positionY - The Y-coordinate of the light's position. 22 | * @param {number} threeLight.positionZ - The Z-coordinate of the light's position. 23 | * @param {number} threeLight.angle - Maximum extent of the spotlight, in radians (for SpotLight). 24 | * @param {number} threeLight.penumbra - Percentage of the spotlight cone that is attenuated due to penumbra (for SpotLight). 25 | * 26 | * @returns {JSX.Element} - Returns a JSX element containing a Three.js light object. 27 | */ 28 | export function ThreeLight(threeLight) { 29 | const { scene } = useThree(); 30 | 31 | useEffect(() => { 32 | let lightInstance; 33 | const color = new Color( threeLight.color ); 34 | 35 | switch (threeLight.type) { 36 | case "directional": 37 | lightInstance = new DirectionalLight(color, threeLight.intensity); 38 | lightInstance.position.set(threeLight.positionX, threeLight.positionY, threeLight.positionZ); 39 | break; 40 | 41 | case "ambient": 42 | lightInstance = new AmbientLight(color, Number(threeLight.intensity)); 43 | break; 44 | 45 | case "point": 46 | lightInstance = new PointLight(color, threeLight.intensity, threeLight.distance, threeLight.decay); 47 | lightInstance.position.set(threeLight.positionX, threeLight.positionY, threeLight.positionZ); 48 | break; 49 | 50 | case "spot": 51 | lightInstance = new SpotLight(color, threeLight.intensity, threeLight.distance, threeLight.angle, threeLight.penumbra); 52 | lightInstance.position.set(threeLight.positionX, threeLight.positionY, threeLight.positionZ); 53 | break; 54 | 55 | default: 56 | console.warn("Invalid light type provided"); 57 | } 58 | 59 | // add the light to the scene 60 | scene.add(lightInstance); 61 | }, []); 62 | 63 | return; 64 | } 65 | -------------------------------------------------------------------------------- /blocks/environment/components/core/front/ThreeSky.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useLoader } from "@react-three/fiber"; 3 | import { TextureLoader, DoubleSide } from "three"; 4 | import { 5 | Sky 6 | } from "@react-three/drei"; 7 | 8 | /** 9 | * Represents a sky in a virtual reality scene. 10 | * 11 | * @param {Object} sky - The props for the sky. 12 | * 13 | * @return {JSX.Element} The sky. 14 | */ 15 | export function ThreeSky(sky) { 16 | const skyUrl = sky.src[0].querySelector("p.sky-block-url") 17 | ? sky.src[0].querySelector("p.sky-block-url").innerText 18 | : ""; 19 | 20 | const distance = sky.src[0].querySelector("p.sky-block-distance") 21 | ? sky.src[0].querySelector("p.sky-block-distance").innerText 22 | : ""; 23 | 24 | const rayleigh = sky.src[0].querySelector("p.sky-block-rayleigh") 25 | ? sky.src[0].querySelector("p.sky-block-rayleigh").innerText 26 | : ""; 27 | 28 | const sunPositionX = sky.src[0].querySelector("p.sky-block-sunPositionX") 29 | ? sky.src[0].querySelector("p.sky-block-sunPositionX").innerText 30 | : ""; 31 | 32 | const sunPositionY = sky.src[0].querySelector("p.sky-block-sunPositionY") 33 | ? sky.src[0].querySelector("p.sky-block-sunPositionY").innerText 34 | : ""; 35 | 36 | const sunPositionZ = sky.src[0].querySelector("p.sky-block-sunPositionZ") 37 | ? sky.src[0].querySelector("p.sky-block-sunPositionZ").innerText 38 | : ""; 39 | 40 | if(skyUrl === "" || skyUrl === undefined || skyUrl === null) { 41 | return ( 42 | 47 | ); 48 | } else { 49 | const texture1 = useLoader(TextureLoader, skyUrl); 50 | return ( 51 | <> 52 | 58 | 59 | 60 | 61 | 62 | ); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /blocks/environment/init.php: -------------------------------------------------------------------------------- 1 | _x( 'Environment Block', 'block title', 'three-object-viewer' ), 10 | 'description' => _x( 'A 3D environment component', 'block description', 'three-object-viewer' ), 11 | ] ); 12 | } 13 | }); 14 | -------------------------------------------------------------------------------- /blocks/environment/style.scss: -------------------------------------------------------------------------------- 1 | .wp-block-three-object-block { 2 | background-color: #fff; 3 | color: #000; 4 | padding: 2px; 5 | } 6 | 7 | #networking { 8 | display: none !important; 9 | } 10 | 11 | .wp-block-three-object-block { 12 | border: 1px dotted #f00; 13 | } 14 | .glb-preview-container { 15 | padding: 100px; 16 | text-align: center; 17 | align-items: center; 18 | align-content: center; 19 | background-color:#f2f2f2; 20 | } 21 | 22 | .glb-preview-container button{ 23 | padding: 10px; 24 | border-radius: 30px; 25 | background-color:#333; 26 | color: white; 27 | } 28 | 29 | .three-object-block-tip { 30 | overflow-wrap: break-word; 31 | background-color: black; 32 | color: white; 33 | padding: 10px; 34 | font-weight: 500; 35 | max-width: 160px; 36 | font-size: 14px; 37 | margin-top: 0px; 38 | margin: 0 auto; 39 | text-align: center; 40 | } 41 | 42 | .glb-preview-container button:hover{ 43 | padding: 10px; 44 | border-radius: 30px; 45 | background-color:rgb(69, 69, 69); 46 | color: white; 47 | cursor: pointer; 48 | } -------------------------------------------------------------------------------- /blocks/model-block/Edit.test.js: -------------------------------------------------------------------------------- 1 | //Import React 2 | import React from "react"; 3 | //Import test renderer 4 | import { render, fireEvent, cleanup } from "@testing-library/react"; 5 | //Import component to test 6 | import { Editor } from "./Edit"; 7 | 8 | describe("Editor componet", () => { 9 | afterEach(cleanup); 10 | it("matches snapshot when selected", () => { 11 | const onChange = jest.fn(); 12 | const { container } = render( 13 | 14 | ); 15 | expect(container).toMatchSnapshot(); 16 | }); 17 | 18 | it("matches snapshot when not selected", () => { 19 | const onChange = jest.fn(); 20 | const { container } = render( 21 | 22 | ); 23 | expect(container).toMatchSnapshot(); 24 | }); 25 | 26 | it("Calls the onchange function", () => { 27 | const onChange = jest.fn(); 28 | const { getByDisplayValue } = render( 29 | 30 | ); 31 | fireEvent.change(getByDisplayValue("Salad"), { 32 | target: { value: "New Value" } 33 | }); 34 | expect(onChange).toHaveBeenCalledTimes(1); 35 | }); 36 | 37 | it("Passes updated value, not event to onChange callback", () => { 38 | const onChange = jest.fn(); 39 | const { getByDisplayValue } = render( 40 | 41 | ); 42 | fireEvent.change(getByDisplayValue("Seltzer"), { 43 | target: { value: "Boring Water" } 44 | }); 45 | expect(onChange).toHaveBeenCalledWith("Boring Water"); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /blocks/model-block/Save.js: -------------------------------------------------------------------------------- 1 | import { __ } from "@wordpress/i18n"; 2 | import { useBlockProps } from "@wordpress/block-editor"; 3 | 4 | export default function save({ attributes }) { 5 | return ( 6 |
7 | <> 8 |
9 |

10 | {attributes.threeObjectUrl} 11 |

12 |

{attributes.scaleX}

13 |

{attributes.scaleY}

14 |

{attributes.scaleZ}

15 |

16 | {attributes.positionX} 17 |

18 |

19 | {attributes.positionY} 20 |

21 |

22 | {attributes.positionZ} 23 |

24 |

25 | {attributes.rotationX} 26 |

27 |

28 | {attributes.rotationY} 29 |

30 |

31 | {attributes.rotationZ} 32 |

33 |

34 | {attributes.animations} 35 |

36 |

37 | {attributes.collidable ? 1 : 0} 38 |

39 |

{attributes.alt}

40 |
41 | 42 |
43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /blocks/model-block/block.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "three-object-viewer/model-block", 3 | "attributes": { 4 | "scaleX": { 5 | "type": "int", 6 | "default":1 7 | }, 8 | "name": { 9 | "type": "string" 10 | }, 11 | "scaleY": { 12 | "type": "int", 13 | "default":1 14 | }, 15 | "scaleZ": { 16 | "type": "int", 17 | "default":1 18 | }, 19 | "positionX": { 20 | "type": "int", 21 | "default":0 22 | }, 23 | "positionY": { 24 | "type": "int", 25 | "default":0 26 | }, 27 | "positionZ": { 28 | "type": "int", 29 | "default":0 30 | }, 31 | "rotationX": { 32 | "type": "int", 33 | "default":0 34 | }, 35 | "rotationY": { 36 | "type": "int", 37 | "default":0 38 | }, 39 | "rotationZ": { 40 | "type": "int", 41 | "default":0 42 | }, 43 | "threeObjectUrl": { 44 | "type": "string", 45 | "default": null 46 | }, 47 | "animations": { 48 | "type": "string", 49 | "default": "" 50 | }, 51 | "alt": { 52 | "type": "string", 53 | "default": "" 54 | }, 55 | "collidable": { 56 | "type": "boolean", 57 | "default": true 58 | } 59 | }, 60 | "category": "design", 61 | "parent": [ "three-object-viewer/environment" ], 62 | "apiVersion": 2, 63 | "supports": { 64 | "html": false, 65 | "multiple": true 66 | }, 67 | "textdomain": "three-object-viewer", 68 | "editorScript": "file:../../build/block-model-block.js", 69 | "editorStyle": "file:../../build/block-model-block.css", 70 | "style": "file:../../build/block-model-block.css" 71 | } 72 | -------------------------------------------------------------------------------- /blocks/model-block/components/ModelEdit.js: -------------------------------------------------------------------------------- 1 | import * as THREE from "three"; 2 | import React, { Suspense, useRef, useState, useEffect } from "react"; 3 | import { Canvas, useLoader, useFrame, useThree } from "@react-three/fiber"; 4 | import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader"; 5 | import { 6 | OrthographicCamera, 7 | PerspectiveCamera, 8 | OrbitControls, 9 | useAnimations 10 | } from "@react-three/drei"; 11 | import { VRM, VRMUtils, VRMSchema, VRMLoaderPlugin } from "@pixiv/three-vrm"; 12 | import { GLTFAudioEmitterExtension } from "three-omi"; 13 | 14 | function ThreeObject(props) { 15 | const [url, set] = useState(props.url); 16 | useEffect(() => { 17 | setTimeout(() => set(props.url), 2000); 18 | }, []); 19 | const [listener] = useState(() => new THREE.AudioListener()); 20 | 21 | useThree(({ camera }) => { 22 | camera.add(listener); 23 | }); 24 | 25 | const gltf = useLoader(GLTFLoader, url, (loader) => { 26 | loader.register( 27 | (parser) => new GLTFAudioEmitterExtension(parser, listener) 28 | ); 29 | loader.register((parser) => { 30 | return new VRMLoaderPlugin(parser); 31 | }); 32 | }); 33 | 34 | const { actions } = useAnimations(gltf.animations, gltf.scene); 35 | 36 | const animationList = props.animations ? props.animations.split(",") : ""; 37 | 38 | useEffect(() => { 39 | if (animationList) { 40 | animationList.forEach((name) => { 41 | if (Object.keys(actions).includes(name)) { 42 | actions[name].play(); 43 | } 44 | }); 45 | } 46 | }, []); 47 | 48 | if (gltf?.userData?.gltfExtensions?.VRM) { 49 | const vrm = gltf.userData.vrm; 50 | vrm.scene.position.set(0, props.positionY, 0); 51 | VRMUtils.rotateVRM0(vrm); 52 | const rotationVRM = vrm.scene.rotation.y + parseFloat(props.rotationY); 53 | vrm.scene.rotation.set(0, rotationVRM, 0); 54 | // vrm.scene.scale.set( props.scaleX, props.scaleY, props.scaleZ ); 55 | return ; 56 | } 57 | gltf.scene.position.set(0, 0, 0); 58 | gltf.scene.rotation.set(0, 0, 0); 59 | gltf.scene.scale.set(1, 1, 1); 60 | return ; 61 | } 62 | 63 | export default function ModelEdit(props) { 64 | return ( 65 | <> 66 | 76 | 82 | 83 | 90 | {props.url && ( 91 | 92 | 97 | 98 | )} 99 | 100 | 101 | {props.hasTip && ( 102 |

Click and drag ^

103 | )} 104 | 105 | ); 106 | } 107 | -------------------------------------------------------------------------------- /blocks/model-block/editor.scss: -------------------------------------------------------------------------------- 1 | 2 | .wp-block-three-object-block { 3 | border: 1px dotted #f00; 4 | } 5 | .glb-preview-container { 6 | padding: 100px; 7 | text-align: center; 8 | align-items: center; 9 | align-content: center; 10 | background-color:#f2f2f2; 11 | } 12 | 13 | .glb-preview-container button{ 14 | padding: 15px; 15 | border-radius: 30px; 16 | } 17 | 18 | .glb-preview-container button:hover{ 19 | border-radius: 30px; 20 | // background-color:rgb(156, 199, 0); 21 | cursor: pointer; 22 | } 23 | 24 | .three-object-block-tip { 25 | overflow-wrap: break-word; 26 | background-color: black; 27 | color: white; 28 | padding: 10px; 29 | font-weight: 500; 30 | max-width: 160px; 31 | font-size: 12px; 32 | margin-top: 0px; 33 | margin: 0 auto; 34 | text-align: center; 35 | } 36 | 37 | .three-object-block-url-input { 38 | padding-bottom: 20px; 39 | } 40 | 41 | .three-object-block-url-input input{ 42 | height: 40px; 43 | } 44 | 45 | .block-editor-block-inspector .components-base-control.position-inputs:last-child { 46 | margin-bottom: 24px !important; 47 | } 48 | .block-editor-block-inspector .components-base-control.position-inputs { 49 | padding-right: 5px; 50 | } 51 | -------------------------------------------------------------------------------- /blocks/model-block/index.js: -------------------------------------------------------------------------------- 1 | import { registerBlockType } from "@wordpress/blocks"; 2 | import Edit from "./Edit"; 3 | import Save from "./Save"; 4 | import { useBlockProps } from "@wordpress/block-editor"; 5 | 6 | const icon = ( 7 | 13 | 14 | 15 | 16 | 17 | ); 18 | 19 | const blockConfig = require("./block.json"); 20 | registerBlockType(blockConfig.name, { 21 | ...blockConfig, 22 | icon, 23 | apiVersion: 2, 24 | edit: Edit, 25 | save: Save, 26 | deprecated: [ 27 | { 28 | attributes: { 29 | scaleX: { 30 | type: "int", 31 | default:1 32 | }, 33 | name: { 34 | type: "string" 35 | }, 36 | scaleY: { 37 | type: "int", 38 | default:1 39 | }, 40 | scaleZ: { 41 | type: "int", 42 | default:1 43 | }, 44 | positionX: { 45 | type: "int", 46 | default:0 47 | }, 48 | positionY: { 49 | type: "int", 50 | default:0 51 | }, 52 | positionZ: { 53 | type: "int", 54 | default:0 55 | }, 56 | rotationX: { 57 | type: "int", 58 | default:0 59 | }, 60 | rotationY: { 61 | type: "int", 62 | default:0 63 | }, 64 | rotationZ: { 65 | type: "int", 66 | default:0 67 | }, 68 | threeObjectUrl: { 69 | type: "string", 70 | default: null 71 | }, 72 | animations: { 73 | type: "string", 74 | default: "" 75 | }, 76 | alt: { 77 | type: "string", 78 | default: "" 79 | }, 80 | collidable: { 81 | type: "boolean", 82 | default: false 83 | } 84 | }, 85 | save(props) { 86 | return ( 87 |
88 | <> 89 |
90 |

91 | {props.attributes.threeObjectUrl} 92 |

93 |

{props.attributes.scaleX}

94 |

{props.attributes.scaleY}

95 |

{props.attributes.scaleZ}

96 |

97 | {props.attributes.positionX} 98 |

99 |

100 | {props.attributes.positionY} 101 |

102 |

103 | {props.attributes.positionZ} 104 |

105 |

106 | {props.attributes.rotationX} 107 |

108 |

109 | {props.attributes.rotationY} 110 |

111 |

112 | {props.attributes.rotationZ} 113 |

114 |

115 | {props.attributes.animations} 116 |

117 |

118 | {props.attributes.collidable ? 1 : 0} 119 |

120 |

{props.attributes.alt}

121 |
122 | 123 |
124 | ); 125 | } 126 | } 127 | ] 128 | }); 129 | -------------------------------------------------------------------------------- /blocks/model-block/init.php: -------------------------------------------------------------------------------- 1 | _x( 'Model Block', 'block title', 'three-object-viewer' ), 10 | 'description' => _x( 'A 3D model for your environment', 'block description', 'three-object-viewer' ), 11 | ] ); 12 | } 13 | }); 14 | -------------------------------------------------------------------------------- /blocks/model-block/style.scss: -------------------------------------------------------------------------------- 1 | .wp-block-three-object-block { 2 | background-color: #fff; 3 | color: #000; 4 | padding: 2px; 5 | } 6 | 7 | .wp-block-three-object-block { 8 | border: 1px dotted #f00; 9 | } 10 | .glb-preview-container { 11 | padding: 100px; 12 | text-align: center; 13 | align-items: center; 14 | align-content: center; 15 | background-color:#f2f2f2; 16 | } 17 | 18 | .glb-preview-container button{ 19 | padding: 10px; 20 | border-radius: 30px; 21 | background-color:#333; 22 | color: white; 23 | } 24 | 25 | .three-object-block-tip { 26 | overflow-wrap: break-word; 27 | background-color: black; 28 | color: white; 29 | padding: 10px; 30 | font-weight: 500; 31 | max-width: 160px; 32 | font-size: 14px; 33 | margin-top: 0px; 34 | margin: 0 auto; 35 | text-align: center; 36 | } 37 | 38 | .glb-preview-container button:hover{ 39 | padding: 10px; 40 | border-radius: 30px; 41 | background-color:rgb(69, 69, 69); 42 | color: white; 43 | cursor: pointer; 44 | } -------------------------------------------------------------------------------- /blocks/npc-block/Edit.test.js: -------------------------------------------------------------------------------- 1 | //Import React 2 | import React from "react"; 3 | //Import test renderer 4 | import { render, fireEvent, cleanup } from "@testing-library/react"; 5 | //Import component to test 6 | import { Editor } from "./Edit"; 7 | 8 | describe("Editor componet", () => { 9 | afterEach(cleanup); 10 | it("matches snapshot when selected", () => { 11 | const onChange = jest.fn(); 12 | const { container } = render( 13 | 14 | ); 15 | expect(container).toMatchSnapshot(); 16 | }); 17 | 18 | it("matches snapshot when not selected", () => { 19 | const onChange = jest.fn(); 20 | const { container } = render( 21 | 22 | ); 23 | expect(container).toMatchSnapshot(); 24 | }); 25 | 26 | it("Calls the onchange function", () => { 27 | const onChange = jest.fn(); 28 | const { getByDisplayValue } = render( 29 | 30 | ); 31 | fireEvent.change(getByDisplayValue("Salad"), { 32 | target: { value: "New Value" } 33 | }); 34 | expect(onChange).toHaveBeenCalledTimes(1); 35 | }); 36 | 37 | it("Passes updated value, not event to onChange callback", () => { 38 | const onChange = jest.fn(); 39 | const { getByDisplayValue } = render( 40 | 41 | ); 42 | fireEvent.change(getByDisplayValue("Seltzer"), { 43 | target: { value: "Boring Water" } 44 | }); 45 | expect(onChange).toHaveBeenCalledWith("Boring Water"); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /blocks/npc-block/Save.js: -------------------------------------------------------------------------------- 1 | import { __ } from "@wordpress/i18n"; 2 | import { useBlockProps } from "@wordpress/block-editor"; 3 | 4 | export default function save({ attributes }) { 5 | return ( 6 |
7 | <> 8 |
9 |

10 | {attributes.threeObjectUrl} 11 |

12 |

13 | {attributes.positionX} 14 |

15 |

16 | {attributes.positionY} 17 |

18 |

19 | {attributes.positionZ} 20 |

21 |

22 | {attributes.rotationX} 23 |

24 |

25 | {attributes.rotationY} 26 |

27 |

28 | {attributes.rotationZ} 29 |

30 |

31 | {attributes.name} 32 |

33 |

34 | {attributes.defaultMessage} 35 |

36 |

37 | {attributes.personality} 38 |

39 |

40 | {attributes.objectAwareness ? 1 : 0} 41 |

42 |
43 | 44 |
45 | ); 46 | } 47 | -------------------------------------------------------------------------------- /blocks/npc-block/block.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "three-object-viewer/npc-block", 3 | "attributes": { 4 | "name": { 5 | "type": "string" 6 | }, 7 | "personality": { 8 | "type": "string" 9 | }, 10 | "defaultMessage": { 11 | "type": "string" 12 | }, 13 | "positionX": { 14 | "type": "int", 15 | "default":0 16 | }, 17 | "positionY": { 18 | "type": "int", 19 | "default":0 20 | }, 21 | "positionZ": { 22 | "type": "int", 23 | "default":0 24 | }, 25 | "rotationX": { 26 | "type": "int", 27 | "default":0 28 | }, 29 | "rotationY": { 30 | "type": "int", 31 | "default":0 32 | }, 33 | "rotationZ": { 34 | "type": "int", 35 | "default":0 36 | }, 37 | "threeObjectUrl": { 38 | "type": "string", 39 | "default": null 40 | }, 41 | "objectAwareness": { 42 | "type": "boolean", 43 | "default": false 44 | } 45 | }, 46 | "category": "design", 47 | "parent": [ "three-object-viewer/environment" ], 48 | "apiVersion": 2, 49 | "supports": { 50 | "html": false, 51 | "multiple": false 52 | }, 53 | "textdomain": "three-object-viewer", 54 | "editorScript": "file:../../build/block-npc-block.js", 55 | "editorStyle": "file:../../build/block-npc-block.css", 56 | "style": "file:../../build/block-npc-block.css" 57 | } 58 | -------------------------------------------------------------------------------- /blocks/npc-block/components/ModelEdit.js: -------------------------------------------------------------------------------- 1 | import * as THREE from "three"; 2 | import React, { Suspense, useRef, useState, useEffect } from "react"; 3 | import { Canvas, useLoader, useFrame, useThree } from "@react-three/fiber"; 4 | import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader"; 5 | import { 6 | OrthographicCamera, 7 | PerspectiveCamera, 8 | OrbitControls, 9 | useAnimations 10 | } from "@react-three/drei"; 11 | import { VRM, VRMUtils, VRMSchema, VRMLoaderPlugin } from "@pixiv/three-vrm"; 12 | import { GLTFAudioEmitterExtension } from "three-omi"; 13 | 14 | function ThreeObject(props) { 15 | const [url, set] = useState(props.url); 16 | useEffect(() => { 17 | setTimeout(() => set(props.url), 2000); 18 | }, []); 19 | const [listener] = useState(() => new THREE.AudioListener()); 20 | 21 | useThree(({ camera }) => { 22 | camera.add(listener); 23 | }); 24 | 25 | const gltf = useLoader(GLTFLoader, url, (loader) => { 26 | loader.register( 27 | (parser) => new GLTFAudioEmitterExtension(parser, listener) 28 | ); 29 | loader.register((parser) => { 30 | return new VRMLoaderPlugin(parser); 31 | }); 32 | }); 33 | 34 | const { actions } = useAnimations(gltf.animations, gltf.scene); 35 | 36 | const animationList = props.animations ? props.animations.split(",") : ""; 37 | 38 | useEffect(() => { 39 | if (animationList) { 40 | animationList.forEach((name) => { 41 | if (Object.keys(actions).includes(name)) { 42 | actions[name].play(); 43 | } 44 | }); 45 | } 46 | }, []); 47 | 48 | if (gltf?.userData?.gltfExtensions?.VRM) { 49 | const vrm = gltf.userData.vrm; 50 | vrm.scene.position.set(0, props.positionY, 0); 51 | VRMUtils.rotateVRM0(vrm); 52 | const rotationVRM = vrm.scene.rotation.y + parseFloat(props.rotationY); 53 | vrm.scene.rotation.set(0, rotationVRM, 0); 54 | // vrm.scene.scale.set( props.scaleX, props.scaleY, props.scaleZ ); 55 | return ; 56 | } 57 | gltf.scene.position.set(0, 0, 0); 58 | gltf.scene.rotation.set(0, 0, 0); 59 | gltf.scene.scale.set(1, 1, 1); 60 | return ; 61 | } 62 | 63 | export default function ModelEdit(props) { 64 | return ( 65 | <> 66 | 76 | 82 | 83 | 90 | {props.url && ( 91 | 92 | 97 | 98 | )} 99 | 100 | 101 | {props.hasTip && ( 102 |

Click and drag ^

103 | )} 104 | 105 | ); 106 | } 107 | -------------------------------------------------------------------------------- /blocks/npc-block/editor.scss: -------------------------------------------------------------------------------- 1 | 2 | .wp-block-three-object-block { 3 | border: 1px dotted #f00; 4 | } 5 | .glb-preview-container { 6 | padding: 100px; 7 | text-align: center; 8 | align-items: center; 9 | align-content: center; 10 | background-color:#f2f2f2; 11 | } 12 | 13 | .glb-preview-container button{ 14 | padding: 15px; 15 | border-radius: 30px; 16 | } 17 | 18 | .glb-preview-container button:hover{ 19 | border-radius: 30px; 20 | // background-color:rgb(156, 199, 0); 21 | cursor: pointer; 22 | } 23 | 24 | .three-object-block-tip { 25 | overflow-wrap: break-word; 26 | background-color: black; 27 | color: white; 28 | padding: 10px; 29 | font-weight: 500; 30 | max-width: 160px; 31 | font-size: 12px; 32 | margin-top: 0px; 33 | margin: 0 auto; 34 | text-align: center; 35 | } 36 | 37 | .three-object-block-url-input { 38 | padding-bottom: 20px; 39 | } 40 | 41 | .three-object-block-url-input input{ 42 | height: 40px; 43 | } 44 | 45 | .block-editor-block-inspector .components-base-control.position-inputs:last-child { 46 | margin-bottom: 24px !important; 47 | } 48 | .block-editor-block-inspector .components-base-control.position-inputs { 49 | padding-right: 5px; 50 | } 51 | -------------------------------------------------------------------------------- /blocks/npc-block/init.php: -------------------------------------------------------------------------------- 1 | _x( 'NPC Block', 'block title', 'three-object-viewer' ), 10 | 'description' => _x( 'A NPC to live in your environment', 'block description', 'three-object-viewer' ), 11 | ] ); 12 | } 13 | }); 14 | -------------------------------------------------------------------------------- /blocks/npc-block/style.scss: -------------------------------------------------------------------------------- 1 | .wp-block-three-object-block { 2 | background-color: #fff; 3 | color: #000; 4 | padding: 2px; 5 | } 6 | 7 | .wp-block-three-object-block { 8 | border: 1px dotted #f00; 9 | } 10 | .glb-preview-container { 11 | padding: 100px; 12 | text-align: center; 13 | align-items: center; 14 | align-content: center; 15 | background-color:#f2f2f2; 16 | } 17 | 18 | .glb-preview-container button{ 19 | padding: 10px; 20 | border-radius: 30px; 21 | background-color:#333; 22 | color: white; 23 | } 24 | 25 | .three-object-block-tip { 26 | overflow-wrap: break-word; 27 | background-color: black; 28 | color: white; 29 | padding: 10px; 30 | font-weight: 500; 31 | max-width: 160px; 32 | font-size: 14px; 33 | margin-top: 0px; 34 | margin: 0 auto; 35 | text-align: center; 36 | } 37 | 38 | .glb-preview-container button:hover{ 39 | padding: 10px; 40 | border-radius: 30px; 41 | background-color:rgb(69, 69, 69); 42 | color: white; 43 | cursor: pointer; 44 | } -------------------------------------------------------------------------------- /blocks/sky-block/Edit.test.js: -------------------------------------------------------------------------------- 1 | //Import React 2 | import React from "react"; 3 | //Import test renderer 4 | import { render, fireEvent, cleanup } from "@testing-library/react"; 5 | //Import component to test 6 | import { Editor } from "./Edit"; 7 | 8 | describe("Editor componet", () => { 9 | afterEach(cleanup); 10 | it("matches snapshot when selected", () => { 11 | const onChange = jest.fn(); 12 | const { container } = render( 13 | 14 | ); 15 | expect(container).toMatchSnapshot(); 16 | }); 17 | 18 | it("matches snapshot when not selected", () => { 19 | const onChange = jest.fn(); 20 | const { container } = render( 21 | 22 | ); 23 | expect(container).toMatchSnapshot(); 24 | }); 25 | 26 | it("Calls the onchange function", () => { 27 | const onChange = jest.fn(); 28 | const { getByDisplayValue } = render( 29 | 30 | ); 31 | fireEvent.change(getByDisplayValue("Salad"), { 32 | target: { value: "New Value" } 33 | }); 34 | expect(onChange).toHaveBeenCalledTimes(1); 35 | }); 36 | 37 | it("Passes updated value, not event to onChange callback", () => { 38 | const onChange = jest.fn(); 39 | const { getByDisplayValue } = render( 40 | 41 | ); 42 | fireEvent.change(getByDisplayValue("Seltzer"), { 43 | target: { value: "Boring Water" } 44 | }); 45 | expect(onChange).toHaveBeenCalledWith("Boring Water"); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /blocks/sky-block/Save.js: -------------------------------------------------------------------------------- 1 | import { __ } from "@wordpress/i18n"; 2 | import { useBlockProps } from "@wordpress/block-editor"; 3 | 4 | export default function save({ attributes }) { 5 | return ( 6 |
7 | <> 8 |
9 |

{attributes.skyUrl}

10 |

{attributes.distance}

11 |

{attributes.rayleigh}

12 |

{attributes.sunPositionX}

13 |

{attributes.sunPositionY}

14 |

{attributes.sunPositionZ}

15 |
16 | 17 |
18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /blocks/sky-block/block.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "three-object-viewer/sky-block", 3 | "attributes": { 4 | "skyUrl": { 5 | "type": "string", 6 | "default": null 7 | }, 8 | "distance": { 9 | "type": "int", 10 | "default": 170000 11 | }, 12 | "rayleigh": { 13 | "type": "int", 14 | "default": 1 15 | }, 16 | "sunPositionX": { 17 | "type": "int", 18 | "default": 0 19 | }, 20 | "sunPositionY": { 21 | "type": "int", 22 | "default": 10000 23 | }, 24 | "sunPositionZ": { 25 | "type": "int", 26 | "default": -10000 27 | } 28 | }, 29 | "category": "design", 30 | "parent": [ "three-object-viewer/environment" ], 31 | "apiVersion": 2, 32 | "supports": { 33 | "html": false, 34 | "multiple": false 35 | }, 36 | "textdomain": "three-object-viewer", 37 | "editorScript": "file:../../build/block-sky-block.js", 38 | "editorStyle": "file:../../build/block-sky-block.css", 39 | "style": "file:../../build/block-sky-block.css" 40 | } 41 | -------------------------------------------------------------------------------- /blocks/sky-block/editor.scss: -------------------------------------------------------------------------------- 1 | 2 | .wp-block-three-object-block { 3 | border: 1px dotted #f00; 4 | } 5 | .glb-preview-container { 6 | padding: 100px; 7 | text-align: center; 8 | align-items: center; 9 | align-content: center; 10 | background-color:#f2f2f2; 11 | } 12 | 13 | .glb-preview-container button{ 14 | padding: 15px; 15 | border-radius: 30px; 16 | } 17 | 18 | .glb-preview-container button:hover{ 19 | border-radius: 30px; 20 | background-color:rgb(156, 199, 0); 21 | cursor: pointer; 22 | } 23 | .three-object-block-tip { 24 | overflow-wrap: break-word; 25 | background-color: black; 26 | color: white; 27 | padding: 10px; 28 | font-weight: 500; 29 | max-width: 160px; 30 | font-size: 12px; 31 | margin-top: 0px; 32 | margin: 0 auto; 33 | text-align: center; 34 | } 35 | 36 | .three-object-block-url-input { 37 | padding-bottom: 20px; 38 | } 39 | 40 | .three-object-block-url-input input{ 41 | height: 40px; 42 | } 43 | -------------------------------------------------------------------------------- /blocks/sky-block/index.js: -------------------------------------------------------------------------------- 1 | import { registerBlockType } from "@wordpress/blocks"; 2 | import Edit from "./Edit"; 3 | import Save from "./Save"; 4 | import { useBlockProps } from "@wordpress/block-editor"; 5 | 6 | const icon = ( 7 | 13 | 14 | 15 | 16 | 17 | ); 18 | 19 | const blockConfig = require("./block.json"); 20 | registerBlockType(blockConfig.name, { 21 | ...blockConfig, 22 | icon, 23 | apiVersion: 2, 24 | edit: Edit, 25 | save: Save, 26 | deprecated: [ 27 | { 28 | attributes: { 29 | skyUrl: { 30 | type: "string", 31 | default: null 32 | }, 33 | }, 34 | save(props) { 35 | return ( 36 |
37 | <> 38 |
39 |

{props.attributes.skyUrl}

40 |
41 | 42 |
43 | ); 44 | } 45 | } 46 | ] 47 | }); 48 | -------------------------------------------------------------------------------- /blocks/sky-block/init.php: -------------------------------------------------------------------------------- 1 | _x( 'Sky Block', 'block title', 'three-object-viewer' ), 10 | 'description' => _x( 'A sky your environment', 'block description', 'three-object-viewer' ), 11 | ] ); 12 | } 13 | }); 14 | -------------------------------------------------------------------------------- /blocks/sky-block/style.scss: -------------------------------------------------------------------------------- 1 | .wp-block-three-object-block { 2 | background-color: #fff; 3 | color: #000; 4 | padding: 2px; 5 | } 6 | 7 | .wp-block-three-object-block { 8 | border: 1px dotted #f00; 9 | } 10 | .glb-preview-container { 11 | padding: 100px; 12 | text-align: center; 13 | align-items: center; 14 | align-content: center; 15 | background-color:#f2f2f2; 16 | } 17 | 18 | .glb-preview-container button{ 19 | padding: 10px; 20 | border-radius: 30px; 21 | background-color:#333; 22 | color: white; 23 | } 24 | 25 | .three-object-block-tip { 26 | overflow-wrap: break-word; 27 | background-color: black; 28 | color: white; 29 | padding: 10px; 30 | font-weight: 500; 31 | max-width: 160px; 32 | font-size: 14px; 33 | margin-top: 0px; 34 | margin: 0 auto; 35 | text-align: center; 36 | } 37 | 38 | .glb-preview-container button:hover{ 39 | padding: 10px; 40 | border-radius: 30px; 41 | background-color:rgb(69, 69, 69); 42 | color: white; 43 | cursor: pointer; 44 | } -------------------------------------------------------------------------------- /blocks/spawn-point-block/Edit.test.js: -------------------------------------------------------------------------------- 1 | //Import React 2 | import React from "react"; 3 | //Import test renderer 4 | import { render, fireEvent, cleanup } from "@testing-library/react"; 5 | //Import component to test 6 | import { Editor } from "./Edit"; 7 | 8 | describe("Editor componet", () => { 9 | afterEach(cleanup); 10 | it("matches snapshot when selected", () => { 11 | const onChange = jest.fn(); 12 | const { container } = render( 13 | 14 | ); 15 | expect(container).toMatchSnapshot(); 16 | }); 17 | 18 | it("matches snapshot when not selected", () => { 19 | const onChange = jest.fn(); 20 | const { container } = render( 21 | 22 | ); 23 | expect(container).toMatchSnapshot(); 24 | }); 25 | 26 | it("Calls the onchange function", () => { 27 | const onChange = jest.fn(); 28 | const { getByDisplayValue } = render( 29 | 30 | ); 31 | fireEvent.change(getByDisplayValue("Salad"), { 32 | target: { value: "New Value" } 33 | }); 34 | expect(onChange).toHaveBeenCalledTimes(1); 35 | }); 36 | 37 | it("Passes updated value, not event to onChange callback", () => { 38 | const onChange = jest.fn(); 39 | const { getByDisplayValue } = render( 40 | 41 | ); 42 | fireEvent.change(getByDisplayValue("Seltzer"), { 43 | target: { value: "Boring Water" } 44 | }); 45 | expect(onChange).toHaveBeenCalledWith("Boring Water"); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /blocks/spawn-point-block/Save.js: -------------------------------------------------------------------------------- 1 | import { __ } from "@wordpress/i18n"; 2 | import { useBlockProps } from "@wordpress/block-editor"; 3 | 4 | export default function save({ attributes }) { 5 | return ( 6 |
7 | <> 8 |
9 |

10 | {attributes.positionX} 11 |

12 |

13 | {attributes.positionY} 14 |

15 |

16 | {attributes.positionZ} 17 |

18 |

19 | {attributes.rotationX} 20 |

21 |

22 | {attributes.rotationY} 23 |

24 |

25 | {attributes.rotationZ} 26 |

27 |
28 | 29 |
30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /blocks/spawn-point-block/block.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "three-object-viewer/spawn-point-block", 3 | "attributes": { 4 | "positionX": { 5 | "type": "int", 6 | "default":0 7 | }, 8 | "positionY": { 9 | "type": "int", 10 | "default":0 11 | }, 12 | "positionZ": { 13 | "type": "int", 14 | "default":0 15 | }, 16 | "rotationX": { 17 | "type": "int", 18 | "default":0 19 | }, 20 | "rotationY": { 21 | "type": "int", 22 | "default":0 23 | }, 24 | "rotationZ": { 25 | "type": "int", 26 | "default":0 27 | } 28 | }, 29 | "category": "design", 30 | "parent": [ "three-object-viewer/environment" ], 31 | "apiVersion": 2, 32 | "supports": { 33 | "html": false, 34 | "multiple": false 35 | }, 36 | "textdomain": "three-object-viewer", 37 | "editorScript": "file:../../build/block-spawn-point-block.js", 38 | "editorStyle": "file:../../build/block-spawn-point-block.css", 39 | "style": "file:../../build/block-spawn-point-block.css" 40 | } 41 | -------------------------------------------------------------------------------- /blocks/spawn-point-block/editor.scss: -------------------------------------------------------------------------------- 1 | 2 | .wp-block-three-object-block { 3 | border: 1px dotted #f00; 4 | } 5 | .glb-preview-container { 6 | padding: 100px; 7 | text-align: center; 8 | align-items: center; 9 | align-content: center; 10 | background-color:#f2f2f2; 11 | } 12 | 13 | .glb-preview-container button{ 14 | padding: 15px; 15 | border-radius: 30px; 16 | } 17 | 18 | .three-object-viewer-button { 19 | color: black; 20 | border: solid 1.5px black; 21 | } 22 | 23 | .three-object-block-tip { 24 | overflow-wrap: break-word; 25 | background-color: black; 26 | color: white; 27 | padding: 10px; 28 | font-weight: 500; 29 | max-width: 160px; 30 | font-size: 12px; 31 | margin-top: 0px; 32 | margin: 0 auto; 33 | text-align: center; 34 | } 35 | 36 | .three-object-block-url-input { 37 | padding-bottom: 20px; 38 | } 39 | 40 | .three-object-block-url-input input{ 41 | height: 40px; 42 | } 43 | -------------------------------------------------------------------------------- /blocks/spawn-point-block/init.php: -------------------------------------------------------------------------------- 1 | _x( 'Spawn Point Block', 'block title', 'three-object-viewer' ), 10 | 'description' => _x( 'A spawn point for your users', 'block description', 'three-object-viewer' ), 11 | ] ); 12 | } 13 | }); 14 | -------------------------------------------------------------------------------- /blocks/spawn-point-block/style.scss: -------------------------------------------------------------------------------- 1 | .wp-block-three-object-block { 2 | background-color: #fff; 3 | color: #000; 4 | padding: 2px; 5 | } 6 | 7 | .wp-block-three-object-block { 8 | border: 1px dotted #f00; 9 | } 10 | .glb-preview-container { 11 | padding: 100px; 12 | text-align: center; 13 | align-items: center; 14 | align-content: center; 15 | background-color:#f2f2f2; 16 | } 17 | 18 | .glb-preview-container button{ 19 | padding: 10px; 20 | border-radius: 30px; 21 | background-color:#333; 22 | color: white; 23 | } 24 | 25 | .three-object-block-tip { 26 | overflow-wrap: break-word; 27 | background-color: black; 28 | color: white; 29 | padding: 10px; 30 | font-weight: 500; 31 | max-width: 160px; 32 | font-size: 14px; 33 | margin-top: 0px; 34 | margin: 0 auto; 35 | text-align: center; 36 | } 37 | 38 | .glb-preview-container button:hover{ 39 | padding: 10px; 40 | border-radius: 30px; 41 | background-color:rgb(69, 69, 69); 42 | color: white; 43 | cursor: pointer; 44 | } -------------------------------------------------------------------------------- /blocks/three-audio-block/Edit.test.js: -------------------------------------------------------------------------------- 1 | //Import React 2 | import React from "react"; 3 | //Import test renderer 4 | import { render, fireEvent, cleanup } from "@testing-library/react"; 5 | //Import component to test 6 | import { Editor } from "./Edit"; 7 | 8 | describe("Editor componet", () => { 9 | afterEach(cleanup); 10 | it("matches snapshot when selected", () => { 11 | const onChange = jest.fn(); 12 | const { container } = render( 13 | 14 | ); 15 | expect(container).toMatchSnapshot(); 16 | }); 17 | 18 | it("matches snapshot when not selected", () => { 19 | const onChange = jest.fn(); 20 | const { container } = render( 21 | 22 | ); 23 | expect(container).toMatchSnapshot(); 24 | }); 25 | 26 | it("Calls the onchange function", () => { 27 | const onChange = jest.fn(); 28 | const { getByDisplayValue } = render( 29 | 30 | ); 31 | fireEvent.change(getByDisplayValue("Salad"), { 32 | target: { value: "New Value" } 33 | }); 34 | expect(onChange).toHaveBeenCalledTimes(1); 35 | }); 36 | 37 | it("Passes updated value, not event to onChange callback", () => { 38 | const onChange = jest.fn(); 39 | const { getByDisplayValue } = render( 40 | 41 | ); 42 | fireEvent.change(getByDisplayValue("Seltzer"), { 43 | target: { value: "Boring Water" } 44 | }); 45 | expect(onChange).toHaveBeenCalledWith("Boring Water"); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /blocks/three-audio-block/Save.js: -------------------------------------------------------------------------------- 1 | import { __ } from "@wordpress/i18n"; 2 | import { useBlockProps } from "@wordpress/block-editor"; 3 | 4 | export default function save({ attributes }) { 5 | return ( 6 |
7 | <> 8 |
9 |

{attributes.audioUrl}

10 |

{attributes.scaleX}

11 |

{attributes.scaleY}

12 |

{attributes.scaleZ}

13 |

14 | {attributes.positionX} 15 |

16 |

17 | {attributes.positionY} 18 |

19 |

20 | {attributes.positionZ} 21 |

22 |

23 | {attributes.rotationX} 24 |

25 |

26 | {attributes.rotationY} 27 |

28 |

29 | {attributes.rotationZ} 30 |

31 |

32 | {attributes.autoPlay ? '1' : '0'} 33 |

34 |

35 | {attributes.loop ? '1' : '0'} 36 |

37 |

38 | {attributes.volume} 39 |

40 |

41 | {attributes.positional ? '1' : '0'} 42 |

43 |

44 | {attributes.coneInnerAngle} 45 |

46 |

47 | {attributes.coneOuterAngle} 48 |

49 |

50 | {attributes.coneOuterGain} 51 |

52 |

53 | {attributes.distanceModel} 54 |

55 |

56 | {attributes.maxDistance} 57 |

58 |

59 | {attributes.refDistance} 60 |

61 |

62 | {attributes.rolloffFactor} 63 |

64 |
65 | 66 |
67 | ); 68 | } 69 | -------------------------------------------------------------------------------- /blocks/three-audio-block/block.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "three-object-viewer/three-audio-block", 3 | "attributes": { 4 | "name": { 5 | "type": "string", 6 | "default": null 7 | }, 8 | "audioUrl": { 9 | "type": "string", 10 | "default": null 11 | }, 12 | "autoPlay": { 13 | "type": "bool", 14 | "default": true 15 | }, 16 | "loop": { 17 | "type": "bool", 18 | "default": true 19 | }, 20 | "volume": { 21 | "type": "int", 22 | "default": 1 23 | }, 24 | "positional": { 25 | "type": "bool", 26 | "default": true 27 | }, 28 | "coneInnerAngle": { 29 | "type": "int", 30 | "default":360 31 | }, 32 | "coneOuterAngle": { 33 | "type": "int", 34 | "default":0 35 | }, 36 | "coneOuterGain": { 37 | "type": "int", 38 | "default":0.8 39 | }, 40 | "distanceModel": { 41 | "type": "string", 42 | "default": "inverse" 43 | }, 44 | "maxDistance": { 45 | "type": "int", 46 | "default":10000 47 | }, 48 | "refDistance": { 49 | "type": "int", 50 | "default":5 51 | }, 52 | "rolloffFactor": { 53 | "type": "int", 54 | "default":5 55 | }, 56 | "positionX": { 57 | "type": "int", 58 | "default":0 59 | }, 60 | "positionY": { 61 | "type": "int", 62 | "default":0 63 | }, 64 | "positionZ": { 65 | "type": "int", 66 | "default":0 67 | }, 68 | "rotationX": { 69 | "type": "int", 70 | "default":0 71 | }, 72 | "rotationY": { 73 | "type": "int", 74 | "default":0 75 | }, 76 | "rotationZ": { 77 | "type": "int", 78 | "default":0 79 | } 80 | }, 81 | "category": "design", 82 | "parent": [ "three-object-viewer/environment" ], 83 | "apiVersion": 2, 84 | "supports": { 85 | "html": false, 86 | "multiple": true 87 | }, 88 | "textdomain": "three-object-viewer", 89 | "editorScript": "file:../../build/block-three-audio-block.js", 90 | "editorStyle": "file:../../build/block-three-audio-block.css", 91 | "style": "file:../../build/block-three-audio-block.css" 92 | } 93 | -------------------------------------------------------------------------------- /blocks/three-audio-block/editor.scss: -------------------------------------------------------------------------------- 1 | 2 | .wp-block-three-object-block { 3 | border: 1px dotted #f00; 4 | } 5 | .glb-preview-container { 6 | padding: 100px; 7 | text-align: center; 8 | align-items: center; 9 | align-content: center; 10 | background-color:#f2f2f2; 11 | } 12 | 13 | .glb-preview-container button{ 14 | padding: 15px; 15 | border-radius: 30px; 16 | } 17 | 18 | .glb-preview-container button:hover{ 19 | border-radius: 30px; 20 | background-color:rgb(156, 199, 0); 21 | cursor: pointer; 22 | } 23 | .three-object-block-tip { 24 | overflow-wrap: break-word; 25 | background-color: black; 26 | color: white; 27 | padding: 10px; 28 | font-weight: 500; 29 | max-width: 160px; 30 | font-size: 12px; 31 | margin-top: 0px; 32 | margin: 0 auto; 33 | text-align: center; 34 | } 35 | 36 | .three-object-block-url-input { 37 | padding-bottom: 20px; 38 | } 39 | 40 | .three-object-block-url-input input{ 41 | height: 40px; 42 | } 43 | -------------------------------------------------------------------------------- /blocks/three-audio-block/init.php: -------------------------------------------------------------------------------- 1 | _x( 'Audio Block', 'block title', 'three-object-viewer' ), 10 | 'description' => _x( 'An audio block for your environment', 'block description', 'three-object-viewer' ), 11 | ] ); 12 | } 13 | }); 14 | -------------------------------------------------------------------------------- /blocks/three-audio-block/style.scss: -------------------------------------------------------------------------------- 1 | .wp-block-three-object-block { 2 | background-color: #fff; 3 | color: #000; 4 | padding: 2px; 5 | } 6 | 7 | .wp-block-three-object-block { 8 | border: 1px dotted #f00; 9 | } 10 | .glb-preview-container { 11 | padding: 100px; 12 | text-align: center; 13 | align-items: center; 14 | align-content: center; 15 | background-color:#f2f2f2; 16 | } 17 | 18 | .glb-preview-container button{ 19 | padding: 10px; 20 | border-radius: 30px; 21 | background-color:#333; 22 | color: white; 23 | } 24 | 25 | .three-object-block-tip { 26 | overflow-wrap: break-word; 27 | background-color: black; 28 | color: white; 29 | padding: 10px; 30 | font-weight: 500; 31 | max-width: 160px; 32 | font-size: 14px; 33 | margin-top: 0px; 34 | margin: 0 auto; 35 | text-align: center; 36 | } 37 | 38 | .glb-preview-container button:hover{ 39 | padding: 10px; 40 | border-radius: 30px; 41 | background-color:rgb(69, 69, 69); 42 | color: white; 43 | cursor: pointer; 44 | } -------------------------------------------------------------------------------- /blocks/three-image-block/Edit.test.js: -------------------------------------------------------------------------------- 1 | //Import React 2 | import React from "react"; 3 | //Import test renderer 4 | import { render, fireEvent, cleanup } from "@testing-library/react"; 5 | //Import component to test 6 | import { Editor } from "./Edit"; 7 | 8 | describe("Editor componet", () => { 9 | afterEach(cleanup); 10 | it("matches snapshot when selected", () => { 11 | const onChange = jest.fn(); 12 | const { container } = render( 13 | 14 | ); 15 | expect(container).toMatchSnapshot(); 16 | }); 17 | 18 | it("matches snapshot when not selected", () => { 19 | const onChange = jest.fn(); 20 | const { container } = render( 21 | 22 | ); 23 | expect(container).toMatchSnapshot(); 24 | }); 25 | 26 | it("Calls the onchange function", () => { 27 | const onChange = jest.fn(); 28 | const { getByDisplayValue } = render( 29 | 30 | ); 31 | fireEvent.change(getByDisplayValue("Salad"), { 32 | target: { value: "New Value" } 33 | }); 34 | expect(onChange).toHaveBeenCalledTimes(1); 35 | }); 36 | 37 | it("Passes updated value, not event to onChange callback", () => { 38 | const onChange = jest.fn(); 39 | const { getByDisplayValue } = render( 40 | 41 | ); 42 | fireEvent.change(getByDisplayValue("Seltzer"), { 43 | target: { value: "Boring Water" } 44 | }); 45 | expect(onChange).toHaveBeenCalledWith("Boring Water"); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /blocks/three-image-block/Save.js: -------------------------------------------------------------------------------- 1 | import { __ } from "@wordpress/i18n"; 2 | import { useBlockProps } from "@wordpress/block-editor"; 3 | 4 | export default function save({ attributes }) { 5 | return ( 6 |
7 | <> 8 |
9 |

{attributes.imageUrl}

10 |

{attributes.scaleX}

11 |

{attributes.scaleY}

12 |

{attributes.scaleZ}

13 |

14 | {attributes.positionX} 15 |

16 |

17 | {attributes.positionY} 18 |

19 |

20 | {attributes.positionZ} 21 |

22 |

23 | {attributes.rotationX} 24 |

25 |

26 | {attributes.rotationY} 27 |

28 |

29 | {attributes.rotationZ} 30 |

31 |

32 | {attributes.aspectHeight} 33 |

34 |

35 | {attributes.aspectWidth} 36 |

37 |

38 | {attributes.transparent ? 1 : 0} 39 |

40 |
41 | 42 |
43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /blocks/three-image-block/block.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "three-object-viewer/three-image-block", 3 | "attributes": { 4 | "imageUrl": { 5 | "type": "string", 6 | "default": null 7 | }, 8 | "transparent": { 9 | "type": "boolean", 10 | "default": false 11 | }, 12 | "scaleX": { 13 | "type": "int", 14 | "default":1 15 | }, 16 | "scaleY": { 17 | "type": "int", 18 | "default":1 19 | }, 20 | "scaleZ": { 21 | "type": "int", 22 | "default":1 23 | }, 24 | "positionX": { 25 | "type": "int", 26 | "default":0 27 | }, 28 | "positionY": { 29 | "type": "int", 30 | "default":0 31 | }, 32 | "positionZ": { 33 | "type": "int", 34 | "default":0 35 | }, 36 | "rotationX": { 37 | "type": "int", 38 | "default":0 39 | }, 40 | "rotationY": { 41 | "type": "int", 42 | "default":0 43 | }, 44 | "rotationZ": { 45 | "type": "int", 46 | "default":0 47 | }, 48 | "aspectHeight": { 49 | "type": "int", 50 | "default":0 51 | }, 52 | "aspectWidth": { 53 | "type": "int", 54 | "default":0 55 | } 56 | }, 57 | "category": "design", 58 | "parent": [ "three-object-viewer/environment" ], 59 | "apiVersion": 2, 60 | "supports": { 61 | "html": false, 62 | "multiple": true 63 | }, 64 | "textdomain": "three-object-viewer", 65 | "editorScript": "file:../../build/block-three-image-block.js", 66 | "editorStyle": "file:../../build/block-three-image-block.css", 67 | "style": "file:../../build/block-three-image-block.css" 68 | } 69 | -------------------------------------------------------------------------------- /blocks/three-image-block/editor.scss: -------------------------------------------------------------------------------- 1 | 2 | .wp-block-three-object-block { 3 | border: 1px dotted #f00; 4 | } 5 | .glb-preview-container { 6 | padding: 100px; 7 | text-align: center; 8 | align-items: center; 9 | align-content: center; 10 | background-color:#f2f2f2; 11 | } 12 | 13 | .glb-preview-container button{ 14 | padding: 15px; 15 | border-radius: 30px; 16 | } 17 | 18 | .three-object-viewer-button { 19 | color: black; 20 | border: solid 1.5px black; 21 | } 22 | 23 | .three-object-block-tip { 24 | overflow-wrap: break-word; 25 | background-color: black; 26 | color: white; 27 | padding: 10px; 28 | font-weight: 500; 29 | max-width: 160px; 30 | font-size: 12px; 31 | margin-top: 0px; 32 | margin: 0 auto; 33 | text-align: center; 34 | } 35 | 36 | .three-object-block-url-input { 37 | padding-bottom: 20px; 38 | } 39 | 40 | .three-object-block-url-input input{ 41 | height: 40px; 42 | } 43 | -------------------------------------------------------------------------------- /blocks/three-image-block/init.php: -------------------------------------------------------------------------------- 1 | _x( '3D Image Block', 'block title', 'three-object-viewer' ), 10 | 'description' => _x( 'An image block for your environment', 'block description', 'three-object-viewer' ), 11 | ] ); 12 | } 13 | }); 14 | -------------------------------------------------------------------------------- /blocks/three-image-block/style.scss: -------------------------------------------------------------------------------- 1 | .wp-block-three-object-block { 2 | background-color: #fff; 3 | color: #000; 4 | padding: 2px; 5 | } 6 | 7 | .wp-block-three-object-block { 8 | border: 1px dotted #f00; 9 | } 10 | .glb-preview-container { 11 | padding: 100px; 12 | text-align: center; 13 | align-items: center; 14 | align-content: center; 15 | background-color:#f2f2f2; 16 | } 17 | 18 | .glb-preview-container button{ 19 | padding: 10px; 20 | border-radius: 30px; 21 | background-color:#333; 22 | color: white; 23 | } 24 | 25 | .three-object-block-tip { 26 | overflow-wrap: break-word; 27 | background-color: black; 28 | color: white; 29 | padding: 10px; 30 | font-weight: 500; 31 | max-width: 160px; 32 | font-size: 14px; 33 | margin-top: 0px; 34 | margin: 0 auto; 35 | text-align: center; 36 | } 37 | 38 | .glb-preview-container button:hover{ 39 | padding: 10px; 40 | border-radius: 30px; 41 | background-color:rgb(69, 69, 69); 42 | color: white; 43 | cursor: pointer; 44 | } -------------------------------------------------------------------------------- /blocks/three-light-block/Edit.test.js: -------------------------------------------------------------------------------- 1 | //Import React 2 | import React from "react"; 3 | //Import test renderer 4 | import { render, fireEvent, cleanup } from "@testing-library/react"; 5 | //Import component to test 6 | import { Editor } from "./Edit"; 7 | 8 | describe("Editor componet", () => { 9 | afterEach(cleanup); 10 | it("matches snapshot when selected", () => { 11 | const onChange = jest.fn(); 12 | const { container } = render( 13 | 14 | ); 15 | expect(container).toMatchSnapshot(); 16 | }); 17 | 18 | it("matches snapshot when not selected", () => { 19 | const onChange = jest.fn(); 20 | const { container } = render( 21 | 22 | ); 23 | expect(container).toMatchSnapshot(); 24 | }); 25 | 26 | it("Calls the onchange function", () => { 27 | const onChange = jest.fn(); 28 | const { getByDisplayValue } = render( 29 | 30 | ); 31 | fireEvent.change(getByDisplayValue("Salad"), { 32 | target: { value: "New Value" } 33 | }); 34 | expect(onChange).toHaveBeenCalledTimes(1); 35 | }); 36 | 37 | it("Passes updated value, not event to onChange callback", () => { 38 | const onChange = jest.fn(); 39 | const { getByDisplayValue } = render( 40 | 41 | ); 42 | fireEvent.change(getByDisplayValue("Seltzer"), { 43 | target: { value: "Boring Water" } 44 | }); 45 | expect(onChange).toHaveBeenCalledWith("Boring Water"); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /blocks/three-light-block/Save.js: -------------------------------------------------------------------------------- 1 | import { __ } from "@wordpress/i18n"; 2 | import { useBlockProps } from "@wordpress/block-editor"; 3 | 4 | export default function save({ attributes }) { 5 | return ( 6 |
7 | <> 8 |
9 |

10 | {attributes.positionX} 11 |

12 |

13 | {attributes.positionY} 14 |

15 |

16 | {attributes.positionZ} 17 |

18 |

19 | {attributes.rotationX} 20 |

21 |

22 | {attributes.rotationY} 23 |

24 |

25 | {attributes.rotationZ} 26 |

27 |

28 | {attributes.type} 29 |

30 |

31 | {attributes.color} 32 |

33 |

34 | {attributes.intensity} 35 |

36 |

37 | {attributes.distance} 38 |

39 |

40 | {attributes.decay} 41 |

42 |

43 | {attributes.targetX} 44 |

45 |

46 | {attributes.targetY} 47 |

48 |

49 | {attributes.targetZ} 50 |

51 |

52 | {attributes.angle} 53 |

54 |

55 | {attributes.penumbra} 56 |

57 |
58 | 59 |
60 | ); 61 | } 62 | -------------------------------------------------------------------------------- /blocks/three-light-block/block.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "three-object-viewer/three-light-block", 3 | "attributes": { 4 | "type": { 5 | "type": "string", 6 | "default": "ambient" 7 | }, 8 | "color": { 9 | "type": "string", 10 | "default": "0xffffff" 11 | }, 12 | "intensity": { 13 | "type": "float", 14 | "default": 0.7 15 | }, 16 | "distance": { 17 | "type": "int", 18 | "default": 100 19 | }, 20 | "decay": { 21 | "type": "int", 22 | "default": 1 23 | }, 24 | "positionX": { 25 | "type": "float", 26 | "default": 0 27 | }, 28 | "positionY": { 29 | "type": "float", 30 | "default": 0 31 | }, 32 | "positionZ": { 33 | "type": "float", 34 | "default": 0 35 | }, 36 | "rotationX": { 37 | "type": "float", 38 | "default": 0 39 | }, 40 | "rotationY": { 41 | "type": "float", 42 | "default": 0 43 | }, 44 | "rotationZ": { 45 | "type": "float", 46 | "default": 0 47 | }, 48 | "angle": { 49 | "type": "float", 50 | "default": 0.78539816339 51 | }, 52 | "penumbra": { 53 | "type": "float", 54 | "default": 0.1 55 | } 56 | }, 57 | "category": "design", 58 | "parent": [ "three-object-viewer/environment" ], 59 | "apiVersion": 2, 60 | "supports": { 61 | "html": false, 62 | "multiple": true 63 | }, 64 | "textdomain": "three-object-viewer", 65 | "editorScript": "file:../../build/block-three-light-block.js", 66 | "editorStyle": "file:../../build/block-three-light-block.css", 67 | "style": "file:../../build/block-three-light-block.css" 68 | } 69 | -------------------------------------------------------------------------------- /blocks/three-light-block/editor.scss: -------------------------------------------------------------------------------- 1 | 2 | .wp-block-three-object-block { 3 | border: 1px dotted #f00; 4 | } 5 | .glb-preview-container { 6 | padding: 100px; 7 | text-align: center; 8 | align-items: center; 9 | align-content: center; 10 | background-color:#f2f2f2; 11 | } 12 | 13 | .glb-preview-container button{ 14 | padding: 15px; 15 | border-radius: 30px; 16 | } 17 | 18 | .glb-preview-container button:hover{ 19 | border-radius: 30px; 20 | background-color:rgb(156, 199, 0); 21 | cursor: pointer; 22 | } 23 | .three-object-block-tip { 24 | overflow-wrap: break-word; 25 | background-color: black; 26 | color: white; 27 | padding: 10px; 28 | font-weight: 500; 29 | max-width: 160px; 30 | font-size: 12px; 31 | margin-top: 0px; 32 | margin: 0 auto; 33 | text-align: center; 34 | } 35 | 36 | .three-object-block-url-input { 37 | padding-bottom: 20px; 38 | } 39 | 40 | .three-object-block-url-input input{ 41 | height: 40px; 42 | } 43 | -------------------------------------------------------------------------------- /blocks/three-light-block/index.js: -------------------------------------------------------------------------------- 1 | import { registerBlockType } from "@wordpress/blocks"; 2 | import Edit from "./Edit"; 3 | import Save from "./Save"; 4 | import { useBlockProps } from "@wordpress/block-editor"; 5 | 6 | const icon = ( 7 | 13 | 14 | 15 | 16 | 17 | ); 18 | 19 | const blockConfig = require("./block.json"); 20 | registerBlockType(blockConfig.name, { 21 | ...blockConfig, 22 | icon, 23 | apiVersion: 2, 24 | edit: Edit, 25 | save: Save 26 | }); 27 | -------------------------------------------------------------------------------- /blocks/three-light-block/init.php: -------------------------------------------------------------------------------- 1 | _x( 'Light Block', 'block title', 'three-object-viewer' ), 10 | 'description' => _x( 'A light block for your environment', 'block description', 'three-object-viewer' ), 11 | ] ); 12 | } 13 | }); 14 | -------------------------------------------------------------------------------- /blocks/three-light-block/style.scss: -------------------------------------------------------------------------------- 1 | .wp-block-three-object-block { 2 | background-color: #fff; 3 | color: #000; 4 | padding: 2px; 5 | } 6 | 7 | .wp-block-three-object-block { 8 | border: 1px dotted #f00; 9 | } 10 | .glb-preview-container { 11 | padding: 100px; 12 | text-align: center; 13 | align-items: center; 14 | align-content: center; 15 | background-color:#f2f2f2; 16 | } 17 | 18 | .glb-preview-container button{ 19 | padding: 10px; 20 | border-radius: 30px; 21 | background-color:#333; 22 | color: white; 23 | } 24 | 25 | .three-object-block-tip { 26 | overflow-wrap: break-word; 27 | background-color: black; 28 | color: white; 29 | padding: 10px; 30 | font-weight: 500; 31 | max-width: 160px; 32 | font-size: 14px; 33 | margin-top: 0px; 34 | margin: 0 auto; 35 | text-align: center; 36 | } 37 | 38 | .glb-preview-container button:hover{ 39 | padding: 10px; 40 | border-radius: 30px; 41 | background-color:rgb(69, 69, 69); 42 | color: white; 43 | cursor: pointer; 44 | } -------------------------------------------------------------------------------- /blocks/three-object-block/Save.js: -------------------------------------------------------------------------------- 1 | import { __ } from '@wordpress/i18n'; 2 | import { useBlockProps } from '@wordpress/block-editor'; 3 | 4 | export default function save( { attributes } ) { 5 | return ( 6 |
7 | <> 8 |
9 |

10 | { attributes.deviceTarget } 11 |

12 |

13 | { attributes.threeObjectUrl } 14 |

15 |

{ attributes.scale }

16 |

17 | { attributes.bg_color } 18 |

19 |

{ attributes.zoom }

20 |

21 | { attributes.hasZoom ? 1 : 0 } 22 |

23 |

24 | { attributes.hasTip ? 1 : 0 } 25 |

26 |

27 | { attributes.positionY } 28 |

29 |

30 | { attributes.rotationY } 31 |

32 |

{ attributes.scale }

33 |

34 | { attributes.animations } 35 |

36 |
37 | 38 |
39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /blocks/three-object-block/block.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "three-object-viewer/three-object-block", 3 | "title": "Three Object Block", 4 | "description": "A 3D object viewer focused on glTF", 5 | "attributes": { 6 | "bg_color": { 7 | "type": "string", 8 | "default": "#FFFFFF" 9 | }, 10 | "zoom": { 11 | "type": "integer", 12 | "default": 1 13 | }, 14 | "scale": { 15 | "type": "integer", 16 | "default": 1 17 | }, 18 | "positionX": { 19 | "type": "integer", 20 | "default": 0 21 | }, 22 | "positionY": { 23 | "type": "integer", 24 | "default": 0 25 | }, 26 | "rotationY": { 27 | "type": "integer", 28 | "default": 0 29 | }, 30 | "threeObjectUrl": { 31 | "type": "string", 32 | "default": null 33 | }, 34 | "hasZoom": { 35 | "type": "bool", 36 | "default": false 37 | }, 38 | "hasTip": { 39 | "type": "bool", 40 | "default": true 41 | }, 42 | "deviceTarget": { 43 | "type": "string", 44 | "default": "2d" 45 | }, 46 | "animations": { 47 | "type": "string", 48 | "default": "" 49 | } 50 | }, 51 | "category": "media", 52 | "apiVersion": 2, 53 | "supports": { 54 | "html": false, 55 | "multiple": true 56 | }, 57 | "editorScript": ["file:../../build/block-three-object-block.js"], 58 | "editorStyle": "file:../../build/block-three-object-block.css", 59 | "style": "file:../../build/block-three-object-block.css" 60 | } 61 | -------------------------------------------------------------------------------- /blocks/three-object-block/components/TeleportTravel.js: -------------------------------------------------------------------------------- 1 | import { Raycaster, Vector3 } from "three"; 2 | import { useXR, Interactive } from "@react-three/xr"; 3 | import { useFrame } from "@react-three/fiber"; 4 | import { useCallback, useRef, useState } from "react"; 5 | 6 | export function TeleportIndicator(props) { 7 | return ( 8 | <> 9 | 10 | 11 | 12 | 13 | 14 | 15 | ); 16 | } 17 | 18 | export default function TeleportTravel(props) { 19 | const { 20 | centerOnTeleport, 21 | Indicator = TeleportIndicator, 22 | useNormal = true 23 | } = props; 24 | const [isHovered, setIsHovered] = useState(false); 25 | const target = useRef(); 26 | const targetLoc = useRef(); 27 | const ray = useRef(new Raycaster()); 28 | 29 | const rayDir = useRef({ 30 | pos: new Vector3(), 31 | dir: new Vector3() 32 | }); 33 | 34 | const { controllers, player } = useXR(); 35 | 36 | useFrame(() => { 37 | if ( 38 | isHovered && 39 | controllers.length > 0 && 40 | ray.current && 41 | target.current && 42 | targetLoc.current 43 | ) { 44 | controllers[0].controller.getWorldDirection(rayDir.current.dir); 45 | controllers[0].controller.getWorldPosition(rayDir.current.pos); 46 | rayDir.current.dir.multiplyScalar(-1); 47 | ray.current.set(rayDir.current.pos, rayDir.current.dir); 48 | 49 | const [intersection] = ray.current.intersectObject(target.current); 50 | 51 | if (intersection) { 52 | if (useNormal) { 53 | const p = intersection.point; 54 | 55 | targetLoc.current.position.set(0, 0, 0); 56 | 57 | const n = intersection.face.normal.clone(); 58 | n.transformDirection(intersection.object.matrixWorld); 59 | 60 | targetLoc.current.lookAt(n); 61 | targetLoc.current.rotateOnAxis( 62 | new Vector3(1, 0, 0), 63 | Math.PI / 2 64 | ); 65 | targetLoc.current.position.copy(p); 66 | } else { 67 | targetLoc.current.position.copy(intersection.point); 68 | } 69 | } 70 | } 71 | }); 72 | 73 | const click = useCallback(() => { 74 | if (isHovered) { 75 | player.position.copy(targetLoc.current.position); 76 | } 77 | }, [centerOnTeleport, isHovered, useNormal]); 78 | 79 | return ( 80 | <> 81 | {isHovered && ( 82 | 83 | 84 | 85 | )} 86 | setIsHovered(true)} 89 | onBlur={() => setIsHovered(false)} 90 | > 91 | {props.children} 92 | 93 | 94 | ); 95 | } 96 | -------------------------------------------------------------------------------- /blocks/three-object-block/components/ThreeObjectEdit.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | import { USDZLoader } from 'three/examples/jsm/loaders/USDZLoader'; 3 | import React, { Suspense, useRef, useState, useEffect } from 'react'; 4 | import { Canvas, useLoader, useFrame, useThree } from '@react-three/fiber'; 5 | import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'; 6 | import { 7 | OrthographicCamera, 8 | PerspectiveCamera, 9 | OrbitControls, 10 | useAnimations, 11 | } from '@react-three/drei'; 12 | import { VRM, VRMUtils, VRMSchema, VRMLoaderPlugin } from '@pixiv/three-vrm' 13 | import { GLTFAudioEmitterExtension } from 'three-omi'; 14 | 15 | 16 | function ThreeObject( props ) { 17 | const [ url, set ] = useState( props.url ); 18 | const {scene} = useThree(); 19 | useEffect( () => { 20 | setTimeout( () => set( props.url ), 2000 ); 21 | }, [] ); 22 | const [ listener ] = useState( () => new THREE.AudioListener() ); 23 | 24 | useThree( ( { camera } ) => { 25 | camera.add( listener ); 26 | } ); 27 | 28 | // USDZ loader. 29 | if(props.url.split(/[#?]/)[0].split('.').pop().trim() === "usdz") { 30 | 31 | const usdz = useLoader( USDZLoader, url); 32 | 33 | return ; 34 | } 35 | 36 | const gltf = useLoader( GLTFLoader, url, ( loader ) => { 37 | loader.register( 38 | ( parser ) => new GLTFAudioEmitterExtension( parser, listener ) 39 | ); 40 | loader.register( ( parser ) => { 41 | 42 | return new VRMLoaderPlugin( parser ); 43 | 44 | } ); 45 | } ); 46 | const { actions } = useAnimations( gltf.animations, gltf.scene ); 47 | 48 | const animationList = props.animations ? props.animations.split( ',' ) : ''; 49 | 50 | useEffect( () => { 51 | if ( animationList ) { 52 | animationList.forEach( ( name ) => { 53 | if ( Object.keys( actions ).includes( name ) ) { 54 | actions[ name ].play(); 55 | } 56 | } ); 57 | } 58 | }, [] ); 59 | 60 | if(gltf?.userData?.gltfExtensions?.VRM){ 61 | const vrm = gltf.userData.vrm; 62 | vrm.scene.position.set( 0, props.positionY, 0 ); 63 | VRMUtils.rotateVRM0( vrm ); 64 | const rotationVRM = vrm.scene.rotation.y + parseFloat(props.rotationY); 65 | vrm.scene.rotation.set( 0, rotationVRM, 0 ); 66 | vrm.scene.scale.set( props.scale, props.scale, props.scale ); 67 | return ; 68 | } 69 | 70 | gltf.scene.position.set( 0, props.positionY, 0 ); 71 | gltf.scene.rotation.set( 0, props.rotationY, 0 ); 72 | gltf.scene.scale.set( props.scale, props.scale, props.scale ); 73 | return ; 74 | } 75 | 76 | export default function ThreeObjectEdit( props ) { 77 | return ( 78 | <> 79 | 89 | 90 | 91 | 98 | { props.url && ( 99 | 100 | 109 | 110 | ) } 111 | 112 | 113 | { props.hasTip && ( 114 |

Click and drag ^

115 | ) } 116 | 117 | ); 118 | } 119 | -------------------------------------------------------------------------------- /blocks/three-object-block/editor.scss: -------------------------------------------------------------------------------- 1 | 2 | .wp-block-three-object-block { 3 | border: 1px dotted #f00; 4 | } 5 | .glb-preview-container { 6 | padding: 100px; 7 | text-align: center; 8 | align-items: center; 9 | align-content: center; 10 | background-color:#f2f2f2; 11 | } 12 | 13 | .glb-preview-container button{ 14 | padding: 15px; 15 | border-radius: 30px; 16 | } 17 | 18 | .three-object-viewer-button { 19 | background-color:rgb(199, 254, 0); 20 | color: black; 21 | border: solid 1.5px black; 22 | } 23 | 24 | .glb-preview-container button:hover{ 25 | border-radius: 30px; 26 | background-color:rgb(156, 199, 0); 27 | cursor: pointer; 28 | } 29 | .three-object-block-tip { 30 | overflow-wrap: break-word; 31 | background-color: black; 32 | color: white; 33 | padding: 10px; 34 | font-weight: 500; 35 | max-width: 160px; 36 | font-size: 12px; 37 | margin-top: 0px; 38 | margin: 0 auto; 39 | text-align: center; 40 | } 41 | 42 | .three-object-block-url-input { 43 | padding-bottom: 20px; 44 | } 45 | 46 | .three-object-block-url-input input{ 47 | height: 40px; 48 | } 49 | -------------------------------------------------------------------------------- /blocks/three-object-block/frontend.js: -------------------------------------------------------------------------------- 1 | const { Component, render } = wp.element; 2 | 3 | import ThreeObjectFront from "./components/ThreeObjectFront"; 4 | 5 | const threeApp = document.querySelectorAll(".three-object-three-app"); 6 | 7 | threeApp.forEach((threeApp) => { 8 | if (threeApp) { 9 | const threeUrl = threeApp.querySelector("p.three-object-block-url") 10 | ? threeApp.querySelector("p.three-object-block-url").innerText 11 | : ""; 12 | const deviceTarget = threeApp.querySelector( 13 | "p.three-object-block-device-target" 14 | ) 15 | ? threeApp.querySelector("p.three-object-block-device-target") 16 | .innerText 17 | : "2D"; 18 | const backgroundColor = threeApp.querySelector( 19 | "p.three-object-background-color" 20 | ) 21 | ? threeApp.querySelector("p.three-object-background-color") 22 | .innerText 23 | : "#ffffff"; 24 | const zoom = threeApp.querySelector("p.three-object-zoom") 25 | ? threeApp.querySelector("p.three-object-zoom").innerText 26 | : 90; 27 | const scale = threeApp.querySelector("p.three-object-scale") 28 | ? threeApp.querySelector("p.three-object-scale").innerText 29 | : 1; 30 | const hasZoom = threeApp.querySelector("p.three-object-has-zoom") 31 | ? threeApp.querySelector("p.three-object-has-zoom").innerText 32 | : false; 33 | const hasTip = threeApp.querySelector("p.three-object-has-tip") 34 | ? threeApp.querySelector("p.three-object-has-tip").innerText 35 | : true; 36 | const positionY = threeApp.querySelector("p.three-object-position-y") 37 | ? threeApp.querySelector("p.three-object-position-y").innerText 38 | : 0; 39 | const rotationY = threeApp.querySelector("p.three-object-rotation-y") 40 | ? threeApp.querySelector("p.three-object-rotation-y").innerText 41 | : 0; 42 | const animations = threeApp.querySelector("p.three-object-animations") 43 | ? threeApp.querySelector("p.three-object-animations").innerText 44 | : ""; 45 | 46 | render( 47 | , 61 | threeApp 62 | ); 63 | } 64 | }); 65 | -------------------------------------------------------------------------------- /blocks/three-object-block/init.php: -------------------------------------------------------------------------------- 1 | { 9 | afterEach(cleanup); 10 | it("matches snapshot when selected", () => { 11 | const onChange = jest.fn(); 12 | const { container } = render( 13 | 14 | ); 15 | expect(container).toMatchSnapshot(); 16 | }); 17 | 18 | it("matches snapshot when not selected", () => { 19 | const onChange = jest.fn(); 20 | const { container } = render( 21 | 22 | ); 23 | expect(container).toMatchSnapshot(); 24 | }); 25 | 26 | it("Calls the onchange function", () => { 27 | const onChange = jest.fn(); 28 | const { getByDisplayValue } = render( 29 | 30 | ); 31 | fireEvent.change(getByDisplayValue("Salad"), { 32 | target: { value: "New Value" } 33 | }); 34 | expect(onChange).toHaveBeenCalledTimes(1); 35 | }); 36 | 37 | it("Passes updated value, not event to onChange callback", () => { 38 | const onChange = jest.fn(); 39 | const { getByDisplayValue } = render( 40 | 41 | ); 42 | fireEvent.change(getByDisplayValue("Seltzer"), { 43 | target: { value: "Boring Water" } 44 | }); 45 | expect(onChange).toHaveBeenCalledWith("Boring Water"); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /blocks/three-portal-block/Save.js: -------------------------------------------------------------------------------- 1 | import { __ } from "@wordpress/i18n"; 2 | import { useBlockProps } from "@wordpress/block-editor"; 3 | 4 | export default function save({ attributes }) { 5 | return ( 6 |
7 | <> 8 |
9 |

10 | {attributes.threeObjectUrl} 11 |

12 |

13 | {attributes.destinationUrl} 14 |

15 |

16 | {attributes.scaleX} 17 |

18 |

19 | {attributes.scaleY} 20 |

21 |

22 | {attributes.scaleZ} 23 |

24 |

25 | {attributes.positionX} 26 |

27 |

28 | {attributes.positionY} 29 |

30 |

31 | {attributes.positionZ} 32 |

33 |

34 | {attributes.rotationX} 35 |

36 |

37 | {attributes.rotationY} 38 |

39 |

40 | {attributes.rotationZ} 41 |

42 |

43 | {attributes.animations} 44 |

45 |

46 | {attributes.label} 47 |

48 |

49 | {attributes.labelOffsetX} 50 |

51 |

52 | {attributes.labelOffsetY} 53 |

54 |

55 | {attributes.labelOffsetZ} 56 |

57 |

58 | {attributes.labelTextColor} 59 |

60 |
61 | 62 |
63 | ); 64 | } 65 | -------------------------------------------------------------------------------- /blocks/three-portal-block/block.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "three-object-viewer/three-portal-block", 3 | "attributes": { 4 | "scaleX": { 5 | "type": "int", 6 | "default":1 7 | }, 8 | "scaleY": { 9 | "type": "int", 10 | "default":1 11 | }, 12 | "scaleZ": { 13 | "type": "int", 14 | "default":1 15 | }, 16 | "positionX": { 17 | "type": "int", 18 | "default":0 19 | }, 20 | "positionY": { 21 | "type": "int", 22 | "default":0 23 | }, 24 | "positionZ": { 25 | "type": "int", 26 | "default":0 27 | }, 28 | "rotationX": { 29 | "type": "int", 30 | "default":0 31 | }, 32 | "rotationY": { 33 | "type": "int", 34 | "default":0 35 | }, 36 | "rotationZ": { 37 | "type": "int", 38 | "default":0 39 | }, 40 | "threeObjectUrl": { 41 | "type": "string", 42 | "default": null 43 | }, 44 | "destinationUrl": { 45 | "type": "string", 46 | "default": null 47 | }, 48 | "label": { 49 | "type": "string", 50 | "default": null 51 | }, 52 | "labelTextColor": { 53 | "type": "string", 54 | "default": "0x000000" 55 | }, 56 | "labelOffsetX": { 57 | "type": "int", 58 | "default":0 59 | }, 60 | "labelOffsetY": { 61 | "type": "int", 62 | "default":0 63 | }, 64 | "labelOffsetZ": { 65 | "type": "int", 66 | "default":0 67 | }, 68 | "animations": { 69 | "type": "string", 70 | "default": "" 71 | }, 72 | "collidable": { 73 | "type": "boolean", 74 | "default": false 75 | } 76 | }, 77 | "category": "design", 78 | "parent": [ "three-object-viewer/environment" ], 79 | "apiVersion": 2, 80 | "supports": { 81 | "html": false, 82 | "multiple": true 83 | }, 84 | "textdomain": "three-object-viewer", 85 | "editorScript": "file:../../build/block-three-portal-block.js", 86 | "editorStyle": "file:../../build/block-three-portal-block.css", 87 | "style": "file:../../build/block-three-portal-block.css" 88 | } 89 | -------------------------------------------------------------------------------- /blocks/three-portal-block/editor.scss: -------------------------------------------------------------------------------- 1 | 2 | .wp-block-three-object-block { 3 | border: 1px dotted #f00; 4 | } 5 | .glb-preview-container { 6 | padding: 100px; 7 | text-align: center; 8 | align-items: center; 9 | align-content: center; 10 | background-color:#f2f2f2; 11 | } 12 | 13 | .glb-preview-container button{ 14 | padding: 15px; 15 | border-radius: 30px; 16 | } 17 | 18 | .glb-preview-container button:hover{ 19 | border-radius: 30px; 20 | background-color:rgb(156, 199, 0); 21 | cursor: pointer; 22 | } 23 | .three-object-block-tip { 24 | overflow-wrap: break-word; 25 | background-color: black; 26 | color: white; 27 | padding: 10px; 28 | font-weight: 500; 29 | max-width: 160px; 30 | font-size: 12px; 31 | margin-top: 0px; 32 | margin: 0 auto; 33 | text-align: center; 34 | } 35 | 36 | .three-object-block-url-input { 37 | padding-bottom: 20px; 38 | } 39 | 40 | .three-object-block-url-input input{ 41 | height: 40px; 42 | } 43 | 44 | .block-editor-block-inspector .components-base-control.position-inputs:last-child { 45 | margin-bottom: 24px !important; 46 | } 47 | .block-editor-block-inspector .components-base-control.position-inputs { 48 | padding-right: 5px; 49 | } 50 | -------------------------------------------------------------------------------- /blocks/three-portal-block/init.php: -------------------------------------------------------------------------------- 1 | _x( 'Portal Block', 'block title', 'three-object-viewer' ), 10 | 'description' => _x( 'A portal for traversal', 'block description', 'three-object-viewer' ), 11 | ] ); 12 | } 13 | }); 14 | -------------------------------------------------------------------------------- /blocks/three-portal-block/style.scss: -------------------------------------------------------------------------------- 1 | .wp-block-three-object-block { 2 | background-color: #fff; 3 | color: #000; 4 | padding: 2px; 5 | } 6 | 7 | .wp-block-three-object-block { 8 | border: 1px dotted #f00; 9 | } 10 | .glb-preview-container { 11 | padding: 100px; 12 | text-align: center; 13 | align-items: center; 14 | align-content: center; 15 | background-color:#f2f2f2; 16 | } 17 | 18 | .glb-preview-container button{ 19 | padding: 10px; 20 | border-radius: 30px; 21 | background-color:#333; 22 | color: white; 23 | } 24 | 25 | .three-object-block-tip { 26 | overflow-wrap: break-word; 27 | background-color: black; 28 | color: white; 29 | padding: 10px; 30 | font-weight: 500; 31 | max-width: 160px; 32 | font-size: 14px; 33 | margin-top: 0px; 34 | margin: 0 auto; 35 | text-align: center; 36 | } 37 | 38 | .glb-preview-container button:hover{ 39 | padding: 10px; 40 | border-radius: 30px; 41 | background-color:rgb(69, 69, 69); 42 | color: white; 43 | cursor: pointer; 44 | } -------------------------------------------------------------------------------- /blocks/three-text-block/Edit.test.js: -------------------------------------------------------------------------------- 1 | //Import React 2 | import React from "react"; 3 | //Import test renderer 4 | import { render, fireEvent, cleanup } from "@testing-library/react"; 5 | //Import component to test 6 | import { Editor } from "./Edit"; 7 | 8 | describe("Editor componet", () => { 9 | afterEach(cleanup); 10 | it("matches snapshot when selected", () => { 11 | const onChange = jest.fn(); 12 | const { container } = render( 13 | 14 | ); 15 | expect(container).toMatchSnapshot(); 16 | }); 17 | 18 | it("matches snapshot when not selected", () => { 19 | const onChange = jest.fn(); 20 | const { container } = render( 21 | 22 | ); 23 | expect(container).toMatchSnapshot(); 24 | }); 25 | 26 | it("Calls the onchange function", () => { 27 | const onChange = jest.fn(); 28 | const { getByDisplayValue } = render( 29 | 30 | ); 31 | fireEvent.change(getByDisplayValue("Salad"), { 32 | target: { value: "New Value" } 33 | }); 34 | expect(onChange).toHaveBeenCalledTimes(1); 35 | }); 36 | 37 | it("Passes updated value, not event to onChange callback", () => { 38 | const onChange = jest.fn(); 39 | const { getByDisplayValue } = render( 40 | 41 | ); 42 | fireEvent.change(getByDisplayValue("Seltzer"), { 43 | target: { value: "Boring Water" } 44 | }); 45 | expect(onChange).toHaveBeenCalledWith("Boring Water"); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /blocks/three-text-block/Save.js: -------------------------------------------------------------------------------- 1 | import { __ } from "@wordpress/i18n"; 2 | import { useBlockProps } from "@wordpress/block-editor"; 3 | 4 | export default function save({ attributes }) { 5 | return ( 6 |
7 | <> 8 |
9 |

10 | {attributes.textContent} 11 |

12 |

13 | {attributes.positionX} 14 |

15 |

16 | {attributes.positionY} 17 |

18 |

19 | {attributes.positionZ} 20 |

21 |

22 | {attributes.rotationX} 23 |

24 |

25 | {attributes.rotationY} 26 |

27 |

28 | {attributes.rotationZ} 29 |

30 |

{attributes.scaleX}

31 |

{attributes.scaleY}

32 |

{attributes.scaleZ}

33 |

{attributes.textColor}

34 |
35 | 36 |
37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /blocks/three-text-block/block.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "three-object-viewer/three-text-block", 3 | "attributes": { 4 | "scaleX": { 5 | "type": "int", 6 | "default":1 7 | }, 8 | "scaleY": { 9 | "type": "int", 10 | "default":1 11 | }, 12 | "scaleZ": { 13 | "type": "int", 14 | "default":1 15 | }, 16 | "positionX": { 17 | "type": "int", 18 | "default":0 19 | }, 20 | "positionY": { 21 | "type": "int", 22 | "default":0 23 | }, 24 | "positionZ": { 25 | "type": "int", 26 | "default":0 27 | }, 28 | "rotationX": { 29 | "type": "int", 30 | "default":0 31 | }, 32 | "rotationY": { 33 | "type": "int", 34 | "default":0 35 | }, 36 | "rotationZ": { 37 | "type": "int", 38 | "default":0 39 | }, 40 | "threeObjectUrl": { 41 | "type": "string", 42 | "default": null 43 | }, 44 | "destinationUrl": { 45 | "type": "string", 46 | "default": null 47 | }, 48 | "textContent": { 49 | "type": "string", 50 | "default": null 51 | }, 52 | "textColor": { 53 | "type": "string", 54 | "default": "0x000000" 55 | }, 56 | "animations": { 57 | "type": "string", 58 | "default": "" 59 | }, 60 | "collidable": { 61 | "type": "boolean", 62 | "default": false 63 | } 64 | }, 65 | "category": "design", 66 | "parent": [ "three-object-viewer/environment" ], 67 | "apiVersion": 2, 68 | "supports": { 69 | "html": false, 70 | "multiple": true 71 | }, 72 | "textdomain": "three-object-viewer", 73 | "editorScript": "file:../../build/block-three-text-block.js", 74 | "editorStyle": "file:../../build/block-three-text-block.css", 75 | "style": "file:../../build/block-three-text-block.css" 76 | } 77 | -------------------------------------------------------------------------------- /blocks/three-text-block/editor.scss: -------------------------------------------------------------------------------- 1 | 2 | .wp-block-three-object-block { 3 | border: 1px dotted #f00; 4 | } 5 | .glb-preview-container { 6 | padding: 100px; 7 | text-align: center; 8 | align-items: center; 9 | align-content: center; 10 | background-color:#f2f2f2; 11 | } 12 | 13 | .glb-preview-container button{ 14 | padding: 15px; 15 | border-radius: 30px; 16 | } 17 | 18 | .glb-preview-container button:hover{ 19 | border-radius: 30px; 20 | background-color:rgb(156, 199, 0); 21 | cursor: pointer; 22 | } 23 | .three-object-block-tip { 24 | overflow-wrap: break-word; 25 | background-color: black; 26 | color: white; 27 | padding: 10px; 28 | font-weight: 500; 29 | max-width: 160px; 30 | font-size: 12px; 31 | margin-top: 0px; 32 | margin: 0 auto; 33 | text-align: center; 34 | } 35 | 36 | .three-object-block-url-input { 37 | padding-bottom: 20px; 38 | } 39 | 40 | .three-object-block-url-input input{ 41 | height: 40px; 42 | } 43 | 44 | .block-editor-block-inspector .components-base-control.position-inputs:last-child { 45 | margin-bottom: 24px !important; 46 | } 47 | .block-editor-block-inspector .components-base-control.position-inputs { 48 | padding-right: 5px; 49 | } 50 | -------------------------------------------------------------------------------- /blocks/three-text-block/init.php: -------------------------------------------------------------------------------- 1 | _x( 'Text Block', 'block title', 'three-object-viewer' ), 10 | 'description' => _x( 'A 3D Text Block', 'block description', 'three-object-viewer' ), 11 | ] ); 12 | } 13 | }); 14 | -------------------------------------------------------------------------------- /blocks/three-text-block/style.scss: -------------------------------------------------------------------------------- 1 | .wp-block-three-object-block { 2 | background-color: #fff; 3 | color: #000; 4 | padding: 2px; 5 | } 6 | 7 | .wp-block-three-object-block { 8 | border: 1px dotted #f00; 9 | } 10 | .glb-preview-container { 11 | padding: 100px; 12 | text-align: center; 13 | align-items: center; 14 | align-content: center; 15 | background-color:#f2f2f2; 16 | } 17 | 18 | .glb-preview-container button{ 19 | padding: 10px; 20 | border-radius: 30px; 21 | background-color:#333; 22 | color: white; 23 | } 24 | 25 | .three-object-block-tip { 26 | overflow-wrap: break-word; 27 | background-color: black; 28 | color: white; 29 | padding: 10px; 30 | font-weight: 500; 31 | max-width: 160px; 32 | font-size: 14px; 33 | margin-top: 0px; 34 | margin: 0 auto; 35 | text-align: center; 36 | } 37 | 38 | .glb-preview-container button:hover{ 39 | padding: 10px; 40 | border-radius: 30px; 41 | background-color:rgb(69, 69, 69); 42 | color: white; 43 | cursor: pointer; 44 | } -------------------------------------------------------------------------------- /blocks/three-video-block/Edit.test.js: -------------------------------------------------------------------------------- 1 | //Import React 2 | import React from "react"; 3 | //Import test renderer 4 | import { render, fireEvent, cleanup } from "@testing-library/react"; 5 | //Import component to test 6 | import { Editor } from "./Edit"; 7 | 8 | describe("Editor componet", () => { 9 | afterEach(cleanup); 10 | it("matches snapshot when selected", () => { 11 | const onChange = jest.fn(); 12 | const { container } = render( 13 | 14 | ); 15 | expect(container).toMatchSnapshot(); 16 | }); 17 | 18 | it("matches snapshot when not selected", () => { 19 | const onChange = jest.fn(); 20 | const { container } = render( 21 | 22 | ); 23 | expect(container).toMatchSnapshot(); 24 | }); 25 | 26 | it("Calls the onchange function", () => { 27 | const onChange = jest.fn(); 28 | const { getByDisplayValue } = render( 29 | 30 | ); 31 | fireEvent.change(getByDisplayValue("Salad"), { 32 | target: { value: "New Value" } 33 | }); 34 | expect(onChange).toHaveBeenCalledTimes(1); 35 | }); 36 | 37 | it("Passes updated value, not event to onChange callback", () => { 38 | const onChange = jest.fn(); 39 | const { getByDisplayValue } = render( 40 | 41 | ); 42 | fireEvent.change(getByDisplayValue("Seltzer"), { 43 | target: { value: "Boring Water" } 44 | }); 45 | expect(onChange).toHaveBeenCalledWith("Boring Water"); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /blocks/three-video-block/Save.js: -------------------------------------------------------------------------------- 1 | import { __ } from "@wordpress/i18n"; 2 | import { useBlockProps } from "@wordpress/block-editor"; 3 | 4 | export default function save({ attributes }) { 5 | return ( 6 |
7 | <> 8 |
9 |
{attributes.videoUrl}
10 |

{attributes.scaleX}

11 |

{attributes.scaleY}

12 |

{attributes.scaleZ}

13 |

14 | {attributes.positionX} 15 |

16 |

17 | {attributes.positionY} 18 |

19 |

20 | {attributes.positionZ} 21 |

22 |

23 | {attributes.rotationX} 24 |

25 |

26 | {attributes.rotationY} 27 |

28 |

29 | {attributes.rotationZ} 30 |

31 |

32 | {attributes.autoPlay ? 1 : 0} 33 |

34 |

35 | {attributes.customModel ? 1 : 0} 36 |

37 |

38 | {attributes.aspectHeight} 39 |

40 |

41 | {attributes.aspectWidth} 42 |

43 |
{attributes.modelUrl}
44 |
45 | 46 |
47 | ); 48 | } 49 | -------------------------------------------------------------------------------- /blocks/three-video-block/block.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "three-object-viewer/three-video-block", 3 | "attributes": { 4 | "videoUrl": { 5 | "type": "string", 6 | "default": null 7 | }, 8 | "modelUrl": { 9 | "type": "string", 10 | "default": null 11 | }, 12 | "autoPlay": { 13 | "type": "bool", 14 | "default": true 15 | }, 16 | "scaleX": { 17 | "type": "int", 18 | "default":1 19 | }, 20 | "scaleY": { 21 | "type": "int", 22 | "default":1 23 | }, 24 | "scaleZ": { 25 | "type": "int", 26 | "default":1 27 | }, 28 | "positionX": { 29 | "type": "int", 30 | "default":0 31 | }, 32 | "positionY": { 33 | "type": "int", 34 | "default":0 35 | }, 36 | "positionZ": { 37 | "type": "int", 38 | "default":0 39 | }, 40 | "rotationX": { 41 | "type": "int", 42 | "default":0 43 | }, 44 | "rotationY": { 45 | "type": "int", 46 | "default":0 47 | }, 48 | "rotationZ": { 49 | "type": "int", 50 | "default":0 51 | }, 52 | "aspectHeight": { 53 | "type": "int", 54 | "default":0 55 | }, 56 | "aspectWidth": { 57 | "type": "int", 58 | "default":0 59 | }, 60 | "customModel": { 61 | "type": "bool", 62 | "default": false 63 | } 64 | }, 65 | "category": "design", 66 | "parent": [ "three-object-viewer/environment" ], 67 | "apiVersion": 2, 68 | "supports": { 69 | "html": false, 70 | "multiple": true 71 | }, 72 | "textdomain": "three-object-viewer", 73 | "editorScript": "file:../../build/block-three-video-block.js", 74 | "editorStyle": "file:../../build/block-three-video-block.css", 75 | "style": "file:../../build/block-three-video-block.css" 76 | } 77 | -------------------------------------------------------------------------------- /blocks/three-video-block/editor.scss: -------------------------------------------------------------------------------- 1 | 2 | .wp-block-three-object-block { 3 | border: 1px dotted #f00; 4 | } 5 | .glb-preview-container { 6 | padding: 100px; 7 | text-align: center; 8 | align-items: center; 9 | align-content: center; 10 | background-color:#f2f2f2; 11 | } 12 | 13 | .glb-preview-container button{ 14 | padding: 15px; 15 | border-radius: 30px; 16 | } 17 | 18 | .glb-preview-container button:hover{ 19 | border-radius: 30px; 20 | background-color:rgb(156, 199, 0); 21 | cursor: pointer; 22 | } 23 | .three-object-block-tip { 24 | overflow-wrap: break-word; 25 | background-color: black; 26 | color: white; 27 | padding: 10px; 28 | font-weight: 500; 29 | max-width: 160px; 30 | font-size: 12px; 31 | margin-top: 0px; 32 | margin: 0 auto; 33 | text-align: center; 34 | } 35 | 36 | .three-object-block-url-input { 37 | padding-bottom: 20px; 38 | } 39 | 40 | .three-object-block-url-input input{ 41 | height: 40px; 42 | } 43 | -------------------------------------------------------------------------------- /blocks/three-video-block/index.js: -------------------------------------------------------------------------------- 1 | import { registerBlockType } from "@wordpress/blocks"; 2 | import Edit from "./Edit"; 3 | import Save from "./Save"; 4 | import { useBlockProps } from "@wordpress/block-editor"; 5 | 6 | const icon = ( 7 | 13 | 14 | 15 | 16 | 17 | ); 18 | 19 | const blockConfig = require("./block.json"); 20 | registerBlockType(blockConfig.name, { 21 | ...blockConfig, 22 | icon, 23 | apiVersion: 2, 24 | edit: Edit, 25 | save: Save, 26 | deprecated: [ 27 | { 28 | attributes: { 29 | videoUrl: { 30 | type: "string", 31 | default: null 32 | }, 33 | modelUrl: { 34 | type: "string", 35 | default: null 36 | }, 37 | autoPlay: { 38 | type: "bool", 39 | default: true 40 | }, 41 | scaleX: { 42 | type: "int", 43 | default: 1 44 | }, 45 | scaleY: { 46 | type: "int", 47 | default: 1 48 | }, 49 | scaleZ: { 50 | type: "int", 51 | default: 1 52 | }, 53 | positionX: { 54 | type: "int", 55 | default: 0 56 | }, 57 | positionY: { 58 | type: "int", 59 | default: 0 60 | }, 61 | positionZ: { 62 | type: "int", 63 | default: 0 64 | }, 65 | rotationX: { 66 | type: "int", 67 | default: 0 68 | }, 69 | rotationY: { 70 | type: "int", 71 | default: 0 72 | }, 73 | rotationZ: { 74 | type: "int", 75 | default: 0 76 | }, 77 | aspectHeight: { 78 | type: "int", 79 | default: 0 80 | }, 81 | aspectWidth: { 82 | type: "int", 83 | default: 0 84 | } 85 | }, 86 | save(props) { 87 | return ( 88 |
89 | <> 90 |
91 |
{props.attributes.videoUrl}
92 |

{props.attributes.scaleX}

93 |

{props.attributes.scaleY}

94 |

{props.attributes.scaleZ}

95 |

96 | {props.attributes.positionX} 97 |

98 |

99 | {props.attributes.positionY} 100 |

101 |

102 | {props.attributes.positionZ} 103 |

104 |

105 | {props.attributes.rotationX} 106 |

107 |

108 | {props.attributes.rotationY} 109 |

110 |

111 | {props.attributes.rotationZ} 112 |

113 |

114 | {props.attributes.aspectHeight} 115 |

116 |

117 | {props.attributes.aspectWidth} 118 |

119 |

120 | {props.attributes.autoPlay ? 1 : 0} 121 |

122 |
123 | 124 |
125 | ); 126 | } 127 | } 128 | ] 129 | }); 130 | -------------------------------------------------------------------------------- /blocks/three-video-block/init.php: -------------------------------------------------------------------------------- 1 | _x( '3D Video Block', 'block title', 'three-object-viewer' ), 10 | 'description' => _x( 'A video block for your environment', 'block description', 'three-object-viewer' ), 11 | ] ); 12 | } 13 | }); 14 | -------------------------------------------------------------------------------- /blocks/three-video-block/style.scss: -------------------------------------------------------------------------------- 1 | .wp-block-three-object-block { 2 | background-color: #fff; 3 | color: #000; 4 | padding: 2px; 5 | } 6 | 7 | .wp-block-three-object-block { 8 | border: 1px dotted #f00; 9 | } 10 | .glb-preview-container { 11 | padding: 100px; 12 | text-align: center; 13 | align-items: center; 14 | align-content: center; 15 | background-color:#f2f2f2; 16 | } 17 | 18 | .glb-preview-container button{ 19 | padding: 10px; 20 | border-radius: 30px; 21 | background-color:#333; 22 | color: white; 23 | } 24 | 25 | .three-object-block-tip { 26 | overflow-wrap: break-word; 27 | background-color: black; 28 | color: white; 29 | padding: 10px; 30 | font-weight: 500; 31 | max-width: 160px; 32 | font-size: 14px; 33 | margin-top: 0px; 34 | margin: 0 auto; 35 | text-align: center; 36 | } 37 | 38 | .glb-preview-container button:hover{ 39 | padding: 10px; 40 | border-radius: 30px; 41 | background-color:rgb(69, 69, 69); 42 | color: white; 43 | cursor: pointer; 44 | } -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "antpb/three-object-viewer", 3 | "description": "threeObjectViewer", 4 | "require": { 5 | "php": "^7.2|^8.0", 6 | "wp-cli/i18n-command": "^2.4" 7 | }, 8 | "type": "wordpress-plugin", 9 | "autoload": { 10 | "psr-4": { 11 | "threeObjectViewer\\": "./php" 12 | } 13 | }, 14 | "autoload-dev": { 15 | "psr-4": { 16 | "threeObjectViewer\\Tests\\": "./tests" 17 | } 18 | }, 19 | "require-dev": { 20 | "phpunit/phpunit": "^7.0", 21 | "yoast/phpunit-polyfills": "^1.0.1", 22 | "mockery/mockery": "1.2", 23 | "brain/monkey": "2.*", 24 | "squizlabs/php_codesniffer": "^3.6" 25 | }, 26 | "scripts": { 27 | "test": "composer test:unit && composer test:wordpress", 28 | "test:unit": "phpunit --config=phpunit-unit.xml", 29 | "test:wordpress": "phpunit --config=phpunit-integration.xml", 30 | "sniffs": "phpcs php/ && phpcs tests/", 31 | "fixes": "phpcbf php/ && phpcbf tests/" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.9" 2 | 3 | services: 4 | 5 | wordpress: 6 | depends_on: 7 | wpdb: 8 | condition: service_healthy 9 | image: wordpress:latest 10 | volumes: 11 | - wordpress_data:/var/www/html 12 | - ./:/var/www/html/wp-content/plugins/three-object-viewer 13 | ports: 14 | - "6039:80" 15 | restart: always 16 | environment: 17 | WORDPRESS_DB_HOST: wpdb:3306 18 | WORDPRESS_DB_USER: wordpress 19 | WORDPRESS_DB_PASSWORD: wordpress 20 | WORDPRESS_DB_NAME: wordpress 21 | 22 | wpdb: 23 | image: mariadb:10.5.8 24 | volumes: 25 | - db_data:/var/lib/mysql 26 | restart: always 27 | environment: 28 | MYSQL_ROOT_PASSWORD: wordpress 29 | MYSQL_DATABASE: wordpress 30 | MYSQL_USER: wordpress 31 | MYSQL_PASSWORD: wordpress 32 | healthcheck: 33 | test: "/usr/bin/mysql --user=wordpress --password=wordpress --execute \"SHOW DATABASES;\"" 34 | interval: 3s 35 | timeout: 1s 36 | retries: 5 37 | 38 | wpcli: 39 | image: wordpress:cli 40 | depends_on: 41 | wpdb: 42 | condition: service_healthy 43 | volumes: 44 | - wordpress_data:/var/www/html 45 | - ./:/var/www/html/wp-content/plugins/three-object-viewer 46 | - ./db:/var/www/html/db 47 | environment: 48 | WORDPRESS_DB_HOST: wpdb:3306 49 | WORDPRESS_DB_USER: wordpress 50 | WORDPRESS_DB_PASSWORD: wordpress 51 | WORDPRESS_DB_NAME: wordpress 52 | ABSPATH: /usr/src/wordpress/ 53 | 54 | phpunit: 55 | image: futureys/phpunit-wordpress-plugin 56 | depends_on: 57 | testwpdb: 58 | condition: service_healthy 59 | command: 60 | - bash 61 | depends_on: 62 | - testwpdb 63 | environment: 64 | DATABASE_PASSWORD: examplepass 65 | DATABASE_HOST: testwpdb 66 | stdin_open: true 67 | tty: true 68 | volumes: 69 | - ./:/plugin 70 | 71 | testwpdb: 72 | environment: 73 | MYSQL_ROOT_PASSWORD: examplepass 74 | image: mariadb:10.5.8 75 | healthcheck: 76 | test: "/usr/bin/mysql --user=wordpress --password=wordpress --execute \"SHOW DATABASES;\"" 77 | interval: 3s 78 | timeout: 1s 79 | retries: 5 80 | volumes: 81 | db_data: {} 82 | wordpress_data: {} 83 | -------------------------------------------------------------------------------- /inc/assets/audio_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antpb/three-object-viewer/907f8d6fdf847141c95cec302d153f0decd9428b/inc/assets/audio_icon.png -------------------------------------------------------------------------------- /inc/assets/default_grid.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antpb/three-object-viewer/907f8d6fdf847141c95cec302d153f0decd9428b/inc/assets/default_grid.glb -------------------------------------------------------------------------------- /inc/assets/light_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antpb/three-object-viewer/907f8d6fdf847141c95cec302d153f0decd9428b/inc/assets/light_icon.png -------------------------------------------------------------------------------- /inc/avatars/3ov_default_avatar.vrm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antpb/three-object-viewer/907f8d6fdf847141c95cec302d153f0decd9428b/inc/avatars/3ov_default_avatar.vrm -------------------------------------------------------------------------------- /inc/avatars/Idle.fbx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antpb/three-object-viewer/907f8d6fdf847141c95cec302d153f0decd9428b/inc/avatars/Idle.fbx -------------------------------------------------------------------------------- /inc/avatars/Running.fbx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antpb/three-object-viewer/907f8d6fdf847141c95cec302d153f0decd9428b/inc/avatars/Running.fbx -------------------------------------------------------------------------------- /inc/avatars/friendly.fbx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antpb/three-object-viewer/907f8d6fdf847141c95cec302d153f0decd9428b/inc/avatars/friendly.fbx -------------------------------------------------------------------------------- /inc/avatars/talking.fbx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antpb/three-object-viewer/907f8d6fdf847141c95cec302d153f0decd9428b/inc/avatars/talking.fbx -------------------------------------------------------------------------------- /inc/avatars/walking.fbx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antpb/three-object-viewer/907f8d6fdf847141c95cec302d153f0decd9428b/inc/avatars/walking.fbx -------------------------------------------------------------------------------- /inc/fonts/roboto.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antpb/three-object-viewer/907f8d6fdf847141c95cec302d153f0decd9428b/inc/fonts/roboto.woff -------------------------------------------------------------------------------- /inc/functions.php: -------------------------------------------------------------------------------- 1 | \n" 6 | "Language-Team: LANGUAGE \n" 7 | "MIME-Version: 1.0\n" 8 | "Content-Type: text/plain; charset=UTF-8\n" 9 | "Content-Transfer-Encoding: 8bit\n" 10 | "POT-Creation-Date: 2023-08-19T05:20:49+00:00\n" 11 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 12 | "Language: \n" 13 | "X-Generator: WP-CLI 2.8.1\n" 14 | "X-Domain: three-object-viewer\n" 15 | 16 | #. Plugin Name of the plugin 17 | msgid "Three Object Viewer" 18 | msgstr "" 19 | 20 | #. Plugin URI of the plugin 21 | msgid "https://3ov.xyz/" 22 | msgstr "" 23 | 24 | #. Description of the plugin 25 | msgid "A plugin for viewing 3D files with support for WebXR and Open Metaverse Interoperability GLTF Extensions." 26 | msgstr "" 27 | 28 | #. Author of the plugin 29 | msgid "antpb" 30 | msgstr "" 31 | 32 | #. Author URI of the plugin 33 | msgid "https://antpb.com" 34 | msgstr "" 35 | 36 | #: admin/three-object-viewer-settings/init.php:75 37 | #: admin/three-object-viewer-settings/init.php:76 38 | msgid "3OV Settings" 39 | msgstr "" 40 | 41 | #: blocks/environment/block.json 42 | msgctxt "block title" 43 | msgid "3D Environment" 44 | msgstr "" 45 | 46 | #: blocks/environment/block.json 47 | msgctxt "block description" 48 | msgid "A 3D environment component" 49 | msgstr "" 50 | 51 | #: blocks/model-block/block.json 52 | msgctxt "block title" 53 | msgid "3D Model" 54 | msgstr "" 55 | 56 | #: blocks/model-block/block.json 57 | msgctxt "block description" 58 | msgid "A 3D model for your environment" 59 | msgstr "" 60 | 61 | #: blocks/npc-block/block.json 62 | msgctxt "block title" 63 | msgid "NPC Block" 64 | msgstr "" 65 | 66 | #: blocks/npc-block/block.json 67 | msgctxt "block description" 68 | msgid "A NPC to live in your environment" 69 | msgstr "" 70 | 71 | #: blocks/sky-block/block.json 72 | msgctxt "block title" 73 | msgid "3D Sky Block" 74 | msgstr "" 75 | 76 | #: blocks/sky-block/block.json 77 | msgctxt "block description" 78 | msgid "A sky your environment" 79 | msgstr "" 80 | 81 | #: blocks/spawn-point-block/block.json 82 | msgctxt "block title" 83 | msgid "Spawn Point" 84 | msgstr "" 85 | 86 | #: blocks/spawn-point-block/block.json 87 | msgctxt "block description" 88 | msgid "A spawn point for your users" 89 | msgstr "" 90 | 91 | #: blocks/three-audio-block/block.json 92 | msgctxt "block title" 93 | msgid "3D Audio" 94 | msgstr "" 95 | 96 | #: blocks/three-audio-block/block.json 97 | msgctxt "block description" 98 | msgid "An audio block for your environment" 99 | msgstr "" 100 | 101 | #: blocks/three-image-block/block.json 102 | msgctxt "block title" 103 | msgid "3D Image" 104 | msgstr "" 105 | 106 | #: blocks/three-image-block/block.json 107 | msgctxt "block description" 108 | msgid "An image block for your environment" 109 | msgstr "" 110 | 111 | #: blocks/three-light-block/block.json 112 | msgctxt "block title" 113 | msgid "3D Light" 114 | msgstr "" 115 | 116 | #: blocks/three-light-block/block.json 117 | msgctxt "block description" 118 | msgid "A light block for your environment" 119 | msgstr "" 120 | 121 | #: blocks/three-object-block/block.json 122 | msgctxt "block title" 123 | msgid "Three Object Block" 124 | msgstr "" 125 | 126 | #: blocks/three-object-block/block.json 127 | msgctxt "block description" 128 | msgid "A 3D object viewer focused on glTF" 129 | msgstr "" 130 | 131 | #: blocks/three-portal-block/block.json 132 | msgctxt "block title" 133 | msgid "3D Portal" 134 | msgstr "" 135 | 136 | #: blocks/three-portal-block/block.json 137 | msgctxt "block description" 138 | msgid "A 3D portal" 139 | msgstr "" 140 | 141 | #: blocks/three-text-block/block.json 142 | msgctxt "block title" 143 | msgid "Three Text Block" 144 | msgstr "" 145 | 146 | #: blocks/three-text-block/block.json 147 | msgctxt "block description" 148 | msgid "A 3D Text Block" 149 | msgstr "" 150 | 151 | #: blocks/three-video-block/block.json 152 | msgctxt "block title" 153 | msgid "3D Video" 154 | msgstr "" 155 | 156 | #: blocks/three-video-block/block.json 157 | msgctxt "block description" 158 | msgid "A video block for your environment" 159 | msgstr "" 160 | -------------------------------------------------------------------------------- /package.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs-extra'); 2 | const path = require('path'); 3 | 4 | const isPro = process.argv.includes('pro'); 5 | 6 | const sourceDirectory = path.join(__dirname); 7 | const targetDirectory = path.join(__dirname, 'plugin-build', isPro ? 'pro/three-object-viewer' : 'free/three-object-viewer'); 8 | 9 | // Explicitly specify directories or files you want to copy for the free version 10 | const itemsToCopy = [ 11 | 'LICENSE', 12 | 'admin', 13 | 'blocks', 14 | 'build', 15 | 'inc', 16 | 'languages', 17 | 'php', 18 | 'readme.txt', 19 | 'three-object-viewer.php', 20 | ]; 21 | 22 | // Ensure the target directory is clean before copying 23 | fs.removeSync(targetDirectory); 24 | fs.ensureDirSync(targetDirectory); 25 | 26 | itemsToCopy.forEach(item => { 27 | const sourcePath = path.join(sourceDirectory, item); 28 | const targetPath = path.join(targetDirectory, item); 29 | fs.copySync(sourcePath, targetPath); 30 | }); 31 | 32 | // If it's the pro build, include the 'pro' directory plus all free version files/directories 33 | if (isPro) { 34 | const sourcePro = path.join(sourceDirectory, 'pro'); 35 | const targetPro = path.join(targetDirectory, 'pro'); 36 | fs.copySync(sourcePro, targetPro); 37 | } 38 | 39 | console.log(`Packaged the ${isPro ? 'pro' : 'free'} version to ${targetDirectory}`); 40 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@antpb/three-object-viewer", 3 | "private": true, 4 | "version": "0.6.3", 5 | "license": "GPL-2.0-or-later", 6 | "main": "build/index.js", 7 | "scripts": { 8 | "test": "yarn test:unit", 9 | "test:unit": "wp-scripts test-unit-js", 10 | "build": "wp-scripts build", 11 | "build:pro": "ISPRO=true wp-scripts build && node package.js pro", 12 | "build:free": "wp-scripts build && node package.js free", 13 | "test:ci": "wp-scripts test-unit-js --passWithNoTests", 14 | "format:js": "wp-scripts format-js", 15 | "lint:css": "wp-scripts lint-style", 16 | "lint:js": "wp-scripts lint-js", 17 | "start": "wp-scripts start", 18 | "rename": "node rename.js", 19 | "make-pot": "vendor/bin/wp i18n make-pot . ./languages/three-object-viewer.pot --slug=three-object-viewer --domain=three-object-viewer", 20 | "make-json": "vendor/bin/wp i18n make-json ./languages/ --no-purge" 21 | }, 22 | "devDependencies": { 23 | "@babel/core": "^7", 24 | "@testing-library/react": "^12", 25 | "@wordpress/eslint-plugin": "^13.5.0", 26 | "@wordpress/scripts": "^16", 27 | "airtap": "^4.0.4", 28 | "browserify": "^17.0.0", 29 | "esbuild": "^0.15.5", 30 | "eslint": "^8.7.0", 31 | "fs-extra": "^11.1.1", 32 | "lint-staged": "^13.0.3", 33 | "prettier": "^2.7.1", 34 | "prettier-standard": "^16.4.1", 35 | "standard": "^17.0.0", 36 | "tape": "^5.6.0", 37 | "tinyify": "^3.1.0" 38 | }, 39 | "dependencies": { 40 | "@babel/plugin-proposal-optional-chaining": "^7.18.9", 41 | "@babel/preset-env": "^7.20.2", 42 | "@babel/preset-react": "^7.18.6", 43 | "@pixiv/three-vrm": "2.0.1", 44 | "@react-three/a11y": "2.2.4", 45 | "@react-three/drei": "8.20.2", 46 | "@react-three/fiber": "8.12.0", 47 | "@react-three/rapier": "1.0.1", 48 | "@react-three/xr": "^3.5.0", 49 | "@wordpress/block-editor": "^6", 50 | "@wordpress/blocks": "^9", 51 | "@wordpress/components": "^14", 52 | "@wordpress/element": "^3", 53 | "@wordpress/i18n": "^4.40.0", 54 | "@wordpress/icons": "^9.17.0", 55 | "array-buffer-to-hex": "^1.0.0", 56 | "axios": "^1.2.1", 57 | "babel-loader": "8", 58 | "base64-arraybuffer": "^1.0.2", 59 | "camera-controls": "^2.7.0", 60 | "convert-hex": "^0.1.0", 61 | "events": "^3.3.0", 62 | "get-browser-rtc": "^1.1.0", 63 | "image-webpack-loader": "^8.1.0", 64 | "json-loader": "^0.5.7", 65 | "r3f-perf": "4.9.1", 66 | "re-resizable": "^6.9.9", 67 | "react-nipple": "^1.0.2", 68 | "react-scrollable-feed": "^1.3.1", 69 | "sass-loader": "^13.2.0", 70 | "style-loader": "2.0.0", 71 | "three": "0.151.1", 72 | "three-icosa": "^0.4.0", 73 | "three-omi": "^0.1.5", 74 | "tiny-simple-peer": "^10.1.1", 75 | "webpack-glsl-loader": "^1.0.1" 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /phpcs.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | PSR2 with tabs instead of spaces. 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /phpunit-integration.xml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | ./tests/Integration 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /phpunit-unit.xml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | ./tests/Unit 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /plugin-build/.gitignore: -------------------------------------------------------------------------------- 1 | !.gitignore 2 | !.gitkeep 3 | -------------------------------------------------------------------------------- /plugin-build/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antpb/three-object-viewer/907f8d6fdf847141c95cec302d153f0decd9428b/plugin-build/.gitkeep -------------------------------------------------------------------------------- /pluginMachine.json: -------------------------------------------------------------------------------- 1 | { 2 | "slug": "three-object-viewer", 3 | "pluginId": 166, 4 | "buildId": 172, 5 | "entryPoints": { 6 | "adminPages": [ 7 | "three-object-viewer-settings" 8 | ], 9 | "proAdminPages": [ 10 | "three-object-viewer-pro-settings" 11 | ], 12 | "blocks": [ 13 | "three-object-block", 14 | "environment", 15 | "model-block", 16 | "npc-block", 17 | "sky-block", 18 | "three-image-block", 19 | "three-video-block", 20 | "three-audio-block", 21 | "three-light-block", 22 | "three-portal-block", 23 | "three-text-block", 24 | "spawn-point-block" 25 | ], 26 | "proBlocks": [ 27 | "three-mirror-block" 28 | ] 29 | }, 30 | "buildIncludes": [ 31 | "three-object-viewer.php", 32 | "readme.txt", 33 | "php", 34 | "vendor", 35 | "build", 36 | "blocks/three-object-block/init.php", 37 | "blocks/environment/init.php", 38 | "blocks/model-block/init.php", 39 | "blocks/npc-block/init.php", 40 | "blocks/sky-block/init.php", 41 | "blocks/three-text-block/init.php", 42 | "blocks/three-image-block/init.php", 43 | "blocks/three-video-block/init.php", 44 | "blocks/three-portal-block/init.php", 45 | "blocks/spawn-point-block/init.php", 46 | "blocks/three-mirror-block/init.php", 47 | "admin/three-object-viewer-settings/init.php", 48 | "pro/admin/three-object-viewer-pro-settings/init.php" 49 | ] 50 | } -------------------------------------------------------------------------------- /rename.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | const HASH_to_file = [ 5 | { '51af751c2048f283b39a777e1a3fae1b': 'three-object-viewer-three-portal-block-editor-script' }, 6 | { 'bd2a3e7576d2e549fc22bfab35d48ca0': 'three-object-viewer-three-text-block-editor-script' }, 7 | { 'a302549a555228fc26253251cff757bd': 'three-object-viewer-model-block-editor-script' }, 8 | { 'a6625e41527a4ebb684bf747e8040213': 'three-object-viewer-three-audio-block-editor-script' }, 9 | { 'f38ba41c49ce087276f6fb65a04d09f5': 'three-object-viewer-three-light-block-editor-script' }, 10 | { '99e744b4fac1824cd9bd14f27bcce02b': 'three-object-viewer-npc-block-editor-script' }, 11 | { '1c9eb460996b98bed51790c80e90f705': 'three-object-viewer-sky-block-editor-script' }, 12 | { '760f26759af0cf80372c18dbbff3b8af': 'three-object-viewer-three-image-block-editor-script' }, 13 | { '2f1be97f0700f196acecdfec131ed2a4': 'three-object-viewer-three-video-block-editor-script' }, 14 | { 'db711d0e65eaf2e9518213c2fa3f74ff': 'three-object-viewer-spawn-point-block-editor-script' }, 15 | { '05c0f6b04d52130d2210e3b0b4859a63': 'three-object-viewer-environment-editor-script' }, 16 | { 'dd10ea5e2a0076c36967f4c3fdb50a0b': 'three-object-viewer-settings'} 17 | ]; 18 | 19 | const folderPath = 'languages'; 20 | 21 | fs.readdirSync(folderPath).forEach((file) => { 22 | const match = file.match(/three-object-viewer-es_MX-(.*).json/); 23 | 24 | if (match && match[1]) { 25 | const hash = match[1]; 26 | const mapping = HASH_to_file.find((obj) => Object.keys(obj)[0] === hash); 27 | 28 | if (mapping) { 29 | const newName = 'three-object-viewer-es_MX-' + Object.values(mapping)[0] + '.json'; 30 | fs.renameSync(path.join(folderPath, file), path.join(folderPath, newName)); 31 | } 32 | } 33 | }); 34 | 35 | fs.readdirSync(folderPath).forEach((file) => { 36 | const match = file.match(/three-object-viewer-ja-(.*).json/); 37 | 38 | if (match && match[1]) { 39 | const hash = match[1]; 40 | const mapping = HASH_to_file.find((obj) => Object.keys(obj)[0] === hash); 41 | 42 | if (mapping) { 43 | const newName = 'three-object-viewer-ja-' + Object.values(mapping)[0] + '.json'; 44 | fs.renameSync(path.join(folderPath, file), path.join(folderPath, newName)); 45 | } 46 | } 47 | }); 48 | 49 | -------------------------------------------------------------------------------- /tests/Integration/EnvironmentTest.php: -------------------------------------------------------------------------------- 1 | assertTrue(is_object($wpdb)); 25 | $id = wp_insert_post([ 26 | 'post_type' => 'post', 27 | 'post_title' => 'roy', 28 | 'post_content' => 'sivan' 29 | ]); 30 | $this->assertTrue(is_numeric($id)); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/Unit/EnvironmentTest.php: -------------------------------------------------------------------------------- 1 | assertIsBool(true); 24 | self::assertIsNotIterable(new \stdClass()); 25 | } 26 | 27 | /** 28 | * A test ensuring that the composer autoloader works 29 | */ 30 | public function testAutoloaderWorks() 31 | { 32 | $this->assertSame('Hey, Listen!', (new Plugin())->listen()); 33 | } 34 | 35 | /** 36 | * Test that we can mock WordPress functions 37 | * 38 | * @see https://giuseppe-mazzapica.gitbook.io/brain-monkey/functions-testing-tools/functions-when#justreturn 39 | */ 40 | public function testMockWordPressFunction() 41 | { 42 | //A fake wp_insert_post() that always returns 1 43 | Functions\when('wp_insert_post')->justReturn(1); 44 | $this->assertIsNumeric( 45 | wp_insert_post([ 46 | 'post_title' => 'If I learn it again, I would recommend:', 47 | 'post_content' => 'grow aloe. then grow cactus. then grow sempervivum. then grow lithops and echeveria' 48 | ]) 49 | ); 50 | $this->assertSame(1, wp_insert_post()); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tests/Unit/TestCase.php: -------------------------------------------------------------------------------- 1 | { 10 | entry[`block-${entryPoint}`] = path.resolve( 11 | process.cwd(), 12 | `blocks/${entryPoint}/index.js` 13 | ); 14 | }); 15 | } 16 | 17 | if (isPro) { 18 | if (entryPoints.hasOwnProperty("proBlocks")) { 19 | entryPoints.proBlocks.forEach((entryPoint) => { 20 | entry[`block-${entryPoint}`] = path.resolve( 21 | process.cwd(), 22 | `pro/blocks/${entryPoint}/index.js` 23 | ); 24 | }); 25 | } 26 | } 27 | 28 | if (entryPoints.hasOwnProperty("adminPages")) { 29 | entryPoints.adminPages.forEach((entryPoint) => { 30 | entry[`admin-page-${entryPoint}`] = path.resolve( 31 | process.cwd(), 32 | `admin/${entryPoint}/index.js` 33 | ); 34 | }); 35 | } 36 | 37 | if (isPro) { 38 | if (entryPoints.hasOwnProperty("proAdminPages")) { 39 | entryPoints.proAdminPages.forEach((entryPoint) => { 40 | entry[`admin-page-${entryPoint}`] = path.resolve( 41 | process.cwd(), 42 | `pro/admin/${entryPoint}/index.js` 43 | ); 44 | }); 45 | } 46 | } 47 | 48 | entry[`./assets/js/blocks.frontend`] = "./blocks/three-object-block/frontend.js"; 49 | 50 | entry[`./assets/js/blocks.frontend-versepress`] = "./blocks/environment/frontend.js"; 51 | 52 | if (isPro) { 53 | entry[`./assets/js/blocks.three-mirror-block`] = "./pro/blocks/three-mirror-block/three-mirror-block-front.js"; 54 | } 55 | 56 | module.exports = { 57 | mode: isProduction ? "production" : "development", 58 | ...defaultConfig, 59 | module: { 60 | ...defaultConfig.module, 61 | 62 | rules: [ 63 | ...defaultConfig.module.rules, 64 | { 65 | test: /\.js$/, 66 | exclude: /node_modules/, 67 | use: 'babel-loader' 68 | }, 69 | { 70 | test: /\.css$/, 71 | use: ["style-loader", "sass-loader", "css-loader"] 72 | }, 73 | { 74 | test: /\.glsl$/, 75 | include: [path.resolve(__dirname, "node_modules/three-icosa")], 76 | use: "webpack-glsl" 77 | }, 78 | { 79 | test: /\.js$/, 80 | include: [path.resolve(__dirname, "node_modules/three-icosa")], 81 | use: "babel-loader" 82 | }, 83 | { 84 | test: /\.vrm$/, 85 | use: [ 86 | { 87 | loader: "file-loader" 88 | } 89 | ] 90 | }, 91 | { 92 | test: /\.glb$/, 93 | use: [ 94 | { 95 | loader: "file-loader" 96 | } 97 | ] 98 | }, 99 | { 100 | test: /\.fbx$/, 101 | use: [ 102 | { 103 | loader: "file-loader" 104 | } 105 | ] 106 | } 107 | ] 108 | }, 109 | entry, 110 | output: { 111 | filename: "[name].js", 112 | path: path.join(__dirname, "./build") 113 | }, 114 | externals: { 115 | react: "React", 116 | "react-dom": "ReactDOM", 117 | }, 118 | resolve: { 119 | modules: ['node_modules'], 120 | extensions: ['.js', '.jsx', '.json', '.ts', '.tsx'], 121 | alias: { 122 | Brushes: path.resolve(__dirname, "brushes"), 123 | '@magickml/editor': path.resolve(__dirname, 'node_modules/@magickml/editor'), 124 | 'draco-decoder': path.resolve(__dirname, 'node_modules/draco3d/') 125 | } 126 | } 127 | }; 128 | --------------------------------------------------------------------------------