This app provides monitoring and information features for the common freifunk user and the technical stuff of a freifunk community.
Code base is taken from a TUM Practical Course project and added here to see if Freifunk Altdorf can use it.
https://www.freifunk-altdorf.de
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
316 lines
8.7 KiB
316 lines
8.7 KiB
var Class = require('../../core/class'); |
|
var Transform = require('../../core/transform'); |
|
var Color = require('../../core/color'); |
|
var Node = require('./node'); |
|
var DOM = require('./dom'); |
|
|
|
var precision = 100; |
|
|
|
var defaultBox = { left: 0, top: 0, width: 500, height: 500 }; |
|
|
|
module.exports = Class(Node, { |
|
|
|
element_initialize: Node.prototype.initialize, |
|
|
|
initialize: function(tag){ |
|
this.element_initialize(tag); |
|
var element = this.element; |
|
|
|
var skew = this.skewElement = DOM.createElement('skew'); |
|
skew.on = true; |
|
element.appendChild(skew); |
|
|
|
var fill = this.fillElement = DOM.createElement('fill'); |
|
fill.on = false; |
|
element.appendChild(fill); |
|
|
|
var stroke = this.strokeElement = DOM.createElement('stroke'); |
|
stroke.on = false; |
|
element.appendChild(stroke); |
|
}, |
|
|
|
/* transform */ |
|
|
|
_transform: function(){ |
|
var container = this.parentNode; |
|
|
|
// Active Transformation Matrix |
|
var m = container ? new Transform(container._activeTransform).transform(this) : this; |
|
|
|
// Box in shape user space |
|
|
|
var box = this._boxCoords || this._size || defaultBox; |
|
|
|
var originX = box.left || 0, |
|
originY = box.top || 0, |
|
width = box.width || 1, |
|
height = box.height || 1; |
|
|
|
// Flipped |
|
var flip = m.yx / m.xx > m.yy / m.xy; |
|
if (m.xx < 0 ? m.xy >= 0 : m.xy < 0) flip = !flip; |
|
flip = flip ? -1 : 1; |
|
|
|
m = new Transform().scale(flip, 1).transform(m); |
|
|
|
// Rotation is approximated based on the transform |
|
var rotation = Math.atan2(-m.xy, m.yy) * 180 / Math.PI; |
|
|
|
// Reverse the rotation, leaving the final transform in box space |
|
var rad = rotation * Math.PI / 180, sin = Math.sin(rad), cos = Math.cos(rad); |
|
|
|
var transform = new Transform( |
|
(m.xx * cos - m.xy * sin), |
|
(m.yx * cos - m.yy * sin) * flip, |
|
(m.xy * cos + m.xx * sin) * flip, |
|
(m.yy * cos + m.yx * sin) |
|
); |
|
|
|
var rotationTransform = new Transform().rotate(rotation, 0, 0); |
|
|
|
var shapeToBox = new Transform().rotate(-rotation, 0, 0).transform(m).moveTo(0,0); |
|
|
|
// Scale box after reversing rotation |
|
width *= Math.abs(shapeToBox.xx); |
|
height *= Math.abs(shapeToBox.yy); |
|
|
|
// Place box |
|
var left = m.x, top = m.y; |
|
|
|
// Compensate for offset by center origin rotation |
|
var vx = -width / 2, vy = -height / 2; |
|
var point = rotationTransform.point(vx, vy); |
|
left -= point.x - vx; |
|
top -= point.y - vy; |
|
|
|
// Adjust box position based on offset |
|
var rsm = new Transform(m).moveTo(0,0); |
|
point = rsm.point(originX, originY); |
|
left += point.x; |
|
top += point.y; |
|
|
|
if (flip < 0) left = -left - width; |
|
|
|
// Place transformation origin |
|
var point0 = rsm.point(-originX, -originY); |
|
var point1 = rotationTransform.point(width, height); |
|
var point2 = rotationTransform.point(width, 0); |
|
var point3 = rotationTransform.point(0, height); |
|
|
|
var minX = Math.min(0, point1.x, point2.x, point3.x), |
|
maxX = Math.max(0, point1.x, point2.x, point3.x), |
|
minY = Math.min(0, point1.y, point2.y, point3.y), |
|
maxY = Math.max(0, point1.y, point2.y, point3.y); |
|
|
|
var transformOriginX = (point0.x - point1.x / 2) / (maxX - minX) * flip, |
|
transformOriginY = (point0.y - point1.y / 2) / (maxY - minY); |
|
|
|
// Adjust the origin |
|
point = shapeToBox.point(originX, originY); |
|
originX = point.x; |
|
originY = point.y; |
|
|
|
// Scale stroke |
|
var strokeWidth = this._strokeWidth; |
|
if (strokeWidth){ |
|
// Scale is the hypothenus between the two vectors |
|
// TODO: Use area calculation instead |
|
var vx = m.xx + m.xy, vy = m.yy + m.yx; |
|
strokeWidth *= Math.sqrt(vx * vx + vy * vy) / Math.sqrt(2); |
|
} |
|
|
|
// convert to multiplied precision space |
|
originX *= precision; |
|
originY *= precision; |
|
left *= precision; |
|
top *= precision; |
|
width *= precision; |
|
height *= precision; |
|
|
|
// Set box |
|
var element = this.element; |
|
element.coordorigin = originX + ',' + originY; |
|
element.coordsize = width + ',' + height; |
|
element.style.left = left + 'px'; |
|
element.style.top = top + 'px'; |
|
element.style.width = width; |
|
element.style.height = height; |
|
element.style.rotation = rotation.toFixed(8); |
|
element.style.flip = flip < 0 ? 'x' : ''; |
|
|
|
// Set transform |
|
var skew = this.skewElement; |
|
skew.matrix = [transform.xx.toFixed(4), transform.xy.toFixed(4), transform.yx.toFixed(4), transform.yy.toFixed(4), 0, 0]; |
|
skew.origin = transformOriginX + ',' + transformOriginY; |
|
|
|
// Set stroke |
|
this.strokeElement.weight = strokeWidth + 'px'; |
|
}, |
|
|
|
/* styles */ |
|
|
|
_createGradient: function(style, stops){ |
|
var fill = this.fillElement; |
|
|
|
// Temporarily eject the fill from the DOM |
|
this.element.removeChild(fill); |
|
|
|
fill.type = style; |
|
fill.method = 'none'; |
|
fill.rotate = true; |
|
|
|
var colors = [], color1, color2; |
|
|
|
var addColor = function(offset, color){ |
|
color = Color.detach(color); |
|
if (color1 == null) color1 = color2 = color; |
|
else color2 = color; |
|
colors.push(offset + ' ' + color[0]); |
|
}; |
|
|
|
// Enumerate stops, assumes offsets are enumerated in order |
|
if ('length' in stops) for (var i = 0, l = stops.length - 1; i <= l; i++) addColor(i / l, stops[i]); |
|
else for (var offset in stops) addColor(offset, stops[offset]); |
|
|
|
fill.color = color1[0]; |
|
fill.color2 = color2[0]; |
|
|
|
//if (fill.colors) fill.colors.value = colors; else |
|
fill.colors = colors; |
|
|
|
// Opacity order gets flipped when color stops are specified |
|
fill.opacity = color2[1]; |
|
fill['ao:opacity2'] = color1[1]; |
|
|
|
fill.on = true; |
|
this.element.appendChild(fill); |
|
return fill; |
|
}, |
|
|
|
_setColor: function(type, color){ |
|
var element = type == 'fill' ? this.fillElement : this.strokeElement; |
|
if (color == null){ |
|
element.on = false; |
|
} else { |
|
color = Color.detach(color); |
|
element.color = color[0]; |
|
element.opacity = color[1]; |
|
element.on = true; |
|
} |
|
}, |
|
|
|
fill: function(color){ |
|
if (arguments.length > 1){ |
|
this.fillLinear(arguments); |
|
} else { |
|
this._boxCoords = defaultBox; |
|
var fill = this.fillElement; |
|
fill.type = 'solid'; |
|
fill.color2 = ''; |
|
fill['ao:opacity2'] = ''; |
|
if (fill.colors) fill.colors.value = ''; |
|
this._setColor('fill', color); |
|
} |
|
return this; |
|
}, |
|
|
|
fillRadial: function(stops, focusX, focusY, radiusX, radiusY, centerX, centerY){ |
|
var fill = this._createGradient('gradientradial', stops); |
|
if (focusX == null) focusX = this.left + this.width * 0.5; |
|
if (focusY == null) focusY = this.top + this.height * 0.5; |
|
if (radiusY == null) radiusY = radiusX || (this.height * 0.5); |
|
if (radiusX == null) radiusX = this.width * 0.5; |
|
if (centerX == null) centerX = focusX; |
|
if (centerY == null) centerY = focusY; |
|
|
|
centerX += centerX - focusX; |
|
centerY += centerY - focusY; |
|
|
|
var box = this._boxCoords = { |
|
left: centerX - radiusX * 2, |
|
top: centerY - radiusY * 2, |
|
width: radiusX * 4, |
|
height: radiusY * 4 |
|
}; |
|
focusX -= box.left; |
|
focusY -= box.top; |
|
focusX /= box.width; |
|
focusY /= box.height; |
|
|
|
fill.focussize = '0 0'; |
|
fill.focusposition = focusX + ',' + focusY; |
|
fill.focus = '50%'; |
|
|
|
this._transform(); |
|
|
|
return this; |
|
}, |
|
|
|
fillLinear: function(stops, x1, y1, x2, y2){ |
|
var fill = this._createGradient('gradient', stops); |
|
fill.focus = '100%'; |
|
if (arguments.length == 5){ |
|
var w = Math.abs(x2 - x1), h = Math.abs(y2 - y1); |
|
this._boxCoords = { |
|
left: Math.min(x1, x2), |
|
top: Math.min(y1, y2), |
|
width: w < 1 ? h : w, |
|
height: h < 1 ? w : h |
|
}; |
|
fill.angle = (360 + Math.atan2((x2 - x1) / h, (y2 - y1) / w) * 180 / Math.PI) % 360; |
|
} else { |
|
this._boxCoords = null; |
|
fill.angle = (x1 == null) ? 0 : (90 + x1) % 360; |
|
} |
|
this._transform(); |
|
return this; |
|
}, |
|
|
|
fillImage: function(url, width, height, left, top, color1, color2){ |
|
var fill = this.fillElement; |
|
if (color1 != null){ |
|
color1 = Color.detach(color1); |
|
if (color2 != null) color2 = Color.detach(color2); |
|
fill.type = 'pattern'; |
|
fill.color = color1[0]; |
|
fill.color2 = color2 == null ? color1[0] : color2[0]; |
|
fill.opacity = color2 == null ? 0 : color2[1]; |
|
fill['ao:opacity2'] = color1[1]; |
|
} else { |
|
fill.type = 'tile'; |
|
fill.color = ''; |
|
fill.color2 = ''; |
|
fill.opacity = 1; |
|
fill['ao:opacity2'] = 1; |
|
} |
|
if (fill.colors) fill.colors.value = ''; |
|
fill.rotate = true; |
|
fill.src = url; |
|
|
|
fill.size = '1,1'; |
|
fill.position = '0,0'; |
|
fill.origin = '0,0'; |
|
fill.aspect = 'ignore'; // ignore, atleast, atmost |
|
fill.on = true; |
|
|
|
if (!left) left = 0; |
|
if (!top) top = 0; |
|
this._boxCoords = width ? { left: left + 0.5, top: top + 0.5, width: width, height: height } : null; |
|
this._transform(); |
|
return this; |
|
}, |
|
|
|
/* stroke */ |
|
|
|
stroke: function(color, width, cap, join){ |
|
var stroke = this.strokeElement; |
|
this._strokeWidth = (width != null) ? width : 1; |
|
stroke.weight = (width != null) ? width + 'px' : 1; |
|
stroke.endcap = (cap != null) ? ((cap == 'butt') ? 'flat' : cap) : 'round'; |
|
stroke.joinstyle = (join != null) ? join : 'round'; |
|
|
|
this._setColor('stroke', color); |
|
return this; |
|
} |
|
|
|
}); |