class LocalStoragePseudoCloud {
constructor() {
this.peer = null;
this.conn = null;
this.isHost = false;
this.VAR_PREFIX = 'pm_var_';
this.LIST_PREFIX = 'pm_list_';
}
getInfo() {
return {
id: 'LocalStoragePseudoCloud',
name: 'Local Storage Cloud (WebRTC)',
color1: '#5ba58c',
color2: '#4b8d78',
blocks: [
// VARIABLES
{
opcode: 'setVar',
blockType: Scratch.BlockType.COMMAND,
text: 'set variable [KEY] to [VALUE]',
arguments: {
KEY: { type: Scratch.ArgumentType.STRING },
VALUE: { type: Scratch.ArgumentType.STRING }
}
},
{
opcode: 'getVar',
blockType: Scratch.BlockType.REPORTER,
text: 'get variable [KEY]',
arguments: {
KEY: { type: Scratch.ArgumentType.STRING }
}
},
// LISTS
{
opcode: 'createList',
blockType: Scratch.BlockType.COMMAND,
text: 'create list [LIST]',
arguments: {
LIST: { type: Scratch.ArgumentType.STRING }
}
},
{
opcode: 'addToList',
blockType: Scratch.BlockType.COMMAND,
text: 'add [ITEM] to list [LIST]',
arguments: {
ITEM: { type: Scratch.ArgumentType.STRING },
LIST: { type: Scratch.ArgumentType.STRING }
}
},
{
opcode: 'setItem',
blockType: Scratch.BlockType.COMMAND,
text: 'set item [INDEX] of list [LIST] to [ITEM]',
arguments: {
INDEX: { type: Scratch.ArgumentType.NUMBER, defaultValue: 1 },
LIST: { type: Scratch.ArgumentType.STRING },
ITEM: { type: Scratch.ArgumentType.STRING }
}
},
{
opcode: 'deleteItem',
blockType: Scratch.BlockType.COMMAND,
text: 'delete item [INDEX] of list [LIST]',
arguments: {
INDEX: { type: Scratch.ArgumentType.NUMBER, defaultValue: 1 },
LIST: { type: Scratch.ArgumentType.STRING }
}
},
{
opcode: 'getItem',
blockType: Scratch.BlockType.REPORTER,
text: 'item [INDEX] of list [LIST]',
arguments: {
INDEX: { type: Scratch.ArgumentType.NUMBER, defaultValue: 1 },
LIST: { type: Scratch.ArgumentType.STRING }
}
},
{
opcode: 'listLength',
blockType: Scratch.BlockType.REPORTER,
text: 'length of list [LIST]',
arguments: {
LIST: { type: Scratch.ArgumentType.STRING }
}
},
{
opcode: 'clearList',
blockType: Scratch.BlockType.COMMAND,
text: 'clear list [LIST]',
arguments: {
LIST: { type: Scratch.ArgumentType.STRING }
}
},
{
opcode: 'listContains',
blockType: Scratch.BlockType.BOOLEAN,
text: 'does list [LIST] contain [ITEM]',
arguments: {
LIST: { type: Scratch.ArgumentType.STRING },
ITEM: { type: Scratch.ArgumentType.STRING }
}
},
{
opcode: 'listAsArray',
blockType: Scratch.BlockType.REPORTER,
text: 'list [LIST] as array',
arguments: {
LIST: { type: Scratch.ArgumentType.STRING }
}
},
// 🌍 CLOUD
{
opcode: 'startHost',
blockType: Scratch.BlockType.COMMAND,
text: 'start cloud host'
},
{
opcode: 'connectCloud',
blockType: Scratch.BlockType.COMMAND,
text: 'connect to cloud with code [CODE]',
arguments: {
CODE: { type: Scratch.ArgumentType.STRING }
}
},
{
opcode: 'cloudStatus',
blockType: Scratch.BlockType.REPORTER,
text: 'cloud status'
}
]
};
}
// ---------- helpers ----------
_send(type, payload) {
if (this.conn && this.conn.open) {
this.conn.send({ type, payload });
}
}
_syncAll() {
const vars = {};
const lists = {};
for (let k in localStorage) {
if (k.startsWith(this.VAR_PREFIX)) vars[k] = localStorage.getItem(k);
if (k.startsWith(this.LIST_PREFIX)) lists[k] = localStorage.getItem(k);
}
this._send('sync', { vars, lists });
}
// ---------- VARIABLES ----------
setVar(a) {
localStorage.setItem(this.VAR_PREFIX + a.KEY, a.VALUE);
this._send('var', a);
}
getVar(a) {
return localStorage.getItem(this.VAR_PREFIX + a.KEY) || '';
}
// ---------- LISTS ----------
createList(a) {
localStorage.setItem(this.LIST_PREFIX + a.LIST, '[]');
this._send('list', { name: a.LIST, value: [] });
}
_getList(name) {
return JSON.parse(localStorage.getItem(this.LIST_PREFIX + name) || '[]');
}
_setList(name, arr) {
localStorage.setItem(this.LIST_PREFIX + name, JSON.stringify(arr));
this._send('list', { name, value: arr });
}
addToList(a) {
const l = this._getList(a.LIST);
l.push(a.ITEM);
this._setList(a.LIST, l);
}
setItem(a) {
const l = this._getList(a.LIST);
if (a.INDEX > 0 && a.INDEX <= l.length) {
l[a.INDEX - 1] = a.ITEM;
this._setList(a.LIST, l);
}
}
deleteItem(a) {
const l = this._getList(a.LIST);
if (a.INDEX > 0 && a.INDEX <= l.length) {
l.splice(a.INDEX - 1, 1);
this._setList(a.LIST, l);
}
}
getItem(a) {
return this._getList(a.LIST)[a.INDEX - 1] || '';
}
listLength(a) {
return this._getList(a.LIST).length;
}
clearList(a) {
this._setList(a.LIST, []);
}
listContains(a) {
return this._getList(a.LIST).includes(a.ITEM);
}
listAsArray(a) {
return JSON.stringify(this._getList(a.LIST));
}
// ---------- CLOUD ----------
startHost() {
this.isHost = true;
this.peer = new Peer();
this.peer.on('open', id => {
alert('Cloud code: ' + id);
});
this.peer.on('connection', conn => {
this.conn = conn;
conn.on('open', () => this._syncAll());
conn.on('data', d => this._handleData(d));
});
}
connectCloud(a) {
this.peer = new Peer();
this.peer.on('open', () => {
this.conn = this.peer.connect(a.CODE);
this.conn.on('data', d => this._handleData(d));
});
}
_handleData(d) {
if (d.type === 'sync') {
Object.entries(d.payload.vars).forEach(([k, v]) => localStorage.setItem(k, v));
Object.entries(d.payload.lists).forEach(([k, v]) => localStorage.setItem(k, v));
}
if (d.type === 'var') {
localStorage.setItem(this.VAR_PREFIX + d.payload.KEY, d.payload.VALUE);
}
if (d.type === 'list') {
localStorage.setItem(this.LIST_PREFIX + d.payload.name, JSON.stringify(d.payload.value));
}
}
cloudStatus() {
if (!this.peer) return 'offline';
if (this.isHost) return 'hosting';
if (this.conn && this.conn.open) return 'connected';
return 'connecting';
}
}
Scratch.extensions.register(new LocalStoragePseudoCloud());










0 comments