From 2292961bcd7e658adab6aae43df9093db1654315 Mon Sep 17 00:00:00 2001 From: mclemente Date: Fri, 21 May 2021 23:57:09 -0300 Subject: [PATCH] FVTT 0.8 support --- .github/workflows/main.yml | 82 ++- DrawingTools/Drawing.js | 690 ------------------------ DrawingTools/DrawingConfig.js | 285 ---------- DrawingTools/DrawingHUD.js | 116 ---- DrawingTools/DrawingTools.js | 100 ---- DrawingTools/DrawingsLayer.js | 215 -------- DrawingTools/RotationHandle.js | 109 ---- Furnace.css | 135 ----- Macros/Macros.js | 410 -------------- Patches/Patches.js | 71 --- QoL/Combat.js | 29 - QoL/Debug.js | 50 -- QoL/Entities.js | 211 -------- QoL/HBHelpers.js | 15 - QoL/Macros.js | 16 - QoL/Playlist.js | 77 --- QoL/Tokens.js | 187 ------- README.md | 136 +---- lang/en.json | 68 --- lang/es.json | 68 --- lang/pt-BR.json | 68 --- lang/zh-CN.json | 3 - libs/LICENSE-just-handelbars-helpers.md | 21 - libs/LICENSE-split-html.md | 7 - libs/LICENSE-tokenize-this.txt | 21 - libs/TokenizeThis.js | 478 ---------------- libs/h.min.js | 1 - libs/hljs/LICENSE | 29 - libs/hljs/README.md | 407 -------------- libs/hljs/highlight.pack.js | 2 - libs/hljs/styles/github-gist.css | 79 --- libs/split-html.js | 148 ----- libs/tokenizer.min.js | 27 - module.json | 112 ++-- packs/macros.db | 17 - templates/drawing-config.html | 193 ------- templates/drawing-hud.html | 57 -- templates/playlist-now-playing.html | 58 -- templates/split-journal.html | 47 -- templates/token-folder-drop.html | 34 -- 40 files changed, 87 insertions(+), 4792 deletions(-) delete mode 100644 DrawingTools/Drawing.js delete mode 100644 DrawingTools/DrawingConfig.js delete mode 100644 DrawingTools/DrawingHUD.js delete mode 100644 DrawingTools/DrawingTools.js delete mode 100644 DrawingTools/DrawingsLayer.js delete mode 100644 DrawingTools/RotationHandle.js delete mode 100644 Furnace.css delete mode 100644 Macros/Macros.js delete mode 100644 Patches/Patches.js delete mode 100644 QoL/Combat.js delete mode 100644 QoL/Debug.js delete mode 100644 QoL/Entities.js delete mode 100644 QoL/HBHelpers.js delete mode 100644 QoL/Macros.js delete mode 100644 QoL/Playlist.js delete mode 100644 QoL/Tokens.js delete mode 100644 lang/en.json delete mode 100644 lang/es.json delete mode 100644 lang/pt-BR.json delete mode 100644 lang/zh-CN.json delete mode 100644 libs/LICENSE-just-handelbars-helpers.md delete mode 100644 libs/LICENSE-split-html.md delete mode 100644 libs/LICENSE-tokenize-this.txt delete mode 100644 libs/TokenizeThis.js delete mode 100644 libs/h.min.js delete mode 100644 libs/hljs/LICENSE delete mode 100644 libs/hljs/README.md delete mode 100644 libs/hljs/highlight.pack.js delete mode 100644 libs/hljs/styles/github-gist.css delete mode 100644 libs/split-html.js delete mode 100644 libs/tokenizer.min.js delete mode 100644 packs/macros.db delete mode 100644 templates/drawing-config.html delete mode 100644 templates/drawing-hud.html delete mode 100644 templates/playlist-now-playing.html delete mode 100644 templates/split-journal.html delete mode 100644 templates/token-folder-drop.html diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a8c0736..02c0b68 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,60 +1,40 @@ -on: - push: - # Sequence of patterns matched against refs/tags - tags: - - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 +name: Release Creation -name: Upload Release Assets +on: + release: + types: [published] jobs: build: - name: Upload Release Asset runs-on: ubuntu-latest steps: - - name: Checkout code - uses: actions/checkout@v2 + - uses: actions/checkout@v2 - - name: Get the version - id: get_version - run: | - echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//} - echo ::set-output name=VERSION_NUMBER::${GITHUB_REF/refs\/tags\/v/} + # Substitute the Manifest and Download URLs in the module.json + - name: Substitute Manifest and Download Links For Versioned Ones + id: sub_manifest_link_version + uses: microsoft/variable-substitution@v1 + with: + files: 'module.json' + env: + version: ${{github.event.release.tag_name}} + url: https://github.com/${{github.repository}} + manifest: https://github.com/${{github.repository}}/releases/latest/download/module.json + download: https://github.com/${{github.repository}}/releases/download/${{github.event.release.tag_name}}/module.zip - #Substitute the Manifest and Download URLs in the module.json - - name: Substitute Manifest and Download Links For Versioned Ones - id: sub_manifest_link_version - uses: microsoft/variable-substitution@v1 - with: - files: 'module.json' - env: - version: ${{ steps.get_version.outputs.VERSION_NUMBER }} - manifest: https://github.com/${{github.repository}}/releases/download/latest/module.json - download: https://github.com/${{github.repository}}/releases/download/${{ steps.get_version.outputs.VERSION }}/furnace.zip + # Create a zip file with all files required by the module to add to the release + - run: zip -r ./module.zip module.json - - name: Zip files - run: zip -r furnace * -x ".github/*" - - # Create a release for this specific version - - name: Create Release - id: create_version_release - uses: ncipollo/release-action@v1 - with: - tag: ${{ steps.get_version.outputs.VERSION }} - name: Release ${{ steps.get_version.outputs.VERSION }} - draft: false - prerelease: false - token: ${{ secrets.GITHUB_TOKEN }} - artifacts: './module.json,./furnace.zip' - - # Update the 'latest' release - - name: Create Release - id: create_latest_release - uses: ncipollo/release-action@v1 - with: - allowUpdates: true - name: Latest - draft: false - prerelease: false - token: ${{ secrets.GITHUB_TOKEN }} - artifacts: './module.json,./furnace.zip' - tag: latest + # Create a release for this specific version + - name: Update Release with Files + id: create_version_release + uses: ncipollo/release-action@v1 + with: + allowUpdates: true # Set this to false if you want to prevent updating existing releases + name: ${{ github.event.release.name }} + draft: false + prerelease: false + token: ${{ secrets.GITHUB_TOKEN }} + artifacts: './module.json, ./module.zip' + tag: ${{ github.event.release.tag_name }} + body: ${{ github.event.release.body }} \ No newline at end of file diff --git a/DrawingTools/Drawing.js b/DrawingTools/Drawing.js deleted file mode 100644 index 7daf87e..0000000 --- a/DrawingTools/Drawing.js +++ /dev/null @@ -1,690 +0,0 @@ -class FurnaceDrawing extends Drawing { - - /* Override the constructor's name so we get createDrawing instead of createFurnaceDrawing */ - static get name() { - return "Drawing" - } - - /** - * Provide a singleton reference to the DrawingConfig sheet for this Drawing instance - * @type {FurnaceDrawingConfig} - */ - get sheet() { - if (!this._sheet) this._sheet = new FurnaceDrawingConfig(this); - return this._sheet; - } - - static async create(data, options = {}) { - for (let key of ["x", "y", "width", "height"]) { - if (data[key]) data[key] = Math.round(data[key]); - } - if (!data.author) - data.author = game.user.id - if (data.z == 0) { - let scene = canvas.scene; - let drawings = scene ? scene.data.drawings || [] : [] - if (drawings.length > 0) - data.z = drawings.reduce((a, v) => { return { z: Math.max(a.z, v.z) } }).z + 1 - } - return super.create(data, options); - } - getFlag(scope, key, def) { - let flag = undefined; - try { - flag = super.getFlag(scope, key) - } catch (err) { } - if (flag === undefined) - return def; - return flag; - } - - - /* -------------------------------------------- */ - - get type() { - return this.data.type; - } - get fillColor() { - return this.data.fillColor ? this.data.fillColor.replace("#", "0x") : 0x000000; - } - /* HACK: PIXI v5 seems to think an alpha of 0 means 'do not draw', so having alpha 0 fill with a texture means - * the texture doesn't get rendered at all. So we force a alpha of 0.0001 to make it draw and avoid - * having a tinted texture as well. - */ - get fillAlpha() { - return this.data.fillAlpha > 0 ? this.data.fillAlpha : 0.0001; - } - get strokeColor() { - return this.data.strokeColor ? this.data.strokeColor.replace("#", "0x") : 0x000000; - } - get strokeAlpha() { - return this.data.strokeAlpha > 0 ? this.data.strokeAlpha : 0.0001; - } - get fillType() { - let fillType = this.getFlag("furnace", "fillType", null) - if (fillType === null) - fillType = this.data.fillType - return fillType; - } - get textureWidth() { - return this.getFlag("furnace", "textureWidth", 0); - } - get textureHeight() { - return this.getFlag("furnace", "textureHeight", 0); - } - get textureAlpha() { - return this.getFlag("furnace", "textureAlpha", 1); - } - get usesFill() { - return [FURNACE_DRAWING_FILL_TYPE.SOLID, - FURNACE_DRAWING_FILL_TYPE.PATTERN, - FURNACE_DRAWING_FILL_TYPE.STRETCH].includes(this.fillType) - } - get usesTexture() { - return [FURNACE_DRAWING_FILL_TYPE.PATTERN, - FURNACE_DRAWING_FILL_TYPE.STRETCH, - FURNACE_DRAWING_FILL_TYPE.CONTOUR, - FURNACE_DRAWING_FILL_TYPE.FRAME].includes(this.fillType) && - this.data.texture && this.textureAlpha > 0 - } - get isTiled() { - return [FURNACE_DRAWING_FILL_TYPE.PATTERN, - FURNACE_DRAWING_FILL_TYPE.CONTOUR].includes(this.fillType) - } - - /* Put the stroke on the outisde */ - get alignment() { - if (this.isPolygon) - return 0.5 - else - return 1; - } - /* _pendingText gets used by core to do weird stuff which messed with the update of text contents */ - get _pendingText() { - return this.data.text; - } - set _pendingText(value) { - } - /* -------------------------------------------- */ - /* Rendering */ - /* -------------------------------------------- */ - - /** - * Draw a single tile - * @return {Promise} - */ - async draw() { - this.clear(); - // Create child elements - this.texture = null; - this.bg = null; - - - if (this.type == CONST.DRAWING_TYPES.TEXT) { - this.img = this.addChild(new PIXI.Text()); - } else { - this.img = this.addChild(new PIXI.Graphics()); - } - this.text = this.addChild(new PIXI.Text()); - - // Control Border - this._createFrame(); - //this.rotateHandle = this.addChild(new RotationHandle(this)); - - // Try to load the texture if one is set - if (this.usesTexture) { - try { - this.texture = await loadTexture(this.data.texture); - // Ensure playback state for videos - let source = this.texture.baseTexture.source; - if (source && source.tagName === "VIDEO") { - source.loop = true; - source.muted = true; - source.play(); - } - - if (this.texture) { - let sprite = null - if (this.isTiled) - sprite = new PIXI.extras.TilingSprite(this.texture) - else - sprite = new PIXI.Sprite(this.texture); - this.bg = this.addChild(new PIXI.Container()); - this.bg.tile = this.bg.addChild(sprite) - } - } catch { - this.texture = null; - this.bg = null; - } - } - // Render the Tile appearance - this.refresh(); - - // Enable interaction - only if the Drawing has an ID (not a preview) - if (this.id) { - this.activateListeners(); - // Rotate handler - /* - this.rotateHandle.addEventListeners(this.layer, { - canclick: event => !this.data.locked - })*/ - } - - // Return the drawn Tile - return this; - } - - updateDragPosition(position) { - if (this.type == CONST.DRAWING_TYPES.FREEHAND) { - let now = Date.now(); - let lastMove = this._lastMoveTime || 0 - if (this.data.points.length > 1 && now - lastMove < Drawing.FREEHAND_SAMPLE_RATE) - this.data.points.pop(); - else - this._lastMoveTime = now; - - this.addPolygonPoint(position) - } else if (this.type == CONST.DRAWING_TYPES.POLYGON) { - if (this.data.points.length > 1) - this.data.points.pop() - this.data.points.push([parseInt(position.x), parseInt(position.y)]) - } else { - this._updateDimensions(position, { snap: false }) - } - } - - addPolygonPoint(position) { - // Points should always have at least one point in it (the origin) - let points = this.data.points; - let drag = this.data.points.pop(); - let last_point = drag; - - let point = [parseInt(position.x), parseInt(position.y)] - - // Always keep our origin point as the first point - if (this.data.points.length == 0) - points.push(drag) - else - last_point = points[points.length - 1]; - // First add our polygon point if it's a new point. - if (point[0] != last_point[0] || point[1] != last_point[1]) - points.push(point) - // then add our drag position back to the list - points.push(drag) - } - - /* -------------------------------------------- */ - refresh() { - // Imagine a case (race condition?) where a refresh is called before draw was called, or - // draw crashed early and didn't finish initialization the object's graphics. - if (!this.img) - return this.draw(); - - // PIXI.Text doesn't have a `.clear()` - if (this.img instanceof PIXI.Graphics) { - this.img.clear().lineStyle(this.data.strokeWidth, this.strokeColor, this.strokeAlpha, this.alignment) - - // Set fill if needed - if (this.usesFill) - this.img.beginFill(this.fillColor, this.fillAlpha); - } - // Render the actual shape/drawing - switch (this.type) { - case CONST.DRAWING_TYPES.RECTANGLE: - this.renderRectangle(this.img) - this.renderSubText(this.text) - break; - case CONST.DRAWING_TYPES.ELLIPSE: - this.renderEllipse(this.img) - this.renderSubText(this.text) - break; - case CONST.DRAWING_TYPES.POLYGON: - this.renderPolygon(this.img) - this.renderSubText(this.text) - break; - case CONST.DRAWING_TYPES.FREEHAND: - this.renderFreehand(this.img) - this.renderSubText(this.text) - break; - case CONST.DRAWING_TYPES.TEXT: - this.renderText(this.img) - break; - } - - // Finish filling data - if (this.img instanceof PIXI.Graphics && this.usesFill) - this.img.endFill() - - // If a texture background was set, then we render/mask it here. - if (this.bg) { - this.bg.alpha = this.textureAlpha - if (this.isTiled) { - this.bg.tile.width = this.data.width; - this.bg.tile.height = this.data.height; - } else { - // PIXI.Sprite does not like negative width/height, and the TilingSprite does not like having its position set. - this.bg.tile.position.set(this.data.width > 0 ? 0 : this.data.width, this.data.height > 0 ? 0 : this.data.height) - this.bg.tile.width = Math.abs(this.data.width); - this.bg.tile.height = Math.abs(this.data.height); - } - if (this.isTiled && - this.textureWidth > 0 && this.textureHeight > 0) { - let scale_x = this.textureWidth / this.texture.width; - let scale_y = this.textureHeight / this.texture.height; - this.bg.tile.tileScale.set(scale_x, scale_y) - } - // Can't clone a PIXI.Text, if mask onto a text, we re-render it. - if (this.bg.tile.mask) this.bg.removeChild(this.bg.tile.mask).destroy({ children: true }) - if (this.type == CONST.DRAWING_TYPES.TEXT) { - this.bg.tile.mask = this.bg.addChild(new PIXI.Text()); - this.renderText(this.bg.tile.mask) - // Mask is only applied on the red channel for some reason. - this.bg.tile.mask.alpha = 1.0; - this.bg.tile.mask.style.fill = 0xFFFFFF; - } else { - this.bg.tile.mask = this.bg.addChild(this.img.clone()); - this.bg.tile.mask.alpha = 1.0; - } - // In case of polygons being out of bounds/scaled, the `this.img.cone()` - // does not copy the position/scale so we need to do it manually. - this.bg.tile.mask.pivot = this.img.pivot; - this.bg.tile.mask.position = this.img.position; - this.bg.tile.mask.scale = this.img.scale; - this._applyMirroring(this.bg); - } - - // Set Tile position, rotation and alpha - this.pivot.set(this.data.width / 2, this.data.height / 2); - this.rotation = toRadians(this.data.rotation); - this.position.set(this.data.x + this.pivot.x, this.data.y + this.pivot.y); - this.alpha = this.data.hidden ? 0.5 : 1.0; - this._applyMirroring(this.img); - - // Toggle visibility - this.visible = !this.data.hidden || game.user.isGM; - - // Draw frame - this._refreshFrame() - - // Reset hit area. img doesn't set a hit area automatically if we don't use 'fill', - // so we need to manually define it. Also take into account negative width/height. - this.hitArea = this.frame.getLocalBounds() - } - - - _applyMirroring(graphics) { - let width = this.data.width; - let height = this.data.height; - if (graphics instanceof PIXI.Text) { - const bounds = graphics.getLocalBounds(); - width = bounds.width * Math.sign(width); - height = bounds.height * Math.sign(height); - } - graphics.pivot.set(width / 2, height / 2); - graphics.position.set(this.data.width / 2, this.data.height / 2); - graphics.scale.y = Math.abs(graphics.scale.y) * (this.getFlag("furnace", "mirrorVert") ? -1 : 1) - graphics.scale.x = Math.abs(graphics.scale.x) * (this.getFlag("furnace", "mirrorHoriz") ? -1 : 1) - } - /** - * Refresh the boundary frame which outlines the Drawing shape - * @private - */ - _refreshFrame() { - let { x, y, width, height } = this.img.getLocalBounds(); - // Fix PIXI bug that returns 0,0 on rectangles with a negative value in either width or height - if (width === 0 && height === 0) { - width = this.data.width; - height = this.data.height; - } - /* Scale the width/height because of text drawings */ - width *= Math.abs(this.img.scale.x) - height *= Math.abs(this.img.scale.y) - - const pad = 10; - let padX = pad, padY = pad; - if (width < 0) padX *= -2; - if (height < 0) padY *= -2; - - super._refreshFrame({x, y, width, height}); - - /* - // Draw Rotation handle - this.rotateHandle.position.set(x + width / 2 + padX / 2, y - padY); - this.rotateHandle.scale.set(Math.sign(this.data.width), Math.sign(this.data.height)); - this.rotateHandle.draw()*/ - - // Don't show the frame when we're creating a new drawing, unless it's text. - this.frame.visible = this._controlled && (this.id || this.type == CONST.DRAWING_TYPES.TEXT); - this.frame.handle.visible = this.frame.visible && !this.data.locked; - //this.rotateHandle.visible = this.frame.handle.visible; - } - - - renderRectangle(graphics) { - let half_stroke = this.data.strokeWidth * this.alignment; - graphics.drawRect(half_stroke, half_stroke, this.data.width - 2 * half_stroke, this.data.height - 2 * half_stroke); - } - - renderEllipse(graphics) { - let half_width = this.data.width / 2; - let half_height = this.data.height / 2; - let half_stroke = this.data.strokeWidth * this.alignment; - graphics.drawEllipse(half_width, half_height, Math.abs(half_width) - half_stroke, Math.abs(half_height) - half_stroke); - } - - // Attribution : - // The equations for how to calculate the bezier control points are derived from - // Rob Spencer's article from 2010. - // http://scaledinnovation.com/analytics/splines/aboutSplines.html - // Returns the cp1 of the previous point and cp0 of the current point. - // This is so it's a bit more optimized than calculating the distances - // twice for each point. - _bezierControlPoints(factor, previous, point, next) { - // Get vector of the opposite line - let vector = { x: next.x - previous.x, y: next.y - previous.y }; - - // Calculate the proportional sizes of each neighboring line - let precedingDistance = Math.hypot(previous.x - point.x, previous.y - point.y); - let followingDistance = Math.hypot(point.x - next.x, point.y - next.y); - let totalDistance = precedingDistance + followingDistance; - let cp0Distance = factor * (precedingDistance / totalDistance); - let cp1Distance = factor * (followingDistance / totalDistance); - if (totalDistance == 0) - cp0Distance = cp1Distance = 0; - // Calculate the control points as porportional from our point in the direction of the - // hypothenus' vector, in either direction from our central point - return { - cp1: { - x: point.x - vector.x * cp0Distance, - y: point.y - vector.y * cp0Distance - }, - next_cp0: { - x: point.x + vector.x * cp1Distance, - y: point.y + vector.y * cp1Distance - } - } - } - - _toPoint(point) { - return { - x: point[0], - y: point[1] - } - } - - renderFreehand(graphics) { - let points = this.data.points; - if (points === undefined || points.length == 0) return; - let origin = this._toPoint(points[0]); - let bezierFactor = this.data.bezierFactor || 0; - - graphics.moveTo(origin.x, origin.y) - if (points.length > 2) { - let point = this._toPoint(points[1]); - let previous = origin; // {x:0, y:0}; - let next = this._toPoint(points[2]); - let last = this._toPoint(points[points.length - 1]); - let closedLoop = (last.x == previous.x && last.y == previous.y) - let cp0; - - // With PIXI 5, line is broken when using bezierCurveTo and having a factor of 0 - if (bezierFactor > 0) { - // Verify if we're closing a loop here and calculate the previous bezier control point - if (closedLoop) { - last = this._toPoint(points[points.length - 2]); - let { next_cp0 } = this._bezierControlPoints(bezierFactor, last, previous, point); - cp0 = next_cp0; - } - // Draw the first line - let { cp1, next_cp0 } = this._bezierControlPoints(bezierFactor, previous, point, next); - if (closedLoop) - graphics.bezierCurveTo(cp0.x, cp0.y, cp1.x, cp1.y, point.x, point.y); - else - graphics.quadraticCurveTo(cp1.x, cp1.y, point.x, point.y); - cp0 = next_cp0; - } else { - graphics.lineTo(point.x, point.y) - } - previous = point; - point = next; - - // Draw subsequent lines - for (let i = 2; i < points.length; i++) { - if (i == points.length - 1) - next = this._toPoint(points[1]); - else - next = this._toPoint(points[i + 1]); - if (bezierFactor > 0) { - let { cp1, next_cp0 } = this._bezierControlPoints(bezierFactor, previous, point, next); - // If the last line, draw it as quadratic if it's not a closed loop. - if (i == points.length - 1 && !closedLoop) - graphics.quadraticCurveTo(cp0.x, cp0.y, point.x, point.y); - else - graphics.bezierCurveTo(cp0.x, cp0.y, cp1.x, cp1.y, point.x, point.y); - cp0 = next_cp0; - } else { - graphics.lineTo(point.x, point.y) - } - previous = point; - point = next; - } - } else if (points.length == 2) { // if point.length < 2, don't do anything - let point = this._toPoint(points[1]); - graphics.lineTo(point.x, point.y) - } - - // Draw circles on each end point to make it look nicer. - // an arc with radius 0 makes it not draw anything. so we put radius of 0.1 and let the - // strokeWidth take care of actually drawing our circle. - // We also need to move to the angle 0 of the arc otherwise we'd cause a small line - // from(0, 0) to(0.1, 0) which can have huge effects visually if the stroke width is large enough. - graphics.moveTo(origin.x + 0.1, origin.y) - graphics.arc(origin.x, origin.y, 0.1, 0, Math.PI * 2) - if (points.length > 1) { - let point = this._toPoint(points[points.length - 1]); - graphics.moveTo(point.x + 0.1, point.y) - this.img.arc(point.x, point.y, 0.1, 0, Math.PI * 2) - } - } - - renderPolygon(graphics) { - this.renderFreehand(graphics) - } - - renderText(sprite) { - sprite.style = new PIXI.TextStyle({ - fontFamily: this.data.fontFamily, - fontSize: this.data.fontSize, - fill: this.data.textColor, - stroke: this.data.strokeColor, - strokeThickness: this.data.strokeWidth - }); - sprite.alpha = this.data.textAlpha; - sprite.text = this.data.text; - this._scaleText(sprite) - } - renderSubText(sprite) { - sprite.style = new PIXI.TextStyle({ - fontFamily: this.data.fontFamily, - fontSize: this.data.fontSize, - fill: this.data.textColor, - stroke: "#111111", - strokeThickness: Math.max(Math.round(this.data.fontSize / 32), 2), - dropShadow: true, - dropShadowColor: "#000000", - dropShadowBlur: Math.max(Math.round(this.data.fontSize / 16), 2), - dropShadowAngle: 0, - dropShadowDistance: 0, - align: "center", - wordWrap: true, - wordWrapWidth: this.data.width - }); - sprite.alpha = this.data.textAlpha; - sprite.text = this.data.text; - let bounds = sprite.getLocalBounds() - sprite.position.set(this.data.width / 2 - bounds.width / 2, this.data.height / 2 - bounds.height / 2) - } - - _scaleText(graphics) { - let bounds = graphics.getLocalBounds() - let scale_x = 1; - let scale_y = 1; - if (this.id || this.id === null) { - if (bounds.width > 0 && bounds.height > 0) { - scale_x = this.data.width / bounds.width; - scale_y = this.data.height / bounds.height; - } - } else if (this.id === undefined) { - /* A bit ugly but `this.id == null` means we're moving the drawing, - * and `this.id == undefined` means it's being currently drawn. - * Here, we update the width/height and set the polygon position relative - * to the starting point's bounds. - */ - this.data.width = bounds.width; - this.data.height = bounds.height; - } - graphics.scale.set(scale_x, scale_y) - } - - - /* -------------------------------------------- */ - - /** - * Get coordinates of a point after a rotation around another point - * @param {Object} dest - */ - _rotatePosition(coordinates, axis, angle) { - // No need to do the math if there is no rotation - if (angle) { - // Translate the coordinates so the rotation axis is (0, 0) - let x = coordinates.x - axis.x; - let y = coordinates.y - axis.y; - - /** - * Formula from https://academo.org/demos/rotation-about-point/ - * x′ = x*cos(θ) − y*sin(θ) - * y′ = y*cos(θ) + x*sin(θ) - */ - let new_x = x * Math.cos(angle) - y * Math.sin(angle); - let new_y = y * Math.cos(angle) + x * Math.sin(angle); - // Translate back from origin point to our center - coordinates.x = new_x + axis.x - coordinates.y = new_y + axis.y - } - return coordinates; - } - /** - * Update the tile dimensions given a requested destination point - * @param {Object} dest - * @param {Boolean} snap - * @param {Boolean} constrain - * @private - */ - _updateDimensions(dest, { snap = true } = {}) { - - // this.rotation is the PIXI object's rotation which is already in radians - dest = this._rotatePosition(dest, this.center, -this.rotation) - - // Determine destination position - if (snap) dest = canvas.grid.getSnappedPosition(dest.x, dest.y); - - // Determine change - let dx = dest.x - this.data.x, - dy = dest.y - this.data.y; - if (dx === 0) dx = canvas.dimensions.size * Math.sign(this.data.width); - if (dy === 0) dy = canvas.dimensions.size * Math.sign(this.data.height); - - let angle = ((a) => ((a % 360) + 360) % 360)(this.data.rotation) - if (angle > 90 && angle < 270) { - /*this.data.rotation = angle - 180; - dx *= -1; - dy *= -1;*/ - } - // Resize, ignoring aspect ratio - this.data.width = dx; - this.data.height = dy; - - } - - /** - * Formula from : https://onlinemschool.com/math/library/vector/angl/ - * cos(α) = (a·b) / (|a|·|b|) - * If we take our base (rotation 0) vector as (0, -1), then it simplifies - * the equation into : - * cos(α) = -b.y / |b| - */ - _updateRotation(coordinates) { - let center = this.center - let x = coordinates.x - center.x; - let y = coordinates.y - center.y; - let sign = Math.sign(this.data.height); - - /** - * The equation always gives us the shortest angle (< 180), but if our X - * is positive then we're to the right of the angle 0, and if it's negative, - * then we're to the left. - */ - let angle = Math.acos(sign * -y / Math.hypot(x, y)) * Math.sign(x) * sign; - this.data.rotation = Math.round(toDegrees(angle)) - } - - /* -------------------------------------------- */ - /* Event Handlers */ - /* -------------------------------------------- */ - - /** - * Default handling for Placeable mouse-over hover event - * @private - * - * _onMouseOver could be called if pressing Alt. Don't highlight Drawings we don't own - * - */ - _onMouseOver(event) { - if (this.owner) { - super._onMouseOver(event); - if (this.layer._active) this.frame.visible = true; - } - } - _onMouseOut(event) { - super._onMouseOut(event); - this.frame.visible = this.layer._active && this._controlled; - } - _rescaleDimensions(original, dx, dy) { - let {width, height} = original; - // Avoid a divide by zero for polygon scale calculation - if (width == 0) original.width = 1; - if (height == 0) original.height = 1; - return super._rescaleDimensions(original, dx, dy); - } - - // I don't want this! - _onDrawingTextKeydown() {} - - /** - * Handle click event on a hovered tile - * @private - */ - _onMouseDown(event) { - // Remove the active Drawing HUD - canvas.hud.drawing.clear(); - // Control the Tile - this.control(); - } - - _onDragCancel(event) { - super._onDragCancel(event); - this.draw() - } - /** - * Event-handling logic for a right-mouse click event on the Drawing container - * @param {PIXI.interaction.InteractionEvent} event - * @private - */ - _onRightDown(event) { - const hud = canvas.hud.drawing, - state = hud._displayState; - if (hud.object === this && state !== hud.constructor.DISPLAY_STATES.NONE) hud.clear(); - else hud.bind(this); - } - -} diff --git a/DrawingTools/DrawingConfig.js b/DrawingTools/DrawingConfig.js deleted file mode 100644 index 87e628d..0000000 --- a/DrawingTools/DrawingConfig.js +++ /dev/null @@ -1,285 +0,0 @@ -/** - * Drawing Config Sheet - * @type {FormApplication} - * - * @params drawing {Drawing} The Drawing object being configured - * @params options {Object} Additional application rendering options - * @params options.preview {Boolean} Configure a preview version of a drawing which is not yet saved - */ -class FurnaceDrawingConfig extends DrawingConfig { - constructor() { - super(...arguments) - this._tabs[0].active = "settings"; - } - // Override the constructor's name - /*static get name() { - return "DrawingConfig" - }*/ - static get defaultOptions() { - const options = super.defaultOptions; - options.classes = ["sheet", "drawing-sheet"]; - options.title = game.i18n.localize("FURNACE.DRAWINGS.ConfigTitle"); - options.template = "modules/furnace/templates/drawing-config.html"; - options.height = 'auto'; - return options; - } - - /* -------------------------------------------- */ - - /** - * Construct and return the data object used to render the HTML template for this form application. - * @return {Object} - */ - getData() { - let data = duplicate(this.object.data) - data.fillType = this.object.fillType - data.textureWidth = this.object.textureWidth - data.textureHeight = this.object.textureHeight - data.textureAlpha = this.object.textureAlpha - return { - object: data, - enableTypeSelection: false, - options: this.options, - drawingTypes: CONFIG.furnaceDrawingTypes, - fillTypes: CONFIG.furnaceDrawingFillTypes, - availableFonts: this.getAvailableFonts(), - submitText: game.i18n.localize(`FURNACE.DRAWINGS.${this.preview ? "createDrawing" : "updateDrawing"}`) - } - } - - getAvailableFonts() { - let availableFonts = {} - for (let font of CONFIG.fontFamilies) - availableFonts[font] = font; - for (let [font, family] of Object.entries(CONFIG.WebSafeFonts)) - availableFonts[font] = family; - return availableFonts; - } - /* Make it interactive a little */ - activateListeners(html) { - super.activateListeners(html); - html.find("select[name=fillType]").change((ev) => this.updateFields(html)) - html.find("button[name=reset]").click((ev) => this.reset(ev, html)) - html.find("input,select").change((ev) => this.refresh(html)) - html.find("textarea").on('input', (ev) => this.refresh(html)) - this.updateFields(html) - html.find("textarea").focus() - } - - updateFields(html) { - // Get fill/drawing type. Use html because of the DrawingDefaultsConfig subclass. - let fillType = Number(html.find("select[name=fillType]").val()) - let drawingType = html.find("select[name=type]").val() - // Determine what options are to be available and which aren't - let enableFillOptions = (fillType != FURNACE_DRAWING_FILL_TYPE.NONE && - fillType != FURNACE_DRAWING_FILL_TYPE.CONTOUR && - fillType != FURNACE_DRAWING_FILL_TYPE.FRAME) - let enableTextureOptions = (fillType == FURNACE_DRAWING_FILL_TYPE.PATTERN || - fillType == FURNACE_DRAWING_FILL_TYPE.STRETCH || - fillType == FURNACE_DRAWING_FILL_TYPE.CONTOUR || - fillType == FURNACE_DRAWING_FILL_TYPE.FRAME) - let enableTextureSizeOptions = (fillType == FURNACE_DRAWING_FILL_TYPE.PATTERN) - let showBezierOptions = (drawingType == CONST.DRAWING_TYPES.POLYGON || drawingType == CONST.DRAWING_TYPES.FREEHAND); - let showTextOptions = drawingType == CONST.DRAWING_TYPES.TEXT; - - // Enable/Disable various options by setting their opacity and pointer-events. - let enable = { "pointer-events": "unset", "opacity": 1.0 }; - let disable = { "pointer-events": "none", "opacity": 0.5 }; - html.find("input[name=fillColor],input[name=fillAlpha]") - .closest(".form-group").css(enableFillOptions ? enable : disable) - html.find(".fill-section .notes,.texture-section").css(enableTextureOptions ? enable : disable) - html.find("input[name=textureWidth],input[name=textureHeight]") - .closest(".form-group").css(enableTextureSizeOptions ? enable : disable) - // Show/hide text options and fillAlpha - //html.find(".text-section")[showTextOptions ? "show" : "hide"]() - html.find("input[name=fillAlpha]").closest(".form-group")[!showTextOptions ? "show" : "hide"]() - html.find("input[name=fillColor]").closest(".form-group")[!showTextOptions ? "show" : "hide"]() - html.find("input[name=strokeAlpha]").closest(".form-group")[!showTextOptions ? "show" : "hide"]() - html.find("input[name=bezierFactor]").closest(".form-group")[showBezierOptions ? "show" : "hide"]() - // FIXME: module-to-core sanity check server side, contour fill isn't valid for text. - html.find(`option[value=${FURNACE_DRAWING_FILL_TYPE.CONTOUR}],option[value=${FURNACE_DRAWING_FILL_TYPE.FRAME}]`).attr("disabled", showTextOptions) - } - - - reset(ev, html) { - ev.preventDefault(); - let type = html.find("select[name=type]").val() - let defaults = canvas.drawings.getDefaultData(type) - for (let input of html.find("input")) { - let name = input.getAttribute("name") - // FIXME: flags - if (!["id", "x", "y", "z", "width", "height", "owner", "hidden", "locked", "flags"].includes(name)) - input.value = defaults[name] - } - html.find("select[name=fillType]").val(defaults["fillType"]) - this.updateFields(html) - this.refresh(html) - } - - fixData(data) { - data["flags.furnace"] = { - textureWidth: data.textureWidth, - textureHeight: data.textureHeight, - textureAlpha: data.textureAlpha, - fillType: null - } - if (!Object.values(CONST.DRAWING_FILL_TYPES).includes(data.fillType)) { - data["flags.furnace"]["fillType"] = data.fillType; - data.fillType = CONST.DRAWING_FILL_TYPES.PATTERN; - } - delete data.textureWidth - delete data.textureHeight - delete data.textureAlpha - return data - } - - async refresh(html) { - - let realData = this.object.data; - - if (html) { - // Temporarily change the object's data in order to preview changes - let newData = this.fixData(validateForm(html[0])); - // Add missing items like 'type' from disabled field. - this.object.data = mergeObject(realData, newData, { inplace: false }); - } - // We actually call draw here and not refresh in case we changed the texture but also - // because if we get a full redraw due to another user creating their own drawings, - // then the refresh will fail because the object's PIXI elements will have been destroyed. - // FIXME: not sure what happens to the sheet in that case? would the object itself become - // invalid or would it still work anyway? - await this.object.draw(); - this.object.data = realData; - } - - /* -------------------------------------------- */ - - /** - * This method is called upon form submission after form data is validated - * @param event {Event} The initial triggering submission event - * @param formData {Object} The object of validated form data with which to update the object - * @private - */ - _updateObject(event, formData) { - formData = this.fixData(formData) - if (this.object.id) { - if (!this.object.owner) throw game.i18n.localize("FURNACE.DRAWINGS.NotAuthorizedToConfigure"); - formData["id"] = this.object.id; - this.object.update(formData) - .then(() => this.object.layer.updateStartingData(this.object)) - } else { - // Creating a new object will need to have things that aren't in the form - //such as 'type' which is disabled and therefore automatically skipped from the data. - mergeObject(this.object.data, formData); - // Refreshing will cause sizes to be recalculated first - this.object.refresh(); - this.object.constructor.create(this.object.data); - } - } - - _onRangeChange(event) { - event.preventDefault(); - let span = $(event.target).siblings(".range-value"); - let percent = Math.floor(event.target.value * 100) - span.text(percent + "%"); - } - /* -------------------------------------------- */ - - /** - * Extend the application close method to clear any preview if one exists - */ - close(...args) { - super.close(...args); - // Refresh in case we changed anything - this.refresh(); - } -} - -/** - * Drawing Config Sheet for Default values - * @type {FormApplication} - * - * @params drawing {Drawing} The Drawing object being configured - * @params options {Object} Additional application rendering options - * @params options.preview {Boolean} Configure a preview version of a drawing which is not yet saved - */ -class DrawingDefaultsConfig extends FurnaceDrawingConfig { - constructor(object, type, options) { - super(object, options); - - // this.object here is actually the Drawingslayer - this._defaults = {} - this.type = type || CONST.DRAWING_TYPES.RECTANGLE - for (let type of Object.values(CONST.DRAWING_TYPES)) { - this._defaults[type] = this.object.getStartingData(type); - } - } - - /* -------------------------------------------- */ - - /** - * Construct and return the data object used to render the HTML template for this form application. - * @return {Object} - */ - getData() { - return { - object: duplicate(this._defaults[this.type]), - enableTypeSelection: true, - options: this.options, - drawingTypes: CONFIG.furnaceDrawingTypes, - fillTypes: CONFIG.furnaceDrawingFillTypes, - availableFonts: this.getAvailableFonts(), - submitText: game.i18n.localize("FURNACE.DRAWINGS.setDefaults") - } - } - - /* List to changing the type of of Drawing to set */ - activateListeners(html) { - super.activateListeners(html); - html.find("select[name=type]").change((ev) => this.changeType(html)) - } - - /* When the user switches types, save their data for that type and re-render */ - changeType(html) { - // Get the current saved settings. Override data.type since it has the new type. - let data = validateForm(html[0]); - let newType = data.type; - data.type = this.type; - this._defaults[this.type] = data; - this.type = newType; - - // Re-render the sheet with the new type data - this.render(true); - } - - refresh() { - // We don't need to refresh anything in this case. - } - /* -------------------------------------------- */ - - /** - * This method is called upon form submission after form data is validated - * @param event {Event} The initial triggering submission event - * @param formData {Object} The object of validated form data with which to update the object - * @private - */ - _updateObject(event, formData) { - // Get the data from the current page, and data saved from the other pages - this._defaults[formData.type] = formData - for (let type in this._defaults) { - this.object.updateStartingData({ data: this._defaults[type] }) - } - // Set the tool to the last configured type. - let tool = "select"; - let tools = ui.controls.control.tools.map(t => t.name) - for (let t of tools) { - if (t[0] === formData.type) { - tool = t; - break; - } - } - ui.controls.control.activeTool = tool; - ui.controls.render(); - } - -} \ No newline at end of file diff --git a/DrawingTools/DrawingHUD.js b/DrawingTools/DrawingHUD.js deleted file mode 100644 index 44b298c..0000000 --- a/DrawingTools/DrawingHUD.js +++ /dev/null @@ -1,116 +0,0 @@ - -/** - * An implementation of the PlaceableHUD base class which renders a heads-up-display interface for Drawing objects. - * @type {BasePlaceableHUD} - */ -class FurnaceDrawingHUD extends DrawingHUD { - // Override the constructor's name - /*static get name() { - return "DrawingHUD" - }*/ - /** - * Assign the default options which are supported by the entity edit sheet - * @type {Object} - */ - static get defaultOptions() { - return mergeObject(super.defaultOptions, { - template: "modules/furnace/templates/drawing-hud.html" - }); - } - - getData() { - const data = super.getData(); - return mergeObject(data, { - isText: this.object.type == CONST.DRAWING_TYPES.TEXT, - mirrorVertClass: this.object.getFlag("furnace", "mirrorVert") ? "active" : "", - mirrorHorizClass: this.object.getFlag("furnace", "mirrorHoriz") ? "active" : "", - }); - } - - /* -------------------------------------------- */ - - /* -------------------------------------------- */ - - setPosition() { - let width = this.object.data.width - let height = this.object.data.height - let x = this.object.data.x + (width > 0 ? 0 : width) - let y = this.object.data.y + (height > 0 ? 0 : height) - const position = { - width: Math.abs(width) + 150, - height: Math.abs(height) + 20, - left: x - 70, - top: y - 5 - }; - this.element.css(position); - } - - /* -------------------------------------------- */ - - /** - * Activate event listeners which provide interactivity for the Token HUD application - * @param html - */ - activateListeners(html) { - super.activateListeners(html); - html.find(".config").click(this._onDrawingConfig.bind(this)); - html.find(".mirror-vert").click(this._onMirror.bind(this, true)); - html.find(".text-autofit").click(this._onTextAutofit.bind(this)); - html.find(".mirror-horiz").click(this._onMirror.bind(this, false)); - // Color change inputs - html.find('input[type="color"]').change(this._onColorPickerChange.bind(this)); - } - - /* -------------------------------------------- */ - async _onColorPickerChange(event) { - event.preventDefault(); - const drawings = this.object._controlled ? canvas.drawings.controlled : [this.object]; - await canvas.scene.updateEmbeddedEntity('Drawing', drawings.map(d => { - let data = { _id: d.id, [event.target.name]: event.target.value } - // If user sets a fill color but fill is NONE then change it - if (event.target.name == "fillColor" && d.fillType == FURNACE_DRAWING_FILL_TYPE.NONE) - data.fillType = FURNACE_DRAWING_FILL_TYPE.SOLID; - return data - }), { updateKeys: ["fillType", event.target.name] }) - this.render() - this.object.layer.updateStartingData(this.object) - } - - _onToggleVisibility(event) { - this._onToggleField(event, "hidden") - } - _onToggleLocked(event) { - this._onToggleField(event, "locked") - } - _onMirror(vertical, event) { - this._onToggleField(event, "flags.furnace.mirror" + (vertical ? "Vert" : "Horiz")) - } - - _onToggleField(event, field) { - event.preventDefault(); - let isEnabled = getProperty(this.object.data, field); - $(event.currentTarget).toggleClass("active"); - const drawings = this.object._controlled ? canvas.drawings.controlled : [this.object]; - canvas.scene.updateEmbeddedEntity('Drawing', drawings.map(d => { - return { _id: d.id, [field]: !isEnabled } - }), { updateKeys: [field] }) - } - _onTextAutofit(event) { - event.preventDefault(); - const drawings = this.object._controlled ? canvas.drawings.controlled : [this.object]; - canvas.scene.updateEmbeddedEntity('Drawing', drawings.map(d => { - const bounds = d.img.getLocalBounds(); - return { _id: d.id, width: bounds.width, height: bounds.height } - }), { updateKeys: ["width", "height"] }) - } - - /** - * Handle Drawing configuration button click - * @private - */ - _onDrawingConfig(event) { - event.preventDefault(); - this.object.sheet.render(true); - } -} - diff --git a/DrawingTools/DrawingTools.js b/DrawingTools/DrawingTools.js deleted file mode 100644 index b59e233..0000000 --- a/DrawingTools/DrawingTools.js +++ /dev/null @@ -1,100 +0,0 @@ -const FURNACE_DRAWING_FILL_TYPE = { - NONE: 0, - SOLID: 1, - PATTERN: 2, - STRETCH: 3, - CONTOUR: 4, - FRAME: 5 -} - -CONFIG.furnaceDrawingFillTypes = { - [FURNACE_DRAWING_FILL_TYPE.NONE]: "None", - [FURNACE_DRAWING_FILL_TYPE.SOLID]: "Solid Color", - [FURNACE_DRAWING_FILL_TYPE.PATTERN]: "Tiled Pattern", - [FURNACE_DRAWING_FILL_TYPE.STRETCH]: "Stretched Texture", - [FURNACE_DRAWING_FILL_TYPE.CONTOUR]: "Tiled Contour", - [FURNACE_DRAWING_FILL_TYPE.FRAME]: "Stretched Contour" -} - -CONFIG.furnaceDrawingTypes = { - [CONST.DRAWING_TYPES.RECTANGLE]: "Rectangle", - [CONST.DRAWING_TYPES.ELLIPSE]: "Ellipse", - [CONST.DRAWING_TYPES.TEXT]: "Text", - [CONST.DRAWING_TYPES.POLYGON]: "Polygon", - [CONST.DRAWING_TYPES.FREEHAND]: "Freehand" -} - - -CONFIG.WebSafeFonts = { - "Arial": "Arial, Helvetica, sans-serif", - "Arial Black": '"Arial Black", Gadget, sans-serif', - "Times New Roman": '"Times New Roman", Times, serif', - "Comic Sans MS": '"Commic Sans MS", cursive, sans-serif', - "Courier New": '"Courier New", Courier, monospace', - // FIXME: There are more, but do we want to show all of the available options?? -} - -class FurnaceDrawingTools { - - static init() { - // Register module configuration settings - game.settings.register("furnace", "enableDrawingTools", { - name: "Advanced Drawing Tools", - hint: "Replaces the core drawing tools with the Furnace's drawing tools, which provides more advanced features, such as tiled/stretched textures and live preview of config changes (Requires reload).", - scope: "world", - config: true, - default: true, - type: Boolean, - onChange: value => window.location.reload() - }) - game.settings.register("furnace", "freehandSampleRate", { - name: "Freehand Sample Rate", - hint: "The sample rate in millisecond for the smoothing of Freehand drawings. Lower value means more accurate drawings but less smooth and containing more points", - scope: "core", - config: true, - choices: { 25: "25 ms", 50: "50 ms", 75: "75 ms", 100: "100 ms" }, - default: 50, - type: Number, - onChange: value => { - Drawing.FREEHAND_SAMPLE_RATE = value - } - }) - game.settings.register("furnace", FurnaceDrawingsLayer.DEFAULT_CONFIG_SETTING, { - name: "Default Drawing Configuration", - scope: "client", - config: false, - default: {}, - type: Object, - }) - if (game.settings.get("furnace", "enableDrawingTools")) { - Drawing = FurnaceDrawing; - DrawingsLayer = FurnaceDrawingsLayer; - DrawingHUD = FurnaceDrawingHUD; - DrawingConfig = FurnaceDrawingConfig; - } - Drawing.FREEHAND_SAMPLE_RATE = game.settings.get("furnace", "freehandSampleRate"); - } - static getSceneControlButtons(buttons) { - if (!game.settings.get("furnace", "enableDrawingTools")) return; - - let drawingsButton = buttons.find(b => b.name == "drawings") - // Replace the drawings scene controls with our own - if (drawingsButton) { - // Replace the tools we want modified - drawingsButton.tools[1].name = "rectangle"; - drawingsButton.tools[1].onClick = () => canvas.drawings._last_tool = CONST.DRAWING_TYPES.RECTANGLE; - drawingsButton.tools[2].onClick = () => canvas.drawings._last_tool = CONST.DRAWING_TYPES.ELLIPSE; - drawingsButton.tools[3].onClick = () => canvas.drawings._last_tool = CONST.DRAWING_TYPES.POLYGON; - drawingsButton.tools[4].onClick = () => canvas.drawings._last_tool = CONST.DRAWING_TYPES.FREEHAND; - drawingsButton.tools[5].onClick = () => canvas.drawings._last_tool = CONST.DRAWING_TYPES.TEXT; - drawingsButton.tools[6].onClick = () => { - canvas.drawings.configureStartingData(); - ui.controls.control.activeTool = "select"; - ui.controls.render(); - }; - } - } -} - -Hooks.on('init', FurnaceDrawingTools.init) -Hooks.on('getSceneControlButtons', FurnaceDrawingTools.getSceneControlButtons) \ No newline at end of file diff --git a/DrawingTools/DrawingsLayer.js b/DrawingTools/DrawingsLayer.js deleted file mode 100644 index d2dedaf..0000000 --- a/DrawingTools/DrawingsLayer.js +++ /dev/null @@ -1,215 +0,0 @@ -/** - * The DrawingsLayer subclass of :class:`PlaceablesLayer` - * - * This layer implements a container for drawings which are rendered immediately above the :class:`TilesLayer` - * and immediately below the :class:`GridLayer` - * - * @type {PlaceablesLayer} - */ -class FurnaceDrawingsLayer extends DrawingsLayer { - // Override the constructor's name - static get name() { - return "DrawingsLayer" - } - constructor() { - super() - - this._last_tool = CONST.DRAWING_TYPES.RECTANGLE - this._startingData = game.settings.get("furnace", FurnaceDrawingsLayer.DEFAULT_CONFIG_SETTING); - } - - static get layerOptions() { - return mergeObject(super.layerOptions, { - snapToGrid: false - }); - } - - /** - * Define a Container implementation used to render placeable objects contained in this layer - * @type {PIXI.Container} - */ - static get placeableClass() { - return FurnaceDrawing; - } - get gridPrecision() { - return 2; - } - - - /* -------------------------------------------- */ - /* Rendering */ - /* -------------------------------------------- */ - - deleteAll() { - const cls = this.constructor.placeableClass; - let title = "Clear All Drawings" - let content = `

Clear all Drawings from this Scene?

` - let placeables = this.placeables; - if (!game.user.isGM) { - if (game.user.can("DRAWING_CREATE")) { - title = "Clear Your Drawings" - content = `

Clear your Drawings from this Scene?

` - placeables = this.placeables.filter(p => p.data.author == game.user.id) - } else { - return ui.notification.error(`You do not have permission to delete Drawings from the Scene.`); - } - } - - new Dialog({ - title: title, - content: content, - buttons: { - yes: { - icon: '', - label: "Yes", - callback: () => canvas.scene.deleteEmbeddedEntity('Drawing', placeables.map(o => o.id)) - }, - no: { - icon: '', - label: "No" - } - }, - default: "yes" - }).render(true); - } - - /* -------------------------------------------- */ - /* -------------------------------------------- */ - - - static DrawingDefaultData(type = "all") { - return { - // Special 'all' type which contains default values common to all types - all: { - x: 0, - y: 0, - z: 0, - width: 0, - height: 0, - author: null, - hidden: false, - locked: false, - flags: { - furnace: { - textureWidth: 0, - textureHeight: 0, - textureAlpha: 1 - } - }, - rotation: 0, - fillType: FURNACE_DRAWING_FILL_TYPE.NONE, - fillColor: game.user.color, - fillAlpha: 1.0, - strokeColor: game.user.color, - strokeAlpha: 1.0, - strokeWidth: 8, - texture: null, - fontFamily: "Signika", - fontSize: 48, - text: "", - textAlpha: 1, - textColor: game.user.color, - bezierFactor: 0, - points: [], - }, - [CONST.DRAWING_TYPES.RECTANGLE]: { - }, - [CONST.DRAWING_TYPES.ELLIPSE]: { - }, - [CONST.DRAWING_TYPES.TEXT]: { - fillType: FURNACE_DRAWING_FILL_TYPE.SOLID, - strokeWidth: 2, - }, - [CONST.DRAWING_TYPES.POLYGON]: { - bezierFactor: 0 - }, - [CONST.DRAWING_TYPES.FREEHAND]: { - bezierFactor: 0.5, - } - }[type] - } - getStartingData(type) { - type = type[0] - if (this._startingData[type] === undefined) - this._startingData[type] = this.getDefaultData(type); - delete this._startingData[type]._id; - this._startingData[type].type = type; - return this._startingData[type] - } - getDefaultData(type) { - type = type[0] - let defaultData = mergeObject(this.constructor.DrawingDefaultData("all"), - this.constructor.DrawingDefaultData(type), - { inplace: false }); - defaultData.type = type - return defaultData; - } - - updateStartingData(drawing) { - let data = duplicate(drawing.data) - mergeObject(data, { x: 0, y: 0, z: 0, width: 0, height: 0, author: null, rotation: 0 }, { overwrite: true }) - data.points = [] - data.text = "" - this._startingData[data.type] = data - game.settings.set("furnace", DrawingsLayer.DEFAULT_CONFIG_SETTING, this._startingData) - } - - /** - * Get the configuration sheet for the layer. - * This allows a user to configure the default values for all tools - */ - configureStartingData() { - // We don't use a singleton because defaults could change between calls. - new DrawingDefaultsConfig(this, this._last_tool).render(true); - } - - _onClickLeft(event) { - super._onClickLeft(event); - // You can place a text by simply clicking, no need to drag it first. - if (game.activeTool === "text") { - // fix for when right panning before the click - canvas.mouseInteractionManager._dragRight = false; - canvas.mouseInteractionManager._handleDragStart(event); - canvas.mouseInteractionManager._handleDragDrop(event); - event.data.createState = 2; - } - } - _onDragLeftStart(event) { - // Snap to grid by default only for shapes and polygons - if (!event.data.originalEvent.shiftKey && ["rectangle", "ellipse", "polygon"].includes(game.activeTool)) { - event.data.origin = canvas.grid.getSnappedPosition(event.data.origin.x, event.data.origin.y, this.gridPrecision); - } - super._onDragLeftStart(event); - } - _onDragLeftDrop(event) { - const { preview } = event.data; - super._onDragLeftDrop(event); - - // Text objects create their sheets for users to enter the text, otherwise create the drawing - if (preview.type == CONST.DRAWING_TYPES.TEXT) { - // Render the preview sheet - preview.sheet.preview = preview; - preview.sheet.render(true); - this.preview.addChild(preview); - } - } - _getNewDrawingData(origin) { - let type = game.activeTool; - let data = mergeObject(this.getStartingData(type), origin, { inplace: false }) - if (type == "freehand" || type == "polygon") { - data.points.push([data.x, data.y]) - data.x = data.y = 0; - } - - return data; - } - _onDeleteKey(event) { - // Skip the _onDeleteKey from DrawingsLayer which ignores Text drawings. - PlaceablesLayer.prototype._onDeleteKey.call(this, event); - } -} - -// Needed so core doesn't break due to our change of the DrawingsLayer. -// We also don't want to derive from DrawingsLayer because it can cause issues -// with things like super._onDragStart -FurnaceDrawingsLayer.DEFAULT_CONFIG_SETTING = "defaultDrawingConfig"; \ No newline at end of file diff --git a/DrawingTools/RotationHandle.js b/DrawingTools/RotationHandle.js deleted file mode 100644 index 5d7c2ff..0000000 --- a/DrawingTools/RotationHandle.js +++ /dev/null @@ -1,109 +0,0 @@ -class RotationHandle extends PIXI.Graphics { - constructor(placeableObject, offset = 25) { - super() - this.object = placeableObject; - this._hover = false; - this._offset = offset; - } - - addEventListeners(layer, permissions) { - new MouseInteractionManager(this, layer, permissions, { - mouseover: event => this._onMouseOver(event), - mouseout: event => this._onMouseOut(event), - mousedown: event => this._onMouseDown(event), - mousemove: event => this._onMouseMove(event), - mouseup: event => this._onMouseUp(event) - }); - } - - draw() { - let scale = this._hover ? 1.5 : 1.0; - let fromAngle = Math.PI / 4; - let toAngle = 3 * Math.PI / 4; - let fromPosition = this.rotatePosition({ x: this._offset, y: 0 }, { x: 0, y: 0 }, -fromAngle) - let toPosition = this.rotatePosition({ x: this._offset, y: 0 }, { x: 0, y: 0 }, -toAngle) - - this.clear() - // FIXME: PIXI 4.x doesn't finish star lines, so we make it not draw the outline - // and only use the fill instead. - .lineStyle(0.0, 0x000000) - .beginFill(0x000000, 1.0) - .moveTo(fromPosition.x, fromPosition.y) - .drawStar(fromPosition.x, fromPosition.y, 3, 6.0 * scale) - .moveTo(toPosition.x, toPosition.y) - .drawStar(toPosition.x, toPosition.y, 3, 6.0 * scale) - .endFill() - .lineStyle(4.0, 0x000000) - .moveTo(fromPosition.x, fromPosition.y) - .arc(0, 0, this._offset, -fromAngle, -toAngle, true) - .lineStyle(2.0, 0x000000) - .moveTo(0, 0) - .lineTo(0, -this._offset) - .beginFill(0xFF9829, 1.0) - .drawCircle(0, -this._offset, 6.0 * scale) - .endFill() - } - - /** - * FIXME: Duplicate in Drawing, this should really go into a sort of util class. - * Get coordinates of a point after a rotation around another point - * @param {Object} dest - */ - rotatePosition(coordinates, axis, angle) { - // No need to do the math if there is no rotation - if (angle) { - // Translate the coordinates so the rotation axis is (0, 0) - let x = coordinates.x - axis.x; - let y = coordinates.y - axis.y; - - /** - * Formula from https://academo.org/demos/rotation-about-point/ - * x′ = x*cos(θ) − y*sin(θ) - * y′ = y*cos(θ) + x*sin(θ) - */ - let new_x = x * Math.cos(angle) - y * Math.sin(angle); - let new_y = y * Math.cos(angle) + x * Math.sin(angle); - // Translate back from origin point to our center - coordinates.x = new_x + axis.x - coordinates.y = new_y + axis.y - } - return coordinates; - } - - /** - * Handle mouse-over event on a control handle - * @private - */ - _onMouseOver(event) { - this._hover = true; - this.draw(); - } - - /** - * Handle mouse-out event on a control handle - * @private - */ - _onMouseOut(event) { - this._hover = false; - this.draw(); - } - _onMouseDown(event) { - event.data.original = duplicate(this.object.data); - } - _onMouseMove(event) { - this.object._updateRotation(event.data.destination); - this.object.refresh(); - } - - /* -------------------------------------------- */ - - _onMouseUp(event) { - let { original, destination } = event.data; - this.object._updateRotation(destination); - - // Update the tile - const data = { rotation: this.object.data.rotation }; - this.object.data = original; - this.object.update(data); - } -} diff --git a/Furnace.css b/Furnace.css deleted file mode 100644 index 5ca0624..0000000 --- a/Furnace.css +++ /dev/null @@ -1,135 +0,0 @@ -/* Playlists QoL */ -#playlists li.sound .sound-name.furnace-qol { - max-width: unset; - margin-right: 3px; -} - -#playlists li.sound .sound-controls.furnace-qol { - flex: 0 1 auto; - flex-wrap: nowrap; - min-width: 36px; -} -#playlists .playlist-sounds { - max-width: 100%; -} - -#playlists .directory-list .now-playing { - border-top: 1px solid #f20; - background-color: rgba(255, 100, 0, 0.1); -} -#playlists li.sound.sound-is-playing { - background-color: rgba(255, 100, 0, 0.2); -} -#playlists li.playlist.now-playing-item { - border-top: 0px; - border-bottom: 0px; - padding: unset; -} -.sound-control-hidden { - display: none; -} - -/* Combat QoL */ -#combat li.combatant.turn-done { - color: #888; - text-decoration: line-through; -} - -/* Tokens QoL */ -form.furnace-drop-actors .form-group input { - flex: 0 1; -} -form.furnace-drop-actors .form-group i { - flex: 1 1; -} - -/* Macro syntax highlighting */ -.macro-sheet .form-group.command .furnace-macro-command { - position: relative; - border: 1px solid black; - height: calc(100% - 2rem); -} - -.macro-sheet .form-group.command .furnace-macro-command.fullscreen { - position: fixed; - top: 0px; - left: 0px; - width: calc(100% - 10rem); - height: calc(100% - 10rem); - margin: 5rem; - background: url(../../ui/parchment.jpg) repeat; -} -.macro-sheet .form-group.command textarea { - width: 100%; - height: 100%; -} - -.macro-sheet .form-group.command .furnace-macro-expand { - position: absolute; - top: 0px; - right: 0px; - padding: 5px; - margin: 3px; - background: rgba(0, 0, 0, 0.2); - border-radius: 5px; - z-index: 10; -} - -.macro-sheet .form-group.command .furnace-macro-command textarea, -.macro-sheet .form-group.command .furnace-macro-command code { - position: relative; - background: transparent; - font-size: 14px; - border: 0px; - margin: 0px; - padding: 5px; - line-height: 21px; - font-family: Consolas,Liberation Mono,Courier,monospace; -} - -.macro-sheet .form-group.command .furnace-macro-command textarea { - z-index: 10; - text-shadow: 0px 0px 0px rgba(0, 0, 0, 0); - text-fill-color: transparent; - -webkit-text-fill-color: transparent; - resize: none; -} -.macro-sheet .form-group.command .furnace-macro-command pre { - position: absolute; - width: 100%; - height: 100%; - top: 0px; - left: 0px; - border: 0px; - margin: 0px; - padding: 0px; - z-index: 9; - overflow: hidden; -} -.macro-sheet .form-group.command .furnace-macro-command textarea, -.macro-sheet .form-group.command .furnace-macro-command pre { - white-space: pre-wrap; - white-space: -moz-pre-wrap; - white-space: -pre-wrap; - white-space: -o-pre-wrap; - word-wrap: break-word; -} - -.furnace-macro-run-as-gm { - position: relative; -} -.furnace-macro-run-as-gm .tooltip { - display: block; - min-width: 148px; - padding: 2px 4px; - position: absolute; - top: -55px; - left: 25px; - background: rgba(0, 0, 0, 0.9); - border: 1px solid #191813; - border-radius: 3px; - color: #f0f0e0; - line-height: 22px; - text-align: center; - white-space: nowrap; - word-break: break-all; \ No newline at end of file diff --git a/Macros/Macros.js b/Macros/Macros.js deleted file mode 100644 index 801fe7b..0000000 --- a/Macros/Macros.js +++ /dev/null @@ -1,410 +0,0 @@ -class FurnaceMacros { - constructor() { - let helpers = { - macro: (name, ...args) => { - const macro = game.macros.entities.find(macro => macro.name === name); - if (!macro) return ""; - const result = macro.renderContent(...args); - if (typeof (result) !== "string") - return ""; - return result; - } - } - Handlebars.registerHelper(helpers) - - this._GMElectionIds = []; - this._requestResolvers = {}; - Hooks.on('init', this.init.bind(this)); - Hooks.once("ready", this.ready.bind(this)); - Hooks.on('renderMacroConfig', this.renderMacroConfig.bind(this)) - // On 0.6.5, unknown commands throw an error which breaks posting macros from chat box - const parse = FurnacePatching.patchFunction(ChatLog.parse, 24, `"invalid": /^(\\/[^\\s]+)/, // Any other message starting with a slash command is invalid`, ""); - if (parse) - ChatLog.parse = parse; - - } - - init() { - game.furnaceMacros = this; - // Register module configuration settings - game.settings.register("furnace", "advancedMacros", { - name: "Advanced Macros", - hint: "Enable the ability to use async script macros, call macros with arguments, use Handlebars templating for chat macros and more. Check out the module website for more information.", - scope: "world", - config: true, - default: true, - type: Boolean, - }); - game.macros = this; - - Hooks.on('preCreateChatMessage', this.preCreateChatMessage.bind(this)) - FurnacePatching.replaceMethod(Macro, "execute", this.executeMacro) - Macro.prototype.renderContent = this.renderMacro; - Macro.prototype.callScriptFunction = this.callScriptMacroFunction; - Object.defineProperty(Macro.prototype, "canRunAsGM", { get: this.canRunAsGM }); - } - ready() { - game.socket.on("module.furnace", this._onSocketMessage.bind(this)); - } - uniqueID() { - return `${game.user.id}-${Date.now()}-${randomID()}`; - } - async _onSocketMessage(message) { - // To run macros as GM, first we elect a GM executor. - // this is to prevent running the macro more than once - // if there are more than one logged in GM or a single GM - // is logged in more than once. - // The election is based on each GM sending a random ID, then the user - // choosing one (the first it receives) and asking that specific GM session - // to execute the macro - if (message.action === "ElectGMExecutor") { - if (!game.user.isGM) return; - const electionId = this.uniqueID(); - this._GMElectionIds.push(electionId); - game.socket.emit("module.furnace", { - action: "GMElectionID", - requestId: message.requestId, - electionId - }); - // Delete the election ID in case we were not chosen after 10s, to avoid a memleak - setTimeout(() => { - this._GMElectionIds = this._GMElectionIds.filter(id => id !== electionId); - }, 10000); - } else if (message.action === "GMElectionID") { - const resolve = this._requestResolvers[message.requestId]; - if (resolve) { - delete this._requestResolvers[message.requestId]; - resolve(message.electionId); - } - } else if (message.action === "GMExecuteMacro") { - if (!game.user.isGM) return; - if (!this._GMElectionIds.includes(message.electionId)) return; - this._GMElectionIds = this._GMElectionIds.filter(id => id !== message.electionId); - - const macro = game.macros.get(message.macroId); - const user = game.users.get(message.userId); - const sendResponse = (error = null, result = null) => game.socket.emit("module.furnace", { - action: "GMMacroResult", - requestId: message.requestId, - error - }); - if (!macro) - return sendResponse("Cannot find macro"); - if (!user) - return sendResponse("Invalid user"); - if (macro.data.type !== "script") - return sendResponse("Invalid macro type"); - if (!Macros.canUseScripts(user)) - return sendResponse(`You are not allowed to use JavaScript macros.`); - if (!macro.getFlag("furnace", "runAsGM") || !macro.canRunAsGM) - return sendResponse(`You are not authorized to run this macro as the GM.`); - - const context = FurnaceMacros.getTemplateContext(message.args, message.context); - try { - const result = macro.callScriptFunction(context); - return sendResponse(null, result); - } catch (err) { - console.error(err); - return sendResponse(`There was an error in your macro syntax. See the console (F12) of the GM '${game.user.name}' for details`); - } - } else if (message.action === "GMMacroResult") { - const resolve = this._requestResolvers[message.requestId]; - if (resolve) { - delete this._requestResolvers[message.requestId]; - resolve({ result: message.result, error: message.error }); - } - } - } - - static getTemplateContext(args = null, remoteContext = null) { - const context = { - game: game, - ui: ui, - canvas: canvas, - scene: canvas.scene, - args, - speaker: {}, - actor: null, - token: null, - character: null - }; - if (remoteContext) { - // Set the context based on the remote context, and make sure data is valid and the remote - // has a token/actor selected. - context.speaker = remoteContext.speaker || {}; - if (remoteContext.actorId) - context.actor = game.actors.get(remoteContext.actorId) || null; - if (remoteContext.sceneId) - context.scene = game.scenes.get(remoteContext.sceneId) || canvas.scene; - if (remoteContext.tokenId) { - if (canvas.scene.id === context.scene.id) { - context.token = canvas.tokens.get(remoteContext.tokenId) || null; - } else { - const tokenData = context.scene.getEmbeddedEntity("Token", remoteContext.tokenId); - if (tokenData) - context.token = new Token(tokenData, context.scene); - } - } - if (remoteContext.characterId) - context.character = game.actors.get(remoteContext.characterId) || null; - } else { - context.speaker = ChatMessage.getSpeaker(); - context.actor = game.actors.get(context.speaker.actor); - context.token = canvas.tokens.get(context.speaker.token); - context.character = game.user.character; - } - return context; - } - - /** - * Defines whether a Macro can run as a GM. - * For security reasons, only macros authored by the GM, and not editable by users - * can be run as GM - */ - canRunAsGM() { - const author = game.users.get(this.data.author); - const permissions = duplicate(this.data.permission) || {}; - game.users.entities.forEach(user => { - if (user.id === this.data.author || user.isGM) - delete permissions[user.id]; - }) - return author && author.isGM && Object.values(permissions).every(p => p < CONST.ENTITY_PERMISSIONS.OWNER) - } - - callScriptMacroFunction(context) { - const asyncFunction = this.data.command.includes("await") ? "async" : ""; - return (new Function(`"use strict"; - return (${asyncFunction} function ({speaker, actor, token, character, args, scene}={}) { - ${this.data.command} - });`))().call(this, context); - } - - renderMacro(...args) { - const context = FurnaceMacros.getTemplateContext(args); - if (this.data.type === "chat") { - if (this.data.command.includes("{{")) { - const compiled = Handlebars.compile(this.data.command); - return compiled(context, { allowProtoMethodsByDefault: true, allowProtoPropertiesByDefault: true }) - } else { - return this.data.command; - } - } - if (this.data.type === "script") { - if (!Macros.canUseScripts(game.user)) - return ui.notifications.warn(`You are not allowed to use JavaScript macros.`); - if (this.getFlag("furnace", "runAsGM") && this.canRunAsGM && !game.user.isGM) - return game.furnaceMacros.executeMacroAsGM(this, context); - return this.callScriptFunction(context); - } - } - async executeMacro(...args) { - if (!game.settings.get("furnace", "advancedMacros")) - return FurnacePatching.callOriginalFunction(this, "execute"); - - // Chat macros - if (this.data.type === "chat") { - try { - const content = this.renderContent(...args); - ui.chat.processMessage(content).catch(err => { - ui.notifications.error("There was an error in your chat message syntax."); - console.error(err); - }); - } catch (err) { - ui.notifications.error(`There was an error in your macro syntax. See the console (F12) for details`); - console.error(err); - } - } - - // Script macros - else if (this.data.type === "script") { - try { - return await this.renderContent(...args); - } catch (err) { - ui.notifications.error(`There was an error in your macro syntax. See the console (F12) for details`); - console.error(err); - } - } - } - - // request execution of macro as a GM - async executeMacroAsGM(macro, context) { - const activeGMs = game.users.entities.filter(u => u.isGM && u.active); - if (activeGMs.length === 0) { - ui.notifications.error(`There are no connected GMs to run the macro ${macro.name} in the GM context.`); - return ""; - } - // Elect a GM to run the Macro - const electionResponse = await new Promise((resolve, reject) => { - const requestId = this.uniqueID(); - this._requestResolvers[requestId] = resolve; - game.socket.emit("module.furnace", { - action: "ElectGMExecutor", - requestId - }) - setTimeout(() => { - delete this._requestResolvers[requestId]; - reject(new Error("Timed out waiting to elect a GM to execute the macro")); - }, 5000); - }) - // Execute the macro in the first elected GM's - const executeResponse = await new Promise((resolve, reject) => { - const requestId = this.uniqueID(); - this._requestResolvers[requestId] = resolve; - game.socket.emit("module.furnace", { - action: "GMExecuteMacro", - requestId, - electionId: electionResponse, - userId: game.user.id, - macroId: macro.id, - args: context.args, - context: { - speaker: context.speaker, - actorId: context.actor ? context.actor.id : null, - sceneId: context.scene ? context.scene.id : null, - tokenId: context.token ? context.token.id : null, - characterId: context.character ? context.character.id : null - } - }) - setTimeout(() => { - delete this._requestResolvers[requestId]; - reject(new Error("Timed out waiting for the GM to execute the macro")); - }, 5000); - }) - if (executeResponse.error) - throw new Error(executeResponse.error); - else - return executeResponse.result; - } - - preCreateChatMessage(data, options, userId) { - if (!game.settings.get("furnace", "advancedMacros")) return; - if (data.content === undefined || data.content.length == 0) return; - - let content = data.content || ""; - let tokenizer = null; - let hasAsyncMacros = false; - let hasMacros = false; - if (content.includes("{{")) { - const context = FurnaceMacros.getTemplateContext(); - const compiled = Handlebars.compile(content); - content = compiled(context, { allowProtoMethodsByDefault: true, allowProtoPropertiesByDefault: true }); - if (content.trim().length === 0) return false; - } - if (content.trim().startsWith("<")) return true; - content = content.replace(/\n/gm, "
"); - content = content.split("
").map(line => { - if (line.startsWith("/")) { - // Ensure tokenizer, but don't consider dash as a token delimiter - if (!tokenizer) - tokenizer = new TokenizeThis({ - shouldTokenize: ['(', ')', ',', '*', '/', '%', '+', '=', '!=', '!', '<', '>', '<=', '>=', '^'] - }); - let command = null; - let args = []; - tokenizer.tokenize(line.substr(1), (token) => { - if (!command) command = token; - else args.push(token); - }) - const macro = game.macros.entities.find(macro => macro.name === command); - if (macro) { - hasMacros = true; - const result = macro.renderContent(...args); - if (result instanceof Promise) { - hasAsyncMacros = true; - return result; - } - if (typeof (result) !== "string") - return ""; - return result.trim(); - } - } - return line.trim(); - }); - - if (hasMacros) { - mergeObject(data, { "flags.furnace.macros.template": data.content }) - // Macros were found; We need to await and cancel this message if async - if (hasAsyncMacros) { - Promise.all(content).then((lines) => { - data.content = lines.join("\n").trim().replace(/\n/gm, "
"); - if (data.content !== undefined && data.content.length > 0) - ChatMessage.create(data, options) - }).catch(err => { - ui.notifications.error(`There was an error in your macro syntax. See the console (F12) for details`); - console.error(err); - }); - return false; - } else { - // If non-async, then still, recreate it so we can do recursive macro calls - data.content = content.join("\n").trim().replace(/\n/gm, "
"); - if (data.content !== undefined && data.content.length > 0) - ChatMessage.create(data, options) - return false; - } - } - data.content = content.join("\n").trim().replace(/\n/gm, "
"); - return true; - } - - _highlightMacroCode(form, textarea, code) { - const type = form.find("select[name=type]").val(); - let content = textarea.val(); - // Add an empty space if the last character is a newline because otherwise it won't let scrollTop reach - // so we get a one line diff between the text area and
 when the last line is empty.
-        if (content.substr(-1) === "\n") content += " ";
-        code.removeClass("javascript handlebars hljs").addClass(type === "script" ? "javascript" : "handlebars");
-        code.text(content);
-        hljs.highlightBlock(code[0]);
-    }
-
-    renderMacroConfig(obj, html, data) {
-        let form = html.find("form");
-        // A re-render will cause the html object to be the internal element, which is the form itself.
-        if (form.length === 0) form = html;
-        // Add runAsGM checkbox
-        if (game.user.isGM) {
-            const runAsGM = obj.object.getFlag("furnace", "runAsGM");
-            const canRunAsGM = obj.object.canRunAsGM;
-            const typeGroup = form.find("select[name=type]").parent(".form-group");
-            const gmDiv = $(`
-                
- -
- `) - gmDiv.insertAfter(typeGroup); - const tooltip = $(` - - This will cause the macro to be executed by the GM user when players execute it
- For security reasons, only applies to GM created macros with no other owners. -
`); - gmDiv.hover((event) => { - if (event.type === "mouseenter") - gmDiv.append(tooltip); - else - tooltip.remove(); - }); - } - // Add syntax highlighting - const textarea = form.find("textarea"); - const div = $(` -
-
-
-
- `) - const code = div.find("code"); - div.insertBefore(textarea); - div.prepend(textarea); - const refreshHighlight = this._highlightMacroCode.bind(this, form, textarea, code); - textarea.on('input', refreshHighlight); - textarea.on('scroll', (ev) => code.parent().scrollTop(textarea.scrollTop())); - form.find("select[name=type]").on('change', refreshHighlight); - div.find(".furnace-macro-expand").on('click', (ev) => div.toggleClass("fullscreen")); - refreshHighlight(); - } -} - -new FurnaceMacros(); diff --git a/Patches/Patches.js b/Patches/Patches.js deleted file mode 100644 index 475d79d..0000000 --- a/Patches/Patches.js +++ /dev/null @@ -1,71 +0,0 @@ - -class FurnacePatching { - - static patchClass(klass, func, line_number, line, new_line) { - // Check in case the class/function had been deprecated/removed - if (func === undefined) - return; - let funcStr = func.toString() - // Check for newlines so it can work on minified content too - const splitChar = funcStr.indexOf("\n") >= 0 ? "\n" : ";"; - let lines = funcStr.split(splitChar) - if (lines[line_number] !== undefined && lines[line_number].trim() == line.trim()) { - lines[line_number] = lines[line_number].replace(line, new_line); - let fixed = lines.join(splitChar) - if (klass !== undefined) { - let classStr = klass.toString() - fixed = classStr.replace(funcStr, fixed) - } else { - // Check if it's a method instead of a function, add 'function' as we define it, but don't do it for 'async function' - if (!fixed.startsWith("function") && !fixed.match(/^async\s+function/)) - fixed = "function " + fixed - if (fixed.startsWith("function async")) - fixed = fixed.replace("function async", "async function"); - } - return Function('"use strict";return (' + fixed + ')')(); - } else { - console.log("Cannot patch function. It has wrong content at line ", line_number, " : ", - lines[line_number] && lines[line_number].trim(), " != ", line.trim(), "\n", funcStr) - } - } - - static patchFunction(func, line_number, line, new_line) { - return FurnacePatching.patchClass(undefined, func, line_number, line, new_line) - } - static patchMethod(klass, func, line_number, line, new_line) { - return FurnacePatching.patchClass(klass, klass.prototype[func], line_number, line, new_line) - } - - static replaceFunction(klass, name, func) { - klass[this.ORIG_PRREFIX + name] = klass[name] - klass[name] = func - } - static replaceMethod(klass, name, func) { - return this.replaceFunction(klass.prototype, name, func) - } - static replaceStaticGetter(klass, name, func) { - let getterProperty = Object.getOwnPropertyDescriptor(klass, name); - if (getterProperty == undefined) - return false; - Object.defineProperty(klass, FurnacePatching.ORIG_PRREFIX + name, getterProperty); - Object.defineProperty(klass, name, { get: func }); - return true; - } - static replaceGetter(klass, name, func) { - return this.replaceStaticGetter(klass.prototype, name, func) - }; - - // Would be the same code for callOriginalMethod as long as 'klass' is actually the instance - static callOriginalFunction(klass, name, ...args) { - return klass[this.ORIG_PRREFIX + name].call(klass, ...args) - } - static callOriginalGetter(klass, name) { - return klass[this.ORIG_PRREFIX + name] - } - - static init() { - } -} -FurnacePatching.ORIG_PRREFIX = "__furnace_original_" - -Hooks.on('init', FurnacePatching.init) \ No newline at end of file diff --git a/QoL/Combat.js b/QoL/Combat.js deleted file mode 100644 index 06cbade..0000000 --- a/QoL/Combat.js +++ /dev/null @@ -1,29 +0,0 @@ -class FurnaceCombatQoL { - static renderCombatTracker(tracker, html, data) { - if (!game.user.isGM) return; - html.find(".token-initiative").off("dblclick").on("dblclick", FurnaceCombatQoL._onInitiativeDblClick) - for (let combatant of html.find("#combat-tracker li.combatant")) { - if (combatant.classList.contains("active")) - break; - combatant.classList.add("turn-done"); - } - } - static _onInitiativeDblClick(event) { - event.stopPropagation(); - event.preventDefault(); - let html = $(event.target).closest(".combatant") - let cid = html.data("combatant-id") - let initiative = html.find(".token-initiative") - let combatant = game.combat.getCombatant(cid) - let input = $(``) - initiative.off("dblclick") - initiative.empty().append(input) - input.focus().select() - input.on('change', ev => game.combat.updateCombatant({ _id: cid, initiative: input.val() })) - input.on('focusout', ev => game.combats.render()) - - - } -} - -Hooks.on('renderCombatTracker', FurnaceCombatQoL.renderCombatTracker) \ No newline at end of file diff --git a/QoL/Debug.js b/QoL/Debug.js deleted file mode 100644 index 213f0ee..0000000 --- a/QoL/Debug.js +++ /dev/null @@ -1,50 +0,0 @@ -CONFIG.debug.furnace = true; - -class FurnaceDebug { - static init() { - game.settings.register("furnace", "enableDebug", { - name: game.i18n.localize("FURNACE.SETTINGS.enableDebug"), - hint: game.i18n.localize("FURNACE.SETTINGS.enableDebugHint"), - scope: "client", - config: true, - default: false, - type: Boolean, - onChange: value => { - CONFIG.debug.furnace = value - this.maybePatchHooks(); - } - }); - CONFIG.debug.furnace = game.settings.get("furnace", "enableDebug"); - this.maybePatchHooks(); - } - static log(...args) { - if (CONFIG.debug.furnace) - console.log("Furnace : ", ...args); - } - static maybePatchHooks() { - if (!CONFIG.debug.furnace) return; - if (this._patchApplied) return; - this._patchApplied = true; - // Replace functions here so we can debug the call to 'init' - FurnacePatching.replaceFunction(Hooks, "callAll", function (hook, ...args) { - if (CONFIG.debug.furnace) { - const args_comma = [] - args.forEach(a => { args_comma.push(a); args_comma.push(",") }) - args_comma.pop(); - console.log("DEBUG | Calling All Hooks : " + hook + "(", ...args_comma, ")") - } - return FurnacePatching.callOriginalFunction(this, "callAll", hook, ...args) - }); - FurnacePatching.replaceFunction(Hooks, "call", function (hook, ...args) { - if (CONFIG.debug.furnace) { - const args_comma = [] - args.forEach(a => { args_comma.push(a); args_comma.push(",") }) - args_comma.pop(); - console.log("DEBUG | Calling Hook : " + hook + "(", ...args_comma, ")") - } - return FurnacePatching.callOriginalFunction(this, "call", hook, ...args) - }); - } -} - -Hooks.on('init', () => FurnaceDebug.init()) \ No newline at end of file diff --git a/QoL/Entities.js b/QoL/Entities.js deleted file mode 100644 index 146de94..0000000 --- a/QoL/Entities.js +++ /dev/null @@ -1,211 +0,0 @@ -class FurnaceSplitJournal extends FormApplication { - constructor(object, options) { - super(object, options); - - this.splitters = {} - this.useSplitter = null - this.content = null - if (object.data.content != "") { - this.content = $("
" + object.data.content + "
") - let try_splitters = { - "h1": "Heading 1", - "h2": "Heading 2", - "h3": "Heading 3", - "h4": "Heading 4", - "h5": "Heading 5", - "h6": "Heading 6", - "h7": "Heading 7" - } - - for (let splitter in try_splitters) { - let parts = this.content.find(splitter) - if (parts.length > 0) { - this.splitters[splitter] = try_splitters[splitter] - if (this.useSplitter == null && parts.length > 1) - this.useSplitter = splitter - } - } - } - } - - static get defaultOptions() { - const options = super.defaultOptions; - options.id = "split-journal"; - options.title = game.i18n.localize("FURNACE.SPLIT.title"); - options.classes = ["sheet"]; - options.template = "modules/furnace/templates/split-journal.html"; - options.width = 400; - options.height = "auto"; - return options; - } - - /* -------------------------------------------- */ - - - /* -------------------------------------------- */ - - async getData() { - let error = false - let errorMessage = "" - let newEntries = [] - if (this.content) { - if (this.useSplitter == null && this.splitters.length > 0) - this.useSplitter = this.splitters[0] - if (this.useSplitter == null) { - error = true; - errorMessage = game.i18n.localize("FURNACE.SPLIT.errorNoHeadings"); - } else { - let parts = this.content.find(this.useSplitter) - newEntries = $.map(parts, h => h.textContent) - } - } else { - error = true; - errorMessage = game.i18n.localize("FURNACE.SPLIT.errorJournalEmpty"); - } - return { - name: this.object.name, - splitters: this.splitters, - useSplitter: this.useSplitter, - newEntries: newEntries, - hasImage: this.object.data.img != "", - error: error, - errorMessage: new Handlebars.SafeString(errorMessage), - submitText: game.i18n.localize(`FURNACE.SPLIT.${error ? "errorOk" : "splitSubmit"}`) - } - } - - activateListeners(html) { - super.activateListeners(html); - html.find("select[name=splitter]").change(async (ev) => { - this.useSplitter = html.find("select[name=splitter]").val() - await this.render(true) - }) - } - - async _updateObject(event, formData) { - if (!this.content || !this.useSplitter) return; - - let folder = game.folders.get(this.object.data.folder); - //console.log("Form data : ", formData); - if (formData.newFolder) { - let parent = folder; - let folderDepth = 0; - while (parent) { - folderDepth++; - parent = game.folders.get(parent.data.parent) - } - console.log("Splitting Journal entry : ", this.object.name, "with depth : ", folderDepth); - - let folderData = { - name: this.object.name, - type: "JournalEntry", - parent: folder ? ((folderDepth >= 3) ? folder.data.parent : folder.id) : null - } - folder = await Folder.create(folderData) - } - let parts = splitHtml(this.content, formData.splitter) - let header = "" - let journalEntries = [] - for (let idx = 0; idx < parts.length; idx++) { - - let name = this.object.name - let content = parts[idx]; - - if (idx == 0) { - name = "Header" - header = content.trim(); - if (header == "" || formData.separateHeader) - continue; - } else { - name = $(parts[idx]).text() - content += parts[++idx] - if (formData.separateHeader) - content = header + content; - } - let img = "" - if (formData.includeImage) - img = this.object.data.img - journalEntries.push({ - "name": name || " ", - "permission": this.object.data.permission, - "flags": { "entityorder": { "order": idx * 100000 } }, - "folder": folder ? folder.id : null, - "entryTime": this.object.data.entryTime, - "img": img, - "content": content - }) - } - if (journalEntries.length > 0) - return JournalEntry.create(journalEntries, { displaySheet: false }) - } - - /* Recursively add nodes until we find the index element we want */ - extractContent(splitter, idx) { - let content = "" - for (let node of this.content[0].childNodes) { - let found = node.nodeName == splitter.toUpperCase || $(node).find(splitter) - if (found && idx > 0) { - - } - if (idx == 0) { - - } - } - return content; - } - static getEntryContext(html, options) { - options.push({ - name: game.i18n.localize("FURNACE.SPLIT.contextMenu"), - icon: '', - condition: game.user.isGM, - callback: header => { - let entityId = header.attr("data-entity-id"); - let entity = game.journal.get(entityId); - new FurnaceSplitJournal(entity).render(true); - - } - }) - } -} - -class FurnaceSortEntities { - static getEntityFolderContext(html, options) { - options.push({ - name: game.i18n.localize("FURNACE.ENTITIES.sortAscending"), - icon: '', - condition: game.user.isGM, - callback: header => FurnaceSortEntities.sortEntities(header, true) - }) - options.push({ - name: game.i18n.localize("FURNACE.ENTITIES.sortDescending"), - icon: '', - condition: game.user.isGM, - callback: header => FurnaceSortEntities.sortEntities(header, false) - }) - } - - - // Re-order the entire list based on their position. - static async sortEntities(header, ascending) { - let folderId = header.parent().attr("data-folder-id"); - let folder = game.folders.get(folderId); - let entities = folder.content; - - // Reset order values according to the new order within this folder - // This won't affect the order with the other folders and the whole collection - // order will be reset on the next render - if (entities.length > 0) { - entities.sort((a, b) => a.data.name.localeCompare(b.data.name) * (ascending ? 1 : -1)) - const updateData = entities.map((e, i) => {return {_id: e.id, sort: i * CONST.SORT_INTEGER_DENSITY}}) - entities[0].constructor.update(updateData) - } - } -} - -// Add sort alphabetically option -Hooks.on('getJournalDirectoryFolderContext', FurnaceSortEntities.getEntityFolderContext); -Hooks.on('getSceneDirectoryFolderContext', FurnaceSortEntities.getEntityFolderContext); -Hooks.on('getActorDirectoryFolderContext', FurnaceSortEntities.getEntityFolderContext); -Hooks.on('getItemDirectoryFolderContext', FurnaceSortEntities.getEntityFolderContext); -// Add Split Journal to the entries -Hooks.on('getJournalDirectoryEntryContext', FurnaceSplitJournal.getEntryContext); \ No newline at end of file diff --git a/QoL/HBHelpers.js b/QoL/HBHelpers.js deleted file mode 100644 index 6b50308..0000000 --- a/QoL/HBHelpers.js +++ /dev/null @@ -1,15 +0,0 @@ - -H.registerHelpers(Handlebars) -Handlebars.registerHelper({ - now: () => new Date(), - roll: function (formula) { - if (formula.name === "roll") return this.roll; - return new Roll(formula).roll().total - }, - multiply: (value1, value2) => Number(value1) * Number(value2), - divide: (value1, value2) => Number(value1) / Number(value2), - idx: function (array, idx) { - if (array.name === "idx") return this.idx; - return array && array.length > idx ? array[idx] : "" - } -}); \ No newline at end of file diff --git a/QoL/Macros.js b/QoL/Macros.js deleted file mode 100644 index 8ec3756..0000000 --- a/QoL/Macros.js +++ /dev/null @@ -1,16 +0,0 @@ -Hooks.on("hotbarDrop", (hotbar, data, slot) => { - if (data.type !== "RollTable") return true; - const table = game.tables.get(data.id); - if (!table) return true; - // Make a new macro for the RollTable - Macro.create({ - name: game.i18n.format("FURNACE.ROLLTABLE.macroName", {tableName: table.name}), - type: "script", - scope: "global", - command: `game.tables.get("${table.id}").draw();`, - img: "icons/svg/d20-grey.svg" - }).then(macro => { - game.user.assignHotbarMacro(macro, slot); - }); - return false; -}); \ No newline at end of file diff --git a/QoL/Playlist.js b/QoL/Playlist.js deleted file mode 100644 index 27dd9b8..0000000 --- a/QoL/Playlist.js +++ /dev/null @@ -1,77 +0,0 @@ -class FurnacePlaylistQoL { - - static init() { - game.settings.register("furnace", "playlistQoL", { - name: "Improve the Playlists UI", - hint: "Hides sound controls until hovered and shows what's playing at the top. Disable if it conflicts with other modules.", - scope: "world", - config: true, - default: true, - type: Boolean, - onChange: value => ui.playlists.render() - }); - game.settings.register("furnace", "volumeDbExponent", { - name: game.i18n.localize("FURNACE.PLAYLIST.volumeDbExponent"), - hint: game.i18n.localize("FURNACE.PLAYLIST.volumeDbExponentHint"), - scope: "world", - config: true, - default: 3, - type: Number, - onChange: value => ui.playlists.render() - }); - - // Replace volume/input controls to use x^3 equation for volume conversion - FurnacePatching.replaceFunction(AudioHelper, "volumeToInput", function (volume, order) { - return FurnacePatching.callOriginalFunction(this, "volumeToInput", volume, order || game.settings.get("furnace", "volumeDbExponent")) - }); - FurnacePatching.replaceFunction(AudioHelper, "inputToVolume", function (volume, order) { - return FurnacePatching.callOriginalFunction(this, "inputToVolume", volume, order || game.settings.get("furnace", "volumeDbExponent")) - }); - } - - static async renderDirectory(obj, html, data) { - if (game.settings.get("furnace", "playlistQoL") === false) - return; - html.find(".sound-control[data-action=sound-stop]").parents(".sound").addClass("sound-is-playing") - const isPlaying = data.entities.map(playlist => playlist.sounds.find(sound => sound.playing)).some(sound => !!sound); - if (isPlaying) { - // On 0.4.4, the sound id is in sound._id instead of sound.id - data.use_id = isNewerVersion(game.data.version, "0.4.3") - const nowPlaying = await renderTemplate("modules/furnace/templates/playlist-now-playing.html", data) - html.find(".directory-item.playlist").eq(0).after(nowPlaying) - } - let sounds = html.find("li.sound"); - for (let sound of sounds) { - sound = $(sound) - sound.find(".sound-name, .sound-controls").addClass("furnace-qol") - sound.find(`.sound-control[data-action=sound-edit], - .sound-control[data-action=sound-delete], - .sound-volume`).addClass("furnace-hide-control sound-control-hidden") - sound.hover(e => $(e.currentTarget).find(".furnace-hide-control").toggleClass("sound-control-hidden")) - - sound.find('.sound-volume').change(event => obj._onSoundVolume(event)); - } - } - - /** - * Helper function to find and toggle play state of a sound in a playlist. - * You can specify the full playlist/sound name or just the start of the name. - * Only the first playlist/sound that is found will be played in case it matches multiple ones. - * - * @param {String} playlistName Playlist name - * @param {String} soundName Sound name - * @param {Boolean} startsWith (Optional) whether to match the whole name or just the beginning - */ - static PlaySound(playlistName, soundName, startsWith=false) { - const playlist = game.playlists.entities.find(p => startsWith ? p.name.startsWith(playlistName) : p.name === playlistName); - if (!playlist) - return; - const sound = playlist.sounds.find(s => startsWith ? s.name.startsWith(soundName) : s.name === soundName); - - if (sound) - playlist.updateEmbeddedEntity("PlaylistSound", {_id: sound._id, playing: !sound.playing}) - } -} - -Hooks.on('init', FurnacePlaylistQoL.init) -Hooks.on('renderPlaylistDirectory', FurnacePlaylistQoL.renderDirectory) \ No newline at end of file diff --git a/QoL/Tokens.js b/QoL/Tokens.js deleted file mode 100644 index cb98ce7..0000000 --- a/QoL/Tokens.js +++ /dev/null @@ -1,187 +0,0 @@ -class FurnaceTokenQoL { - static init() { - game.settings.register("furnace", "tokenIgnoreVision", { - name: game.i18n.localize("FURNACE.ACTORS.tokenIgnoreVisionGM"), - scope: "world", - config: false, - default: false, - type: Boolean, - onChange: value => { - canvas.initializeSources() - } - }); - } - static getSceneControlButtons(buttons) { - let tokenButton = buttons.find(b => b.name == "token") - - if (tokenButton) { - tokenButton.tools.push({ - name: "vision", - title: game.i18n.localize("FURNACE.ACTORS.tokenIgnoreVision"), - icon: "far fa-eye-slash", - toggle: true, - active: game.settings.get("furnace", "tokenIgnoreVision"), - visible: game.user.isGM, - onClick: (value) => game.settings.set("furnace", "tokenIgnoreVision", value) - }); - } - } - static setup() { - // Disable sight layer's token vision if GM and option enabled - FurnacePatching.replaceGetter(SightLayer, 'tokenVision', function () { - if (game.user.isGM && game.settings.get("furnace", "tokenIgnoreVision")) - return false; - return FurnacePatching.callOriginalGetter(this, "tokenVision"); - }); - - // Drop Actor folder onto canvas - FurnacePatching.replaceMethod(Canvas, "_onDrop", async function (event) { - const ret = FurnacePatching.callOriginalFunction(canvas, "_onDrop", event); - // Try to extract the data - let data; - try { - data = JSON.parse(event.dataTransfer.getData('text/plain')); - } catch (err) { - return ret; - } - if (data.type === "Folder" && data.entity === "Actor") - FurnaceTokenQoL._onDropActorFolder(event, data) - return ret; - }); - } - - static isGridFree(x, width, y, height) { - for (let token of canvas.tokens.placeables) { - if ((token.x < x + width && token.x + token.w > x) || - (token.y < y + height && token.y + token.h > y)) - return false; - } - return true; - } - - static async _onDropActorFolder(event, data) { - const folder = game.folders.get(data.id); - const actors = folder.content; - if (actors.length === 0) return; - - const content = await renderTemplate("modules/furnace/templates/token-folder-drop.html", {folder, actors}); - - new Dialog({ - title: game.i18n.localize("FURNACE.ACTORS.dropActorsFolder"), - content: content, - buttons: { - yes: { - icon: '', - label: game.i18n.localize("FURNACE.ACTORS.dropTokens", {numTokens: actors.length}), - callback: async (html) => { - const choice = html.find("input[name='arrangement']:checked").val() - const hidden = event.altKey; - // Acquire cursor position transformed to Canvas coordinates - const t = canvas.tokens.worldTransform, - tx = (event.clientX - t.tx) / canvas.stage.scale.x, - ty = (event.clientY - t.ty) / canvas.stage.scale.y; - const topLeft = canvas.grid.getTopLeft(tx, ty); - let position = {x: topLeft[0], y: topLeft[1]} - if (choice === "same") { - for (let actor of actors) { - await this.dropActor(actor, {x: position.x, y: position.y, hidden}); - } - } else if (choice === "random") { - let distance = 0; - let dropped = 0; - let offsetX = 0; - let offsetY = 0; - for (let actor of actors) { - const w = getProperty(actor, "data.token.width") || 1; - const h = getProperty(actor, "data.token.height") || 1; - const total_tries = Math.pow(1 + distance*2, 2) - Math.pow(distance*2 - 1, 2); - let tries = Math.pow(1 + distance*2, 2) - dropped; - while (tries > 0) { - console.log(distance, dropped, tries, total_tries, offsetX, offsetY); - if (true || FurnaceTokenQoL.isGridFree(position.x + offsetX, w, position.y + offsetY, h)) - await this.dropActor(actor, {x: position.x + offsetX, y: position.y + offsetY, hidden}); - if (total_tries - tries < total_tries / 4) - offsetX += canvas.grid.w; - else if (total_tries - tries < 2 * total_tries / 4) - offsetY += canvas.grid.h; - else if (total_tries - tries < 3 * total_tries / 4) - offsetX -= canvas.grid.w; - else - offsetY -= canvas.grid.h; - tries -= 1; - break; - } - dropped += 1; - if (dropped === Math.pow(1 + distance*2, 2)) { - distance += 1; - offsetX = -1 * distance * canvas.grid.w; - offsetY = -1 * distance * canvas.grid.h; - } - } - } else { - // Line up - const horizontal = (choice === "lr" || choice === "rl"); - const direction = (choice === "lr" || choice === "tb") ? 1 : -1; - const step = horizontal ? canvas.grid.w : canvas.grid.h; - const total_grids = (horizontal ? canvas.grid.width : canvas.grid.height) / step; - const current_pos = (horizontal ? position.x : position.y) / step; - const grids_needed = actors.reduce((acc, actor) => { - return acc + getProperty(actor, horizontal ? "data.token.width" : "data.token.height") || 1 - }, 0); - if (current_pos + grids_needed > total_grids) - return ui.notifications.error(`Not enough space in the scene to line up ${actors.length} tokens.`) - if (false && FurnaceTokenQoL.isGridFree(position.x, horizontal ? step * grids_needed * direction : canvas.grid.w, position.y, horizontal ? canvas.grid.h : step * grids_needed * direction)) - return ui.notifications.error(`One or more tokens collides with an existing token in the scene.`) - - let offsetX = 0, offsetY = 0; - let previous_w = 0, previous_h = 0; - for (let actor of actors) { - const w = getProperty(actor, "data.token.width") || 1; - const h = getProperty(actor, "data.token.height") || 1; - // If going backwards, we need to offset our own size rather than the previous token's size - if (direction === -1) { - offsetX = offsetX - previous_w + w * step * direction - previous_w = w * step * direction; - offsetY = offsetY - previous_h + h * step * direction - previous_h = h * step * direction; - } - await this.dropActor(actor, {x: position.x + offsetX, y: position.y + offsetY, hidden}); - if (horizontal) - offsetX += w * step * direction; - else - offsetY += h * step * direction; - } - - } - } - }, - no: { - icon: '', - label: game.i18n.localize("FURNACE.ACTORS.cancelDrop"), - } - }, - default: "yes" - }, {width: 700}).render(true); - - } - - /* Copied from TokenLayer dropActor logic pre 0.7.0 */ - static async dropActor(actor, tokenData) { - // Merge Token data with the default for the Actor - tokenData = mergeObject(actor.data.token, tokenData, {inplace: false}); - // Get the Token image - if ( tokenData.randomImg ) { - let images = await actor.getTokenImages(); - images = images.filter(i => (images.length === 1) || !(i === canvas.tokens._lastWildcard)); - const image = images[Math.floor(Math.random() * images.length)]; - tokenData.img = canvas.tokens._lastWildcard = image; - } - - // Submit the Token creation request and activate the Tokens layer (if not already active) - return Token.create(tokenData); - } -} - -Hooks.on('init', FurnaceTokenQoL.init) -Hooks.on('getSceneControlButtons', FurnaceTokenQoL.getSceneControlButtons) -Hooks.on('setup', FurnaceTokenQoL.setup) \ No newline at end of file diff --git a/README.md b/README.md index de73538..efcc9e4 100644 --- a/README.md +++ b/README.md @@ -2,133 +2,21 @@ The Furnace is an essential part of every Foundry. -This Foundry VTT module brings Quality of Life Improvements to the VTT. -It started by adding Drawing Tools functionality to FVTT and then an experimental Macro system. It has now evolved into many small QoL Improvement features, some of which were later integrated into the core Foundry software. +This Foundry VTT module brought Quality of Life Improvements to the VTT. +It started by adding Drawing Tools functionality to FVTT and then an experimental Macro system. It has evolved into many small QoL Improvement features, some of which were later integrated into the core Foundry software. -The current features are : -- **Advanced Drawing Tools :** Improved drawing tools with different pattern fill types, new HUD, and various options. -- **Advanced Macros:** Use async script macros, handlebars templating, recursive macro calls and call macros with arguments or directly from chat. Also adds syntax highlighting, fullscreen mode and a test run button to the macro editor. -- **Split Journal :** Select the split option from the context menu on a journal entry to split it into multiple entries. -- **Tokens :** As GM, you can enable/disable token vision for yourself. You can also drop an actor folder into a scene to deploy multiple tokens at once. -- **Combat :** Double click the initiative value in the combat tracker to quickly modify it. -- **Playlists :** Adds a 'Now Playing' section, and auto-hides sound controls until hovered. Helps in fine tuning of low level volumes - -Check out the Macros compendium for some useful macros that showcase the advanced macros system as well as provide additional features. - -More QoL improvements are planned. - -# Installation - -You can now install this module manually by specifying the following public module URL : `https://raw.githubusercontent.com/League-of-Foundry-Developers/fvtt-module-furnace/master/module.json` - -As GM go to the `Manage Modules` options menu in your World Settings tab then enable the `The Furnace` module. - -# How to use - -This module aggregates many small improvements, some are self explanatory, but some do require some instructions. Here are quick howtos for the more advanced features of The Furnace. - -Let's start with the easy start and progressively go to the more complicated features. - -## Combat, Playlist, Tokens, Other - -- You can double click the combat tracker's initiative area to edit its value quickly. - -- The playlist will feature a "Now Playing" section to make it easier to find and manage your currently playing music. - -- A "Toggle Vision" button in the token's scene controls is added to the GM controls which allows the GM to disable vision when selecting tokens. - -- Party line-up allows you to drag&drop an entire folder of tokens into the map and have the tokens lining up any way you like. The party line up follows the order that the actors are sorted in within the folder. - -- Handlebar helpers are available to use by templates. Use `Handlebars.helpers` to see the available helpers. - -- A function `FurnacePlaylistQoL.PlaySound` is available for macros to use. It takes two mandatory arguments, the first being the name of a playlist, the second the name of the sound. - -- A "Debug" setting allows you to see in the developer's console all API hooks that Foundry VTT uses. Useful for debugging and for module developers. - -- A set of function/method replacement and monkey patching utility functions are available for use. Refer to the `Patches/Patches.js` file for details on the API available. - -## Split Journal - -If you have a journal entry with a lot of room descriptions and you want to split it into smaller journal entries, you can right click on it and select the "Split Journal" option. It will let you choose which heading to split it at then do the split for you. - -If you cannot split a journal or it doesn't suggest the right sections for you to split at, make sure you set your delimiters as one of the available headings in the journal entry's rich text editor. - - -## Advanced Drawing Tools - -The Drawing Tools in Furnace predate the implementation in Core, and it has been quite difficult to adapt the extensive work I did to work around the implementation in FVTT, so The Furnace simply replaces the drawing tools of Foundry with its own implementation. Therefore, it doesn't simply 'add features', but is a different implementation altogether. - -Since this is a big change to the core software, the advanced drawing tools **must be enabled in the Furnace settings**. - -The biggest difference is probably the various options available for how to apply textures to drawings. You can set a texture to be stretched, tiled, stretched on the contour only or tiled on the contour, and you can set the tiling size of a texture as well. - -You will also find a new 'Text' tool which can be used to draw text which can be scaled to whatever size you want, instead of being a label of fixed size on an existing drawing. - -The Drawing tools from Furnace are also more stable (as far as I'm aware) than the core implementation. This allows you to have textures of any size, the freehand drawings will be smoother, and there's a handle for rotation drawings. It is not without bugs however and some things are still unfortunately clunky to use. - -## Advanced Macros - -With Advanced Macros, a "Run Macro" button will appear in macro configuration windows to allow you to quickly test your macro and see its results. Do note that if you use an asynchronous macro which never resolves, you may end up with a macro in an invalid state if you end up cancelling the changes. - -**When advanced macros are enabled**, this option grants you additional power over the type of macros you can create. - -In the case of chat macros, you can now use [handlebars](https://handlebarsjs.com/) templating to render your chat text using common helpers, or use it along with the `macro` helper to call other macros, like for example `{{macro "name of my macro" actor 3 "a text argument"}}` - -In the case of script macros, you can now use a `return` statement for the early return paradigm, but also to retun a string which can then be used in chat macros. You will also be able to receive arguments via an array named `args`, and you can use the `await` keyword to make your script asynchronous. Do note however that if you create an async macro, it cannot be used to return text in a chat macro when using the `{{macro}}` helper. It will still be executed if used, but will be considered to have returned an empty string. If you want to use an async macro that prints its results to chat, read further for the use of recursive async chat commands. - -Besides those two enhancements to macros, you can now also create a temporary chat macro with handlebars templating directly from the chat entry, by entering text in the chat that includes the handlebars mustache characters `{{ }}`. You will also be able to call your macro directly from chat with the `/` prefix. As an example, you can send in the chat `/my-macro-name argument1 argument2 argument3`. -If your macro name has spaces in it, you can call it with `/"My macro name" "argument one" 100` for example. You can also call multiple macros by writing them one per line. - -In addition, you can now recursively call macros, so you could call a script or chat macro which returns a `/macro-name` text for it to call the macros recursively. - -Here is an example of use : -- **Macro name**: `Move token` -- **Type**: script -- **Content**: -```js -if (!token) return; -await token.update({x: args[0], y: args[1]}) -return `Token moved to (${args[0]}, ${args[1]})` -``` - -- **Macro name**: `pan` -- **Type**: script -- **Content**: -```js -canvas.pan({x: args[0], y: args[1], scale: args[2]}) -``` - -- **Macro name**: `current-time` -- **Type**: script -- **Content**: -```js -const now = new Date(); -return `${now.getHours()}:${now.getMinutes()}`; -``` - -- **Macro name**: `Return to corner` -- **Type**: chat -- **Content**: -``` -/"Move token" 0 0 -/pan 1000 1000 0.5 -It's currently {{macro "current-time"}} -``` - -- **Macro name**: `run` -- **Type**: chat -- **Content**: -``` -/"Return to corner" -``` - -You can then type `/run` in the chat to execute the 'run' macro which executes 'Return to corner' which will move the token to position (0, 0), then pan the canvas to position (1000, 1000) with a zoom of 50%, then output to the chat `Token moved to (0,0)\n\nIt's currently 21:45` - -**Note**: HTML content will not be parsed for /command macros, though you will still be able to use the `{{macro}}` helper in that case. -**Note 2**: You can only use one space to separate each argument. If you use more than one space, FVTT will replace the second with ` ` as it transforms the chat input into html, which would break your argument list. +Currently, this module does not have any functionality and serves besides pointing users to its successors. +| Feature | Description | Succeeding Project | +|---|---|---| +| Advanced Drawing Tools | Improved drawing tools with different pattern fill types, new HUD, and various options. | - | +| Advanced Macros | Use async script macros^1^, handlebars templating, recursive macro calls and call macros with arguments or directly from chat. Also adds syntax highlighting, fullscreen mode and a test run button to the macro editor.^2^ | 1: Integrated into Core
2: [Advanced Macros](https://github.com/League-of-Foundry-Developers/fvtt-advanced-macros) +| Combat | Double click the initiative value in the combat tracker to quickly modify it. | [Initiative Double Click](https://github.com/League-of-Foundry-Developers/fvtt-initiative-double-click) +| Playlists | Adds a 'Now Playing' section, and auto-hides sound controls until hovered. Helps in fine tuning of low level volumes | Integrated into Core +| Split Journal | Select the split option from the context menu on a journal entry to split it into multiple entries. | [Split Journal](https://github.com/League-of-Foundry-Developers/fvtt-split-journal) +| Tokens | As GM, you can enable/disable token vision for yourself.^1^ You can also drop an actor folder into a scene to deploy multiple tokens at once.^2^ | 1: [Less Fog](https://github.com/trdischat/lessfog)
2: [DFreds Droppables](https://github.com/DFreds/dfreds-droppables) # License This Foundry VTT module, writen by KaKaRoTo, is licensed under a [Creative Commons Attribution 4.0 International License](http://creativecommons.org/licenses/by/4.0/). -This work is licensed under Foundry Virtual Tabletop [EULA - Limited License Agreement for module development v 0.1.6](http://foundryvtt.com/pages/license.html). +This work is licensed under Foundry Virtual Tabletop [EULA - Limited License Agreement for module development v 0.1.6](http://foundryvtt.com/pages/license.html). \ No newline at end of file diff --git a/lang/en.json b/lang/en.json deleted file mode 100644 index cb9bf22..0000000 --- a/lang/en.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "FURNACE.DRAWINGS.ConfigTitle": "Drawing Configuration", - "FURNACE.DRAWINGS.createDrawing": "Create", - "FURNACE.DRAWINGS.updateDrawing": "Update", - "FURNACE.DRAWINGS.setDefaults": "Set New Drawing Defaults", - "FURNACE.DRAWINGS.NotAuthorizedToConfigure": "You do not have the ability to configure a Drawing object.", - "FURNACE.DRAWINGS.hudAutoFit": "Resize to fit text", - "FURNACE.DRAWINGS.mirrorVertically": "Mirror Vertically", - "FURNACE.DRAWINGS.mirrorHorizontally": "Mirror Horizontally", - "FURNACE.DRAWINGS.type": "Drawing Type", - "FURNACE.DRAWINGS.tabSettings": "Settings", - "FURNACE.DRAWINGS.tabTexture": "Texture", - "FURNACE.DRAWINGS.positionHint": "Adjust the position and rotation of the Drawing.", - "FURNACE.DRAWINGS.xPosition": "X-Position", - "FURNACE.DRAWINGS.yPosition": "Y-Position", - "FURNACE.DRAWINGS.zPosition": "Z-Position", - "FURNACE.DRAWINGS.pixelsUnit": "(pixels)", - "FURNACE.DRAWINGS.degreesUnit": "(degrees)", - "FURNACE.DRAWINGS.textContent": "Text Content", - "FURNACE.DRAWINGS.settingsHint": "Configure most of the Drawing's settings.", - "FURNACE.DRAWINGS.textureHint": "Configure the Drawing's texture information.", - "FURNACE.DRAWINGS.textureHint1": "You can set a tint to the texture with the Fill Color and Opacity. To remove the tint, simply set Fill Opacity to 0.", - "FURNACE.DRAWINGS.textureHint2": "Remember to lower or zero the fill/stroke opacity for the texture to appear.", - "FURNACE.DRAWINGS.textureOpacity": "Texture Opacity", - "FURNACE.DRAWINGS.textureSizeHint": "Set the width and height to 0 for the tiled pattern to retain the original image size, or override the tiled image's size here.", - "FURNACE.DRAWINGS.textureWidth": "Texture Width", - "FURNACE.DRAWINGS.textureHeight": "Texture Height", - - "FURNACE.SPLIT.title": "Split Journal", - "FURNACE.SPLIT.splitSubmit": "Split Journal", - "FURNACE.SPLIT.contextMenu": "Split Journal", - "FURNACE.SPLIT.description": "Split the journal entry '{name}' into multiple entries.", - "FURNACE.SPLIT.splitOn": "Split on", - "FURNACE.SPLIT.newEntries": "New journal entries ({numEntries}) :", - "FURNACE.SPLIT.separateHeader": "Include page header in each new journal entry", - "FURNACE.SPLIT.includeImage": "Include Journal's image in each new entry", - "FURNACE.SPLIT.newFolder": "Split in new Folder", - "FURNACE.SPLIT.errorNoHeadings": "This Journal entry had no headings.

Create headings in your journal then try again

", - "FURNACE.SPLIT.errorJournalEmpty": "This Journal entry is empty.

There is nothing to split

", - "FURNACE.SPLIT.errorOk": "OK", - - "FURNACE.ENTITIES.sortAscending": "Sort Alphabetically (Ascending)", - "FURNACE.ENTITIES.sortDescending": "Sort Alphabetically (Descending)", - - "FURNACE.ACTORS.droppedFolder": "You have dropped {numActors} actors from the folder '{folderName}' onto the scene.", - "FURNACE.ACTORS.dropQuestion": "How would you like to arrange their tokens ?", - "FURNACE.ACTORS.arrangementSame": "All {totalTokens} tokens on top of each other.", - "FURNACE.ACTORS.arrangementRandom": "Randomly spread out (packed close) around the dropped point.", - "FURNACE.ACTORS.arrangementTopBottom": "Line up (marching order based on folder order) from Top to Bottom.", - "FURNACE.ACTORS.arrangementBottomTop": "Line up from Bottom to Top.", - "FURNACE.ACTORS.arrangementLeftRight": "Line up from Left to Right.", - "FURNACE.ACTORS.arrangementRightLeft": "Line up from Right to Left.", - "FURNACE.ACTORS.dropActorsFolder": "Drop Actor Folder to Canvas", - "FURNACE.ACTORS.dropTokens": "Drop {numTokens} tokens", - "FURNACE.ACTORS.tokenIgnoreVisionGM": "Ignore Token Vision for the GM", - "FURNACE.ACTORS.tokenIgnoreVision": "Ignore Token Vision", - "FURNACE.ACTORS.cancelDrop": "Cancel Drop", - - "FURNACE.SETTINGS.enableDebug": "Enable Hooks Debugging", - "FURNACE.SETTINGS.enableDebugHint": "Useful for module developers", - - "FURNACE.ROLLTABLE.macroName": "RollTable: {tableName}", - - - "FURNACE.PLAYLIST.NowPlaying": "Now Playing", - "FURNACE.PLAYLIST.volumeDbExponent": "Volume slider linear to logarithmic exponential approximation", - "FURNACE.PLAYLIST.volumeDbExponentHint": "A volume slider is linear but the audio level perception in humans is logarithmic. It can be approximated with an exponential equation.\nFVTT uses a value of 2 (equation X^2) by default, X^3 or X^4 makes it easier to fine tune lower volume levels.\nSet the value higher if your music is always too loud and you can't lower the volume to a sensible value without it dropping too low." -} \ No newline at end of file diff --git a/lang/es.json b/lang/es.json deleted file mode 100644 index 21fc3d5..0000000 --- a/lang/es.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "FURNACE.DRAWINGS.ConfigTitle": "Ajustes de Dibujo", - "FURNACE.DRAWINGS.createDrawing": "Crear", - "FURNACE.DRAWINGS.updateDrawing": "Actualizar", - "FURNACE.DRAWINGS.setDefaults": "Set New Dibujo Defaults", - "FURNACE.DRAWINGS.NotAuthorizedToConfigure": "No tiene permisos para configurar un objeto Dibujo.", - "FURNACE.DRAWINGS.hudAutoFit": "Redimensionar para ajustar al texto", - "FURNACE.DRAWINGS.mirrorVertically": "Voltear Verticalmente", - "FURNACE.DRAWINGS.mirrorHorizontally": "Voltear Horizontalmente", - "FURNACE.DRAWINGS.type": "Tipo de Dibujo", - "FURNACE.DRAWINGS.tabSettings": "Ajustes", - "FURNACE.DRAWINGS.tabTexture": "Textura", - "FURNACE.DRAWINGS.positionHint": "Ajustar la posición y la rotación del Dibujo.", - "FURNACE.DRAWINGS.xPosition": "Posición-X", - "FURNACE.DRAWINGS.yPosition": "Posición-Y", - "FURNACE.DRAWINGS.zPosition": "Posición-Z", - "FURNACE.DRAWINGS.pixelsUnit": "(píxeles)", - "FURNACE.DRAWINGS.degreesUnit": "(grados)", - "FURNACE.DRAWINGS.textContent": "Contenido del Texto", - "FURNACE.DRAWINGS.settingsHint": "Ajusta la mayoría de las opciones del Dibujo.", - "FURNACE.DRAWINGS.textureHint": "Ajusta la información de la textura del Dibujo.", - "FURNACE.DRAWINGS.textureHint1": "Puede ajustar el tono de la textura con el Color de Relleno y la Opacidad. Para quitar el tono, fije la Opacidad a 0.", - "FURNACE.DRAWINGS.textureHint2": "Recuerde bajar (o poner a 0) la opacidad del relleno/trazo para que aparezca la textura.", - "FURNACE.DRAWINGS.textureOpacity": "Opacidad de Textura", - "FURNACE.DRAWINGS.textureSizeHint": "Ajuste el alto y ancho del patrón en baldosas a 0 para preservar el tamaño original de la imagen, o configure aquí directamente el tamaño de la imagen en baldosas.", - "FURNACE.DRAWINGS.textureWidth": "Ancho de la Textura", - "FURNACE.DRAWINGS.textureHeight": "Alto de la Textura", - - "FURNACE.SPLIT.title": "Dividir Diario", - "FURNACE.SPLIT.splitSubmit": "Dividir Diario", - "FURNACE.SPLIT.contextMenu": "Dividir Diario", - "FURNACE.SPLIT.description": "Divide la entrada de diario '{name}' en múltiples entradas.", - "FURNACE.SPLIT.splitOn": "Dividir", - "FURNACE.SPLIT.newEntries": "Nuevas entradas de diario ({numEntries}) :", - "FURNACE.SPLIT.separateHeader": "Incluir el encabezado de página en cada nueva entrada", - "FURNACE.SPLIT.includeImage": "Incluir la imagen del diario en cada nueva entrada", - "FURNACE.SPLIT.newFolder": "Dividir en una nueva carpeta", - "FURNACE.SPLIT.errorNoHeadings": "Esta entrada de diario no tiene encabezados.

Añada encabezados e inténtelo de nuevo

", - "FURNACE.SPLIT.errorJournalEmpty": "Esta entrada de diario está vacía.

No hay nada que dividir

", - "FURNACE.SPLIT.errorOk": "OK", - - "FURNACE.ENTITIES.sortAscending": "Ordenar Alfabéticamente (Ascendente)", - "FURNACE.ENTITIES.sortDescending": "Ordenar Alfabéticamente (Descendente)", - - "FURNACE.ACTORS.droppedFolder": "Ha soltado {numActors} actores desde el directorio '{folderName}' a la escena.", - "FURNACE.ACTORS.dropQuestion": "¿Cómo desea organizar los iconos ?", - "FURNACE.ACTORS.arrangementSame": "Los {totalTokens} iconos uno encima del otro.", - "FURNACE.ACTORS.arrangementRandom": "Diseminarlos al azar (modo compacto) en torno al punto donde los suelte.", - "FURNACE.ACTORS.arrangementTopBottom": "Alinear (basado en el orden del directorio) de Arriba a Abajo.", - "FURNACE.ACTORS.arrangementBottomTop": "Alinear de Abajo a Arriba.", - "FURNACE.ACTORS.arrangementLeftRight": "Alinear de Izquierda a Derecha.", - "FURNACE.ACTORS.arrangementRightLeft": "Alinear de Derecha a Izquierda.", - "FURNACE.ACTORS.dropActorsFolder": "Soltar Directorio de Actores al Lienzo", - "FURNACE.ACTORS.dropTokens": "Soltar {numTokens} iconos", - "FURNACE.ACTORS.tokenIgnoreVisionGM": "Ignorar Visión de Icono para el GM", - "FURNACE.ACTORS.tokenIgnoreVision": "Ignorar Visión de Icono", - "FURNACE.ACTORS.cancelDrop": "Cancelar Soltar", - - "FURNACE.SETTINGS.enableDebug": "Activar depuración", - "FURNACE.SETTINGS.enableDebugHint": "Opción útil para desarrolladores de módulos", - - "FURNACE.ROLLTABLE.macroName": "Tabla dinámica: {tableName}", - - - "FURNACE.PLAYLIST.NowPlaying": "Reproducción en curso", - "FURNACE.PLAYLIST.volumeDbExponent": "Barra de volumen con aproximación exponencial lineal a logarítmica", - "FURNACE.PLAYLIST.volumeDbExponentHint": "Una barra de volumen es lineal, pero la nivel de percepción de audio humano es logarítmico. Puede ser aproximado mediante una ecuación exponencial.\nFVTT usa por defecto un valor de 2 (ecuación X^2), pero X^3 o X^4 permite ajustar mejor los niveles bajos de sonido.\nAjuste este número a valores mayores si la música siempre está demasiado alta y no le es posible bajar el volumen hasta un valor sensible sin que resulte demasiado bajo." -} \ No newline at end of file diff --git a/lang/pt-BR.json b/lang/pt-BR.json deleted file mode 100644 index c5516b8..0000000 --- a/lang/pt-BR.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "FURNACE.DRAWINGS.ConfigTitle": "Configuração de Desenho", - "FURNACE.DRAWINGS.createDrawing": "Criar", - "FURNACE.DRAWINGS.updateDrawing": "Atualizar", - "FURNACE.DRAWINGS.setDefaults": "Definir Novos Padrões de Desenho", - "FURNACE.DRAWINGS.NotAuthorizedToConfigure": "Você não tem a capacidade de configurar um objeto de desenho.", - "FURNACE.DRAWINGS.hudAutoFit": "Redimensionar para caber o texto", - "FURNACE.DRAWINGS.mirrorVertically": "Espelhar Verticalmente", - "FURNACE.DRAWINGS.mirrorHorizontally": "Espelhar Horizontalmente", - "FURNACE.DRAWINGS.type": "Tipo de Desenho", - "FURNACE.DRAWINGS.tabSettings": "Definições", - "FURNACE.DRAWINGS.tabTexture": "Textura", - "FURNACE.DRAWINGS.positionHint": "Ajuste a posição e rotação do desenho.", - "FURNACE.DRAWINGS.xPosition": "Posição-X", - "FURNACE.DRAWINGS.yPosition": "Posição-Y", - "FURNACE.DRAWINGS.zPosition": "Posição-Z", - "FURNACE.DRAWINGS.pixelsUnit": "(pixels)", - "FURNACE.DRAWINGS.degreesUnit": "(graus)", - "FURNACE.DRAWINGS.textContent": "Conteudo do Texto", - "FURNACE.DRAWINGS.settingsHint": "Defina a maioria das configurações do desenho.", - "FURNACE.DRAWINGS.textureHint": "Configure as informações de textura do desenho.", - "FURNACE.DRAWINGS.textureHint1": "Você pode definir uma tonalidade para a textura com a cor de preenchimento e opacidade. Para remover a tonalidade, basta definir a Opacidade de preenchimento como 0.", - "FURNACE.DRAWINGS.textureHint2": "Lembre-se de diminuir ou zerar a opacidade de preenchimento/traço para que a textura apareça.", - "FURNACE.DRAWINGS.textureOpacity": "Opacidade da Textura", - "FURNACE.DRAWINGS.textureSizeHint": "Defina a largura e a altura como 0 para que o padrão lado a lado retenha o tamanho original da imagem ou substitua o tamanho da imagem lado a lado aqui.", - "FURNACE.DRAWINGS.textureWidth": "Largura da Textura", - "FURNACE.DRAWINGS.textureHeight": "Altura da Textura", - - "FURNACE.SPLIT.title": "Dividir Diário", - "FURNACE.SPLIT.splitSubmit": "Dividir Diário", - "FURNACE.SPLIT.contextMenu": "Dividir Diário", - "FURNACE.SPLIT.description": "Divida a entrada de diário '{nome}' em várias partes.", - "FURNACE.SPLIT.splitOn": "Dividir em", - "FURNACE.SPLIT.newEntries": "Novas entradas de diário ({numEntries}) :", - "FURNACE.SPLIT.separateHeader": "Incluir o cabeçalho da página em cada nova entrada de diário", - "FURNACE.SPLIT.includeImage": "Incluir imagem do diário em cada nova entrada", - "FURNACE.SPLIT.newFolder": "Dividir em uma Nova Pasta", - "FURNACE.SPLIT.errorNoHeadings": "Esta entrada de diário não tinha títulos.

Crie títulos em seu diário e tente novamente

", - "FURNACE.SPLIT.errorJournalEmpty": "Esta entrada do diário está vazia.

Não há nada para dividir

", - "FURNACE.SPLIT.errorOk": "OK", - - "FURNACE.ENTITIES.sortAscending": "Classificar em ordem alfabética (crescente)", - "FURNACE.ENTITIES.sortDescending": "Classificar em ordem alfabética (decrescente)", - - "FURNACE.ACTORS.droppedFolder": "Você colocou {numActors} atores da pasta '{folderName}' na cena.", - "FURNACE.ACTORS.dropQuestion": "Como você gostaria de organizar seus tokens ?", - "FURNACE.ACTORS.arrangementSame": "Todos os {totalTokens} tokens um em cima do outro.", - "FURNACE.ACTORS.arrangementRandom": "Distribuído aleatoriamente (compactado) em torno do ponto que largou.", - "FURNACE.ACTORS.arrangementTopBottom": "Alinhe (ordem de marcha com base na ordem da pasta) de cima para baixo.", - "FURNACE.ACTORS.arrangementBottomTop": "Alinhe de Baixo para Cima.", - "FURNACE.ACTORS.arrangementLeftRight": "Alinhe da Esquerda para a Direita.", - "FURNACE.ACTORS.arrangementRightLeft": "Alinhe da Direita para a Esquerda.", - "FURNACE.ACTORS.dropActorsFolder": "Soltar pasta de ator na tela", - "FURNACE.ACTORS.dropTokens": "Largar {numTokens} tokens", - "FURNACE.ACTORS.tokenIgnoreVisionGM": "Ignorar Visão do Token para o Mestre", - "FURNACE.ACTORS.tokenIgnoreVision": "Ignorar Visão do Token", - "FURNACE.ACTORS.cancelDrop": "Cancelar Soltura", - - "FURNACE.SETTINGS.enableDebug": "Habilitar depuração de ganchos", - "FURNACE.SETTINGS.enableDebugHint": "Útil para desenvolvedores de módulo", - - "FURNACE.ROLLTABLE.macroName": "RollTable: {tableName}", - - - "FURNACE.PLAYLIST.NowPlaying": "Tocando agora", - "FURNACE.PLAYLIST.volumeDbExponent": "Controle deslizante de volume linear para aproximação exponencial logarítmica", - "FURNACE.PLAYLIST.volumeDbExponentHint": "Um controle deslizante de volume é linear, mas a percepção do nível de áudio em humanos é logarítmica. Pode ser aproximado com uma equação exponencial. \n FVTT usa um valor de 2 (equação X^2) por padrão, X^3 ou X^4 torna mais fácil ajustar os níveis de volume mais baixos. \n Defina o valor mais alto se sua música é sempre muito alto e você não pode abaixar o volume para um valor razoável sem cair muito baixo." -} diff --git a/lang/zh-CN.json b/lang/zh-CN.json deleted file mode 100644 index 3edeab9..0000000 --- a/lang/zh-CN.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "FURNACE.PLAYLIST.NowPlaying": "正在播放" -} diff --git a/libs/LICENSE-just-handelbars-helpers.md b/libs/LICENSE-just-handelbars-helpers.md deleted file mode 100644 index 15eae14..0000000 --- a/libs/LICENSE-just-handelbars-helpers.md +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 - present Leapfrog Technology Inc. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/libs/LICENSE-split-html.md b/libs/LICENSE-split-html.md deleted file mode 100644 index 882f54b..0000000 --- a/libs/LICENSE-split-html.md +++ /dev/null @@ -1,7 +0,0 @@ -Copyright (c) 2016 P'unk Avenue LLC - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/libs/LICENSE-tokenize-this.txt b/libs/LICENSE-tokenize-this.txt deleted file mode 100644 index ceca9e4..0000000 --- a/libs/LICENSE-tokenize-this.txt +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2016 shaunpersad - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/libs/TokenizeThis.js b/libs/TokenizeThis.js deleted file mode 100644 index ddeac51..0000000 --- a/libs/TokenizeThis.js +++ /dev/null @@ -1,478 +0,0 @@ -"use strict"; - -const MODE_NONE = 'modeNone'; -const MODE_DEFAULT = 'modeDefault'; -const MODE_MATCH = 'modeMatch'; - -/** - * Sorts the tokenizable substrings by their length DESC. - * - * @param {string} a - * @param {string} b - * @returns {number} - */ -const sortTokenizableSubstrings = (a, b) => { - - if (a.length > b.length) { - return -1; - } - if (a.length < b.length) { - return 1; - } - return 0; -}; - -const endsWith = (str, suffix) => { - return str.indexOf(suffix, str.length - suffix.length) !== -1; -}; - -/** - * Create an instance of this class for each new string you wish to parse. - */ -class Tokenizer { - - /** - * - * @param {TokenizeThis} factory - * @param {string} str - * @param {function} forEachToken - */ - constructor(factory, str, forEachToken) { - - /** - * Holds the processed configuration. - * - * @type {TokenizeThis} - */ - this.factory = factory; - - /** - * The string to tokenize. - * - * @type {string} - */ - this.str = str; - - /** - * The function to call for teach token. - * - * @type {Function} - */ - this.forEachToken = forEachToken; - - /** - * The previous character consumed. - * - * @type {string} - */ - this.previousChr = ''; - - /** - * The current quote to match. - * - * @type {string} - */ - this.toMatch = ''; - - /** - * The current token being created. - * - * @type {string} - */ - this.currentToken = ''; - - /** - * Keeps track of the current "mode" of tokenization. - * The tokenization rules are different depending if you are tokenizing an explicit string (surrounded by quotes), - * versus a non-explicit string (not surrounded by quotes). - * - * @type {*[]} - */ - this.modeStack = [MODE_NONE]; - - this.currentIndex = 0; - } - - /** - * - * @returns {string} - */ - getCurrentMode() { - - return this.modeStack[this.modeStack.length - 1]; - } - - /** - * - * @param {string} mode - * @returns {Number} - */ - setCurrentMode(mode) { - - return this.modeStack.push(mode); - } - - /** - * - * @returns {string} - */ - completeCurrentMode() { - - const currentMode = this.getCurrentMode(); - - if (currentMode === MODE_DEFAULT) { - this.pushDefaultModeTokenizables(); - } - - /** - * Don't push out empty tokens, unless they were an explicit string, e.g. "" - */ - if ((currentMode === MODE_MATCH && this.currentToken === '') || this.currentToken !== '') { - this.push(this.currentToken); - } - this.currentToken = ''; - - return this.modeStack.pop(); - } - - /** - * - * @param {*} token - */ - push(token) { - - let surroundedBy = ''; - - if (this.factory.convertLiterals && this.getCurrentMode() !== MODE_MATCH) { - - /** - * Convert the string version of literals into their...literal..form. - */ - switch(token.toLowerCase()) { - case 'null': - token = null; - break; - case 'true': - token = true; - break; - case 'false': - token = false; - break; - default: - if (isFinite(token)) { - token = Number(token); - } - break; - } - } else { - - /** - * The purpose of also transmitting the surroundedBy quote is to inform whether or not the token was an explicit string, - * versus a non-explicit string, e.g. "=" vs. = - * @type {string} - */ - surroundedBy = this.toMatch; - } - - if (this.forEachToken) { - this.forEachToken(token, surroundedBy, this.currentIndex); - } - } - - tokenize() { - - let index = 0; - - while(index < this.str.length) { - - this.currentIndex = index; - this.consume(this.str.charAt(index++)); - } - - while (this.getCurrentMode() !== MODE_NONE) { - - this.completeCurrentMode(); - } - } - - /** - * - * @param {string} chr - */ - consume(chr) { - - this[this.getCurrentMode()](chr); - this.previousChr = chr; - } - - /** - * - * @param {string} chr - * @returns {*} - */ - [MODE_NONE](chr) { - - if (!this.factory.matchMap[chr]) { - - this.setCurrentMode(MODE_DEFAULT); - return this.consume(chr); - } - - this.setCurrentMode(MODE_MATCH); - this.toMatch = chr; - } - - /** - * - * @param {string} chr - * @returns {string} - */ - [MODE_DEFAULT](chr) { - - /** - * If we encounter a delimiter, its time to push out the current token. - */ - if (this.factory.delimiterMap[chr]) { - - return this.completeCurrentMode(); - } - - /** - * If we encounter a quote, only push out the current token if there's a sub-token directly before it. - */ - if (this.factory.matchMap[chr]) { - - let tokenizeIndex = 0; - - while (tokenizeIndex < this.factory.tokenizeList.length) { - - if (endsWith(this.currentToken, this.factory.tokenizeList[tokenizeIndex++])) { - this.completeCurrentMode(); - return this.consume(chr); - } - } - } - - this.currentToken+=chr; - - return this.currentToken; - } - - /** - * This crazy function parses out potential tokenizable substrings out of the current token. - * - * @returns {*} - */ - pushDefaultModeTokenizables() { - - // TODO: refactor this to be more performant. - - let tokenizeIndex = 0; - let lowestIndexOfTokenize = Infinity; - let toTokenize = null; - - /** - * Iterate through the list of tokenizable substrings. - */ - while(this.currentToken && tokenizeIndex < this.factory.tokenizeList.length) { - - const tokenize = this.factory.tokenizeList[tokenizeIndex++]; - const indexOfTokenize = this.currentToken.indexOf(tokenize); - - /** - * Find the substring closest to the beginning of the current token. - */ - if (indexOfTokenize !== -1 && indexOfTokenize < lowestIndexOfTokenize) { - - lowestIndexOfTokenize = indexOfTokenize; - toTokenize = tokenize; - } - } - - /** - * No substrings to tokenize. You're done. - */ - if (!toTokenize) { - return; - } - - /** - * A substring was found, but not at the very beginning of the string, e.g. A=B, where "=" is the substring. - * This will push out "A" first. - */ - if (lowestIndexOfTokenize > 0) { - this.push(this.currentToken.substring(0, lowestIndexOfTokenize)); - } - - /** - * Push out the substring, then modify the current token to be everything past that substring. - * Recursively call this function again until there are no more substrings to tokenize. - */ - if (lowestIndexOfTokenize !== -1) { - - this.push(toTokenize); - this.currentToken = this.currentToken.substring(lowestIndexOfTokenize + toTokenize.length); - return this.pushDefaultModeTokenizables(); - } - } - - /** - * - * @param {string} chr - * @returns {string} - */ - [MODE_MATCH](chr) { - - if (chr === this.toMatch) { - - if (this.previousChr !== this.factory.escapeCharacter) { - - return this.completeCurrentMode(); - } - this.currentToken = this.currentToken.substring(0, this.currentToken.length - 1); - } - - this.currentToken+=chr; - - return this.currentToken; - } -} - -/** - * This is the main class. It takes in the config, processes it, and creates tokenizer instances based on that config. - */ -class TokenizeThis { - - /** - * - * @param {{shouldTokenize: string[], shouldMatch: string[], shouldDelimitBy: string[]}} [config] - */ - constructor(config) { - - if (!config) { - config = {}; - } - - /** - * - * @type {{shouldTokenize: string[], shouldMatch: string[], shouldDelimitBy: string[], convertLiterals: boolean, escapeCharacter: string}} - */ - config = Object.assign({}, this.constructor.defaultConfig, config); - - /** - * - * @type {boolean} - */ - this.convertLiterals = config.convertLiterals; - - /** - * - * @type {string} - */ - this.escapeCharacter = config.escapeCharacter; - - /** - * Holds the list of tokenizable substrings. - * - * @type {Array} - */ - this.tokenizeList = []; - - /** - * Holds an easy lookup map of tokenizable substrings. - * - * @type {{}} - */ - this.tokenizeMap = {}; - - /** - * Holds the list of quotes to match explicit strings with. - * - * @type {Array} - */ - this.matchList = []; - - /** - * Holds an easy lookup map of quotes to match explicit strings with. - * - * @type {{}} - */ - this.matchMap = {}; - - /** - * Holds the list of delimiters. - * - * @type {Array} - */ - this.delimiterList = []; - - /** - * Holds an easy lookup map of delimiters. - * - * @type {{}} - */ - this.delimiterMap = {}; - - /** - * Sorts the tokenizable substrings based on their length, - * such that "<=" will get matched before "<" does. - */ - config.shouldTokenize.sort(sortTokenizableSubstrings).forEach((token) => { - - if (!this.tokenizeMap[token]) { - this.tokenizeList.push(token); - this.tokenizeMap[token] = token; - } - }); - - config.shouldMatch.forEach((match) => { - - if (!this.matchMap[match]) { - this.matchList.push(match); - this.matchMap[match] = match; - } - }); - - config.shouldDelimitBy.forEach((delimiter) => { - - if (!this.delimiterMap[delimiter]) { - this.delimiterList.push(delimiter); - this.delimiterMap[delimiter] = delimiter; - } - }); - } - - /** - * Creates a Tokenizer, then immediately calls "tokenize". - * - * @param {string} str - * @param {function} forEachToken - * @returns {*} - */ - tokenize(str, forEachToken) { - - const tokenizerInstance = new Tokenizer(this, str, forEachToken); - return tokenizerInstance.tokenize(); - } - - /** - * - * @returns {{shouldTokenize: string[], shouldMatch: string[], shouldDelimitBy: string[], convertLiterals: boolean, escapeCharacter: string}} - */ - static get defaultConfig() { - - return { - shouldTokenize: ['(', ')', ',', '*', '/', '%', '+', '-', '=', '!=', '!', '<', '>', '<=', '>=', '^'], - shouldMatch: ['"', "'", '`'], - shouldDelimitBy: [' ', "\n", "\r", "\t"], - convertLiterals: true, - escapeCharacter: "\\" - }; - } -} - -/** - * - * @type {TokenizeThis} - */ -module.exports = TokenizeThis; diff --git a/libs/h.min.js b/libs/h.min.js deleted file mode 100644 index 9732b01..0000000 --- a/libs/h.min.js +++ /dev/null @@ -1 +0,0 @@ -!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).H=e()}}(function(){return function o(u,f,c){function l(n,e){if(!f[n]){if(!u[n]){var t="function"==typeof require&&require;if(!e&&t)return t(n,!0);if(s)return s(n,!0);var r=new Error("Cannot find module '"+n+"'");throw r.code="MODULE_NOT_FOUND",r}var i=f[n]={exports:{}};u[n][0].call(i.exports,function(e){return l(u[n][1][e]||e)},i,i.exports,o,u,f,c)}return f[n].exports}for(var s="function"==typeof require&&require,e=0;e"+t+""}).join("\n")}},{}],7:[function(e,n,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.sum=function(e,n){return Number(e)+Number(n)},t.difference=function(e,n){return Number(e)-Number(n)},t.ceil=function(e){return Math.ceil(Number(e))},t.floor=function(e){return Math.floor(Number(e))},t.abs=function(e){return Math.abs(Number(e))}},{}],8:[function(e,n,t){(function(u){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.excerpt=function(e,n){if(n=parseInt(n)||50,"string"!=typeof e||"number"!=typeof n)return e;if(e.length")},t.capitalizeEach=function(e){return"string"!=typeof e?e:e.toLowerCase().replace(/\w\S*/g,function(e){return e.charAt(0).toUpperCase()+e.substr(1)})},t.capitalizeFirst=function(e){return"string"!=typeof e?e:e.charAt(0).toUpperCase()+e.slice(1)},t.sprintf=function(e){var n=u.vsprintf;(0,f.isFunction)(n)||(window.sprintf,n=window.vsprintf);for(var t=[],r=arguments.length,i=Array(1 - - -``` - -This will find and highlight code inside of `
` tags; it tries
-to detect the language automatically. If automatic detection doesn’t
-work for you, you can specify the language in the `class` attribute:
-
-```html
-
...
-``` - -Classes may also be prefixed with either `language-` or `lang-`. - -```html -
...
-``` - -### Plaintext and Disabling Highlighting - -To style arbitrary text like code, but without any highlighting, use the -`plaintext` class: - -```html -
...
-``` - -To disable highlighting of a tag completely, use the `nohighlight` class: - -```html -
...
-``` - -### Supported Languages - -The table below shows the full list of supported languages (and corresponding classes) that are bundled with the library. Note: Which languages are available may depend on how you've built or included the library in your app. See [Getting the Library](#getting-the-library) below. - -
-Reveal the full list of languages... - -| Language | Classes | Package | -| :-----------------------| :--------------------- | :------ | -| 1C | 1c | | -| ABNF | abnf | | -| Access logs | accesslog | | -| Ada | ada | | -| ARM assembler | armasm, arm | | -| AVR assembler | avrasm | | -| ActionScript | actionscript, as | | -| Alan | alan, i | [highlightjs-alan](https://github.com/highlightjs/highlightjs-alan) | -| AngelScript | angelscript, asc | | -| Apache | apache, apacheconf | | -| AppleScript | applescript, osascript | | -| Arcade | arcade | | -| AsciiDoc | asciidoc, adoc | | -| AspectJ | aspectj | | -| AutoHotkey | autohotkey | | -| AutoIt | autoit | | -| Awk | awk, mawk, nawk, gawk | | -| Axapta | axapta | | -| Bash | bash, sh, zsh | | -| Basic | basic | | -| BNF | bnf | | -| Brainfuck | brainfuck, bf | | -| C# | cs, csharp | | -| C++ | cpp, c, cc, h, c++, h++, hpp | | -| C/AL | cal | | -| Cache Object Script | cos, cls | | -| CMake | cmake, cmake.in | | -| Coq | coq | | -| CSP | csp | | -| CSS | css | | -| Cap’n Proto | capnproto, capnp | | -| Clojure | clojure, clj | | -| CoffeeScript | coffeescript, coffee, cson, iced | | -| Crmsh | crmsh, crm, pcmk | | -| Crystal | crystal, cr | | -| Cypher (Neo4j) | cypher | [highlightjs-cypher](https://github.com/highlightjs/highlightjs-cypher) | -| D | d | | -| DNS Zone file | dns, zone, bind | | -| DOS | dos, bat, cmd | | -| Dart | dart | | -| Delphi | delphi, dpr, dfm, pas, pascal, freepascal, lazarus, lpr, lfm | | -| Diff | diff, patch | | -| Django | django, jinja | | -| Dockerfile | dockerfile, docker | | -| dsconfig | dsconfig | | -| DTS (Device Tree) | dts | | -| Dust | dust, dst | | -| Dylan | dylan | [highlight-dylan](https://github.com/highlightjs/highlight-dylan) | -| EBNF | ebnf | | -| Elixir | elixir | | -| Elm | elm | | -| Erlang | erlang, erl | | -| Excel | excel, xls, xlsx | | -| Extempore | extempore, xtlang, xtm | [highlightjs-xtlang](https://github.com/highlightjs/highlightjs-xtlang) | -| F# | fsharp, fs | | -| FIX | fix | | -| Fortran | fortran, f90, f95 | | -| G-Code | gcode, nc | | -| Gams | gams, gms | | -| GAUSS | gauss, gss | | -| GDScript | godot, gdscript | [highlightjs-gdscript](https://github.com/highlightjs/highlightjs-gdscript) | -| Gherkin | gherkin | | -| GN for Ninja | gn, gni | [highlightjs-GN](https://github.com/highlightjs/highlightjs-GN/blob/master/gn.js) | -| Go | go, golang | | -| Grammatical Framework | gf | [highlightjs-gf](https://github.com/johnjcamilleri/highlightjs-gf) | -| Golo | golo, gololang | | -| Gradle | gradle | | -| Groovy | groovy | | -| HTML, XML | xml, html, xhtml, rss, atom, xjb, xsd, xsl, plist, svg | | -| HTTP | http, https | | -| Haml | haml | | -| Handlebars | handlebars, hbs, html.hbs, html.handlebars | | -| Haskell | haskell, hs | | -| Haxe | haxe, hx | | -| Hy | hy, hylang | | -| Ini, TOML | ini, toml | | -| Inform7 | inform7, i7 | | -| IRPF90 | irpf90 | | -| JSON | json | | -| Java | java, jsp | | -| JavaScript | javascript, js, jsx | | -| Kotlin | kotlin, kt | | -| Leaf | leaf | | -| Lasso | lasso, ls, lassoscript | | -| Less | less | | -| LDIF | ldif | | -| Lisp | lisp | | -| LiveCode Server | livecodeserver | | -| LiveScript | livescript, ls | | -| Lua | lua | | -| Makefile | makefile, mk, mak | | -| Markdown | markdown, md, mkdown, mkd | | -| Mathematica | mathematica, mma, wl | | -| Matlab | matlab | | -| Maxima | maxima | | -| Maya Embedded Language | mel | | -| Mercury | mercury | | -| mIRC Scripting Language | mirc, mrc | [highlightjs-mirc](https://github.com/highlightjs/highlightjs-mirc) | -| Mizar | mizar | | -| Mojolicious | mojolicious | | -| Monkey | monkey | | -| Moonscript | moonscript, moon | | -| N1QL | n1ql | | -| NSIS | nsis | | -| Nginx | nginx, nginxconf | | -| Nimrod | nimrod, nim | | -| Nix | nix | | -| OCaml | ocaml, ml | | -| Objective C | objectivec, mm, objc, obj-c | | -| OpenGL Shading Language | glsl | | -| OpenSCAD | openscad, scad | | -| Oracle Rules Language | ruleslanguage | | -| Oxygene | oxygene | | -| PF | pf, pf.conf | | -| PHP | php, php3, php4, php5, php6, php7 | | -| Parser3 | parser3 | | -| Perl | perl, pl, pm | | -| Plaintext: no highlight | plaintext | | -| Pony | pony | | -| PostgreSQL & PL/pgSQL | pgsql, postgres, postgresql | | -| PowerShell | powershell, ps, ps1 | | -| Processing | processing | | -| Prolog | prolog | | -| Properties | properties | | -| Protocol Buffers | protobuf | | -| Puppet | puppet, pp | | -| Python | python, py, gyp | | -| Python profiler results | profile | | -| Q | k, kdb | | -| QML | qml | | -| R | r | | -| Razor CSHTML | cshtml, razor, razor-cshtml | [highlightjs-cshtml-razor](https://github.com/highlightjs/highlightjs-cshtml-razor) | -| ReasonML | reasonml, re | | -| RenderMan RIB | rib | | -| RenderMan RSL | rsl | | -| Roboconf | graph, instances | | -| Robot Framework | robot, rf | [highlightjs-robot](https://github.com/highlightjs/highlightjs-robot) | -| RPM spec files | rpm-specfile, rpm, spec, rpm-spec, specfile | [highlightjs-rpm-specfile](https://github.com/highlightjs/highlightjs-rpm-specfile) | -| Ruby | ruby, rb, gemspec, podspec, thor, irb | | -| Rust | rust, rs | | -| SAS | SAS, sas | | -| SCSS | scss | | -| SQL | sql | | -| STEP Part 21 | p21, step, stp | | -| Scala | scala | | -| Scheme | scheme | | -| Scilab | scilab, sci | | -| Shape Expressions | shexc | [highlightjs-shexc](https://github.com/highlightjs/highlightjs-shexc) | -| Shell | shell, console | | -| Smali | smali | | -| Smalltalk | smalltalk, st | | -| Solidity | solidity, sol | [highlightjs-solidity](https://github.com/highlightjs/highlightjs-solidity) | -| Stan | stan, stanfuncs | | -| Stata | stata | | -| Structured Text | iecst, scl, stl, structured-text | [highlightjs-structured-text](https://github.com/highlightjs/highlightjs-structured-text) | -| Stylus | stylus, styl | | -| SubUnit | subunit | | -| Supercollider | supercollider, sc | [highlightjs-supercollider](https://github.com/highlightjs/highlightjs-supercollider) | -| Swift | swift | | -| Tcl | tcl, tk | | -| Terraform (HCL) | terraform, tf, hcl | [highlightjs-terraform](https://github.com/highlightjs/highlightjs-terraform) | -| Test Anything Protocol | tap | | -| TeX | tex | | -| Thrift | thrift | | -| TP | tp | | -| Twig | twig, craftcms | | -| TypeScript | typescript, ts | | -| VB.Net | vbnet, vb | | -| VBScript | vbscript, vbs | | -| VHDL | vhdl | | -| Vala | vala | | -| Verilog | verilog, v | | -| Vim Script | vim | | -| x86 Assembly | x86asm | | -| XL | xl, tao | | -| XQuery | xquery, xpath, xq | | -| YAML | yml, yaml | | -| Zephir | zephir, zep | | - -Languages with the specified package name are defined in separate repositories -and not included in `highlight.pack.js`. -
- - -## Custom Initialization - -When you need a bit more control over the initialization of -highlight.js, you can use the [`highlightBlock`][3] and [`configure`][4] -functions. This allows you to control *what* to highlight and *when*. - -Here’s an equivalent way to calling [`initHighlightingOnLoad`][1] using -vanilla JS: - -```js -document.addEventListener('DOMContentLoaded', (event) => { - document.querySelectorAll('pre code').forEach((block) => { - hljs.highlightBlock(block); - }); -}); -``` - -You can use any tags instead of `
` to mark up your code. If
-you don't use a container that preserves line breaks you will need to
-configure highlight.js to use the `
` tag: - -```js -hljs.configure({useBR: true}); - -document.querySelectorAll('div.code').forEach((block) => { - hljs.highlightBlock(block); -}); -``` - -For other options refer to the documentation for [`configure`][4]. - - -## Web Workers - -You can run highlighting inside a web worker to avoid freezing the browser -window while dealing with very big chunks of code. - -In your main script: - -```js -addEventListener('load', () => { - const code = document.querySelector('#code'); - const worker = new Worker('worker.js'); - worker.onmessage = (event) => { code.innerHTML = event.data; } - worker.postMessage(code.textContent); -}); -``` - -In worker.js: - -```js -onmessage = (event) => { - importScripts('/highlight.pack.js'); - const result = self.hljs.highlightAuto(event.data); - postMessage(result.value); -}; -``` - -## Node.js - -You can use highlight.js with node to highlight content before sending it to the browser. -Make sure to use the `.value` property to get the formatted html. -For more info about the returned object refer to the api docs https://highlightjs.readthedocs.io/en/latest/api.html - - -```js -// require the highlight.js library including all languages -const hljs = require('./highlight.js'); -const highlightedCode = hljs.highlightAuto('Hello World!').value -``` - -```js -// require the highlight.js library without languages -const hljs = require("highlight.js/lib/highlight.js"); -// separately require languages -hljs.registerLanguage('html', require('highlight.js/lib/languages/html')); -hljs.registerLanguage('sql', require('highlight.js/lib/languages/sql')); -// highlight with providing the language -const highlightedCode = hljs.highlight('html', 'Hello World!').value -``` - -## Getting the Library - -You can get highlight.js as a hosted, or custom-build, browser script or -as a server module. Right out of the box the browser script supports -both AMD and CommonJS, so if you wish you can use RequireJS or -Browserify without having to build from source. The server module also -works perfectly fine with Browserify, but there is the option to use a -build specific to browsers rather than something meant for a server. -Head over to the [download page][5] for all the options. - -**Don't link to GitHub directly.** The library is not supposed to work straight -from the source, it requires building. If none of the pre-packaged options -work for you refer to the [building documentation][6]. - -**The CDN-hosted package doesn't have all the languages.** Otherwise it'd be -too big. If you don't see the language you need in the ["Common" section][5], -it can be added manually: - -```html - -``` - -**On Almond.** You need to use the optimizer to give the module a name. For -example: - -```bash -r.js -o name=hljs paths.hljs=/path/to/highlight out=highlight.js -``` - - -### CommonJS - -You can import Highlight.js as a CommonJS-module: - -```bash -npm install highlight.js --save -``` - -In your application: - -```js -import hljs from 'highlight.js'; -``` - -The default import imports all languages! Therefore it is likely to be more efficient to import only the library and the languages you need: - -```js -import hljs from 'highlight.js/lib/highlight'; -import javascript from 'highlight.js/lib/languages/javascript'; -hljs.registerLanguage('javascript', javascript); -``` - -To set the syntax highlighting style, if your build tool processes CSS from your JavaScript entry point, you can import the stylesheet directly into your CommonJS-module: - -```js -import hljs from 'highlight.js/lib/highlight'; -import 'highlight.js/styles/github.css'; -``` - -## License - -Highlight.js is released under the BSD License. See [LICENSE][7] file -for details. - -## Links - -The official site for the library is at . - -Further in-depth documentation for the API and other topics is at -. - -Authors and contributors are listed in the [AUTHORS.en.txt][8] file. - -[1]: http://highlightjs.readthedocs.io/en/latest/api.html#inithighlightingonload -[2]: http://highlightjs.readthedocs.io/en/latest/css-classes-reference.html -[3]: http://highlightjs.readthedocs.io/en/latest/api.html#highlightblock-block -[4]: http://highlightjs.readthedocs.io/en/latest/api.html#configure-options -[5]: https://highlightjs.org/download/ -[6]: http://highlightjs.readthedocs.io/en/latest/building-testing.html -[7]: https://github.com/highlightjs/highlight.js/blob/master/LICENSE -[8]: https://github.com/highlightjs/highlight.js/blob/master/AUTHORS.en.txt diff --git a/libs/hljs/highlight.pack.js b/libs/hljs/highlight.pack.js deleted file mode 100644 index 776d452..0000000 --- a/libs/hljs/highlight.pack.js +++ /dev/null @@ -1,2 +0,0 @@ -/*! highlight.js v9.18.1 | BSD3 License | git.io/hljslicense */ -!function(e){var n="object"==typeof window&&window||"object"==typeof self&&self;"undefined"==typeof exports||exports.nodeType?n&&(n.hljs=e({}),"function"==typeof define&&define.amd&&define([],function(){return n.hljs})):e(exports)}(function(a){var f=[],i=Object.keys,_={},c={},C=!0,n=/^(no-?highlight|plain|text)$/i,l=/\blang(?:uage)?-([\w-]+)\b/i,t=/((^(<[^>]+>|\t|)+|(?:\n)))/gm,r={case_insensitive:"cI",lexemes:"l",contains:"c",keywords:"k",subLanguage:"sL",className:"cN",begin:"b",beginKeywords:"bK",end:"e",endsWithParent:"eW",illegal:"i",excludeBegin:"eB",excludeEnd:"eE",returnBegin:"rB",returnEnd:"rE",variants:"v",IDENT_RE:"IR",UNDERSCORE_IDENT_RE:"UIR",NUMBER_RE:"NR",C_NUMBER_RE:"CNR",BINARY_NUMBER_RE:"BNR",RE_STARTERS_RE:"RSR",BACKSLASH_ESCAPE:"BE",APOS_STRING_MODE:"ASM",QUOTE_STRING_MODE:"QSM",PHRASAL_WORDS_MODE:"PWM",C_LINE_COMMENT_MODE:"CLCM",C_BLOCK_COMMENT_MODE:"CBCM",HASH_COMMENT_MODE:"HCM",NUMBER_MODE:"NM",C_NUMBER_MODE:"CNM",BINARY_NUMBER_MODE:"BNM",CSS_NUMBER_MODE:"CSSNM",REGEXP_MODE:"RM",TITLE_MODE:"TM",UNDERSCORE_TITLE_MODE:"UTM",COMMENT:"C",beginRe:"bR",endRe:"eR",illegalRe:"iR",lexemesRe:"lR",terminators:"t",terminator_end:"tE"},m="",O="Could not find the language '{}', did you forget to load/include a language module?",B={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0},o="of and for in not or if then".split(" ");function x(e){return e.replace(/&/g,"&").replace(//g,">")}function g(e){return e.nodeName.toLowerCase()}function u(e){return n.test(e)}function s(e){var n,t={},r=Array.prototype.slice.call(arguments,1);for(n in e)t[n]=e[n];return r.forEach(function(e){for(n in e)t[n]=e[n]}),t}function E(e){var a=[];return function e(n,t){for(var r=n.firstChild;r;r=r.nextSibling)3===r.nodeType?t+=r.nodeValue.length:1===r.nodeType&&(a.push({event:"start",offset:t,node:r}),t=e(r,t),g(r).match(/br|hr|img|input/)||a.push({event:"stop",offset:t,node:r}));return t}(e,0),a}function d(e,n,t){var r=0,a="",i=[];function o(){return e.length&&n.length?e[0].offset!==n[0].offset?e[0].offset"}function l(e){a+=""}function u(e){("start"===e.event?c:l)(e.node)}for(;e.length||n.length;){var s=o();if(a+=x(t.substring(r,s[0].offset)),r=s[0].offset,s===e){for(i.reverse().forEach(l);u(s.splice(0,1)[0]),(s=o())===e&&s.length&&s[0].offset===r;);i.reverse().forEach(c)}else"start"===s[0].event?i.push(s[0].node):i.pop(),u(s.splice(0,1)[0])}return a+x(t.substr(r))}function R(n){return n.v&&!n.cached_variants&&(n.cached_variants=n.v.map(function(e){return s(n,{v:null},e)})),n.cached_variants?n.cached_variants:function e(n){return!!n&&(n.eW||e(n.starts))}(n)?[s(n,{starts:n.starts?s(n.starts):null})]:Object.isFrozen(n)?[s(n)]:[n]}function p(e){if(r&&!e.langApiRestored){for(var n in e.langApiRestored=!0,r)e[n]&&(e[r[n]]=e[n]);(e.c||[]).concat(e.v||[]).forEach(p)}}function v(n,r){var a={};return"string"==typeof n?t("keyword",n):i(n).forEach(function(e){t(e,n[e])}),a;function t(t,e){r&&(e=e.toLowerCase()),e.split(" ").forEach(function(e){var n=e.split("|");a[n[0]]=[t,function(e,n){return n?Number(n):function(e){return-1!=o.indexOf(e.toLowerCase())}(e)?0:1}(n[0],n[1])]})}}function S(r){function s(e){return e&&e.source||e}function f(e,n){return new RegExp(s(e),"m"+(r.cI?"i":"")+(n?"g":""))}function a(a){var i,e,o={},c=[],l={},t=1;function n(e,n){o[t]=e,c.push([e,n]),t+=function(e){return new RegExp(e.toString()+"|").exec("").length-1}(n)+1}for(var r=0;r')+n+(t?"":m)}function l(){p+=null!=d.sL?function(){var e="string"==typeof d.sL;if(e&&!_[d.sL])return x(v);var n=e?T(d.sL,v,!0,R[d.sL]):w(v,d.sL.length?d.sL:void 0);return 0")+'"');if("end"===n.type){var r=function(e){var n=e[0],t=i.substr(e.index),r=o(d,t);if(r){var a=d;for(a.skip?v+=n:(a.rE||a.eE||(v+=n),l(),a.eE&&(v=n));d.cN&&(p+=m),d.skip||d.sL||(M+=d.relevance),(d=d.parent)!==r.parent;);return r.starts&&(r.endSameAsBegin&&(r.starts.eR=r.eR),u(r.starts)),a.rE?0:n.length}}(n);if(null!=r)return r}return v+=t,t.length}var g=D(n);if(!g)throw console.error(O.replace("{}",n)),new Error('Unknown language: "'+n+'"');S(g);var E,d=t||g,R={},p="";for(E=d;E!==g;E=E.parent)E.cN&&(p=c(E.cN,"",!0)+p);var v="",M=0;try{for(var b,h,N=0;d.t.lastIndex=N,b=d.t.exec(i);)h=r(i.substring(N,b.index),b),N=b.index+h;for(r(i.substr(N)),E=d;E.parent;E=E.parent)E.cN&&(p+=m);return{relevance:M,value:p,i:!1,language:n,top:d}}catch(e){if(e.message&&-1!==e.message.indexOf("Illegal"))return{i:!0,relevance:0,value:x(i)};if(C)return{relevance:0,value:x(i),language:n,top:d,errorRaised:e};throw e}}function w(t,e){e=e||B.languages||i(_);var r={relevance:0,value:x(t)},a=r;return e.filter(D).filter(L).forEach(function(e){var n=T(e,t,!1);n.language=e,n.relevance>a.relevance&&(a=n),n.relevance>r.relevance&&(a=r,r=n)}),a.language&&(r.second_best=a),r}function M(e){return B.tabReplace||B.useBR?e.replace(t,function(e,n){return B.useBR&&"\n"===e?"
":B.tabReplace?n.replace(/\t/g,B.tabReplace):""}):e}function b(e){var n,t,r,a,i,o=function(e){var n,t,r,a,i=e.className+" ";if(i+=e.parentNode?e.parentNode.className:"",t=l.exec(i)){var o=D(t[1]);return o||(console.warn(O.replace("{}",t[1])),console.warn("Falling back to no-highlight mode for this block.",e)),o?t[1]:"no-highlight"}for(n=0,r=(i=i.split(/\s+/)).length;n/g,"\n"):n=e,i=n.textContent,r=o?T(o,i,!0):w(i),(t=E(n)).length&&((a=document.createElement("div")).innerHTML=r.value,r.value=d(t,E(a),i)),r.value=M(r.value),e.innerHTML=r.value,e.className=function(e,n,t){var r=n?c[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),-1===e.indexOf(r)&&a.push(r),a.join(" ").trim()}(e.className,o,r.language),e.result={language:r.language,re:r.relevance},r.second_best&&(e.second_best={language:r.second_best.language,re:r.second_best.relevance}))}function h(){if(!h.called){h.called=!0;var e=document.querySelectorAll("pre code");f.forEach.call(e,b)}}var N={disableAutodetect:!0};function D(e){return e=(e||"").toLowerCase(),_[e]||_[c[e]]}function L(e){var n=D(e);return n&&!n.disableAutodetect}return a.highlight=T,a.highlightAuto=w,a.fixMarkup=M,a.highlightBlock=b,a.configure=function(e){B=s(B,e)},a.initHighlighting=h,a.initHighlightingOnLoad=function(){window.addEventListener("DOMContentLoaded",h,!1),window.addEventListener("load",h,!1)},a.registerLanguage=function(n,e){var t;try{t=e(a)}catch(e){if(console.error("Language definition for '{}' could not be registered.".replace("{}",n)),!C)throw e;console.error(e),t=N}p(_[n]=t),t.rawDefinition=e.bind(null,a),t.aliases&&t.aliases.forEach(function(e){c[e]=n})},a.listLanguages=function(){return i(_)},a.getLanguage=D,a.requireLanguage=function(e){var n=D(e);if(n)return n;throw new Error("The '{}' language is required, but not loaded.".replace("{}",e))},a.autoDetection=L,a.inherit=s,a.debugMode=function(){C=!1},a.IR=a.IDENT_RE="[a-zA-Z]\\w*",a.UIR=a.UNDERSCORE_IDENT_RE="[a-zA-Z_]\\w*",a.NR=a.NUMBER_RE="\\b\\d+(\\.\\d+)?",a.CNR=a.C_NUMBER_RE="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",a.BNR=a.BINARY_NUMBER_RE="\\b(0b[01]+)",a.RSR=a.RE_STARTERS_RE="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",a.BE=a.BACKSLASH_ESCAPE={b:"\\\\[\\s\\S]",relevance:0},a.ASM=a.APOS_STRING_MODE={cN:"string",b:"'",e:"'",i:"\\n",c:[a.BE]},a.QSM=a.QUOTE_STRING_MODE={cN:"string",b:'"',e:'"',i:"\\n",c:[a.BE]},a.PWM=a.PHRASAL_WORDS_MODE={b:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},a.C=a.COMMENT=function(e,n,t){var r=a.inherit({cN:"comment",b:e,e:n,c:[]},t||{});return r.c.push(a.PWM),r.c.push({cN:"doctag",b:"(?:TODO|FIXME|NOTE|BUG|XXX):",relevance:0}),r},a.CLCM=a.C_LINE_COMMENT_MODE=a.C("//","$"),a.CBCM=a.C_BLOCK_COMMENT_MODE=a.C("/\\*","\\*/"),a.HCM=a.HASH_COMMENT_MODE=a.C("#","$"),a.NM=a.NUMBER_MODE={cN:"number",b:a.NR,relevance:0},a.CNM=a.C_NUMBER_MODE={cN:"number",b:a.CNR,relevance:0},a.BNM=a.BINARY_NUMBER_MODE={cN:"number",b:a.BNR,relevance:0},a.CSSNM=a.CSS_NUMBER_MODE={cN:"number",b:a.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0},a.RM=a.REGEXP_MODE={cN:"regexp",b:/\//,e:/\/[gimuy]*/,i:/\n/,c:[a.BE,{b:/\[/,e:/\]/,relevance:0,c:[a.BE]}]},a.TM=a.TITLE_MODE={cN:"title",b:a.IR,relevance:0},a.UTM=a.UNDERSCORE_TITLE_MODE={cN:"title",b:a.UIR,relevance:0},a.METHOD_GUARD={b:"\\.\\s*"+a.UIR,relevance:0},[a.BE,a.ASM,a.QSM,a.PWM,a.C,a.CLCM,a.CBCM,a.HCM,a.NM,a.CNM,a.BNM,a.CSSNM,a.RM,a.TM,a.UTM,a.METHOD_GUARD].forEach(function(e){!function n(t){Object.freeze(t);var r="function"==typeof t;Object.getOwnPropertyNames(t).forEach(function(e){!t.hasOwnProperty(e)||null===t[e]||"object"!=typeof t[e]&&"function"!=typeof t[e]||r&&("caller"===e||"callee"===e||"arguments"===e)||Object.isFrozen(t[e])||n(t[e])});return t}(e)}),a});hljs.registerLanguage("xml",function(e){var c={cN:"symbol",b:"&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;"},s={b:"\\s",c:[{cN:"meta-keyword",b:"#?[a-z_][a-z1-9_-]+",i:"\\n"}]},a=e.inherit(s,{b:"\\(",e:"\\)"}),t=e.inherit(e.ASM,{cN:"meta-string"}),l=e.inherit(e.QSM,{cN:"meta-string"}),r={eW:!0,i:/`]+/}]}]}]};return{aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"],cI:!0,c:[{cN:"meta",b:"",relevance:10,c:[s,l,t,a,{b:"\\[",e:"\\]",c:[{cN:"meta",b:"",c:[s,a,l,t]}]}]},e.C("\x3c!--","--\x3e",{relevance:10}),{b:"<\\!\\[CDATA\\[",e:"\\]\\]>",relevance:10},c,{cN:"meta",b:/<\?xml/,e:/\?>/,relevance:10},{b:/<\?(php)?/,e:/\?>/,sL:"php",c:[{b:"/\\*",e:"\\*/",skip:!0},{b:'b"',e:'"',skip:!0},{b:"b'",e:"'",skip:!0},e.inherit(e.ASM,{i:null,cN:null,c:null,skip:!0}),e.inherit(e.QSM,{i:null,cN:null,c:null,skip:!0})]},{cN:"tag",b:")",e:">",k:{name:"style"},c:[r],starts:{e:"",rE:!0,sL:["css","xml"]}},{cN:"tag",b:")",e:">",k:{name:"script"},c:[r],starts:{e:"<\/script>",rE:!0,sL:["actionscript","javascript","handlebars","xml"]}},{cN:"tag",b:"",c:[{cN:"name",b:/[^\/><\s]+/,relevance:0},r]}]}});hljs.registerLanguage("handlebars",function(e){var t={"builtin-name":"each in with if else unless bindattr action collection debugger log outlet template unbound view yield lookup"},a={b:/".*?"|'.*?'|\[.*?\]|\w+/},l=e.inherit(a,{k:t,starts:{eW:!0,relevance:0,c:[e.inherit(a,{relevance:0})]}}),i=e.inherit(l,{cN:"name"}),n=e.inherit(l,{relevance:0});return{aliases:["hbs","html.hbs","html.handlebars"],cI:!0,sL:"xml",c:[{b:/\\\{\{/,skip:!0},{b:/\\\\(?=\{\{)/,skip:!0},e.C(/\{\{!--/,/--\}\}/),e.C(/\{\{!/,/\}\}/),{cN:"template-tag",b:/\{\{\{\{(?!\/)/,e:/\}\}\}\}/,c:[i],starts:{e:/\{\{\{\{\//,rE:!0,sL:"xml"}},{cN:"template-tag",b:/\{\{\{\{\//,e:/\}\}\}\}/,c:[i]},{cN:"template-tag",b:/\{\{[#\/]/,e:/\}\}/,c:[i]},{cN:"template-variable",b:/\{\{\{/,e:/\}\}\}/,k:t,c:[n]},{cN:"template-variable",b:/\{\{/,e:/\}\}/,k:t,c:[n]}]}});hljs.registerLanguage("javascript",function(e){var r="<>",a="",t={b:/<[A-Za-z0-9\\._:-]+/,e:/\/[A-Za-z0-9\\._:-]+>|\/>/},c="[A-Za-z$_][0-9A-Za-z$_]*",n={keyword:"in of if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const export super debugger as async await static import from as",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document Symbol Set Map WeakSet WeakMap Proxy Reflect Promise"},s={cN:"number",v:[{b:"\\b(0[bB][01]+)n?"},{b:"\\b(0[oO][0-7]+)n?"},{b:e.CNR+"n?"}],relevance:0},o={cN:"subst",b:"\\$\\{",e:"\\}",k:n,c:[]},i={b:"html`",e:"",starts:{e:"`",rE:!1,c:[e.BE,o],sL:"xml"}},b={b:"css`",e:"",starts:{e:"`",rE:!1,c:[e.BE,o],sL:"css"}},l={cN:"string",b:"`",e:"`",c:[e.BE,o]};o.c=[e.ASM,e.QSM,i,b,l,s,e.RM];var u=o.c.concat([e.CBCM,e.CLCM]);return{aliases:["js","jsx","mjs","cjs"],k:n,c:[{cN:"meta",relevance:10,b:/^\s*['"]use (strict|asm)['"]/},{cN:"meta",b:/^#!/,e:/$/},e.ASM,e.QSM,i,b,l,e.CLCM,e.C("/\\*\\*","\\*/",{relevance:0,c:[{cN:"doctag",b:"@[A-Za-z]+",c:[{cN:"type",b:"\\{",e:"\\}",relevance:0},{cN:"variable",b:c+"(?=\\s*(-)|$)",endsParent:!0,relevance:0},{b:/(?=[^\n])\s/,relevance:0}]}]}),e.CBCM,s,{b:/[{,\n]\s*/,relevance:0,c:[{b:c+"\\s*:",rB:!0,relevance:0,c:[{cN:"attr",b:c,relevance:0}]}]},{b:"("+e.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[e.CLCM,e.CBCM,e.RM,{cN:"function",b:"(\\(.*?\\)|"+c+")\\s*=>",rB:!0,e:"\\s*=>",c:[{cN:"params",v:[{b:c},{b:/\(\s*\)/},{b:/\(/,e:/\)/,eB:!0,eE:!0,k:n,c:u}]}]},{cN:"",b:/\s/,e:/\s*/,skip:!0},{v:[{b:r,e:a},{b:t.b,e:t.e}],sL:"xml",c:[{b:t.b,e:t.e,skip:!0,c:["self"]}]}],relevance:0},{cN:"function",bK:"function",e:/\{/,eE:!0,c:[e.inherit(e.TM,{b:c}),{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,c:u}],i:/\[|%/},{b:/\$[(.]/},e.METHOD_GUARD,{cN:"class",bK:"class",e:/[{;=]/,eE:!0,i:/[:"\[\]]/,c:[{bK:"extends"},e.UTM]},{bK:"constructor get set",e:/\{/,eE:!0}],i:/#(?!!)/}}); \ No newline at end of file diff --git a/libs/hljs/styles/github-gist.css b/libs/hljs/styles/github-gist.css deleted file mode 100644 index 18240c8..0000000 --- a/libs/hljs/styles/github-gist.css +++ /dev/null @@ -1,79 +0,0 @@ -/** - * GitHub Gist Theme - * Author : Anthony Attard - https://github.com/AnthonyAttard - * Author : Louis Barranqueiro - https://github.com/LouisBarranqueiro - */ - -.hljs { - display: block; - background: white; - padding: 0.5em; - color: #333333; - overflow-x: auto; -} - -.hljs-comment, -.hljs-meta { - color: #969896; -} - -.hljs-variable, -.hljs-template-variable, -.hljs-strong, -.hljs-emphasis, -.hljs-quote { - color: #df5000; -} - -.hljs-keyword, -.hljs-selector-tag, -.hljs-type { - color: #d73a49; -} - -.hljs-literal, -.hljs-symbol, -.hljs-bullet, -.hljs-attribute { - color: #0086b3; -} - -.hljs-section, -.hljs-name { - color: #63a35c; -} - -.hljs-tag { - color: #333333; -} - -.hljs-title, -.hljs-attr, -.hljs-selector-id, -.hljs-selector-class, -.hljs-selector-attr, -.hljs-selector-pseudo { - color: #6f42c1; -} - -.hljs-addition { - color: #55a532; - background-color: #eaffea; -} - -.hljs-deletion { - color: #bd2c00; - background-color: #ffecec; -} - -.hljs-link { - text-decoration: underline; -} - -.hljs-number { - color: #005cc5; -} - -.hljs-string { - color: #032f62; -} diff --git a/libs/split-html.js b/libs/split-html.js deleted file mode 100644 index e84b9e7..0000000 --- a/libs/split-html.js +++ /dev/null @@ -1,148 +0,0 @@ -// https://github.com/apostrophecms/split-html - -(function() { - var cheerio; - if (typeof window === 'undefined') { - // node - module.exports = splitHtml; - cheerio = require('cheerio'); - } else { - window.splitHtml = splitHtml; - // In the browser, use actual jQuery in place of Cheerio. - // Create a simulated cheerio object. - cheerio = { - load: function(html) { - var $wrapper = jQuery('
'); - var $el = jQuery(html); - $wrapper.append($el); - function c(s) { - if (s[0] === '<') { - return jQuery(s); - } - return $wrapper.find(s); - } - c.html = function() { - return $wrapper.html(); - }; - return c; - }, - }; - } - function splitHtml(html, splitOn, test) { - if (!test) { - test = function($el) { - return true; - }; - } - var result = []; - var splitAttr = 'data-' + token(); - var ignoreAttr = 'data-' + token(); - var $; - var $matches; - var i; - var $match; - var $wrapper; - var tag; - var second; - while (true) { - $ = cheerio.load(html); - $matches = $(splitOn); - $match = null; - for (i = 0; (i < $matches.length); i++) { - $match = $matches.eq(i); - if ((!$match.attr(ignoreAttr)) && test($match)) { - break; - } else { - $match.attr(ignoreAttr, '1'); - } - $match = null; - } - if (!$match) { - result.push(html); - break; - } - $match.attr(splitAttr, '1'); - var markup = $.html(); - var splitAt = markup.indexOf(splitAttr); - var leftAt = markup.lastIndexOf('<', splitAt); - if (leftAt === -1) { - result.push(html); - break; - } - var first = markup.substr(0, leftAt); - - // For the second segment we need to reopen the - // open tags from the first segment. Reconstruct that. - - var reopen = ''; - $wrapper = cheerio.load('
')('div').eq(0); - var $parents = $match.parents(); - for (i = 0; (i < $parents.length); i++) { - var $original = $parents.eq(i); - if ($original.is('[data-cheerio-root]')) { - // Simulated cheerio used in browser has - // a wrapper element - break; - } - var $parent = $original.clone(); - $parent.empty(); - $wrapper.empty(); - $wrapper.append($parent); - var parentMarkup = $wrapper.html(); - var endTagAt = parentMarkup.indexOf('>'); - tag = tagName($parent); - // Cheerio tolerates missing closing tags, - // but real jQuery will discard any text - // preceding them, so play nice - first += ''; - reopen = parentMarkup.substr(0, endTagAt + 1) + reopen; - } - - // We can't just split off the next fragment at - // > because the matching tag may be a container. - // Move it to a wrapper to get its full markup, - // then remove it from the original document. The - // remainder of the original document now begins - // where the matching tag used to - - markup = $.html(); - - $wrapper = cheerio.load('
')('div').eq(0); - $match.removeAttr(splitAttr); - $wrapper.append($match); - tag = $wrapper.html(); - $match.remove(); - markup = $.html(); - second = reopen + markup.substr(leftAt); - // Let Cheerio close the open tags in the - // first segment for us. Also mop up the attributes - // we used to mark elements that matched the selector - // but didn't match our test function - first = cleanup(first); - result.push(first); - result.push(tag); - html = cleanup(second); - } - return result; - // Use Cheerio to strip out any attributes we used to keep - // track of our work, then generate new HTML. This also - // closes any tags we opened but did not close. - function cleanup(html) { - html = cheerio.load(html); - html('[' + ignoreAttr + ']').removeAttr(ignoreAttr); - html = html.html(); - return html; - } - - function token() { - return Math.floor(Math.random() * 1000000000).toString(); - } - } - - function tagName($el) { - // Different in DOM and Cheerio. Cheerio - // doesn't support prop() either. - return $el[0].tagName || $el[0].name; - } -})(); - diff --git a/libs/tokenizer.min.js b/libs/tokenizer.min.js deleted file mode 100644 index 7783080..0000000 --- a/libs/tokenizer.min.js +++ /dev/null @@ -1,27 +0,0 @@ -"use strict";const MODE_NONE='modeNone';const MODE_DEFAULT='modeDefault';const MODE_MATCH='modeMatch';const sortTokenizableSubstrings=(a,b)=>{if(a.length>b.length){return-1} -if(a.length{return str.indexOf(suffix,str.length-suffix.length)!==-1};class Tokenizer{constructor(factory,str,forEachToken){this.factory=factory;this.str=str;this.forEachToken=forEachToken;this.previousChr='';this.toMatch='';this.currentToken='';this.modeStack=[MODE_NONE];this.currentIndex=0} -getCurrentMode(){return this.modeStack[this.modeStack.length-1]} -setCurrentMode(mode){return this.modeStack.push(mode)} -completeCurrentMode(){const currentMode=this.getCurrentMode();if(currentMode===MODE_DEFAULT){this.pushDefaultModeTokenizables()} -if((currentMode===MODE_MATCH&&this.currentToken==='')||this.currentToken!==''){this.push(this.currentToken)} -this.currentToken='';return this.modeStack.pop()} -push(token){let surroundedBy='';if(this.factory.convertLiterals&&this.getCurrentMode()!==MODE_MATCH){switch(token.toLowerCase()){case 'null':token=null;break;case 'true':token=!0;break;case 'false':token=!1;break;default:if(isFinite(token)){token=Number(token)} -break}}else{surroundedBy=this.toMatch} -if(this.forEachToken){this.forEachToken(token,surroundedBy,this.currentIndex)}} -tokenize(){let index=0;while(index0){this.push(this.currentToken.substring(0,lowestIndexOfTokenize))} -if(lowestIndexOfTokenize!==-1){this.push(toTokenize);this.currentToken=this.currentToken.substring(lowestIndexOfTokenize+toTokenize.length);return this.pushDefaultModeTokenizables()}}[MODE_MATCH](chr){if(chr===this.toMatch){if(this.previousChr!==this.factory.escapeCharacter){return this.completeCurrentMode()} -this.currentToken=this.currentToken.substring(0,this.currentToken.length-1)} -this.currentToken+=chr;return this.currentToken}} -class TokenizeThis{constructor(config){if(!config){config={}} -config=Object.assign({},this.constructor.defaultConfig,config);this.convertLiterals=config.convertLiterals;this.escapeCharacter=config.escapeCharacter;this.tokenizeList=[];this.tokenizeMap={};this.matchList=[];this.matchMap={};this.delimiterList=[];this.delimiterMap={};config.shouldTokenize.sort(sortTokenizableSubstrings).forEach((token)=>{if(!this.tokenizeMap[token]){this.tokenizeList.push(token);this.tokenizeMap[token]=token}});config.shouldMatch.forEach((match)=>{if(!this.matchMap[match]){this.matchList.push(match);this.matchMap[match]=match}});config.shouldDelimitBy.forEach((delimiter)=>{if(!this.delimiterMap[delimiter]){this.delimiterList.push(delimiter);this.delimiterMap[delimiter]=delimiter}})} -tokenize(str,forEachToken){const tokenizerInstance=new Tokenizer(this,str,forEachToken);return tokenizerInstance.tokenize()} -static get defaultConfig(){return{shouldTokenize:['(',')',',','*','/','%','+','-','=','!=','!','<','>','<=','>=','^'],shouldMatch:['"',"'",'`'],shouldDelimitBy:[' ',"\n","\r","\t"],convertLiterals:!0,escapeCharacter:"\\"}}} diff --git a/module.json b/module.json index f918314..c5aaf8b 100644 --- a/module.json +++ b/module.json @@ -1,69 +1,45 @@ { - "name": "furnace", - "title": "The Furnace", - "description": "

No Foundry is complete without it's Furnace!

This module contains a set of features that improve the Quality of Life of the DM/Players. The current features are :

Advanced Drawing Tools :Improved drawing tools with different pattern fill types, new HUD, and various options.

Advanced Macros: Use async script macros, handlebars templating, recursive macro calls and call macros with arguments or directly from chat.

Split Journal : Select the split option from the context menu on a journal entry to split it into multiple entries.

Tokens : As GM, you can enable/disable token vision for yourself. You can also drop an actor folder into a scene to deploy multiple tokens at once.

Combat : Double click the initiative value in the combat tracker to quickly modify it.

Playlists : Adds a 'Now Playing' section, and auto-hides sound controls until hovered. Helps in fine tuning of low level volumes.


See website from URL below for instructions on how to use all these features.


", - "version": "2.6.0", - "author": "KaKaRoTo", - "scripts": [ - "libs/split-html.js", - "libs/h.min.js", - "libs/tokenizer.min.js", - "libs/hljs/highlight.pack.js", - "Patches/Patches.js", - "Macros/Macros.js", - "DrawingTools/DrawingsLayer.js", - "DrawingTools/RotationHandle.js", - "DrawingTools/Drawing.js", - "DrawingTools/DrawingHUD.js", - "DrawingTools/DrawingConfig.js", - "DrawingTools/DrawingTools.js", - "QoL/HBHelpers.js", - "QoL/Debug.js", - "QoL/Entities.js", - "QoL/Tokens.js", - "QoL/Combat.js", - "QoL/Playlist.js", - "QoL/Macros.js" - ], - "styles": [ - "Furnace.css", - "libs/hljs/styles/github-gist.css" - ], - "packs": [ - { - "name": "macros", - "label": "Advanced Macros", - "path": "packs/macros.db", - "module": "furnace", - "entity": "Macro" - } - ], - "languages": [ - { - "lang": "en", - "name": "English", - "path": "lang/en.json" - }, - { - "lang": "pt-BR", - "name": "Português (Brasil)", - "path": "lang/pt-BR.json" - }, - { - "lang": "es", - "name": "Spanish", - "path": "lang/es.json" - }, - { - "lang": "cn", - "name": "中文", - "path": "lang/zh-CN.json" - } - ], - "url": "https://github.com/League-of-Foundry-Developers/fvtt-module-furnace", - "manifest": "https://raw.githubusercontent.com/League-of-Foundry-Developers/fvtt-module-furnace/master/module.json", - "download": "https://github.com/League-of-Foundry-Developers/fvtt-module-furnace/releases/download/v2.6.0/furnace.zip", - "socket": true, - "minimumCoreVersion": "0.7.7", - "compatibleCoreVersion": "0.8.0" -} + "name": "furnace", + "title": "The Furnace", + "description": "No Foundry is complete without it's Furnace!", + "author": "KaKaRoTo", + "authors": [], + "url": "https://github.com/League-of-Foundry-Developers/fvtt-module-furnace", + "flags": {}, + "version": "2.6.1", + "minimumCoreVersion": "0.8.4", + "compatibleCoreVersion": "0.8.6", + "scripts": [], + "esmodules": [], + "styles": [], + "languages": [], + "packs": [], + "system": [], + "dependencies": [ + { + "name": "advanced-macros", + "manifest": "https://github.com/League-of-Foundry-Developers/fvtt-advanced-macros/releases/latest/download/module.json" + }, + { + "name": "initiative-double-click", + "manifest": "https://github.com/League-of-Foundry-Developers/fvtt-initiative-double-click/releases/latest/download/module.json" + }, + { + "name": "dfreds-droppables", + "manifest": "https://github.com/DFreds/dfreds-droppables/releases/latest/download/module.json" + }, + { + "name": "lessfog", + "manifest": "https://github.com/trdischat/lessfog/releases/latest/download/module.json" + }, + { + "name": "split-journal", + "manifest": "https://github.com/League-of-Foundry-Developers/fvtt-split-journal/releases/latest/download/module.json" + } + ], + "socket": true, + "manifest": "https://github.com/League-of-Foundry-Developers/fvtt-module-furnace/releases/latest/download/module.json", + "download": "https://github.com/League-of-Foundry-Developers/fvtt-module-furnace/releases/latest/download/module.zip", + "protected": false, + "coreTranslation": false +} \ No newline at end of file diff --git a/packs/macros.db b/packs/macros.db deleted file mode 100644 index 24853c3..0000000 --- a/packs/macros.db +++ /dev/null @@ -1,17 +0,0 @@ -{"_id":"23kTIYmN7vqvmjF5","name":"toggle-playlist-sound","permission":{"default":0,"PEJBB2AYKoE5ZuxD":3},"type":"script","flags":{},"scope":"global","command":"/* This macro requires the advanced macros of Furnace and Playlist QoL\r\n * This will toggle the playing state of a sound in your playlists\r\n * Takes the playlist name as first argument, and the sound name as second argument \r\n * Example: /toggle-playlist-sound \"SFX\" \"Sword Clash\"\r\n */\r\n\r\nFurnacePlaylistQoL.PlaySound(args[0], args[1]);","author":"PEJBB2AYKoE5ZuxD","img":"icons/svg/dice-target.svg","actorIds":[]} -{"_id":"3t22b7DeBqd8OKz6","name":"Canvas Always-on-top","permission":{"default":0,"PEJBB2AYKoE5ZuxD":3},"type":"script","flags":{"furnace":{"runAsGM":false},"core":{"sourceId":"Macro.tHqyy0sqn6mHlV0m"}},"scope":"global","command":"const video = $(``)[0];\nvideo.srcObject = $(\"#board\")[0].captureStream();\nconst playVideo = () => {\n if (video.readyState < 2) setTimeout(playVideo, 0);\n else video.play();\n};\nplayVideo();","author":"PEJBB2AYKoE5ZuxD","img":"icons/svg/dice-target.svg","actorIds":[]} -{"_id":"4aUMx41pvnZCJ52b","name":"teleport","permission":{"FRJk2q4bH9IdLZDf":3},"type":"script","sort":100001,"flags":{},"scope":"global","command":"// This macro only serves as an example. For a proper teleportation macro, check out the Dynamic Effects module.\r\n//\r\n// This macro requires the advanced macros of Furnace\r\n// This macro depends on /move-token\r\n// Takes X and Y as arguments\r\n\r\nconst macro = game.macros.getName(\"move-token\");\r\nif (!macro) {\r\n ui.notifications.error(\"This macro depends on the 'move-token' macro, which could not be found.\");\r\n return;\r\n}\r\nmacro.execute(args[0], args[1], true);\r\n","author":"FRJk2q4bH9IdLZDf","img":"icons/svg/dice-target.svg","actorIds":[]} -{"_id":"63bPkXo7BUsZEQJ0","name":"animate-pan","permission":{"default":0,"PEJBB2AYKoE5ZuxD":3},"type":"script","flags":{},"scope":"global","command":"/* Pan the camera to the X, Y and scale positions.\r\n * See the 'pan-camera' macro for more details.\r\n * The first argument is the duration in milliseconds for the panning animation\r\n * The second argument is X, third argument is Y and fourth argument is the zoom level\r\n * Example: /animate-pan 500 1500 1500 0.5\r\n */\r\n\r\ncanvas.animatePan({duration: args[0], x: args[1], y: args[2], scale: args[3]})","author":"PEJBB2AYKoE5ZuxD","img":"icons/svg/dice-target.svg","actorIds":[]} -{"_id":"7g5Bw4kquOY7sr7j","name":"journal-dialog","permission":{"8pIGCEozfXNtPyHL":3},"type":"script","sort":400000,"flags":{},"scope":"global","command":"// This macro requires the advanced macros of Furnace\r\n// This will display the contents of a journal as a dialog\r\n// Takes one argument, the journal name\r\n// Example: /journal-dialog \"Welcome to my world\"\r\n\r\nconst journal = game.journal.entities.find(j => j.name === args[0])\r\nif (!journal) return;\r\nnew Dialog({content: journal.data.content,\r\n buttons: {ok: {label: args[1] || \"ok\"}}}).render(true)","author":"8pIGCEozfXNtPyHL","img":"icons/svg/dice-target.svg","actorIds":[]} -{"_id":"CRaAk3V9gF6ihk1P","name":"Measure Token Distances","permission":{"default":0,"PEJBB2AYKoE5ZuxD":3},"type":"script","flags":{"furnace":{"runAsGM":false}},"scope":"global","command":"// This macro will measure the distance between the selected tokens\r\n// and every targetted tokens within a scene.\r\n// It will then output the measured distances to the chat\r\n// If called with an argument, determines who to whisper the message to,\r\n// otherwise sends it as a public message\r\n\r\nlet message = \"\"\r\nfor (let token of canvas.tokens.controlled) {\r\n let ruler = canvas.controls.ruler;\r\n for (let target of game.user.targets) {\r\n ruler.clear()\r\n ruler.waypoints.push(token.center)\r\n ruler.labels.addChild(new PIXI.Text(\"\"));\r\n ruler.measure(target.center);\r\n let distance = ruler.labels.children[0].text;\r\n message += `From '${token.name}' to '${target.name}' : ${distance}
`\r\n ruler.clear();\r\n }\r\n}\r\nif (message) {\r\n const whisper = args[0] ? ChatMessage.getWhisperIDs(args[0]) : undefined;\r\n ChatMessage.create({content: message, whisper});\r\n}","author":"PEJBB2AYKoE5ZuxD","img":"icons/svg/dice-target.svg","actorIds":[]} -{"name":"Full Scene vision","permission":{"default":0,"PEJBB2AYKoE5ZuxD":3},"type":"script","flags":{"furnace":{"runAsGM":false},"core":{"sourceId":"Macro.Rpgok2vhQ4nNton4"}},"scope":"global","command":"// This macro will effectively set the vision area in a scene to the entire gridded area\n// without having to shift any walls or tiles or tokens.\n// It will do this by removing the scene padding by setting it to 0,\n// then changing the scene dimensions so the overall scene keeps the exact same size\n// It will also move the background image into a tile so it can be positiioned \n// at the same position as it was when the scene had padding\n\nconst width = canvas.scene.data.width;\nconst height = canvas.scene.data.height;\nconst grid = canvas.scene.data.grid;\nconst img = canvas.scene.data.img;\nconst padding = canvas.scene.data.padding;\nconst paddingX = Math.ceil(width * padding / grid) * grid;\nconst paddingY = Math.ceil(height * padding / grid) * grid;\n\nawait canvas.scene.update({img: null, width: width + 2 * paddingX, height: height + 2 * paddingY, padding: 0})\nif (img) {\n const minZ = canvas.scene.data.tiles.length ? Math.min(...canvas.scene.data.tiles.map(t => t.z)) : 0;\n await canvas.scene.createEmbeddedEntity('Tile', {width, height, img, scale: 1, rotation: 0, locked: true, hidden: false, x: paddingX, y: paddingY, z: minZ - 1});\n}","author":"PEJBB2AYKoE5ZuxD","img":"icons/svg/dice-target.svg","actorIds":[],"_id":"MO5ZBnOxAMeMy7mU"} -{"_id":"Ns2y2EcwosuZOwtu","name":"roll-skill","permission":{"default":0,"PEJBB2AYKoE5ZuxD":3},"type":"script","flags":{},"scope":"global","command":"/* This macro requires the advanced macros of Furnace and the LMRTFY module, v0.7+\r\n * This will request a skill check for the currently selected player using LMRTFY roll request dialog\r\n * Takes the skill as its first argument, or if not set, requests all\r\n * Example: /roll-skill \"prc\"\r\n */\r\n\r\nconst skill = args[0] || Object.keys(CONFIG.DND5E.skills);\r\nif (!actor) return;\r\nLMRTFYRoller.requestSkillChecks(actor, skill);","author":"PEJBB2AYKoE5ZuxD","img":"icons/svg/dice-target.svg","actorIds":[]} -{"_id":"PoygoBaDDvbDCluL","name":"current-time","permission":{"default":0,"PEJBB2AYKoE5ZuxD":3},"type":"script","flags":{},"scope":"global","command":"// Returns the current time in format \"HH:MM\" (24 hour format)\r\nconst now = new Date();\r\nreturn `${now.getHours()}:${now.getMinutes()}`;","author":"PEJBB2AYKoE5ZuxD","img":"icons/svg/dice-target.svg","actorIds":[]} -{"_id":"T39hRCH2Ozk4oUZU","name":"Restore token creation","permission":{"FRJk2q4bH9IdLZDf":3},"type":"script","sort":100001,"flags":{},"scope":"global","command":"// This will restore your ability (if your player role had it) to drop actors onto a scene to create new tokens.\r\n// This is the complement for the \"Disallow token creation\" macro.\r\n\r\nif (game.disallow_token_creation_id) Hooks.off('preCreateToken', game.disallow_token_creation_id);\r\ngame.disallow_token_creation_id= 0;","author":"FRJk2q4bH9IdLZDf","img":"icons/svg/dice-target.svg","actorIds":[]} -{"_id":"Tu27SXg668TmcI1X","name":"Disallow token creation","permission":{"FRJk2q4bH9IdLZDf":3},"type":"script","sort":100001,"flags":{},"scope":"global","command":"// After running this macro, it will be impossible to drop any new tokens onto the scene, regardless of user permission\r\n// Running the 'Restore token creation' macro will restore the normal behavior.\r\n\r\nif (game.disallow_token_creation_id)\r\n Hooks.off('preCreatetoken', game.disallow_token_creation_id);\r\ngame.disallow_token_creation_id = Hooks.on(\"preCreateToken\", () => false);","author":"FRJk2q4bH9IdLZDf","img":"icons/svg/dice-target.svg","actorIds":[]} -{"_id":"hbpdu5LvSAOAp5PX","name":"Say hello","permission":{"default":0,"PEJBB2AYKoE5ZuxD":3},"type":"chat","flags":{},"scope":"global","command":"Hi {{game.user.name}}, it is now {{macro \"current-time\"}}","author":"PEJBB2AYKoE5ZuxD","img":"icons/svg/dice-target.svg","actorIds":[]} -{"_id":"rKmHs6LsBtDjPDhK","name":"Set Token bars and nameplate","permission":{"bWT4RleWjkaYiYKA":3},"type":"script","sort":100001,"flags":{},"scope":"global","command":"/* This will set every token in scene to always display their\r\n * token bars and nameplate, and sets the first bar to represent \r\n * HP and removes the second token bar.\r\n*/\r\n\r\nconst tokens =canvas.tokens.placeables.map(token => {\r\n return {\r\n _id: token.id,\r\n \"bar1.attribute\": \"attributes.hp\",\r\n \"bar2.attribute\": \"\",\r\n \"displayName\": CONST.TOKEN_DISPLAY_MODES.ALWAYS,\r\n \"displayBars\": CONST.TOKEN_DISPLAY_MODES.ALWAYS\r\n };\r\n});\r\n\r\ncanvas.scene.updateEmbeddedEntity('Token', tokens)","author":"bWT4RleWjkaYiYKA","img":"icons/svg/dice-target.svg","actorIds":[]} -{"_id":"rMOgmYRkV0AZ1sjc","name":"move-token","permission":{"FRJk2q4bH9IdLZDf":3},"type":"script","sort":100001,"flags":{},"scope":"global","command":"/* This macro requires the advanced macros of Furnace\r\n * This will move the selected token to the designated position\r\n * Takes X and Y as arguments for the position (in pixels)\r\n * A third, optional, argument, if set to true, will disable the movement animation\r\n * Example: /move-token 1000 1500 false\r\n */\r\n\r\nconst x = args[0];\r\nconst y = args[1];\r\nconst noAnimate = args[2];\r\n\r\nif (!token) return;\r\nif (noAnimate) token._noAnimate = true;\r\nawait token.update({x, y})\r\ntoken._noAnimate = false;","author":"FRJk2q4bH9IdLZDf","img":"icons/svg/dice-target.svg","actorIds":[]} -{"_id":"wg8eFxpVcgewRvsu","name":"pan-camera","permission":{"FRJk2q4bH9IdLZDf":3},"type":"script","sort":100001,"flags":{},"scope":"global","command":"// Pan the canvas camera to a position X and Y, in pixels.\r\n// Can also set the zoom level using the third optional argument.\r\n// Example: /pan-camera 1500\r\n// Example: /pan-camera 2500 2000 0.3\r\n\r\ncanvas.pan({x: args[0], y: args[1], scale: args[2]})","author":"FRJk2q4bH9IdLZDf","img":"icons/svg/dice-target.svg","actorIds":[]} -{"_id":"yUlSqozYscIEOGz1","name":"play-audio","permission":{"FRJk2q4bH9IdLZDf":3},"type":"script","sort":100001,"flags":{},"scope":"global","command":"/* This macro requires the advanced macros of Furnace\r\n * This will play audio from a URL\r\n * Takes the URL of the audio file as its first argument\r\n * The second argument, if set to true, will play the audio for every other player too.\r\n * Example: /play-audio \"https://example.com/sound-effects/explosion.mp3\" true\r\n */\r\nconst url = args[0];\r\nconst push = args[1];\r\n\r\nAudioHelper.play({src: [url]}, push);","author":"FRJk2q4bH9IdLZDf","img":"icons/svg/dice-target.svg","actorIds":[]} -{"name":"Change Token Vision","permission":{"default":0,"PEJBB2AYKoE5ZuxD":3},"type":"script","flags":{},"scope":"global","command":"// Open a dialog for quickly changing token vision parameters of the controlled tokens.\r\n// This macro was written by @Sky#9453\r\n// https://github.com/Sky-Captain-13/foundry\r\n\r\nif (canvas.tokens.controlled.length === 0)\r\n return ui.notifications.error(\"Please select a token first\");\r\n\r\nlet applyChanges = false;\r\nnew Dialog({\r\n title: `Token Vision Configuration`,\r\n content: `\r\n
\r\n
\r\n \r\n \r\n
\r\n
\r\n \r\n \r\n
\r\n
\r\n `,\r\n buttons: {\r\n yes: {\r\n icon: \"\",\r\n label: `Apply Changes`,\r\n callback: () => applyChanges = true\r\n },\r\n no: {\r\n icon: \"\",\r\n label: `Cancel Changes`\r\n },\r\n },\r\n default: \"yes\",\r\n close: html => {\r\n if (applyChanges) {\r\n for ( let token of canvas.tokens.controlled ) {\r\n let visionType = html.find('[name=\"vision-type\"]')[0].value || \"none\";\r\n let lightSource = html.find('[name=\"light-source\"]')[0].value || \"none\";\r\n let dimSight = 0;\r\n let brightSight = 0;\r\n let dimLight = 0;\r\n let brightLight = 0;\r\n let lightAngle = 360;\r\n let lockRotation = token.data.lockRotation;\r\n // Get Vision Type Values\r\n switch (visionType) {\r\n case \"dim0\":\r\n dimSight = 0;\r\n brightSight = 0;\r\n break;\r\n case \"dim30\":\r\n dimSight = 30;\r\n brightSight = 0;\r\n break;\r\n case \"dim60\":\r\n dimSight = 60;\r\n brightSight = 0;\r\n break;\r\n case \"dim90\":\r\n dimSight = 90;\r\n brightSight = 0;\r\n break;\r\n case \"dim120\":\r\n dimSight = 120;\r\n brightSight = 0;\r\n break;\r\n case \"dim150\":\r\n dimSight = 150;\r\n brightSight = 0;\r\n break;\r\n case \"dim180\":\r\n dimSight = 180;\r\n brightSight = 0;\r\n break;\r\n case \"bright120\":\r\n dimSight = 0;\r\n brightSight= 120;\r\n break;\r\n case \"nochange\":\r\n default:\r\n dimSight = token.data.dimSight;\r\n brightSight = token.data.brightSight;\r\n }\r\n // Get Light Source Values\r\n switch (lightSource) {\r\n case \"none\":\r\n dimLight = 0;\r\n brightLight = 0;\r\n break;\r\n case \"candle\":\r\n dimLight = 10;\r\n brightLight = 5;\r\n break;\r\n case \"lamp\":\r\n dimLight = 45;\r\n brightLight = 15;\r\n break;\r\n case \"bullseye\":\r\n dimLight = 120;\r\n brightLight = 60;\r\n lockRotation = false;\r\n lightAngle = 52.5;\r\n break;\r\n case \"hooded-dim\":\r\n dimLight = 5;\r\n brightLight = 0;\r\n break;\r\n case \"hooded-bright\":\r\n dimLight = 60;\r\n brightLight = 30;\r\n break;\r\n case \"light\":\r\n dimLight = 40;\r\n brightLight = 20;\r\n break;\r\n case \"torch\":\r\n dimLight = 40;\r\n brightLight = 20;\r\n break;\r\n case \"nochange\":\r\n default:\r\n dimLight = token.data.dimLight;\r\n brightLight = token.data.brightLight;\r\n lightAngle = token.data.lightAngle;\r\n lockRotation = token.data.lockRotation;\r\n }\r\n // Update Token\r\n console.log(token);\r\n token.update({\r\n vision: true,\r\n dimSight: dimSight,\r\n brightSight: brightSight,\r\n dimLight: dimLight,\r\n brightLight: brightLight,\r\n lightAngle: lightAngle,\r\n lockRotation: lockRotation\r\n });\r\n }\r\n }\r\n }\r\n}).render(true);","author":"PEJBB2AYKoE5ZuxD","img":"icons/svg/dice-target.svg","actorIds":[],"_id":"zEOWQFblsTskw7jX"} diff --git a/templates/drawing-config.html b/templates/drawing-config.html deleted file mode 100644 index 300b2a6..0000000 --- a/templates/drawing-config.html +++ /dev/null @@ -1,193 +0,0 @@ -
- -
- - -
- - - - - - {{#unless enableTypeSelection}} - -
-

{{localize "FURNACE.DRAWINGS.positionHint"}}

- -
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- - -
-
- {{/unless}} - - - -
-

{{localize "FURNACE.DRAWINGS.settingsHint"}}

- -
-
- -
- - -
-
- - -
-
- - -
- -
- - -
- -
- - - {{floor (multiply object.textAlpha 100)}}% -
- -
-
-
-
- - -
- -
- - -
- -
- - - {{floor (multiply object.strokeAlpha 100)}}% -
- -
- - - {{floor (multiply object.bezierFactor 100)}}% -
-
- -
-
-
- - -
-
- - -
- -
- - - {{floor (multiply object.fillAlpha 100)}}% -
-
-
- -
-

{{localize "FURNACE.DRAWINGS.textureHint"}}

-
-
-
- - {{filePicker target="texture" type="image"}} - -
- -

{{localize "FURNACE.DRAWINGS.textureHint1"}}

-

{{localize "FURNACE.DRAWINGS.textureHint2"}}

- -
- - - {{floor (multiply object.textureAlpha 100)}}% -
- - -
-

{{localize "FURNACE.DRAWINGS.textureSizeHint"}}

- - - -
- -
- - -
- -
-
- -
-
- - -
-
- -
\ No newline at end of file diff --git a/templates/drawing-hud.html b/templates/drawing-hud.html deleted file mode 100644 index 35f8f83..0000000 --- a/templates/drawing-hud.html +++ /dev/null @@ -1,57 +0,0 @@ -
- -
-
- - -
- {{#if isText}} -
- - -
- {{else}} -
- - -
- {{/if}} -
- -
-
- -
-
- -
-
- -
- -
- -
- {{#if isText}} -
- -
- {{/if}} - -
- -
- -
- -
- -
- -
- -
- -
-
-
diff --git a/templates/playlist-now-playing.html b/templates/playlist-now-playing.html deleted file mode 100644 index 7111f53..0000000 --- a/templates/playlist-now-playing.html +++ /dev/null @@ -1,58 +0,0 @@ -
  • -
    -

    {{localize "FURNACE.PLAYLIST.NowPlaying"}}

    -
    - -
      - {{#each entities as |playlist pid|}} - {{#each playlist.sounds as |sound i|}} - {{#if sound.playing}} -
    1. -
        - {{#if @root.use_id}} -
      1. - {{else}} -
      2. - {{/if}} -

        {{sound.name}}

        -
        - - {{#if @root.isGM}} - - - - {{/if}} - - - - {{#if @root.isGM}} - - - - {{/if}} - - - - - - {{#if sound.playing}} - - - - {{else}} - - - - - {{/if}} -
        -
      3. -
      -
    2. - {{/if}} - {{/each}} - {{/each}} -
    -
  • diff --git a/templates/split-journal.html b/templates/split-journal.html deleted file mode 100644 index fb7fc0f..0000000 --- a/templates/split-journal.html +++ /dev/null @@ -1,47 +0,0 @@ -
    - {{#if error}} -

    {{errorMessage}}

    - {{else}} -

    {{localize "FURNACE.SPLIT.description" name=name}}

    - -
    - - -
    - {{#if hasImage}} -
    - - -
    - {{/if}} -
    - - -
    -
    - - -
    -
    -
    - {{localize "FURNACE.SPLIT.newEntries" numEntries=newEntries.length}} -
    -
    -
      - {{#each newEntries}} -
    • {{this}}
    • - {{/each}} -
    -
    -
    - {{/if}} -
    - -
    -
    \ No newline at end of file diff --git a/templates/token-folder-drop.html b/templates/token-folder-drop.html deleted file mode 100644 index 45c5d66..0000000 --- a/templates/token-folder-drop.html +++ /dev/null @@ -1,34 +0,0 @@ -
    -

    {{{localize "FURNACE.ACTORS.droppedFolder" numActors=actors.length folderName=folder.name}}}

    -

    {{localize "FURNACE.ACTORS.dropQuestion"}}

    -
    - - - -
    -
    - - - -
    -
    - - - -
    -
    - - - -
    -
    - - - -
    -
    - - - -
    -
    \ No newline at end of file