{"id":1089,"date":"2019-02-09T07:45:31","date_gmt":"2019-02-09T07:45:31","guid":{"rendered":"http:\/\/www.netexl.com\/blog\/?p=1089"},"modified":"2019-02-09T14:43:59","modified_gmt":"2019-02-09T14:43:59","slug":"phaser-3-scale-manager-is-here-exploring-phaser-3-with-3-16-1-version-now","status":"publish","type":"post","link":"https:\/\/www.netexl.com\/blog\/phaser-3-scale-manager-is-here-exploring-phaser-3-with-3-16-1-version-now\/","title":{"rendered":"Phaser 3 Scale Manager is here. Exploring Phaser 3 with 3.16.1 version now"},"content":{"rendered":"<p>I just checked Phaser release log and have been pleasantly surprised to see the new Scale Manager available now with version 3.16.1 so we are now going to update our Peg Solitaire code to make\u00a0it responsive and use the newly released scale manager.<\/p>\n<p>One thing I would like to do away with is to stop using absolute size values and calculate those values depending upon display area available for the game (just like\u00a0we did it in Phaser 2).<\/p>\n<p>The first thing we need to change with 3.16.x version is to add scale property in game configuration as following<\/p>\n<pre class=\"lang:default mark:3-9 decode:true\">    let config = {\r\n        type: Phaser.AUTO,\r\n        scale: {\r\n            parent: 'mygame',\r\n            mode: Phaser.Scale.FIT,\r\n            autoCenter: Phaser.Scale.CENTER_BOTH,\r\n            width: 500,\r\n            height: 500\r\n        },\r\n        backgroundColor: 0xFF0000,\r\n        scene: TheGame\r\n    }\r\n\r\n    let game = new Phaser.Game(config);\r\n<\/pre>\n<p>Another change I did was to remove css for canvas element which we had earlier added to center align our game. Our app.css file now looks like this<\/p>\n<pre class=\"lang:default decode:true\">body {\r\n    padding: 0px;\r\n    margin: 0px;\r\n    background: #000;\r\n    overflow-x: hidden;\r\n    font-family:cursive;\r\n}<\/pre>\n<p>You can also see that we have\u00a0declared\u00a0&#8216;parent&#8217;\u00a0attribute as &#8216;mygame&#8217; in our scale\u00a0configuration\u00a0so we are going to add a div in our html with id &#8216;mygame&#8217;. Our updated html\u00a0is as following now<\/p>\n<pre class=\"lang:default decode:true\">&lt;!DOCTYPE html&gt;\r\n&lt;html&gt;\r\n    &lt;head&gt;\r\n        &lt;title&gt;Peg Solitaire&lt;\/title&gt;\r\n        &lt;meta http-equiv=\"Content-type\" content=\"text\/html; charset=utf-8\"&gt;\r\n        &lt;meta name=\"viewport\" content=\"width=device-width, minimum-scale=1, initial-scale=1, user-scalable=no\"&gt;\r\n        &lt;link rel=\"stylesheet\" href=\"css\/app.css\"&gt;\r\n\r\n        &lt;script src=\"js\/phaser.min.js\"&gt;&lt;\/script&gt;\r\n        &lt;script src=\"js\/game.js\"&gt;&lt;\/script&gt;\r\n    &lt;\/head&gt;\r\n    &lt;body&gt;\r\n        &lt;div id=\"mygame\"&gt;&lt;\/div&gt;\r\n    &lt;\/body&gt;\r\n&lt;\/html&gt;<\/pre>\n<p>Now the game is resized to fit in the available space (it fits in the screen according to the aspect ratio for declared width and height). This\u00a0can be\u00a0compared to SHOW_ALL scale mode in Phaser 2\u00a0with additional advantage of automatic resizing of the game in available space. We are going to make further changes to scale properties and use entire screen area for our game so there won&#8217;t be fixed width and height for the game. We will take width and height as the available width and height of the window object.<\/p>\n<pre class=\"lang:default mark:7-8 decode:true\">    let config = {\r\n        type: Phaser.AUTO,\r\n        scale: {\r\n            parent: 'mygame',\r\n            mode: Phaser.Scale.FIT,\r\n            autoCenter: Phaser.Scale.CENTER_BOTH,\r\n            width: window.innerWidth,\r\n            height: window.innerHeight\r\n        },\r\n        backgroundColor: 0xFF0000,\r\n        scene: TheGame\r\n    }\r\n\r\n    let game = new Phaser.Game(config);<\/pre>\n<p>With this change when the game is loaded, it takes full screen space. We will be calculating size for rest of the components in the game now\u00a0which makes it\u00a0work in a responsive manner. Once the game with <strong>FIT<\/strong> scale is loaded in the browser and then you resize it, it will resize in the same aspect ratio as the initial load. If\u00a0we want the game to always take entire available space and\u00a0components in the game to re-align themseleves accordingly, then we need to use RESIZE scale mode (which is similar to RESIZE scale mode\u00a0from Phaser 2). We will look at that as well in later articles. For now we are going to focus on using FIT scale mode and making our game responsive for initial load. For most of the games this works well since aspect ratio won&#8217;t be changing for games unless it is played on desktop where we can resize browser window or, on mobile when we are switching between full screen or normal screen mode which changes the aspect ratio of the game.<\/p>\n<p>We are going to define a LocalScaleManager for our game which will take care of scaling our sprites and texts in available area. This code I picked from my previous Phaser 2 game and just made changes to use Phaser 3 api which does pretty much same thing in a different way.<\/p>\n<pre class=\"lang:default decode:true\">function LocalScaleManager() {\r\n}\r\n\r\nLocalScaleManager.prototype = {\r\n    scaleSprite: function (sprite, availableSpaceWidth, availableSpaceHeight, padding, scaleMultiplier, isFullScale) {\r\n        let scale = this.getSpriteScale(sprite.frame.width, sprite.frame.height, availableSpaceWidth, availableSpaceHeight, padding, isFullScale);\r\n        sprite.setScale(scale * scaleMultiplier);\r\n        return scale;\r\n    },\r\n    scaleSpriteTo: function (sprite, scale) {\r\n        sprite.setScale(scale);\r\n    },\r\n    scaleText: function (sprite, availableSpaceWidth, availableSpaceHeight, padding, scaleMultiplier, isFullScale) {\r\n        let originalWidth = sprite.width;\r\n        let originalHeight = sprite.height;\r\n        let scale = this.getSpriteScale(originalWidth, originalHeight, availableSpaceWidth, availableSpaceHeight, padding, isFullScale);\r\n        sprite.setScale(scale * scaleMultiplier);\r\n    },\r\n    getSpriteScale: function (spriteWidth, spriteHeight, availableSpaceWidth, availableSpaceHeight, minPadding, isFullScale) {\r\n        let ratio = 1;\r\n        let currentDevicePixelRatio = window.devicePixelRatio;\r\n        \/\/ Sprite needs to fit in either width or height\r\n        let widthRatio = (spriteWidth * currentDevicePixelRatio + 2 * minPadding) \/ availableSpaceWidth;\r\n        let heightRatio = (spriteHeight * currentDevicePixelRatio + 2 * minPadding) \/ availableSpaceHeight;\r\n        if (widthRatio &gt; 1 || heightRatio &gt; 1) {\r\n            ratio = 1 \/ Math.max(widthRatio, heightRatio);\r\n        } else {\r\n            if (isFullScale)\r\n                ratio = 1 \/ Math.max(widthRatio, heightRatio);\r\n        }\r\n        return ratio * currentDevicePixelRatio;\r\n    }\r\n};\r\n\r\nlet localScaleManager = new LocalScaleManager;<\/pre>\n<p>We have instantiated localScaleManager which will be used by our scenes to scale its components.<\/p>\n<p>In Phaser 3 cameras are created by default with the same size as the game so\u00a0we can find out our game&#8217;s width and height as following<\/p>\n<pre class=\"lang:default decode:true\">var gameWidth = this.cameras.main.width;\r\nvar gameHeight = this.cameras.main.height;<\/pre>\n<p>We will make a change to our code and add components (sprite, text etc) outside main view area and then position components in &#8220;positionControls&#8221; method which takes the available game width and height. Below is the updated code for TheGame class<\/p>\n<pre class=\"lang:default decode:true \">class TheGame extends Phaser.Scene {\r\n\r\n    constructor() {\r\n        super(\"TheGame\");\r\n    }\r\n\r\n    preload() {\r\n        this.load.spritesheet(\"pegs\", \"images\/pegs.png\", {\r\n            frameWidth: 60,\r\n            frameHeight: 60\r\n        });\r\n    }\r\n\r\n    create() {\r\n        this.boardDef = [\r\n            [-1, -1, 1, 1, 1, -1, -1],\r\n            [-1, -1, 1, 1, 1, -1, -1],\r\n            [1, 1, 1, 1, 1, 1, 1],\r\n            [1, 1, 1, 0, 1, 1, 1],\r\n            [1, 1, 1, 1, 1, 1, 1],\r\n            [-1, -1, 1, 1, 1, -1, -1],\r\n            [-1, -1, 1, 1, 1, -1, -1]\r\n        ];\r\n\r\n        \/\/  If a Game Object is clicked on, this event is fired.\r\n        \/\/  We can use it to emit the 'clicked' event on the game object itself.\r\n        this.input.on('gameobjectup', function (pointer, gameObject) {\r\n            gameObject.emit('clicked', gameObject);\r\n        }, this);\r\n\r\n        \/\/ add our sprites\r\n        this.board = [];\r\n        this.selectedPeg = null;\r\n        this.movesCount = 0;\r\n        this.isMoving = false;\r\n\r\n        for (let i = 0, len = this.boardDef.length; i &lt; len; i++) {\r\n            let r = this.boardDef[i];\r\n            let row = [];\r\n            this.board.push(row);\r\n            for (let j = 0, cnt = r.length; j &lt; cnt; j++) {\r\n                let c = r[j];\r\n                if (c &gt;= 0) {\r\n                    let cell = this.add.image(-900, -900, \"pegs\");\r\n                    cell.setFrame(c &gt; 0 ? 1 : 0);\r\n                    cell.setOrigin(0);\r\n\r\n                    \/\/ enable input events\r\n                    cell.setInteractive();\r\n                    cell.on('clicked', this.clickPeg, this);\r\n                    cell.gridX = i;\r\n                    cell.gridY = j;\r\n                    row.push(cell);\r\n                } else {\r\n                    row.push(null);\r\n                }\r\n            }\r\n        }\r\n        this.movesLabel = this.add.text(-900, -900, 'Moves: ' + this.movesCount, { fontFamily: \"Arial Black\", fontSize: 40, color: \"#fff\" });\r\n        this.movesLabel.setShadow(2, 2, 'rgba(0, 0, 0, 0.5)', 2);\r\n\r\n        this.tempPeg = this.add.sprite(-200, -200, \"pegs\");\r\n        this.tempPeg.setFrame(1);\r\n        this.tempPeg.setOrigin(0);\r\n\r\n        var gameWidth = this.cameras.main.width;\r\n        var gameHeight = this.cameras.main.height;\r\n        this.positionControls(gameWidth, gameHeight);\r\n    }\r\n\r\n    positionControls(width, height) {\r\n        \/\/ 7 pegs + leave space equivalent for 1 peg on each side\r\n        var pegSize = Math.min(width \/ 9, height \/ 9);\r\n        var pegScale = localScaleManager.scaleSprite(this.tempPeg, pegSize, pegSize, 0, 1, true);\r\n        var horizontalMargin = (width - 7 * pegSize) \/ 2;\r\n        var verticalMargin = (height - 7 * pegSize) \/ 2;\r\n\r\n        let colsCount = this.board.length;\r\n        for (let i = 0; i &lt; colsCount; i++) {\r\n            let col = this.board[i];\r\n            for (let j = 0, cnt = col.length; j &lt; cnt; j++) {\r\n                let c = col[j];\r\n                if (c) {\r\n                    localScaleManager.scaleSpriteTo(c, pegScale);\r\n                    c.setPosition(horizontalMargin + i * pegSize, verticalMargin + j * pegSize);\r\n                }\r\n            }\r\n        }\r\n\r\n        localScaleManager.scaleText(this.movesLabel, width, pegSize, Math.min(width, pegSize * 0.2), 1, true);\r\n        this.movesLabel.setPosition(width \/ 2 - this.movesLabel.displayWidth \/ 2, 0);\r\n        this.pegSize = pegSize;\r\n    }\r\n\r\n    updateMoves(movesCount) {\r\n        var width = this.cameras.main.width;\r\n        this.movesLabel.setText('Moves: ' + movesCount);\r\n        this.movesLabel.setPosition(width \/ 2 - this.movesLabel.displayWidth \/ 2, 0);\r\n    }\r\n\r\n    gameOver() {\r\n        this.registry.set('gamedata', { movesCount: this.movesCount, remainingPegs: this.remainingPegs() });\r\n        this.cameras.main.fade(500);\r\n        this.time.delayedCall(500, function () {\r\n            let gameOver = new GameOver('GameOver');\r\n            this.scene.add('GameOver', gameOver, true);\r\n            this.scene.remove('TheGame');\r\n        }, [], this)\r\n    }\r\n\r\n    isAnyValidMove() {\r\n        let colsCount = this.board.length;\r\n        for (let i = 0; i &lt; colsCount; i++) {\r\n            let col = this.board[i];\r\n            for (let j = 0, endIndex = col.length - 3; j &lt;= endIndex; j++) {\r\n                let c1 = col[j];\r\n                let c2 = col[j + 1];\r\n                let c3 = col[j + 2];\r\n\r\n                if (c1 &amp;&amp; c2 &amp;&amp; c3) {\r\n                    if (c1.frame.name !== 0 &amp;&amp; c2.frame.name !== 0 &amp;&amp; c3.frame.name === 0) return true;\r\n                    if (c1.frame.name === 0 &amp;&amp; c2.frame.name !== 0 &amp;&amp; c3.frame.name !== 0) return true;\r\n                }\r\n            }\r\n        }\r\n\r\n        let rowsCount = this.board[0].length;\r\n        for (let i = 0, len = colsCount - 3; i &lt;= len; i++) {\r\n            let r1 = this.board[i];\r\n            let r2 = this.board[i + 1];\r\n            let r3 = this.board[i + 2];\r\n            for (let j = 0; j &lt; rowsCount; j++) {\r\n                let c1 = r1[j];\r\n                let c2 = r2[j];\r\n                let c3 = r3[j];\r\n\r\n                if (c1 &amp;&amp; c2 &amp;&amp; c3) {\r\n                    if (c1.frame.name !== 0 &amp;&amp; c2.frame.name !== 0 &amp;&amp; c3.frame.name === 0) return true;\r\n                    if (c1.frame.name === 0 &amp;&amp; c2.frame.name !== 0 &amp;&amp; c3.frame.name !== 0) return true;\r\n                }\r\n            }\r\n        }\r\n        return false;\r\n    }\r\n\r\n    remainingPegs() {\r\n        let pegs = 0;\r\n        for (let i = 0, len = this.board.length; i &lt; len; i++) {\r\n            let row = this.board[i];\r\n            for (let j = 0, cnt = row.length; j &lt; cnt; j++) {\r\n                let cell = row[j];\r\n                if (cell &amp;&amp; cell.frame.name !== 0) {\r\n                    pegs++\r\n                }\r\n            }\r\n        }\r\n        return pegs;\r\n    }\r\n\r\n    clickPeg(peg) {\r\n        if (this.isMoving) return;\r\n\r\n        if (peg.frame.name === 0) {\r\n            \/\/ if we have not selected a peg to jump then no need to move any further\r\n            if (!this.selectedPeg)\r\n                return;\r\n\r\n            let clickedX = peg.gridX;\r\n            let clickedY = peg.gridY;\r\n            let selectedX = this.selectedPeg.gridX;\r\n            let selectedY = this.selectedPeg.gridY;\r\n\r\n            if ((clickedX + 2 === selectedX || clickedX - 2 === selectedX) &amp;&amp; clickedY === selectedY) {\r\n                \/\/ move horizontal\r\n                let pegToRemove = this.board[(selectedX + clickedX) \/ 2][clickedY];\r\n                if (pegToRemove.frame.name === 0)\r\n                    return;\r\n\r\n                this.updateMoves(++this.movesCount);\r\n                this.removePeg(this.tempPeg, this.selectedPeg, peg, pegToRemove);\r\n\r\n                this.selectedPeg.setFrame(0);\r\n                this.selectedPeg = null;\r\n\r\n            } else if ((clickedY + 2 === selectedY || clickedY - 2 === selectedY) &amp;&amp; clickedX === selectedX) {\r\n                \/\/ move vertical\r\n                let pegToRemove = this.board[clickedX][(selectedY + clickedY) \/ 2];\r\n                if (pegToRemove.frame.name === 0)\r\n                    return;\r\n\r\n                this.updateMoves(++this.movesCount);\r\n                this.removePeg(this.tempPeg, this.selectedPeg, peg, pegToRemove);\r\n\r\n                this.selectedPeg.setFrame(0);\r\n                this.selectedPeg = null;\r\n            }\r\n\r\n        } else {\r\n            if (this.selectedPeg) {\r\n                if (peg === this.selectedPeg) {\r\n                    peg.setFrame(1);\r\n                    this.selectedPeg = null;\r\n                } else {\r\n                    this.selectedPeg.setFrame(1);\r\n                    this.selectedPeg = peg;\r\n                    peg.setFrame(2);\r\n                }\r\n            } else {\r\n                this.selectedPeg = peg;\r\n                peg.setFrame(2);\r\n            }\r\n        }\r\n    }\r\n\r\n    removePeg(tempPeg, selectedPeg, targetPeg, pegToRemove) {\r\n        tempPeg.setPosition(selectedPeg.x, selectedPeg.y);\r\n        tempPeg.targetPeg = targetPeg;\r\n        tempPeg.removePeg = pegToRemove;\r\n        tempPeg.visible = true;\r\n        var self = this;\r\n        this.isMoving = true;\r\n        this.pegTween = this.tweens.add({\r\n            targets: tempPeg,\r\n            x: targetPeg.x,\r\n            y: targetPeg.y,\r\n            duration: 200,\r\n            delay: 50,\r\n            onStart: function (tween) {\r\n                let sprite = tween.targets[0];\r\n                sprite.removePeg.setFrame(0);\r\n            },\r\n            onComplete: function (tween) {\r\n                self.isMoving = false;\r\n                let sprite = tween.targets[0];\r\n                sprite.targetPeg.setFrame(1);\r\n                sprite.visible = false;\r\n                if (!self.isAnyValidMove()) {\r\n                    self.cameras.main.shake(2000, 0.005); \/\/ second parameter is just the shake intensity\r\n                    let timedEvent = self.time.addEvent({\r\n                        delay: 2000,\r\n                        callbackScope: this,\r\n                        callback: function () {\r\n                            self.gameOver();\r\n                        }\r\n                    });\r\n                }\r\n            }\r\n        });\r\n    }\r\n}<\/pre>\n<p>I\u00a0\u00a0made few changes to the code from previous articles. I have updated gameover code to add some camera effects while transitioning to GameOver scene, updated text styling, some additional checks in the game.<\/p>\n<p>GameOver scene code is also updated. Below is the updated GameOver scene code<\/p>\n<pre class=\"lang:default decode:true\">class GameOver extends Phaser.Scene {\r\n \r\n    constructor() {\r\n        super(\"GameOver\");\r\n    }\r\n\r\n    preload() {\r\n        this.load.image(\"restart\", \"images\/restart.png\");\r\n    }\r\n \r\n    create() {\r\n        let gamedata = this.registry.get('gamedata');\r\n\r\n        this.messageText = this.add.text(-900, -900, 'Game Over', { fontFamily: \"Arial Black\", fontSize: 40, color: \"#fff\" });\r\n        this.movesText = this.add.text(-900, -900, 'Moves: ' + gamedata.movesCount, { fontFamily: \"Arial Black\", fontSize: 40, color: \"#fff\" });\r\n        if (gamedata.remainingPegs &gt; 1) {\r\n            this.remainingPegsText = this.add.text(-900, -900, 'Remaining Pegs: ' + gamedata.remainingPegs, { fontFamily: \"Arial Black\", fontSize: 40, color: \"#ffff00\" });\r\n        }\r\n\r\n        let btn = this.add.image(-900, -900, 'restart');\r\n        btn.setInteractive();\r\n        btn.on('pointerup', this.startGame, this);\r\n        this.restartButton = btn;\r\n\r\n        let gameWidth = this.cameras.main.width;\r\n        let gameHeight = this.cameras.main.height;\r\n        this.positionControls(gameWidth, gameHeight);\r\n    }\r\n\r\n    positionControls(width, height) {\r\n        \/\/ 20% height for messageText\r\n        \/\/ 20% for movesText\r\n        \/\/ 20% for remainingPegsText\r\n        \/\/ 25% for restartButton\r\n        localScaleManager.scaleText(this.messageText, width, height * 0.20, Math.min(width, height * 0.20) * 0.1, 1, false);\r\n        this.messageText.setPosition(width \/ 2 - this.messageText.displayWidth \/ 2, height * 0.15);\r\n\r\n        localScaleManager.scaleText(this.movesText, width, height * 0.20, Math.min(width, height * 0.20) * 0.1, 1, false);\r\n        this.movesText.setPosition(width \/ 2 - this.movesText.displayWidth \/ 2, height * 0.35);\r\n\r\n        if (this.remainingPegsText) {\r\n            localScaleManager.scaleText(this.remainingPegsText, width, height * 0.20, Math.min(width, height * 0.20) * 0.1, 1, false);\r\n            this.remainingPegsText.setPosition(width \/ 2 - this.remainingPegsText.displayWidth \/ 2, height * 0.55);\r\n        }\r\n\r\n        localScaleManager.scaleSprite(this.restartButton, width, height * 0.25, Math.min(width, height * 0.20) * 0.1, 1, true);\r\n        this.restartButton.setPosition(width \/ 2, height * 0.825);\r\n    }\r\n\r\n    startGame() {\r\n        this.time.delayedCall(100, function () {\r\n            let theGame = new TheGame('TheGame');\r\n            this.scene.add('TheGame', theGame, true);\r\n            this.scene.remove('GameOver');\r\n        }, [], this)\r\n    }\r\n}<\/pre>\n<p>Check out the game in your browser window\u00a0<a href=\"https:\/\/www.netexl.com\/blog\/demo\/17\/index.html\" target=\"_blank\">here<\/a>. Try resizing it in your browser window to see scaling at work.<\/p>\n<p>You can\u00a0play the game here<\/p>\n\n<!-- iframe plugin v.5.2 wordpress.org\/plugins\/iframe\/ -->\n<iframe loading=\"lazy\" src=\"https:\/\/www.netexl.com\/blog\/demo\/17\/index.html\" width=\"600px\" height=\"600px\" scrolling=\"yes\" class=\"iframe-class\" frameborder=\"0\"><\/iframe>\n\n","protected":false},"excerpt":{"rendered":"<p>I just checked Phaser release log and have been pleasantly surprised to see the new Scale Manager available now with version 3.16.1 so we are now going to update our Peg Solitaire code to make\u00a0it responsive and use the newly released scale manager. One thing I would like to do away with is to stop using absolute size values and calculate those values depending upon display area available for the game (just like\u00a0we did it in Phaser 2). The first thing we need to change with 3.16.x version is to add scale property in game configuration as following let config = { type: Phaser.AUTO, scale: { parent: &#8216;mygame&#8217;, mode: Phaser.Scale.FIT, autoCenter: Phaser.Scale.CENTER_BOTH, width: 500, height: 500 }, backgroundColor: 0xFF0000, scene: TheGame } let game = new Phaser.Game(config); Another change I did was to remove css for canvas element which we had earlier added to center align our game. Our app.css[&#8230;]<\/p>\n","protected":false},"author":5,"featured_media":293,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3,24,4],"tags":[],"class_list":["post-1089","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-html5","category-javascript","category-phaser"],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.netexl.com\/blog\/wp-json\/wp\/v2\/posts\/1089","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.netexl.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.netexl.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.netexl.com\/blog\/wp-json\/wp\/v2\/users\/5"}],"replies":[{"embeddable":true,"href":"https:\/\/www.netexl.com\/blog\/wp-json\/wp\/v2\/comments?post=1089"}],"version-history":[{"count":4,"href":"https:\/\/www.netexl.com\/blog\/wp-json\/wp\/v2\/posts\/1089\/revisions"}],"predecessor-version":[{"id":1103,"href":"https:\/\/www.netexl.com\/blog\/wp-json\/wp\/v2\/posts\/1089\/revisions\/1103"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.netexl.com\/blog\/wp-json\/wp\/v2\/media\/293"}],"wp:attachment":[{"href":"https:\/\/www.netexl.com\/blog\/wp-json\/wp\/v2\/media?parent=1089"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.netexl.com\/blog\/wp-json\/wp\/v2\/categories?post=1089"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.netexl.com\/blog\/wp-json\/wp\/v2\/tags?post=1089"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}