ggbro Plugins

client-lab

Sandbox example — full pack source. Everything below runs in the plugin iframe via window.ggbro. This is the complete plugin.

manifest.json

Raw
{
  "id": "com.ggbro.sample.client-lab",
  "name": "Client Lab",
  "version": "3.1.0",
  "author": "ggbro",
  "description": "Full toolkit demo — input/textarea/slider/color/progress, context, storage, sounds, message decorations.",
  "main": "plugin.js",
  "permissions": ["storage", "ui.sound", "ui.panel", "ui.toast", "messages.read_display", "messages.decorate", "app.info", "app.context"]
}

plugin.js

Raw
/**
 * Client Lab v3.1 — full field kit + context + decorations.
 */
(async function () {
  var decorateOn = true;
  var ownMark = '★';
  var otherMark = '·';
  var runs = 0;
  var decorateCount = 0;
  var note = '';
  var intensity = 40;
  var accent = '#5865f2';
  var ctxLabel = '—';
  var journal = '';

  try {
    runs = parseInt((await ggbro.storage.get('runs')).value || '0', 10) || 0;
    runs += 1;
    await ggbro.storage.set('runs', String(runs));
    decorateOn = ((await ggbro.storage.get('decorate')).value || '1') !== '0';
    decorateCount = parseInt((await ggbro.storage.get('decorated')).value || '0', 10) || 0;
    note = (await ggbro.storage.get('note')).value || '';
    intensity = parseInt((await ggbro.storage.get('intensity')).value || '40', 10) || 40;
    accent = (await ggbro.storage.get('accent')).value || '#5865f2';
    journal = (await ggbro.storage.get('journal')).value || '';
  } catch (e) {
    ggbro.log('storage', e && e.message);
  }

  async function refreshContext() {
    try {
      var ctx = await ggbro.getContext();
      ctxLabel = ctx.view
        + (ctx.channelId ? ' · ch ' + String(ctx.channelId).slice(0, 8) : '')
        + (ctx.serverId ? ' · srv ' + String(ctx.serverId).slice(0, 8) : '')
        + (ctx.dmUserId ? ' · dm ' + String(ctx.dmUserId).slice(0, 8) : '')
        + (ctx.groupId ? ' · grp ' + String(ctx.groupId).slice(0, 8) : '');
    } catch (e) {
      ctxLabel = 'context unavailable';
    }
  }

  async function paint() {
    await refreshContext();
    await ggbro.ui.registerPanel({
      id: 'main',
      title: 'Client Lab',
      schema: [
        {
          type: 'notice',
          id: 'tip',
          text: 'Kitchen sink — re-call registerPanel to refresh progress, labels, and values live.',
          tone: 'info',
        },
        { type: 'section', id: 's0', title: 'Stats' },
        { type: 'text', id: 'runs', label: 'Loads on this PC', value: String(runs) },
        { type: 'text', id: 'decoN', label: 'Decorate batches', value: String(decorateCount) },
        { type: 'text', id: 'ctx', label: 'Navigation context', value: ctxLabel },
        { type: 'progress', id: 'prog', label: 'Intensity meter', value: intensity, max: 100 },
        { type: 'section', id: 's1', title: 'Controls' },
        { type: 'toggle', id: 'decorate', label: 'Decorate visible messages', value: decorateOn },
        { type: 'input', id: 'note', label: 'Sticky note', value: note, placeholder: 'Type and Apply' },
        {
          type: 'textarea',
          id: 'journal',
          label: 'Lab journal',
          value: journal,
          placeholder: 'Longer notes…',
          rows: 3,
        },
        { type: 'slider', id: 'intensity', label: 'Intensity', value: intensity, min: 0, max: 100, step: 1 },
        { type: 'color', id: 'accent', label: 'Accent preview', value: accent },
        { type: 'button', id: 'ping', label: 'Toast ping' },
        { type: 'button', id: 'ding', label: 'Play boing' },
        { type: 'button', id: 'ctxBtn', label: 'Refresh context' },
        { type: 'button', id: 'clear', label: 'Clear decorations' },
        { type: 'link', id: 'docs', label: 'Plugin docs', href: 'https://ggbro.app/developers/plugins' },
      ],
    });
  }

  await paint();
  await ggbro.toast({ message: 'Client Lab ready (run #' + runs + ')', type: 'info' });
  ggbro.playSound({ id: 'notification' }).catch(function () {});

  ggbro.onEvent(function (event, payload) {
    if (event === 'context') {
      paint();
      return;
    }
    if (event !== 'panelAction' || !payload) return;
    if (payload.fieldId === 'ping') {
      ggbro.toast({
        message: note ? ('Note: ' + note) : 'Ping from Client Lab panel',
        type: 'success',
      });
      return;
    }
    if (payload.fieldId === 'ding') {
      ggbro.playSound({ id: 'boing' }).catch(function () {});
      return;
    }
    if (payload.fieldId === 'ctxBtn') {
      paint();
      ggbro.toast({ message: 'Context refreshed', type: 'info' });
      return;
    }
    if (payload.fieldId === 'note') {
      note = String(payload.value || '').slice(0, 200);
      ggbro.storage.set('note', note).catch(function () {});
      paint();
      ggbro.toast({ message: 'Note saved', type: 'success' });
      return;
    }
    if (payload.fieldId === 'journal') {
      journal = String(payload.value || '').slice(0, 1000);
      ggbro.storage.set('journal', journal).catch(function () {});
      paint();
      ggbro.toast({ message: 'Journal saved', type: 'success' });
      return;
    }
    if (payload.fieldId === 'intensity') {
      intensity = Math.max(0, Math.min(100, Number(payload.value) || 0));
      ggbro.storage.set('intensity', String(intensity)).catch(function () {});
      paint();
      return;
    }
    if (payload.fieldId === 'accent') {
      accent = String(payload.value || '#5865f2');
      ggbro.storage.set('accent', accent).catch(function () {});
      paint();
      return;
    }
    if (payload.fieldId === 'decorate') {
      decorateOn = !!payload.value;
      ggbro.storage.set('decorate', decorateOn ? '1' : '0').catch(function () {});
      ggbro.toast({
        message: decorateOn ? 'Decorations on' : 'Decorations off',
        type: 'info',
      });
      return;
    }
    if (payload.fieldId === 'clear') {
      decorateOn = false;
      ggbro.storage.set('decorate', '0').catch(function () {});
      paint();
      ggbro.toast({
        message: 'Decorate off — reload chat or wait for new messages to drop marks.',
        type: 'warning',
      });
    }
  });

  ggbro.onMessages(function (messages) {
    if (!decorateOn || !messages || !messages.length) return;
    var patches = messages.slice(-12).map(function (m) {
      return {
        id: m.id,
        prefixText: m.isOwn ? ownMark : otherMark,
      };
    });
    ggbro.decorateMessages(patches).then(function () {
      decorateCount += 1;
      ggbro.storage.set('decorated', String(decorateCount)).catch(function () {});
    }).catch(function () {});
  });
})();

README.md

Raw
# Client Lab (v2)

Kitchen-sink demo of host-mediated APIs: panel buttons, storage stats, sounds, and local message decorations (`★` / `·`).