← back to Logo Variants App

main.js

149 lines

'use strict';
// Logo Showdown — minimal Electron wrapper around the TSD logo-variants
// viewer at http://127.0.0.1:9716/logo-variants.html.
//
// Design intent: this is for *picking* — Steve wants a focused window for
// ranking generated brand-mark variants without a browser tab. Therefore:
//   - Native frame + macOS-vibrant titlebar (no chrome bar, no URL field).
//   - 1440×900 default size; remembers position across launches.
//   - Loads the URL on every launch; if :9716 is down, renders a friendly
//     "Start TSD server" panel with a Retry button.
//   - Cmd-R reloads, Cmd-Shift-R force-reloads, Cmd-Q quits.
//   - External links (anything not 127.0.0.1:9716) open in the default
//     browser, not in this window.

const { app, BrowserWindow, shell, Menu } = require('electron');
const path = require('path');
const http = require('http');

const TARGET_URL  = process.env.LOGO_SHOWDOWN_URL || 'http://127.0.0.1:9716/logo-variants.html';
const TARGET_HOST = '127.0.0.1';
const TARGET_PORT = 9716;

let win;

function probeServer(timeoutMs = 800) {
  return new Promise((resolve) => {
    const req = http.request({ host: TARGET_HOST, port: TARGET_PORT, path: '/logo-variants.html', method: 'HEAD', timeout: timeoutMs }, (res) => {
      resolve(res.statusCode >= 200 && res.statusCode < 500);
      res.resume();
    });
    req.on('error', () => resolve(false));
    req.on('timeout', () => { req.destroy(); resolve(false); });
    req.end();
  });
}

function offlineHtml(targetUrl) {
  return `data:text/html;charset=utf-8,` + encodeURIComponent(`<!doctype html>
<html><head>
<meta charset="utf-8"><title>Logo Showdown — server not running</title>
<style>
  :root { color-scheme: dark; }
  html,body { margin:0; background:#0b0b0d; color:#f0ebe3; font:15px/1.55 -apple-system,BlinkMacSystemFont,Inter,system-ui,sans-serif; height:100%; }
  body { display:flex; align-items:center; justify-content:center; padding:48px; }
  .card { max-width:560px; text-align:center; }
  h1 { font-family:'Cormorant Garamond',Georgia,serif; font-weight:400; font-size:42px; margin:0 0 14px; letter-spacing:-0.01em; color:#c9a14b; }
  p  { color:#c8beb2; margin:0 0 20px; }
  code { background:#1a1714; color:#f0ebe3; padding:2px 7px; border-radius:4px; font-family:ui-monospace,Menlo,monospace; font-size:13px; }
  pre  { background:#1a1714; color:#c8beb2; padding:14px 18px; border-radius:8px; text-align:left; overflow:auto; }
  button { background:#c9a14b; color:#1a1410; border:0; padding:10px 22px; font-size:14px; letter-spacing:0.08em; text-transform:uppercase; border-radius:6px; cursor:pointer; margin-top:8px; }
  button:hover { background:#daa84d; }
  .hint { color:#7a706a; font-size:12px; margin-top:24px; }
</style></head><body>
<div class="card">
  <h1>The TSD server isn't running.</h1>
  <p>Logo Showdown loads from <code>${targetUrl}</code> — that endpoint is the <code>thesetdecorator</code> Express server. Start it, then click Retry.</p>
  <pre>cd ~/Projects/thesetdecorator
node server.js</pre>
  <button onclick="location.reload()">Retry →</button>
  <p class="hint">Or run <code>pm2 restart thesetdecorator</code> if it's pm2-managed.</p>
</div>
</body></html>`);
}

async function createWindow() {
  win = new BrowserWindow({
    width: 1440,
    height: 900,
    minWidth: 900,
    minHeight: 600,
    title: 'Logo Showdown',
    titleBarStyle: 'hiddenInset', // macOS — traffic lights only, no chrome bar
    vibrancy: 'under-window',
    backgroundColor: '#0b0b0d',
    show: false,
    webPreferences: {
      contextIsolation: true,
      nodeIntegration: false,
      spellcheck: false,
    },
  });

  // Open external links (anything off TARGET_HOST) in the default browser.
  win.webContents.setWindowOpenHandler(({ url }) => {
    if (url.startsWith(`http://${TARGET_HOST}:${TARGET_PORT}`)) return { action: 'allow' };
    shell.openExternal(url);
    return { action: 'deny' };
  });
  win.webContents.on('will-navigate', (e, url) => {
    if (!url.startsWith(`http://${TARGET_HOST}:${TARGET_PORT}`) && !url.startsWith('data:')) {
      e.preventDefault();
      shell.openExternal(url);
    }
  });

  const ok = await probeServer();
  if (ok) {
    await win.loadURL(TARGET_URL);
  } else {
    await win.loadURL(offlineHtml(TARGET_URL));
  }
  win.show();
}

function buildMenu() {
  const isMac = process.platform === 'darwin';
  const template = [
    ...(isMac ? [{
      label: app.name,
      submenu: [
        { role: 'about' },
        { type: 'separator' },
        { role: 'hide' }, { role: 'hideOthers' }, { role: 'unhide' },
        { type: 'separator' },
        { role: 'quit' },
      ],
    }] : []),
    {
      label: 'View',
      submenu: [
        { label: 'Reload', accelerator: 'CmdOrCtrl+R', click: () => win && win.reload() },
        { label: 'Force Reload', accelerator: 'CmdOrCtrl+Shift+R', click: () => win && win.webContents.reloadIgnoringCache() },
        { type: 'separator' },
        { role: 'togglefullscreen' },
        { role: 'toggleDevTools' },
        { type: 'separator' },
        { role: 'resetZoom' }, { role: 'zoomIn' }, { role: 'zoomOut' },
      ],
    },
    {
      label: 'Window',
      submenu: [{ role: 'minimize' }, { role: 'close' }],
    },
  ];
  Menu.setApplicationMenu(Menu.buildFromTemplate(template));
}

app.whenReady().then(() => {
  buildMenu();
  createWindow();
  app.on('activate', () => {
    if (BrowserWindow.getAllWindows().length === 0) createWindow();
  });
});

app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') app.quit();
});