import paper from 'paper';
import LZString from 'lz-string';
import steg from './vendors/steganography';
import { state, objectCreateCommand, applyCommand } from './state';
import { downloadDataURL, downloadDataURLForiOSSafari } from './helpers/download';
import { layers } from './layers';
import { colors, getColorDataFromEncodedName } from './colors';
import { getGridRaster } from './grid';
import { objectMap } from './helpers/objectMap';
import { toolCategoryDefinition } from './tools';
import { getMobileOperatingSystem } from "./helpers/getMobileOperatingSystem";
function removeFloatingPointError(f) {
return Math.round((f + Number.EPSILON) * 100) / 100;
}
function encodePoint(p) {
return [removeFloatingPointError(p.x), removeFloatingPointError(p.y)];
}
function decodeObject(encodedData, encodingVersion) {
const position = new paper.Point(encodedData.position);
const objectData = {
category: encodedData.category,
type: encodedData.type,
};
if (
toolCategoryDefinition[encodedData.category].tools &&
toolCategoryDefinition[encodedData.category].tools.value
) {
const objectDefinition =
toolCategoryDefinition[encodedData.category].tools.value[objectData.type];
if (objectDefinition.legacy) {
objectData.type = objectDefinition.legacy;
}
if (objectDefinition.legacyCategory) {
objectData.category = objectDefinition.legacyCategory;
}
if (objectDefinition.rename) {
if (encodingVersion <= objectDefinition.rename[0]) {
objectData.type = objectDefinition.rename[1];
}
}
}
applyCommand(objectCreateCommand(objectData, position), true);
return {
position,
category: encodedData.category,
type: encodedData.type,
};
}
function encodeObjectGroups(objects) {
const objectGroups = {};
Object.values(objects).forEach((object) => {
const key = `${object.data.category}_${object.data.type}`;
if (!objectGroups[key]) {
objectGroups[key] = [];
}
const encodedPoint = encodePoint(object.position);
objectGroups[key].push(encodedPoint[0], encodedPoint[1]);
});
return objectGroups;
}
function decodeObjectGroups(objectGroups, encodingVersion) {
if (objectGroups == null) return {};
if (encodingVersion === 0) {
return objectMap(objectGroups, (encodedData) => {
return decodeObject(encodedData, paper.version);
});
}
const objects = {};
Object.keys(objectGroups).forEach((key) => {
const keySplit = key.split('_');
const category = keySplit[0];
const type = keySplit[1];
const positionArray = objectGroups[key];
for (let i = 0; i < positionArray.length; i += 2) {
decodeObject(
{
category,
type,
position: [positionArray[i], positionArray[i + 1]],
},
encodingVersion,
);
}
});
return objects;
}
function encodePath(p) {
const positions: number[] = [];
p.segments.forEach((s) => {
const encodedPoint = encodePoint(s.point);
positions.push(encodedPoint[0], encodedPoint[1]);
});
return positions;
}
function decodePath(positionArray) {
const points: paper.Point[] = [];
for (let i = 0; i < positionArray.length; i += 2) {
points.push(new paper.Point(positionArray[i], positionArray[i + 1]));
}
return points;
}
function encodeDrawing(drawing) {
const encodedDrawing = {};
Object.keys(drawing).forEach((colorKey) => {
const pathItem = drawing[colorKey];
let p;
if (pathItem.children) {
p = pathItem.children.map((path) => {
return encodePath(path);
});
} else {
p = encodePath(pathItem);
}
const encodedColorName = colors[colorKey].name;
encodedDrawing[encodedColorName] = p;
});
return encodedDrawing;
}
function decodeDrawing(encodedDrawing, version) {
const decodedDrawing = {};
Object.keys(encodedDrawing).forEach((colorName) => {
const colorData = getColorDataFromEncodedName(colorName);
const pathData = encodedDrawing[colorName];
let p;
if (pathData.length === 0) {
p = new paper.Path();
} else if (version === 0) {
if (typeof pathData[0][0] === 'number') {
p = new paper.Path(
pathData.map((p) => {
return new paper.Point(p);
}),
);
} else {
p = new paper.CompoundPath({
children: pathData.map((pathData) => {
return new paper.Path(
pathData.map((p) => {
return new paper.Point(p);
}),
);
}),
});
}
} else if (typeof pathData[0] === 'number') {
p = new paper.Path(decodePath(pathData));
} else {
p = new paper.CompoundPath({
children: pathData.map((pathData) => {
return new paper.Path(decodePath(pathData));
}),
});
}
p.locked = true;
p.fillColor = colorData.color;
decodedDrawing[colorData.key] = p;
});
return decodedDrawing;
}
export function encodeMap() {
const o = {
version: 1,
objects: encodeObjectGroups(state.objects),
drawing: encodeDrawing(state.drawing),
};
if (Object.keys(o.objects).length === 0) {
delete o.objects;
}
return JSON.stringify(o);
}
export function decodeMap(json) {
layers.mapLayer.activate();
if (json == null) return;
if (json.version == 1 && json.drawing && json.objects && Object.keys(json.objects).length == 0) {
var index = 0;
Object.keys(json.drawing).forEach(function(colorName) {
if (colorName.match(/ØoveØ[0-9]/)) {
var newKey = ('level' + colorName.slice(-1));
json.drawing[newKey] = json.drawing[colorName];
delete json.drawing[colorName];
var keys = Object.keys(json.drawing);
for (var i = index; i < keys.length - 1; i++) {
var key = keys[i];
var data = json.drawing[key];
delete json.drawing[key];
json.drawing[key] = data;
}
}
index++;
})
}
const { version } = json;
return {
version: json.version,
drawing: decodeDrawing(json.drawing, version),
objects: decodeObjectGroups(json.objects, version),
};
}
export function autosaveMap() {
if (localStorage) {
localStorage.setItem('autosave', encodeMap());
state.actionsSinceSave = 0;
return true;
}
console.log('Cannot autosave: your browser does not support local storage.');
return false;
}
window.clearAutosave = clearAutosave;
export function clearAutosave() {
if (localStorage) {
localStorage.removeItem('autosave');
}
}
export function saveMapToFile() {
let mapJson = encodeMap();
mapJson = LZString.compressToUTF16(mapJson);
const saveMargins = new paper.Size(10, 10);
layers.uiLayer.activate();
const mapRaster = layers.mapLayer.rasterize();
const mapPositionDelta = layers.mapLayer.globalToLocal(
layers.mapLayer.bounds.topLeft,
);
const iconsRaster = layers.mapIconLayer.rasterize();
const iconsPositionDelta = layers.mapIconLayer.globalToLocal(
layers.mapIconLayer.bounds.topLeft,
);
const gridRaster = getGridRaster();
const gridClone = gridRaster.clone();
const mapBounds = gridRaster.bounds.clone();
mapBounds.size = mapBounds.size.add(saveMargins);
mapBounds.point = mapBounds.point.subtract(saveMargins.divide(2).height);
const mapBoundsClippingMask = new paper.Path.Rectangle(mapBounds);
const background = mapBoundsClippingMask.clone();
background.fillColor = colors.water.color;
mapBoundsClippingMask.clipMask = true;
const text = new paper.PointText(
mapBounds.bottomRight.subtract(new paper.Point(2, 2)),
);
text.justification = 'right';
text.content = 'made at eugeneration.github.io/HappyIslandDesigner';
text.fontFamily = 'TTNorms, sans-serif';
text.fillColor = colors.oceanDark.color;
text.strokeWidth = 0;
text.fontSize = 2;
text.selected = true;
const group = new paper.Group();
group.clipped = true;
group.addChildren([
mapBoundsClippingMask,
background,
mapRaster,
iconsRaster,
gridClone,
text,
]);
mapRaster.scaling = mapRaster.scaling.divide(layers.mapLayer.scaling);
mapRaster.bounds.topLeft = mapPositionDelta;
iconsRaster.scaling = iconsRaster.scaling.divide(layers.mapLayer.scaling);
iconsRaster.bounds.topLeft = iconsPositionDelta;
const combinedImage = group.rasterize(708.5);
combinedImage.position.x += 200;
combinedImage.remove();
group.remove();
const mapRasterSize = combinedImage.size;
let mapRasterData = combinedImage.toDataURL();
const shadowCanvas = document.createElement('canvas');
shadowCanvas.style.display = 'none';
const image = new Image();
image.src = mapRasterData;
const os = getMobileOperatingSystem();
var isSafari = navigator.vendor && navigator.vendor.indexOf('Apple') > -1 &&
navigator.userAgent &&
navigator.userAgent.indexOf('CriOS') == -1 &&
navigator.userAgent.indexOf('FxiOS') == -1;
var w;
if (os == "iOS" && !isSafari) {
w = window.open('about:blank');
}
image.addEventListener(
'load',
() => {
mapRasterData = steg.encode(mapJson, mapRasterData, {
height: mapRasterSize.height,
width: mapRasterSize.width,
});
const filename = `HappyIslandDesigner_${Date.now()}.png`;
if (os == "iOS") {
if (isSafari) {
downloadDataURLForiOSSafari(filename, mapRasterData)
} else {
image.src = mapRasterData;
image.addEventListener(
'load',
() => {
w?.document.write(image.outerHTML);
},
false,
);
}
} else {
downloadDataURL(filename, mapRasterData);
}
},
false,
);
autosaveMap();
}