diff --git a/src/App.ts b/src/App.ts
index c5db425..3b47b62 100644
--- a/src/App.ts
+++ b/src/App.ts
@@ -54,6 +54,8 @@ export default class App extends Vue {
private lastTimeChecked: number = 0;
private desiredPlayTime: number = 0;
private currentMessages: ButtplugMessage[] = [];
+ private showHapticsAlert: boolean = false;
+ private showFileDownload: boolean = false;
// Buttplug properties
private devices: ButtplugClientDevice[] = [];
@@ -129,6 +131,10 @@ export default class App extends Vue {
/////////////////////////////////////
private SetHapticsFile(aFile: File) {
+ if (/html$/.test(aFile.name)) {
+ this.showBadHapticsAlert();
+ return;
+ }
this.hapticsFile = aFile;
LoadFile(this.hapticsFile).then((h: HapticFileHandler) => {
this.hapticsCommands = h.Commands as FunscriptCommand[];
@@ -137,11 +143,13 @@ export default class App extends Vue {
});
}
- private onVideoLoaded(duration: number) {
+ private onVideoLoaded(duration: number, currentTime: number) {
if (this.hapticsCommands.length === 0) {
this.hapticsCommands.push(new FunscriptCommand(0, 0));
this.hapticsCommands.push(new FunscriptCommand(duration, 0));
}
+ // seeking the time with hotkeys sends a video loaded event
+ this.advanceFrame(currentTime);
}
private onHapticsFileChange(hapticsFile: FileList) {
@@ -179,15 +187,45 @@ export default class App extends Vue {
this.currentPlayTime = this.desiredPlayTime;
}
- private advanceFrame(direction: number) {
- this.desiredPlayTime = (this.currentPlayTime) + (((1.0 / 60.0) * direction) * 1000);
- this.currentPlayTime = this.desiredPlayTime;
+ private advanceFrame(currentTime: number) {
+ this.currentPlayTime = currentTime;
+ this.runHapticsLoop();
+ }
+
+ private onExport() {
+ if (!this.hapticsCommands.length) {
+ alert("Error: No haptics events set. \n\nPlease enter haptics into timeline before exporting.");
+ }
+
+ const actions = this.hapticsCommands.map((command) => ({
+ at: command.Time,
+ pos: command.Position,
+ }));
+ const script = {
+ version: "1.0",
+ inverted: false,
+ range: 90,
+ actions,
+ };
+
+ const file = new Blob(
+ [JSON.stringify(script)],
+ { type: "application/json" },
+ );
+ const fileURL = URL.createObjectURL(file);
+
+ this.showFileDownload = true;
+
+ setTimeout(() => {
+ const $downloadBtn = document.querySelector(".download-link");
+ $downloadBtn.setAttribute("href", fileURL);
+ $downloadBtn.setAttribute("download", "new-script.funscript");
+ }, 500);
}
private runHapticsLoop() {
window.requestAnimationFrame(() => {
- // If we paused before this fired, just return
- if (this.paused || this.commands.size === 0) {
+ if (this.commands.size === 0) {
return;
}
// Backwards seek. Reset index retreived.
@@ -225,6 +263,10 @@ export default class App extends Vue {
});
}
+ private showBadHapticsAlert() {
+ this.showHapticsAlert = true;
+ }
+
private loadHapticsTestData() {
// tslint:disable-next-line:max-line-length
const testFile = '{"version": "1.0", "inverted": false, "actions": [{"at": 367, "pos": 20}, {"at": 667, "pos": 80}, {"at": 1101, "pos": 20}, {"at": 1535, "pos": 80}, {"at": 1902, "pos": 20}, {"at": 2269, "pos": 80}, {"at": 2569, "pos": 20}, {"at": 3036, "pos": 80}, {"at": 3403, "pos": 20}, {"at": 3670, "pos": 80}, {"at": 4137, "pos": 20}, {"at": 4505, "pos": 80}, {"at": 4838, "pos": 20}, {"at": 5472, "pos": 30}, {"at": 5706, "pos": 50}, {"at": 5873, "pos": 30}, {"at": 6206, "pos": 30}, {"at": 6840, "pos": 40}, {"at": 7307, "pos": 30}, {"at": 7674, "pos": 20}, {"at": 8175, "pos": 80}, {"at": 8575, "pos": 20}, {"at": 8876, "pos": 80}, {"at": 9276, "pos": 20}, {"at": 9576, "pos": 80}, {"at": 9943, "pos": 20}, {"at": 10277, "pos": 80}, {"at": 10644, "pos": 20}, {"at": 11144, "pos": 80}, {"at": 11512, "pos": 20}, {"at": 11945, "pos": 80}, {"at": 12279, "pos": 20}, {"at": 12713, "pos": 80}, {"at": 13113, "pos": 20}, {"at": 13447, "pos": 80}, {"at": 13947, "pos": 20}, {"at": 14281, "pos": 80}, {"at": 14581, "pos": 20}, {"at": 15048, "pos": 80}, {"at": 15382, "pos": 20}, {"at": 15749, "pos": 80}, {"at": 16116, "pos": 20}, {"at": 16517, "pos": 80}, {"at": 16917, "pos": 20}, {"at": 17217, "pos": 80}, {"at": 17551, "pos": 20}, {"at": 17918, "pos": 80}, {"at": 18318, "pos": 20}, {"at": 18685, "pos": 80}, {"at": 19019, "pos": 20}, {"at": 19386, "pos": 80}, {"at": 19686, "pos": 20}, {"at": 20020, "pos": 70}, {"at": 20354, "pos": 20}, {"at": 20687, "pos": 70}, {"at": 21021, "pos": 20}, {"at": 21455, "pos": 50}, {"at": 21889, "pos": 20}, {"at": 22189, "pos": 50}, {"at": 22723, "pos": 20}, {"at": 23223, "pos": 60}, {"at": 23557, "pos": 10}, {"at": 23957, "pos": 60}, {"at": 24358, "pos": 60}, {"at": 24925, "pos": 60}, {"at": 25225, "pos": 10}, {"at": 25626, "pos": 50}, {"at": 26026, "pos": 10}, {"at": 26493, "pos": 50}, {"at": 26660, "pos": 10}, {"at": 26927, "pos": 50}, {"at": 27261, "pos": 10}, {"at": 27528, "pos": 50}, {"at": 27861, "pos": 10}, {"at": 28195, "pos": 50}, {"at": 28629, "pos": 10}, {"at": 28996, "pos": 50}, {"at": 29296, "pos": 10}, {"at": 29596, "pos": 50}, {"at": 30163, "pos": 10}, {"at": 30230, "pos": 10}, {"at": 30664, "pos": 50}, {"at": 31064, "pos": 10}, {"at": 31532, "pos": 50}, {"at": 31798, "pos": 40}, {"at": 31999, "pos": 70}, {"at": 32299, "pos": 20}, {"at": 32733, "pos": 70}, {"at": 33100, "pos": 20}, {"at": 33367, "pos": 60}, {"at": 33901, "pos": 20}, {"at": 34301, "pos": 70}, {"at": 34601, "pos": 20}, {"at": 34968, "pos": 60}, {"at": 35369, "pos": 20}, {"at": 35736, "pos": 60}, {"at": 36103, "pos": 20}, {"at": 36470, "pos": 60}, {"at": 36803, "pos": 20}, {"at": 37137, "pos": 60}, {"at": 37504, "pos": 20}, {"at": 37938, "pos": 60}, {"at": 38338, "pos": 20}, {"at": 38672, "pos": 60}, {"at": 39072, "pos": 20}, {"at": 39506, "pos": 60}, {"at": 39840, "pos": 20}, {"at": 40207, "pos": 60}, {"at": 40507, "pos": 20}, {"at": 41208, "pos": 50}, {"at": 41508, "pos": 10}, {"at": 41842, "pos": 50}, {"at": 42175, "pos": 10}, {"at": 42476, "pos": 50}, {"at": 42910, "pos": 10}, {"at": 43176, "pos": 50}, {"at": 43443, "pos": 10}, {"at": 43677, "pos": 50}, {"at": 44011, "pos": 10}, {"at": 44244, "pos": 50}, {"at": 44344, "pos": 10}, {"at": 44511, "pos": 50}, {"at": 44745, "pos": 10}, {"at": 45112, "pos": 40}, {"at": 45779, "pos": 10}, {"at": 46847, "pos": 0}], "range": 100}';
diff --git a/src/App.vue b/src/App.vue
index 5635d53..6675e34 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -76,6 +76,7 @@
@pause="onPause"
@timeUpdate="onTimeUpdate"
@inputTimeUpdate="onInputTimeUpdate"
+ @export="onExport"
@dragStart="onDragStart"
@dragStop="onDragStop"
/>
@@ -157,6 +158,31 @@
+ An HTML file was uploaded for haptics. Please make sure that you use a supported haptics file format. Commonly used file extension for haptics are .json, .funscript, .csv, etc. For a more complete list of accepted file formats see this page of Video Encoding Formats Click the download button to save the file and enjoy!Haptics File Error
+
+
+
+
+ Haptics File Download
+
+
+
+ Download
+
+