Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: spotlight spreadnob clean for tom

+128 -80
+128 -80
system/public/aesthetic.computer/disks/ableton.mjs
··· 19 19 { 20 20 id: "featured-spreadnob-clean", 21 21 label: "spreadnob clean", 22 + badge: "FOR TOM", 22 23 piece: "spreadnob-clean", 23 24 fileName: "AC 🎹 spreadnob-clean (aesthetic.computer).amxd", 24 - blurb: "main version - compact and octave-aware", 25 + downloadLabel: "FOR TOM", 26 + blurb: "main version - compact, octave-aware, and the one to grab", 25 27 }, 26 28 { 27 29 id: "featured-spreadnob", ··· 264 266 regions.push({ id, x, y, w, h, action }); 265 267 } 266 268 269 + function trimLabel(str = "", maxChars = 24) { 270 + if (str.length <= maxChars) return str; 271 + return `${str.slice(0, max(1, maxChars - 1))}…`; 272 + } 273 + 274 + function layoutButtons(startX, startY, maxWidth, specs, gap = 6, rowGap = 5) { 275 + let x = startX; 276 + let y = startY; 277 + let rowHeight = 0; 278 + const limitX = startX + maxWidth; 279 + const buttons = []; 280 + 281 + for (const spec of specs) { 282 + if (x !== startX && x + spec.w > limitX) { 283 + x = startX; 284 + y += rowHeight + rowGap; 285 + rowHeight = 0; 286 + } 287 + 288 + buttons.push({ ...spec, x, y }); 289 + x += spec.w + gap; 290 + rowHeight = max(rowHeight, spec.h); 291 + } 292 + 293 + return buttons; 294 + } 295 + 296 + function buttonsBottom(buttons, fallbackY = 0) { 297 + if (!buttons.length) return fallbackY; 298 + return buttons.reduce((bottom, button) => max(bottom, button.y + button.h), fallbackY); 299 + } 300 + 267 301 function hitRegion(pen) { 268 302 if (!pen) return null; 269 303 for (const region of regions) { ··· 345 379 const margin = 12; 346 380 const contentWidth = width - margin * 2; 347 381 const cardWidth = max(120, contentWidth); 382 + const maxTextChars = max(18, floor(contentWidth / 6)); 348 383 let y = 20; 349 384 350 385 drawWave(ink, line, width, y + 4, dark, frame); ··· 361 396 ink(...accent).plot(floor(dotX) + 1, y); 362 397 y += 12; 363 398 364 - ink(...(dark ? [22, 24, 34] : [228, 232, 242])).box(0, y - 4, width, 74); 365 - ink(...accent).write("Custom Instrument Builder", { x: margin, y }); 366 - y += 12; 367 - 368 - const currentPieceLabel = `piece ${customPiece}`; 369 - ink(dim).write("Cycle pieces or open ableton <piece> for any target.", { x: margin, y }); 370 - y += 13; 371 - 372 - const pieceBtn = { id: "builder-piece", x: margin, y, w: max(92, currentPieceLabel.length * 6 + 10), h: 14 }; 373 - const offlineBtn = { id: "builder-offline", x: pieceBtn.x + pieceBtn.w + 6, y, w: 84, h: 14 }; 374 - const onlineBtn = { id: "builder-online", x: offlineBtn.x + offlineBtn.w + 6, y, w: 76, h: 14 }; 375 - const openBtn = { id: "builder-open", x: onlineBtn.x + onlineBtn.w + 6, y, w: 58, h: 14 }; 399 + const builderCardY = y - 4; 400 + const currentPieceLabel = `piece ${trimLabel(customPiece, max(10, maxTextChars - 8))}`; 401 + const builderButtons = layoutButtons(margin, y + 25, contentWidth, [ 402 + { id: "builder-piece", w: max(92, currentPieceLabel.length * 6 + 10), h: 14, label: currentPieceLabel, colors: btn.warm, action: { type: "cycle-piece" } }, 403 + { id: "builder-offline", w: 84, h: 14, label: "offline", colors: btn.primary, action: { type: "build-offline", piece: customPiece } }, 404 + { id: "builder-online", w: 76, h: 14, label: "online", colors: btn.alt, action: { type: "build-online", piece: customPiece } }, 405 + { id: "builder-open", w: 58, h: 14, label: "open", colors: btn.warm, action: { type: "open-piece", piece: customPiece } }, 406 + ]); 407 + const builderButtonsBottom = buttonsBottom(builderButtons, y + 25); 408 + const builderStatus = customBusy || customMessage; 409 + const builderStatusY = builderButtonsBottom + 8; 410 + const builderCardHeight = (builderStatus ? builderStatusY + 10 : builderButtonsBottom + 4) - builderCardY; 376 411 377 - registerRegion(pieceBtn.id, pieceBtn.x, pieceBtn.y, pieceBtn.w, pieceBtn.h, { type: "cycle-piece" }); 378 - registerRegion(offlineBtn.id, offlineBtn.x, offlineBtn.y, offlineBtn.w, offlineBtn.h, { type: "build-offline", piece: customPiece }); 379 - registerRegion(onlineBtn.id, onlineBtn.x, onlineBtn.y, onlineBtn.w, onlineBtn.h, { type: "build-online", piece: customPiece }); 380 - registerRegion(openBtn.id, openBtn.x, openBtn.y, openBtn.w, openBtn.h, { type: "open-piece", piece: customPiece }); 412 + ink(...(dark ? [22, 24, 34] : [228, 232, 242])).box(0, builderCardY, width, builderCardHeight); 413 + ink(...accent).write("Custom Instrument Builder", { x: margin, y }); 414 + ink(dim).write("Cycle pieces or open ableton <piece> for any target.", { x: margin, y: y + 12 }); 381 415 382 - drawButton({ ink, box }, pieceBtn, currentPieceLabel, btn.warm, hoverId === pieceBtn.id); 383 - drawButton({ ink, box }, offlineBtn, "offline", btn.primary, hoverId === offlineBtn.id); 384 - drawButton({ ink, box }, onlineBtn, "online", btn.alt, hoverId === onlineBtn.id); 385 - drawButton({ ink, box }, openBtn, "open", btn.warm, hoverId === openBtn.id); 416 + for (const button of builderButtons) { 417 + registerRegion(button.id, button.x, button.y, button.w, button.h, button.action); 418 + drawButton({ ink, box }, button, button.label, button.colors, hoverId === button.id); 419 + } 386 420 387 - y += 20; 388 421 if (customBusy) { 389 - ink(...accent).write(customBusy, { x: margin, y }); 390 - y += 11; 422 + ink(...accent).write(trimLabel(customBusy, maxTextChars), { x: margin, y: builderStatusY }); 391 423 } else if (customMessage) { 392 - ink(dim).write(customMessage, { x: margin, y }); 393 - y += 11; 394 - } else { 395 - y += 2; 424 + ink(dim).write(trimLabel(customMessage, maxTextChars), { x: margin, y: builderStatusY }); 396 425 } 397 426 398 - y += 8; 427 + y = builderCardY + builderCardHeight + 12; 399 428 400 - ink(...(dark ? [24, 19, 33] : [247, 236, 244])).box(0, y - 4, width, 50); 429 + const featuredCardStartY = y - 4; 430 + let featuredY = y + 12; 431 + const featuredLayouts = FEATURED_DOWNLOADS.map((featured) => { 432 + const innerX = margin + 6; 433 + let labelY = featuredY + 6; 434 + if (featured.badge) labelY += 10; 435 + const buttonSpecs = layoutButtons(innerX, labelY + 18, contentWidth - 12, [ 436 + { 437 + id: `${featured.id}-download`, 438 + w: featured.badge ? 92 : 84, 439 + h: 14, 440 + label: featured.downloadLabel || "download", 441 + colors: featured.badge ? btn.warm : btn.primary, 442 + action: { type: "download-featured", featured }, 443 + }, 444 + { 445 + id: `${featured.id}-open`, 446 + w: 58, 447 + h: 14, 448 + label: "piece", 449 + colors: btn.alt, 450 + action: { type: "open-piece", piece: featured.piece }, 451 + }, 452 + ]); 453 + const cardHeight = buttonsBottom(buttonSpecs, labelY + 18) - featuredY + 8; 454 + const layout = { featured, y: featuredY, labelY, buttons: buttonSpecs, cardHeight }; 455 + featuredY += cardHeight + 6; 456 + return layout; 457 + }); 458 + const featuredSectionHeight = featuredY - featuredCardStartY; 459 + 460 + ink(...(dark ? [24, 19, 33] : [247, 236, 244])).box(0, featuredCardStartY, width, featuredSectionHeight); 401 461 ink(255, 118, 184).write("Featured Downloads", { x: margin, y }); 402 - y += 12; 462 + for (const layout of featuredLayouts) { 463 + const { featured, labelY, buttons, cardHeight } = layout; 464 + const cardBg = featured.badge 465 + ? (dark ? [48, 26, 40] : [255, 235, 244]) 466 + : (dark ? [30, 24, 38] : [252, 243, 248]); 467 + const cardBorder = featured.badge ? [255, 118, 184] : [212, 135, 180]; 403 468 404 - for (let i = 0; i < FEATURED_DOWNLOADS.length; i++) { 405 - const featured = FEATURED_DOWNLOADS[i]; 406 - const rowY = y + i * 16; 407 - const openFeaturedBtn = { id: `${featured.id}-open`, x: margin, y: rowY, w: 58, h: 14 }; 408 - const downloadFeaturedBtn = { id: `${featured.id}-download`, x: margin + 64, y: rowY, w: 84, h: 14 }; 469 + ink(...cardBg).box(margin - 6, layout.y, cardWidth + 12, cardHeight); 470 + ink(...cardBorder, featured.badge ? 170 : 110).box(margin - 6, layout.y, cardWidth + 12, cardHeight, "outline"); 409 471 410 - registerRegion(openFeaturedBtn.id, openFeaturedBtn.x, openFeaturedBtn.y, openFeaturedBtn.w, openFeaturedBtn.h, { 411 - type: "open-piece", 412 - piece: featured.piece, 413 - }); 414 - registerRegion(downloadFeaturedBtn.id, downloadFeaturedBtn.x, downloadFeaturedBtn.y, downloadFeaturedBtn.w, downloadFeaturedBtn.h, { 415 - type: "download-featured", 416 - featured, 417 - }); 472 + if (featured.badge) { 473 + ink(255, 118, 184).box(margin, layout.y + 4, 48, 10); 474 + ink(35, 12, 22).write(featured.badge, { x: margin + 4, y: layout.y + 6 }); 475 + } 418 476 419 - drawButton({ ink, box }, openFeaturedBtn, "piece", btn.alt, hoverId === openFeaturedBtn.id); 420 - drawButton({ ink, box }, downloadFeaturedBtn, "download", btn.primary, hoverId === downloadFeaturedBtn.id); 421 - ink(fg).write(featured.label, { x: margin + 154, y: rowY + 1 }); 422 - ink(dim).write(featured.blurb, { x: margin + 154, y: rowY + 8 }); 477 + ink(fg).write(trimLabel(featured.label, maxTextChars - 2), { x: margin, y: labelY }); 478 + ink(dim).write(trimLabel(featured.blurb, maxTextChars), { x: margin, y: labelY + 9 }); 479 + 480 + for (const button of buttons) { 481 + registerRegion(button.id, button.x, button.y, button.w, button.h, button.action); 482 + drawButton({ ink, box }, button, button.label, button.colors, hoverId === button.id); 483 + } 423 484 } 424 485 425 - y += FEATURED_DOWNLOADS.length * 16 + 8; 486 + y = featuredCardStartY + featuredSectionHeight + 12; 426 487 427 488 if (loading) { 428 489 const dots = ".".repeat((floor(frame / 15) % 3) + 1); ··· 445 506 446 507 for (let i = 0; i < plugins.length; i++) { 447 508 const plugin = plugins[i]; 448 - const name = stripEmoji(plugin.device?.displayName || plugin.device?.name || plugin.code); 509 + const name = trimLabel(stripEmoji(plugin.device?.displayName || plugin.device?.name || plugin.code), maxTextChars - 3); 449 510 const category = plugin.device?.category || "instrument"; 450 511 const piece = plugin.metadata?.piece || plugin.device?.name || "notepat"; 451 - const desc = stripEmoji(plugin.metadata?.description || ""); 512 + const desc = trimLabel(stripEmoji(plugin.metadata?.description || ""), maxTextChars); 452 513 const version = plugin.version?.string || "0.0.0"; 514 + const metaLabel = trimLabel(`v${version} ${category} ${piece}`, maxTextChars); 453 515 const borderColor = category === "midi" 454 516 ? [120, 150, 255] 455 517 : category === "effect" 456 518 ? [255, 170, 90] 457 519 : [90, 210, 255]; 458 520 const cardBg = dark ? [22, 22, 28] : [248, 248, 244]; 521 + const cardY = y - 4; 522 + const btnY = y + 25; 523 + const cardButtons = layoutButtons(margin, btnY, contentWidth, [ 524 + { id: `download-${plugin.code}`, w: 84, h: 14, label: downloading === plugin.code ? "loading" : "download", colors: downloading === plugin.code ? btn.alt : btn.primary, action: { type: "download-plugin", plugin } }, 525 + { id: `use-${plugin.code}`, w: 68, h: 14, label: "builder", colors: btn.warm, action: { type: "use-piece", piece } }, 526 + { id: `open-${plugin.code}`, w: 58, h: 14, label: "piece", colors: btn.alt, action: { type: "open-piece", piece } }, 527 + ]); 528 + const cardHeight = buttonsBottom(cardButtons, btnY) - cardY + 6; 459 529 460 - ink(...cardBg).box(margin - 6, y - 4, cardWidth + 12, 58); 461 - ink(...borderColor, 120).box(margin - 6, y - 4, cardWidth + 12, 58, "outline"); 530 + ink(...cardBg).box(margin - 6, cardY, cardWidth + 12, cardHeight); 531 + ink(...borderColor, 120).box(margin - 6, cardY, cardWidth + 12, cardHeight, "outline"); 462 532 463 533 if (category === "midi") iconMidi(ink, margin, y, frame + i * 40); 464 534 else if (category === "effect") iconEffect(ink, circle, line, margin, y, frame + i * 40); ··· 467 537 ink(fg).write(name, { x: margin + 14, y }); 468 538 y += 12; 469 539 470 - ink(...accent).write(`v${version} ${category} ${piece}`, { x: margin, y }); 540 + ink(...accent).write(metaLabel, { x: margin, y }); 471 541 y += 12; 472 542 473 543 ink(dim).write(desc, { x: margin, y }); 474 544 475 - const btnY = y + 13; 476 - const downloadBtn = { id: `download-${plugin.code}`, x: margin, y: btnY, w: 84, h: 14 }; 477 - const builderBtn = { id: `use-${plugin.code}`, x: margin + 90, y: btnY, w: 68, h: 14 }; 478 - const pieceBtnCard = { id: `open-${plugin.code}`, x: margin + 164, y: btnY, w: 58, h: 14 }; 479 - 480 - registerRegion(downloadBtn.id, downloadBtn.x, downloadBtn.y, downloadBtn.w, downloadBtn.h, { 481 - type: "download-plugin", 482 - plugin, 483 - }); 484 - registerRegion(builderBtn.id, builderBtn.x, builderBtn.y, builderBtn.w, builderBtn.h, { 485 - type: "use-piece", 486 - piece, 487 - }); 488 - registerRegion(pieceBtnCard.id, pieceBtnCard.x, pieceBtnCard.y, pieceBtnCard.w, pieceBtnCard.h, { 489 - type: "open-piece", 490 - piece, 491 - }); 492 - 493 - const isDownloading = downloading === plugin.code; 494 - if (isDownloading) { 495 - drawButton({ ink, box }, downloadBtn, "loading", btn.alt, hoverId === downloadBtn.id); 496 - } else { 497 - drawButton({ ink, box }, downloadBtn, "download", btn.primary, hoverId === downloadBtn.id); 545 + for (const button of cardButtons) { 546 + registerRegion(button.id, button.x, button.y, button.w, button.h, button.action); 547 + drawButton({ ink, box }, button, button.label, button.colors, hoverId === button.id); 498 548 } 499 - drawButton({ ink, box }, builderBtn, "builder", btn.warm, hoverId === builderBtn.id); 500 - drawButton({ ink, box }, pieceBtnCard, "piece", btn.alt, hoverId === pieceBtnCard.id); 501 549 502 - y += 35; 550 + y = cardY + cardHeight + 8; 503 551 ink(sep).line(margin, y, width - margin, y); 504 552 y += 12; 505 553 }