feat(media): implement integrated media manager with sidebar and picker
- Implement DriveService and ShopifyMediaService for backend operations - Create MediaSidebar.html with premium UI and auto-polling - Integrate Google Picker API for robust file selection - Orchestrate sync logic via MediaService (Drive -> Staged Upload -> Shopify) - Add secure config handling for API keys and tokens - Update ppsscript.json with required OAuth scopes - Update MEMORY.md and README.md with architecture details
This commit is contained in:
122
src/mediaHandlers.ts
Normal file
122
src/mediaHandlers.ts
Normal file
@ -0,0 +1,122 @@
|
||||
import { GASSpreadsheetService } from "./services/GASSpreadsheetService"
|
||||
import { GASDriveService } from "./services/GASDriveService"
|
||||
import { ShopifyMediaService } from "./services/ShopifyMediaService"
|
||||
import { GASNetworkService } from "./services/GASNetworkService"
|
||||
import { MediaService } from "./services/MediaService"
|
||||
import { Shop } from "./shopifyApi"
|
||||
import { Config } from "./config"
|
||||
import { Product } from "./Product"
|
||||
|
||||
export function showMediaSidebar() {
|
||||
const html = HtmlService.createHtmlOutputFromFile("MediaSidebar")
|
||||
.setTitle("Media Manager")
|
||||
.setWidth(350);
|
||||
SpreadsheetApp.getUi().showSidebar(html);
|
||||
}
|
||||
|
||||
export function getSelectedSku(): string | null {
|
||||
const ss = new GASSpreadsheetService()
|
||||
const sheet = SpreadsheetApp.getActiveSheet()
|
||||
if (sheet.getName() !== "product_inventory") return null
|
||||
|
||||
const row = sheet.getActiveRange().getRow()
|
||||
if (row <= 1) return null // Header
|
||||
|
||||
const sku = ss.getCellValueByColumnName("product_inventory", row, "sku")
|
||||
return sku ? String(sku) : null
|
||||
}
|
||||
|
||||
export function getPickerConfig() {
|
||||
const config = new Config()
|
||||
return {
|
||||
apiKey: config.googlePickerApiKey,
|
||||
token: ScriptApp.getOAuthToken(),
|
||||
email: Session.getEffectiveUser().getEmail(),
|
||||
parentId: config.productPhotosFolderId // Root folder to start picker in? Optionally could be SKU folder
|
||||
}
|
||||
}
|
||||
|
||||
export function getMediaForSku(sku: string): any[] {
|
||||
const config = new Config()
|
||||
const driveService = new GASDriveService()
|
||||
|
||||
try {
|
||||
const folder = driveService.getOrCreateFolder(sku, config.productPhotosFolderId)
|
||||
const files = driveService.getFiles(folder.getId())
|
||||
|
||||
return files.map(f => {
|
||||
let thumb = ""
|
||||
try {
|
||||
const bytes = f.getThumbnail().getBytes()
|
||||
thumb = "data:image/png;base64," + Utilities.base64Encode(bytes)
|
||||
} catch (e) {
|
||||
console.log(`Failed to get thumbnail for ${f.getName()}`)
|
||||
// Fallback or empty
|
||||
}
|
||||
return {
|
||||
id: f.getId(),
|
||||
name: f.getName(),
|
||||
thumbnailLink: thumb
|
||||
}
|
||||
})
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
export function saveFileToDrive(sku: string, filename: string, mimeType: string, base64Data: string) {
|
||||
const config = new Config()
|
||||
const driveService = new GASDriveService()
|
||||
|
||||
const folder = driveService.getOrCreateFolder(sku, config.productPhotosFolderId)
|
||||
const blob = Utilities.newBlob(Utilities.base64Decode(base64Data), mimeType, filename)
|
||||
|
||||
driveService.saveFile(blob, folder.getId())
|
||||
|
||||
// Auto-sync after upload?
|
||||
// syncMediaForSku(sku) // Optional: auto-sync
|
||||
}
|
||||
|
||||
// Picker Callback specific handler if needed, or we just rely on frontend passing back file ID
|
||||
// Implementing a "copy from Picker" handler
|
||||
export function importFromPicker(sku: string, fileId: string, mimeType: string) {
|
||||
const config = new Config()
|
||||
const driveService = new GASDriveService()
|
||||
|
||||
// Check if file is already in our folder structure?
|
||||
// If user picks from "Photos", it's a separate Blob. We might need to copy it to our SKU folder.
|
||||
// Use DriveApp to get the file (if we have permissions) and make a copy.
|
||||
|
||||
console.log(`Importing ${fileId} for ${sku}`)
|
||||
const file = DriveApp.getFileById(fileId) // Assuming we have scope
|
||||
const folder = driveService.getOrCreateFolder(sku, config.productPhotosFolderId)
|
||||
|
||||
file.makeCopy(file.getName(), folder)
|
||||
}
|
||||
|
||||
export function syncMediaForSku(sku: string) {
|
||||
const config = new Config()
|
||||
const driveService = new GASDriveService()
|
||||
const shop = new Shop()
|
||||
const shopifyMediaService = new ShopifyMediaService(shop)
|
||||
const networkService = new GASNetworkService()
|
||||
|
||||
const mediaService = new MediaService(driveService, shopifyMediaService, networkService, config)
|
||||
|
||||
// Need Shopify Product ID
|
||||
// We can get it from the Product class or Sheet
|
||||
const product = new Product(sku)
|
||||
if (!product.shopify_id) {
|
||||
product.MatchToShopifyProduct(shop)
|
||||
}
|
||||
|
||||
if (!product.shopify_id) {
|
||||
throw new Error("Product not found on Shopify. Please sync product first.")
|
||||
}
|
||||
|
||||
mediaService.syncMediaForSku(sku, product.shopify_id)
|
||||
|
||||
// Update thumbnail in sheet
|
||||
// TODO: Implement thumbnail update in sheet if desired
|
||||
}
|
||||
Reference in New Issue
Block a user