Skip to content

Commit

Permalink
Added export functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
greeny committed Aug 13, 2023
1 parent bfdfe1e commit 58ccb17
Show file tree
Hide file tree
Showing 12 changed files with 388 additions and 43 deletions.
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,12 @@
"@types/angular-sanitize": "^1.7.0",
"@types/angular-ui-router": "^1.1.40",
"@types/axios": "^0.14.0",
"@types/base-64": "^1.0.0",
"@types/bootstrap": "^5.1.6",
"@types/cytoscape": "^3.19.2",
"@types/file-saver": "^2.0.5",
"@types/node": "^16.11.10",
"@types/pako": "^2.0.0",
"@types/perfect-scrollbar": "^1.3.0",
"@types/sharp": "^0.29.4",
"angular-templatecache-loader": "^0.2.0",
Expand Down Expand Up @@ -48,13 +51,16 @@
"angular-ui-router": "^1.0.25",
"angular-ui-sortable": "^0.19.0",
"axios": "^0.24.0",
"base-64": "^1.0.0",
"bootstrap": "^4.5.3",
"cytoscape": "^3.20.0",
"cytoscape-elk": "^2.0.2",
"cytoscape-html-node": "^2.2.0",
"cytoscape-node-html-label": "^1.2.2",
"elkjs": "^0.7.1",
"file-saver": "^2.0.5",
"jquery": "^3.4.1",
"pako": "^2.1.0",
"perfect-scrollbar": "^1.5.0",
"popper.js": "^1.16.1",
"ui-bootstrap4": "^3.0.6",
Expand Down
61 changes: 61 additions & 0 deletions src/Export/FileExporter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import {IProductionData} from '@src/Tools/Production/IProductionData';
import * as pako from 'pako';
import {Strings} from '@src/Utils/Strings';
import {ProductionTab} from '@src/Tools/Production/ProductionTab';

export class FileExporter
{

private static version = '0';

public static exportTabs(tabs: ProductionTab[]): string
{
const data: {type: string, tabs: IProductionData[]} = {
type: 'tabs',
tabs: []
};

let info = '';

for (const tab of tabs) {
data.tabs.push(tab.data);
info += '# - ' + tab.name + '\n';
}

let result = '# Satisfactory Tools export (version 1)\n\n# Contains these production lines:\n' + info + '\n';

result += FileExporter.version + Strings.base64encode(Strings.bufferToString(pako.deflate(JSON.stringify(data))));

result += '\n\n# Exported on ' + new Date().toISOString() + '\n';

return result;
}

public static importTabs(data: string): IProductionData[]
{
data = data.split('\n').map((line) => line.trim()).filter((line) => line !== '' && line.charAt(0) !== '#').join();

const version = data.charAt(0);
if (version !== FileExporter.version) {
throw new Error('Invalid version specified: ' + version);
}
const parsed = JSON.parse(pako.inflate(Strings.stringToBuffer(Strings.base64decode(data.substring(1))), {to: 'string'}))

if (parsed.type !== 'tabs') {
throw new Error('Invalid file type');
}

return parsed.tabs;
}

public static importData(data: string): string
{
const version = data.charAt(0);
if (version !== FileExporter.version) {
throw new Error('Invalid version specified: ' + version);
}
console.log(Strings.stringToBuffer(Strings.base64decode(data.substring(1))));
return JSON.parse(pako.inflate(Strings.stringToBuffer(Strings.base64decode(data.substring(1))), {to: 'string'}));
}

}
17 changes: 17 additions & 0 deletions src/Module/AppModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,10 @@ export class AppModule
return AppModule.generateNumberFormattingFunction();
});

this.app.filter('numberShort', () => {
return AppModule.generateShortNumberFormattingFunction();
});

this.app.directive('app', () => {
return new AppDirective;
});
Expand Down Expand Up @@ -422,4 +426,17 @@ export class AppModule
};
}

private static generateShortNumberFormattingFunction()
{
return (value: number) => {
if (typeof value === 'undefined') {
return 'NaN';
} else if (value === ~~value) {
return value;
} else {
return (Number(value)).toFixed(3).replace(/\.?0+$/, '');
}
};
}

}
85 changes: 85 additions & 0 deletions src/Module/Controllers/ProductionController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,20 @@ import {DataStorageService} from '@src/Module/Services/DataStorageService';
import axios from 'axios';
import {IProductionData} from '@src/Tools/Production/IProductionData';
import {IBuildingSchema} from '@src/Schema/IBuildingSchema';
import {FileExporter} from '@src/Export/FileExporter';
import {Strings} from '@src/Utils/Strings';

export class ProductionController
{

public selectedTabs: ProductionTab[] = [];
public tab: ProductionTab|null = null;
public tabs: ProductionTab[] = [];
public addingInProgress: boolean;
public cloningInProgress: boolean;
public importOpen: boolean = false;
public importFiles: File[] = [];

public readonly rawResources: IResourceSchema[] = data.getResources();
public readonly craftableItems: IItemSchema[] = model.getAutomatableItems();
public readonly inputableItems: IItemSchema[] = model.getInputableItems();
Expand Down Expand Up @@ -67,6 +73,85 @@ export class ProductionController
}
}

public toggleImport(): void
{
this.importOpen = !this.importOpen;
}

public tryImport(): void
{
const input: HTMLInputElement = document.getElementById('importFile') as HTMLInputElement;
const files = input.files as FileList;

if (files.length === 0) {
return;
}

const file = files[0];
const reader = new FileReader();
reader.readAsText(file, 'utf-8');
reader.onload = () => {
try {
const tabs = FileExporter.importTabs(reader.result as string);

for (const tab of tabs) {
this.tabs.push(new ProductionTab(this.scope, tab));
}

Strings.addNotification('Import complete', 'Successfuly imported ' + tabs.length + ' tab' + (tabs.length === 1 ? '' : 's') + '.');
this.scope.$apply();
input.value = '';
} catch (e) {
Strings.addNotification('ERROR', 'Couldn\'t import file: ' + e.message, 5000);
return;
}
}
}

public selectAllTabs(): void
{
this.selectedTabs = [];
for (const tab of this.tabs) {
this.selectedTabs.push(tab);
}
}

public toggleTab(tab: ProductionTab): void
{
const index = this.selectedTabs.indexOf(tab);
if (index === -1) {
this.selectedTabs.push(tab);
} else {
this.selectedTabs.splice(index, 1);
}
}

public isTabSelected(tab: ProductionTab): boolean
{
return this.selectedTabs.indexOf(tab) !== -1;
}

public removeSelectedTabs(): void
{
if (this.selectedTabs.length === 0) {
return;
}
if (confirm('Do you really want to remove ' + this.selectedTabs.length + ' tab' + (this.selectedTabs.length > 1 ? 's?' : '?'))) {
for (const tab of this.selectedTabs) {
this.removeTab(tab);
}
this.selectedTabs = [];
}
}

public exportSelectedTabs(): void
{
if (this.selectedTabs.length === 0) {
return;
}
Strings.downloadFile('sftools-export-' + Strings.dateToIso(new Date()) + '.sft', FileExporter.exportTabs(this.selectedTabs));
}

public addEmptyTab(): void
{
this.addingInProgress = true;
Expand Down
2 changes: 2 additions & 0 deletions src/Tools/Production/Result/IResultDetails.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ export interface IResultDetails
hasInput: boolean;
rawResources: {[key: string]: IRawResourceResultDetails};
output: {[key: string]: number};
hasOutput: boolean;
byproducts: {[key: string]: number};
hasByproducts: boolean;
alternatesNeeded: IRecipeSchema[],

}
Expand Down
5 changes: 5 additions & 0 deletions src/Tools/Production/Result/ProductionResult.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@ export class ProductionResult
hasInput: false,
rawResources: {},
output: {},
hasOutput: false,
byproducts: {},
hasByproducts: false,
alternatesNeeded: [],
};

Expand Down Expand Up @@ -261,8 +263,11 @@ export class ProductionResult
for (const node of this.graph.nodes) {
if (node instanceof ProductNode) {
ProductionResult.addProduct(products, node.itemAmount.item, node.itemAmount.amount);
this.details.hasOutput = true;
} else if (node instanceof ByproductNode) {
ProductionResult.addProduct(byproducts, node.itemAmount.item, node.itemAmount.amount);
this.details.hasByproducts = true;
this.details.hasOutput = true;
}
}

Expand Down
78 changes: 67 additions & 11 deletions src/Utils/Strings.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import {Objects} from '@src/Utils/Objects';
import {Constants} from '@src/Constants';
import {saveAs} from 'file-saver';
import * as base64 from 'base-64';

export class Strings
{
Expand All @@ -11,7 +13,7 @@ export class Strings
public static readonly SEPARATOR = 'separator';
public static readonly UNKNOWN = 'unknown';

private static schematicTypes: {[key: string]: string} = {
private static schematicTypes: { [key: string]: string } = {
EST_Milestone: 'Milestone',
EST_MAM: 'MAM Research',
EST_Alternate: 'Alternate recipe',
Expand All @@ -20,7 +22,7 @@ export class Strings
EST_ResourceSink: 'AWESOME Sink',
};

public static formatNumber(num: number|string, decimals: number = 3)
public static formatNumber(num: number | string, decimals: number = 3)
{
if (typeof num === 'string') {
num = parseFloat(num);
Expand All @@ -33,6 +35,40 @@ export class Strings
return name.replace(/[\s|.]+/gi, '-').replace(/[:]/gi, '').toLowerCase();
}

public static stringToBuffer(string: string): ArrayBuffer
{
const stringLength = string.length;
const buffer = new ArrayBuffer(stringLength);
const bufferView = new Uint8Array(buffer);
for (let i = 0; i < stringLength; i++) {
bufferView[i] = string.charCodeAt(i);
}
return buffer;
}

public static bufferToString(buffer: ArrayBuffer): string
{
return String.fromCharCode.apply(null, Array.from(new Uint16Array(buffer)));
}

public static downloadFile(filename: string, data: string, type: string = 'text/plain'): void
{
const blob = new Blob([data], {
type: type + ';charset=utf-8',
})
saveAs(blob, Strings.webalize(filename));
}

public static base64encode(string: string): string
{
return base64.encode(string);
}

public static base64decode(string: string): string
{
return base64.decode(string);
}

public static copyToClipboard(text: string, displayNotification: string = '', delay: number = 3000): boolean
{
const textArea = document.createElement('textarea');
Expand Down Expand Up @@ -62,20 +98,40 @@ export class Strings
document.body.removeChild(textArea);

if (displayNotification !== '') {
const toast = document.createElement('div');
toast.className = 'toast';
toast.innerHTML = '<div class="toast-header"><span class="far fa-copy mr-2"></span><strong class="mr-auto">Copied</strong><button type="button" class="close" data-dismiss="toast"><span class="fas fa-times"></span></button></div>' +
'<div class="toast-body">' + displayNotification + '</div>';
document.getElementById('toasts')?.appendChild(toast);
$(toast).toast({
delay: delay,
});
$(toast).toast('show');
Strings.addNotification('Copied', displayNotification, delay);
}

return result;
}

public static addNotification(header: string, body: string, delay: number = 3000): void
{
const toast = document.createElement('div');
toast.className = 'toast';
toast.innerHTML = '<div class="toast-header"><span class="far fa-copy mr-2"></span><strong class="mr-auto">' + header + '</strong><button type="button" class="close" data-dismiss="toast"><span class="fas fa-times"></span></button></div>' +
'<div class="toast-body">' + body + '</div>';
document.getElementById('toasts')?.appendChild(toast);
$(toast).toast({
delay: delay,
});
$(toast).toast('show');
}

public static dateToIso(date: Date): string
{
return date.getFullYear() + '-' +
Strings.padNum(date.getMonth()) + '-' +
Strings.padNum(date.getDay()) + '-' +
Strings.padNum(date.getHours()) + '-' +
Strings.padNum(date.getMinutes()) + '-' +
Strings.padNum(date.getSeconds());
}

public static padNum(number: number, length: number = 2): string
{
return ('' + number).padStart(length, '0');
}

public static stackSizeFromEnum(size: string): number
{
switch (size) {
Expand Down
Loading

0 comments on commit 58ccb17

Please sign in to comment.