experiments in a post-browser web
at main 173 lines 4.8 kB view raw
1/** 2 * Example Extension - Image Gallery 3 * 4 * Demonstrates: 5 * - Feature detection for the Peek API (works as extension or regular website) 6 * - Command registration with mime type acceptance 7 * - Receiving and storing image data 8 * - Displaying stored images 9 * 10 * See docs/PEEK-API.md for the complete API reference. 11 */ 12 13// Feature detection - check if Peek API is available 14const hasPeekAPI = typeof window.app !== 'undefined'; 15const api = hasPeekAPI ? window.app : null; 16 17// In-memory image storage (when running without Peek API) 18// When Peek API is available, we use the datastore 19const localImageStore = new Map(); 20 21/** 22 * Store an image 23 * @param {string} id - Unique image ID 24 * @param {object} imageData - { data: base64, mimeType, name, timestamp } 25 */ 26async function storeImage(id, imageData) { 27 if (hasPeekAPI) { 28 // Use Peek datastore for persistent storage 29 await api.datastore.setRow('example_images', id, imageData); 30 console.log('[example] Image stored in datastore:', id); 31 } else { 32 // Fall back to in-memory storage 33 localImageStore.set(id, imageData); 34 console.log('[example] Image stored locally:', id); 35 } 36} 37 38/** 39 * Get all stored images 40 * @returns {Promise<object>} Map of id -> imageData 41 */ 42async function getStoredImages() { 43 if (hasPeekAPI) { 44 const result = await api.datastore.getTable('example_images'); 45 return result.success ? result.data : {}; 46 } else { 47 return Object.fromEntries(localImageStore); 48 } 49} 50 51/** 52 * Generate a unique image ID 53 */ 54function generateImageId() { 55 return `img_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`; 56} 57 58/** 59 * Handle received image data from command execution 60 * @param {object} ctx - Command execution context with input data 61 */ 62async function handleImageReceived(ctx) { 63 console.log('[example] Received image data:', ctx); 64 65 // ctx.input contains the data passed to the command 66 // For images, this would be { data: base64, mimeType, name } 67 if (!ctx.input || !ctx.input.data) { 68 console.error('[example] No image data in context'); 69 return; 70 } 71 72 const imageId = generateImageId(); 73 const imageData = { 74 data: ctx.input.data, // Base64 encoded image 75 mimeType: ctx.input.mimeType || 'image/png', 76 name: ctx.input.name || 'Untitled', 77 timestamp: Date.now() 78 }; 79 80 await storeImage(imageId, imageData); 81 82 // Notify that a new image was added 83 if (hasPeekAPI) { 84 api.publish('example:image-added', { id: imageId, ...imageData }, api.scopes.GLOBAL); 85 } 86 87 // Open the gallery to show the new image 88 openGallery(); 89} 90 91/** 92 * Open the image gallery window 93 */ 94function openGallery() { 95 if (hasPeekAPI) { 96 api.window.open('peek://ext/example/gallery.html', { 97 key: 'example-gallery', 98 width: 800, 99 height: 600, 100 title: 'Image Gallery' 101 }); 102 } else { 103 // When running as a regular website, navigate or open in new tab 104 window.open('./gallery.html', '_blank'); 105 } 106} 107 108const extension = { 109 id: 'example', 110 labels: { 111 name: 'Example Gallery' 112 }, 113 114 /** 115 * Register commands - called when cmd extension is ready 116 */ 117 registerCommands() { 118 // Command that accepts images 119 // The 'accepts' array specifies which mime types this command can receive 120 api.commands.register({ 121 name: 'example:save-image', 122 description: 'Save an image to the gallery', 123 accepts: ['image/png', 'image/jpeg', 'image/gif', 'image/webp', 'image/*'], 124 execute: handleImageReceived 125 }); 126 127 // Command to open the gallery 128 api.commands.register({ 129 name: 'example:gallery', 130 description: 'Open the image gallery', 131 execute: openGallery 132 }); 133 134 console.log('[example] Commands registered'); 135 }, 136 137 init() { 138 console.log('[example] init - Peek API available:', hasPeekAPI); 139 140 if (!hasPeekAPI) { 141 console.log('[example] Running without Peek API - limited functionality'); 142 return; 143 } 144 145 // Wait for cmd:ready before registering commands 146 api.subscribe('cmd:ready', () => { 147 this.registerCommands(); 148 }, api.scopes.GLOBAL); 149 150 // Query in case cmd is already ready 151 api.publish('cmd:query', {}, api.scopes.GLOBAL); 152 153 // Note: No global shortcut - use the cmd palette to access this extension 154 // (avoids conflicts with other extensions like groups which uses Option+g) 155 156 console.log('[example] Extension loaded'); 157 }, 158 159 uninit() { 160 console.log('[example] Cleaning up...'); 161 162 if (hasPeekAPI) { 163 api.commands.unregister('example:save-image'); 164 api.commands.unregister('example:gallery'); 165 } 166 } 167}; 168 169// Export for ES module usage (Peek extension) 170export default extension; 171 172// Also expose utilities for the gallery page 173export { hasPeekAPI, api, getStoredImages, storeImage, generateImageId };