Skip to content

Commit 6d09a65

Browse files
committed
finish vicarsync
1 parent 2949a65 commit 6d09a65

File tree

9 files changed

+6761
-6582
lines changed

9 files changed

+6761
-6582
lines changed

package.json

-2
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
"html2pdf.js": "^0.10.1",
1919
"js-yaml": "^4.1.0",
2020
"peerjs": "1.4.6",
21-
"socket.io-client": "4.5.1",
2221
"uuidv4": "6.2.13",
2322
"vue": "^2.6.14",
2423
"vue-class-component": "7.2.3",
@@ -28,7 +27,6 @@
2827
"vue-property-decorator": "9.1.2",
2928
"vue-resize-text": "0.1.1",
3029
"vue-router": "^3.5.1",
31-
"vue-socket.io-extended": "4.2.0",
3230
"vuex": "^3.6.2",
3331
"vuex-class": "0.3.2",
3432
"vuex-persist": "3.1.3"

src/assets/langs/de-DE.json

+11-1
Original file line numberDiff line numberDiff line change
@@ -418,5 +418,15 @@
418418
"homebrew.uninstall.confirm": "Inhalt {name} sicher deinstallieren?",
419419
"character.homebrew.update": "Homebrew-Content aktualisieren",
420420
"character.homebrew.updated": "Aktualisiert!",
421-
"main.confirmmodal.button": "Bestätigen"
421+
"main.confirmmodal.button": "Bestätigen",
422+
423+
"character.sync.enable.text": "Wie möchtest du diesen Charakter mit VicarSync synchronisieren?",
424+
"character.sync.enable.type.out": "Zu VicarSync senden/ Auswärts",
425+
"character.sync.enable.type.in": "Von VicarSync empfangen/ Einwärts",
426+
"character.sync.enable": "Synchronisieren",
427+
"character.sync.info.text": "VicarSync-Verwaltung dieses Charakters",
428+
"character.sync.info.hashcopy": "Kopieren",
429+
"character.sync.info.disable": "Synchronisation entfernen",
430+
"character.sync.info.disable.confirm": "Synchronisation entfernen, sicher?",
431+
"character.sync.hash": "VicarSync-Charakter-Hash"
422432
}

src/components/main/Settings.vue

+8-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
</div>
4141
</div>
4242

43-
<VicarLoginModal ref="vicarLoginModal" @login="$forceUpdate()"/>
43+
<VicarLoginModal ref="vicarLoginModal" @login="onLogin"/>
4444
</div>
4545
</template>
4646

@@ -52,6 +52,7 @@ import CharacterStorage from "@/libs/io/character-storage";
5252
import {DataSync} from "@/libs/data/data-sync";
5353
import {VicarNet} from "@/libs/io/vicar-net";
5454
import VicarLoginModal from "@/components/main/modals/VicarLoginModal.vue";
55+
import {VicarSync} from "@/libs/io/vicar-sync";
5556
5657
@Component({
5758
components: {VicarLoginModal}
@@ -100,9 +101,15 @@ export default class Settings extends Vue {
100101
}
101102
102103
private logout() {
104+
VicarSync.stopRetrieveInterval();
103105
VicarNet.logout();
104106
this.$forceUpdate();
105107
}
108+
109+
private onLogin() {
110+
this.$forceUpdate();
111+
VicarSync.startRetrieveInterval();
112+
}
106113
}
107114
</script>
108115

src/components/main/characters/Character.vue

+101-1
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,50 @@
2525
<IconButton icon="fa-trash" @click="beginCharDeletion(character)"/>
2626
<IconButton icon="fa-copy" @click="cloneCharacter(character)"/>
2727
<IconButton icon="fa-file-arrow-down" @click="exportCharacter(character)"/>
28+
29+
<IconButton v-if="!VicarSync.isCharacterSyncedOut(character) && !VicarSync.isCharacterSyncedIn(character)" icon="fas fa-link" @click="linkCharacterWithSync(character)"/>
30+
<IconButton v-else icon="fas fa-cloud-upload-alt" @click="infoSyncModalVisible = true"/>
31+
2832
<IconButton v-if="isShareAvailable()" icon="fa-share-nodes" @click="shareCharacter(character)"/>
2933
<IconButton icon="fa-eye" @click="viewCharacter(character)"/>
3034
</div>
35+
36+
<Modal :shown="enableSyncModalVisible" @close="enableSyncModalVisible = false">
37+
<div class="p-10 w-400 d-flex flex-column" style="gap: 1rem; font-size: 1rem">
38+
<b style="font-size: 1.2rem">{{$t('character.sync.enable.text')}}</b>
39+
<select class="form-control" v-model="enableSyncModalType">
40+
<option value="out">{{$t('character.sync.enable.type.out')}}</option>
41+
<option value="in">{{$t('character.sync.enable.type.in')}}</option>
42+
</select>
43+
44+
<div class="form-group mb-0" v-if="enableSyncModalType === 'in'">
45+
<label>{{$t('character.sync.hash')}}:</label>
46+
<input class="form-control" v-model="enableSyncModalInHash"/>
47+
</div>
48+
49+
<div style="width: 100%; display: flex; justify-content: center; align-items: center">
50+
<button class="btn btn-primary" :disabled="enableSyncModalType === 'in' && enableSyncModalInHash.length <= 0" @click="finishLinkCharacterWithSync">{{$t('character.sync.enable')}}</button>
51+
</div>
52+
</div>
53+
</Modal>
54+
55+
<Modal :shown="infoSyncModalVisible" @close="infoSyncModalVisible = false">
56+
<div class="p-10 w-400 d-flex flex-column" style="gap: 1rem; font-size: 1rem">
57+
<b style="font-size: 1.2rem">{{$t('character.sync.info.text')}}</b>
58+
59+
<div class="form-group mb-0" v-if="VicarSync.isCharacterSyncedOut(character)">
60+
<label>{{$t('character.sync.hash')}}:</label>
61+
<div style="display: flex; justify-content: center; align-items: center">
62+
<input class="form-control" :value="VicarSync.getCharacterSyncOutId(character)"/>
63+
<button class="btn btn-primary" @click="copySyncOutId">{{$t('character.sync.info.hashcopy')}}</button>
64+
</div>
65+
</div>
66+
67+
<div style="width: 100%; display: flex; justify-content: center; align-items: center">
68+
<button class="btn btn-primary" @click="beginUnlinkCharacterWithSync">{{$t(infoSyncModalDisableConfirm ? 'character.sync.info.disable.confirm' : 'character.sync.info.disable')}}</button>
69+
</div>
70+
</div>
71+
</Modal>
3172
</div>
3273
</template>
3374

@@ -40,9 +81,11 @@ import {ICharacter} from "@/types/models";
4081
import CharacterStorage from "@/libs/io/character-storage";
4182
import FileCreator from "@/libs/io/file-creator";
4283
import {Mutation} from "vuex-class";
84+
import {VicarSync} from "@/libs/io/vicar-sync";
85+
import Modal from "@/components/modal/Modal.vue";
4386
4487
@Component({
45-
components: {IconButton, Avatar, Bullet}
88+
components: {Modal, IconButton, Avatar, Bullet}
4689
})
4790
export default class Character extends Vue {
4891
@@ -55,6 +98,15 @@ export default class Character extends Vue {
5598
@Mutation("setLevelMode")
5699
private setLevelMode!: (mode: boolean) => void;
57100
101+
private VicarSync = VicarSync;
102+
103+
private enableSyncModalVisible = false;
104+
private enableSyncModalType: "in" | "out" = "out";
105+
private enableSyncModalInHash = "";
106+
107+
private infoSyncModalVisible = false;
108+
private infoSyncModalDisableConfirm: number | null = null;
109+
58110
private cloneCharacter(character: ICharacter) {
59111
const newChar = {...character};
60112
newChar.name += " - " + this.$t('character.copy').toString();
@@ -72,6 +124,54 @@ export default class Character extends Vue {
72124
this.$router.push({name: 'viewer'});
73125
}
74126
127+
private linkCharacterWithSync() {
128+
this.enableSyncModalVisible = true;
129+
}
130+
131+
private async finishLinkCharacterWithSync() {
132+
if (this.enableSyncModalType === "out") {
133+
const hash = await VicarSync.enableCharacterOutSync(this.character);
134+
if (hash) {
135+
await navigator.clipboard.writeText(hash);
136+
}
137+
} else {
138+
await VicarSync.syncCharacterIn(this.character, this.enableSyncModalInHash);
139+
}
140+
this.$forceUpdate();
141+
this.enableSyncModalVisible = false;
142+
this.enableSyncModalInHash = "";
143+
this.enableSyncModalType = "out";
144+
}
145+
146+
private async beginUnlinkCharacterWithSync() {
147+
if (this.infoSyncModalDisableConfirm === null) {
148+
this.infoSyncModalDisableConfirm = setTimeout(() => {
149+
this.infoSyncModalDisableConfirm = null;
150+
}, 3000);
151+
} else {
152+
await this.unlinkCharacterWithSync();
153+
154+
this.infoSyncModalVisible = false;
155+
156+
clearTimeout(this.infoSyncModalDisableConfirm);
157+
this.infoSyncModalDisableConfirm = null;
158+
}
159+
}
160+
161+
private async unlinkCharacterWithSync() {
162+
if (VicarSync.isCharacterSyncedOut(this.character)) {
163+
await VicarSync.disableCharacterOutSync(this.character);
164+
} else if (VicarSync.isCharacterSyncedIn(this.character)) {
165+
await VicarSync.unsyncCharacterIn(this.character);
166+
}
167+
168+
this.$forceUpdate();
169+
}
170+
171+
private async copySyncOutId() {
172+
await navigator.clipboard.writeText(VicarSync.getCharacterSyncOutId(this.character));
173+
}
174+
75175
@Inject("begin-char-deletion")
76176
private beginCharDeletion!: (character: ICharacter) => void;
77177

src/libs/data/data-manager.ts

+2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {
2929
//@ts-ignore
3030
import {v4 as uuidv4} from 'uuid';
3131
import {HomebrewManager} from "@/libs/data/homebrew-manager";
32+
import {VicarSync} from "@/libs/io/vicar-sync";
3233

3334
export default class DataManager {
3435

@@ -133,6 +134,7 @@ export default class DataManager {
133134
}
134135

135136
await HomebrewManager.loadInstalledContent();
137+
await VicarSync.initialize();
136138
}
137139

138140
public static findAvailableClan(books: number[], clanId: number): IClan|undefined {

src/libs/io/rest.ts

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export async function get<T>(url: string, headers?: {[key: string]: string}): Pr
88

99
export async function post<T>(url: string, body?: any, headers?: {[key: string]: string}): Promise<[number, T]> {
1010
const response = await invoke<[number, string]>("post_request", {url: buildUrl(url), json: JSON.stringify(body || {}), headers: buildHeaders(headers)});
11+
console.log(response);
1112
return [response[0], JSON.parse(response[1])];
1213
}
1314

src/libs/io/vicar-net.ts

+12
Original file line numberDiff line numberDiff line change
@@ -393,4 +393,16 @@ export class VicarNet {
393393
console.error(e);
394394
}
395395
}
396+
397+
public static async retrieveCharSyncs(ids: string[]): Promise<{[key: string]: string}> {
398+
try {
399+
const [_, data] = await post<{[key: string]: string}>(`/sync/characters`, {
400+
ids
401+
});
402+
return data;
403+
} catch (e) {
404+
console.error(e);
405+
return {};
406+
}
407+
}
396408
}

src/libs/io/vicar-sync.ts

+116-6
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ export class VicarSync {
1313

1414
private static readonly lastSyncedData: {[key: string]: string} = {};
1515
private static readonly syncIntervals: {[key: string]: number} = {};
16-
private static readonly syncInterval = 1000 * 30;
16+
private static readonly syncInterval = 1000 * 5;
17+
private static readonly retrieveInterval = 1000 * 10;
18+
private static retrieveIntervalId: number | undefined;
1719
private static map: SyncMap = {
1820
outs: {},
1921
ins: {}
@@ -24,7 +26,71 @@ export class VicarSync {
2426
if (storage) {
2527
this.map = JSON.parse(storage);
2628

27-
//TODO send all rooms to server
29+
if (Object.keys(this.map.ins).length > 0) {
30+
this.startRetrieveInterval();
31+
}
32+
}
33+
}
34+
35+
public static isCharacterSyncedOut(char: ICharacter): boolean {
36+
return !!this.map.outs[char.id];
37+
}
38+
39+
public static async enableCharacterOutSync(char: ICharacter): Promise<string | undefined> {
40+
if (this.map.outs[char.id]) {
41+
return;
42+
}
43+
44+
const hash = this.computeCharSyncOutId(char);
45+
46+
this.map.outs[char.id] = hash;
47+
await this.save();
48+
49+
return hash;
50+
}
51+
52+
public static async disableCharacterOutSync(char: ICharacter) {
53+
if (!this.map.outs[char.id]) {
54+
return;
55+
}
56+
57+
delete this.map.outs[char.id];
58+
await this.save();
59+
}
60+
61+
public static getCharacterSyncOutId(char: ICharacter): string {
62+
return this.map.outs[char.id] || "";
63+
}
64+
65+
public static isCharacterSyncedIn(char: ICharacter): boolean {
66+
const roomId = this.findCharSyncOutHashByInCharacter(char);
67+
return !!roomId;
68+
}
69+
70+
public static async unsyncCharacterIn(char: ICharacter) {
71+
const roomId = this.findCharSyncOutHashByInCharacter(char);
72+
if (!roomId) {
73+
return;
74+
}
75+
76+
delete this.map.ins[roomId];
77+
await this.save();
78+
79+
if (Object.keys(this.map.ins).length <= 0) {
80+
this.stopRetrieveInterval();
81+
}
82+
}
83+
84+
public static async syncCharacterIn(char: ICharacter, roomId: string) {
85+
if (this.map.ins[roomId]) {
86+
return;
87+
}
88+
89+
this.map.ins[roomId] = char.id;
90+
await this.save();
91+
92+
if (Object.keys(this.map.ins).length > 0 && !this.retrieveIntervalId) {
93+
this.startRetrieveInterval();
2894
}
2995
}
3096

@@ -42,6 +108,20 @@ export class VicarSync {
42108
}, this.syncInterval);
43109
}
44110

111+
public static startRetrieveInterval() {
112+
this.stopRetrieveInterval();
113+
114+
this.retrieveIntervalId = setInterval(async () => {
115+
await this.retrieveCharacters();
116+
}, this.retrieveInterval);
117+
}
118+
119+
public static stopRetrieveInterval() {
120+
if (this.retrieveIntervalId) {
121+
clearInterval(this.retrieveIntervalId);
122+
}
123+
}
124+
45125
private static syncCharacter(char: ICharacter) {
46126
const roomId = this.map.outs[char.id];
47127
const data = this.getCharData(char);
@@ -54,11 +134,19 @@ export class VicarSync {
54134
VicarNet.postCharSync(roomId, data).then().catch(e => console.error(e));
55135
}
56136

57-
private static async resolveInCharacter(message: string) {
58-
const split = message.split("@");
59-
const roomId = split[0];
60-
const data = split[1];
137+
private static async retrieveCharacters() {
138+
const ids = Object.keys(this.map.ins);
139+
if (ids.length <= 0) {
140+
return;
141+
}
142+
143+
const chars = await VicarNet.retrieveCharSyncs(ids);
144+
for (const [roomId, data] of Object.entries(chars)) {
145+
await this.resolveInCharacter(roomId, data);
146+
}
147+
}
61148

149+
private static async resolveInCharacter(roomId: string, data: string) {
62150
const charId = this.map.ins[roomId];
63151
if (!charId) {
64152
return;
@@ -98,4 +186,26 @@ export class VicarSync {
98186
char.stains = data.stains;
99187
char.inventory = data.inventory;
100188
}
189+
190+
private static computeCharSyncOutId(char: ICharacter): string {
191+
const id1 = char.id;
192+
const id2 = Date.now().toString(36);
193+
const id3 = Math.floor(Math.random() * 1000).toString(36);
194+
const id = `${id1}${id2}${id3}`;
195+
const base64 = btoa(id);
196+
return base64.replace(/=/g, "");
197+
}
198+
199+
private static findCharSyncOutHashByInCharacter(char: ICharacter): string|undefined {
200+
const pair = Object.entries(this.map.ins).find(([roomId, charId]) => charId === char.id);
201+
if (!pair) {
202+
return;
203+
}
204+
205+
return pair[0];
206+
}
207+
208+
private static async save() {
209+
await Storage.writeStorage("vicar-sync", JSON.stringify(this.map));
210+
}
101211
}

0 commit comments

Comments
 (0)