Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,656 changes: 1,583 additions & 73 deletions package-lock.json

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"test": "vitest run",
"lint": "prettier --check . && eslint .",
"format": "prettier --write ."
},
Expand All @@ -22,7 +23,9 @@
"eslint": "^10.0.3",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-svelte": "^3.15.2",
"fake-indexeddb": "^6.2.5",
"globals": "^17.4.0",
"jsdom": "^26.1.0",
"prettier": "^3.8.1",
"prettier-plugin-svelte": "^3.5.1",
"svelte": "^5.54.0",
Expand All @@ -31,11 +34,13 @@
"tslib": "^2.8.1",
"typescript": "^5.9.3",
"typescript-eslint": "^8.57.1",
"vite": "^8.0.1"
"vite": "^8.0.1",
"vitest": "^3.2.4"
},
"type": "module",
"dependencies": {
"blockly": "^12.4.1",
"idb": "^8.0.3",
"socket.io": "^4.8.3",
"socket.io-client": "^4.8.3",
"uuid": "^13.0.0",
Expand Down
188 changes: 117 additions & 71 deletions src/data/attribute-store.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,43 @@
import type { Writable } from 'svelte/store';
import { get, writable } from 'svelte/store';
import { browser } from '$app/environment';
import YAML from 'yaml';
import FabledAttribute from '$api/fabled-attribute.svelte';
import type { MultiAttributeYamlData } from '$api/types';
import { sort } from '$api/api';
import { parseYaml } from '$api/yaml';
import { active, saveError } from './store';
import { base } from '$app/paths';
import { goto } from '$app/navigation';
import { socketService } from '$api/socket/socket-connector';
import { classStore } from './class-store.svelte';
import type { Writable } from 'svelte/store';
import {
get,
writable
} from 'svelte/store';
import FabledAttribute from '$api/fabled-attribute.svelte';
import type {
MultiAttributeYamlData
} from '$api/types';
import {
sort
} from '$api/api';
import {
parseYaml
} from '$api/yaml';
import {
active,
saveError
} from './store';
import {
base
} from '$app/paths';
import {
goto
} from '$app/navigation';
import {
socketService
} from '$api/socket/socket-connector';
import {
classStore
} from './class-store.svelte';
import {
beginPersistenceSave,
finishPersistenceSave
} from './persistence-state';
import {
getPersistedAttribute,
listPersistedAttributeRecords,
savePersistedAttributes
} from './editor-persistence';

class AttributeStore {
tooBig: Writable<boolean> = writable(false);
Expand Down Expand Up @@ -47,25 +74,16 @@ class AttributeStore {
}

private setupAttributeStore = <T extends FabledAttribute[]>(
key: string,
_key: string,
def: T,
mapper: (data: string) => T,
setAction: (data: T) => T,
postLoad?: (saved: T) => void): Writable<T> => {
postLoad?: (saved: T) => void
): Writable<T> => {
let saved: T = def;
if (browser) {
const stored = localStorage.getItem(key);
if (stored) {
saved = mapper(stored);
if (postLoad) postLoad(saved);
}
}
if (postLoad) postLoad(saved);

const {
subscribe,
set,
update
} = writable<T>(saved);
const { subscribe, set, update } = writable<T>(saved);
return {
subscribe,
set: (value: T) => {
Expand All @@ -76,36 +94,39 @@ class AttributeStore {
};
};

hydratePersistedData = async () => {
const attributes = listPersistedAttributeRecords().map((record) => {
const attribute = new FabledAttribute({ name: record.name, location: 'local' });
attribute.load(record.data);
return attribute;
});

this.attributes.set(sort<FabledAttribute>(attributes));
};

getDefaultAttributes = async (): Promise<FabledAttribute[]> => {
const yaml = parseYaml(await fetch('https://raw.githubusercontent.com/magemonkeystudio/fabled/dev/src/main/resources/attributes.yml').then(r => r.text()));
const yaml = parseYaml(
await fetch(
'https://raw.githubusercontent.com/magemonkeystudio/fabled/dev/src/main/resources/attributes.yml'
).then((r) => r.text())
);
if (!yaml) return [];
return Object.keys(yaml).map((key: string) => {
const attrib: FabledAttribute = new FabledAttribute({ name: key });
attrib.load(yaml[key]);
return attrib;
});

};

attributes: Writable<FabledAttribute[]> = this.setupAttributeStore<FabledAttribute[]>(
'attribs',
'attributes',
[],
(data: string) => {
if (data.split('\n').length < 3 && data.charAt(0) !== '{') { // Old format
return data.replace('\n', '').split(',').map((key: string) => new FabledAttribute({ name: key }));
}
const yaml = <MultiAttributeYamlData>parseYaml(data);
if (!yaml) return [];
return Object.keys(yaml).map((key: string) => {
const attrib: FabledAttribute = new FabledAttribute({ name: key });
attrib.load(yaml[key]);
return attrib;
});
},
(_data: string) => [],
(value: FabledAttribute[]) => {
classStore.updateAllAttributes(value.map((attr: FabledAttribute) => attr.name));
return sort<FabledAttribute>(value);
});
}
);

getAttributeNames = (): string[] => {
return get(this.attributes).map((attr) => attr.name);
Expand All @@ -127,15 +148,14 @@ class AttributeStore {
while (!name && this.isAttributeNameTaken(name || 'attribute ' + index)) {
index++;
}
const attrib = new FabledAttribute({ name: (name || 'attribute ' + index) });
const attrib = new FabledAttribute({ name: name || 'attribute ' + index });
allAttributes.push(attrib);

this.attributes.set(allAttributes);
attrib.save();
return attrib;
};


loadAttributes = (e: ProgressEvent<FileReader>) => {
const text: string = <string>e.target?.result;
if (!text) return;
Expand All @@ -154,7 +174,7 @@ class AttributeStore {
// Get the current attributes
const currentAttributes = get(this.attributes);
// Create a map of current attributes for easy lookup
const currentAttributesMap = new Map(currentAttributes.map(attr => [attr.name, attr]));
const currentAttributesMap = new Map(currentAttributes.map((attr) => [attr.name, attr]));

// Merge the current attributes with the new ones
const mergedAttributes = [...currentAttributes];
Expand All @@ -172,19 +192,18 @@ class AttributeStore {
this.refreshAttributes();
};

loadAttribute = (data: FabledAttribute) => {
loadAttribute = async (data: FabledAttribute) => {
if (data.loaded) return;

if (data.location === 'local') {
const yamlData = <MultiAttributeYamlData>parseYaml(localStorage.getItem('attribs') || '');
const yamlData = await getPersistedAttribute(data.name);
if (!yamlData) return;
const attrib = yamlData[data.name];
data.load(attrib);
data.load(yamlData);
}
};
Comment on lines +195 to 203

cloneAttribute = (data: FabledAttribute): FabledAttribute => {
if (!data.loaded) this.loadAttribute(data);
cloneAttribute = async (data: FabledAttribute): Promise<FabledAttribute> => {
if (!data.loaded) await this.loadAttribute(data);

const attr: FabledAttribute[] = get(this.attributes);
let name = data.name + ' (Copy)';
Expand All @@ -207,7 +226,7 @@ class AttributeStore {
refreshAttributes = () => this.attributes.set(sort<FabledAttribute>(get(this.attributes)));

deleteAttribute = (data: FabledAttribute) => {
const filtered = get(this.attributes).filter(c => c != data);
const filtered = get(this.attributes).filter((c) => c != data);
const act = get(active);
this.attributes.set(filtered);
this.saveAll();
Expand All @@ -217,16 +236,19 @@ class AttributeStore {
if (filtered.length === 0) {
goto(`${base}/`).then(() => {
});
} else if (!filtered.find(attr => attr === get(active))) {
} else if (!filtered.find((attr) => attr === get(active))) {
goto(`${base}/attribute/${filtered[0].name}/edit`).then(() => {
});
}
};

saveAll = () => {
if (get(this.tooBig)) return;

if (get(this.tooBig) && !get(this.acknowledged)) {
const pendingPersist = beginPersistenceSave({
name: 'Attributes',
tooBig: get(this.tooBig),
acknowledged: get(this.acknowledged)
});
if (!pendingPersist.shouldPersist) {
saveError.set({ name: 'Attributes', acknowledged: false });
return;
}
Expand All @@ -235,24 +257,48 @@ class AttributeStore {
for (const attr of get(this.attributes)) {
attributeYaml[attr.name] = attr.serializeYaml();
}
const yaml = YAML.stringify(attributeYaml, { lineWidth: 0, aliasDuplicateObjects: false });

try {
localStorage.setItem('attribs', yaml);
this.tooBig.set(false);
} catch (e: any) {
// If the data is too big
if (!e?.message?.includes('quota')) {
console.error('Attributes Save error', e);
void savePersistedAttributes(
Object.entries(attributeYaml).map(([name, data]) => ({
name,
data
}))
).then((result) => {
if (!result.ok) {
if (!result.quotaExceeded) {
console.error('Attributes Save error', result.error);
} else {
const persistState = finishPersistenceSave(
{
name: 'Attributes',
tooBig: get(this.tooBig),
acknowledged: get(this.acknowledged)
},
result
);
this.tooBig.set(persistState.state.tooBig);
this.acknowledged.set(persistState.state.acknowledged);
saveError.set({ name: 'Attributes', acknowledged: false });
}
} else {
localStorage.removeItem('attribs');
this.tooBig.set(true);
saveError.set({ name: 'Attributes', acknowledged: false });
const persistState = finishPersistenceSave(
{
name: 'Attributes',
tooBig: get(this.tooBig),
acknowledged: get(this.acknowledged)
},
result
);
this.tooBig.set(persistState.state.tooBig);
this.acknowledged.set(persistState.state.acknowledged);
if (persistState.clearSaveError && get(saveError)?.name === 'Attributes') {
saveError.set(undefined);
}
}
}

console.log('Saved attributes 😎');
console.log('Saved attributes 😎');
});
};
}

export const attributeStore = new AttributeStore();
export const attributeStore = new AttributeStore();
Loading