Created
February 24, 2026 09:43
-
-
Save pleabargain/6e092b1e9cb413b1883c2314464c5c63 to your computer and use it in GitHub Desktop.
google apps script that takes a google sheet and generate flash cards in google slides
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * CONFIGURATION & STYLING | |
| */ | |
| const CONFIG = { | |
| MENU_NAME: "🎲 SLIDE APP", | |
| TAGS: { | |
| WORD: "word_txt_v6", | |
| HEADER: "header_txt_v6", | |
| BACK: "back_btn_v6" | |
| }, | |
| STYLES: { | |
| TEXT_COLOR: "#000000", | |
| HEADER_COLOR: "#D93025", // Bright Red for ID/D/T | |
| WORD_FONT_SIZE: 80, | |
| HEADER_FONT_SIZE: 60, | |
| FONT_FAMILY: "Arial" | |
| } | |
| }; | |
| function onOpen() { | |
| SlidesApp.getUi().createMenu(CONFIG.MENU_NAME) | |
| .addItem('🚀 Import & Build Deck', 'startWorkflow') | |
| .addSeparator() | |
| .addItem('🧹 Clear All Slides', 'clearAll') | |
| .addToUi(); | |
| } | |
| /** | |
| * Main Workflow | |
| */ | |
| function startWorkflow() { | |
| const ui = SlidesApp.getUi(); | |
| const url = "https://docs.google.com/spreadsheets/d/1K6cfV_c0aQhHmBRtuZgDtP2d8XvQfyIi2gwvRPEi6Is/edit?gid=510679276#gid=510679276"; | |
| try { | |
| // 1. Fetch Data | |
| const data = fetchData(url); | |
| if (!data.length) throw new Error("No data found in columns A, B, or C."); | |
| // 2. Shuffle Data (Fisher-Yates) | |
| const shuffled = shuffle(data); | |
| // 3. Build Slides | |
| const deck = SlidesApp.getActivePresentation(); | |
| buildSlides(deck, shuffled); | |
| ui.alert("Success!", `Deck built with ${shuffled.length} words.\n\nInstructions:\n1. Enter Present Mode.\n2. Space bar = Reveal ID/D/T.\n3. Space bar again = Next Word.`, ui.ButtonSet.OK); | |
| } catch (e) { | |
| ui.alert("Error", e.message, ui.ButtonSet.OK); | |
| } | |
| } | |
| /** | |
| * Grabs words from Col A (ID), B (D), and C (T) | |
| */ | |
| function fetchData(url) { | |
| const ss = SpreadsheetApp.openByUrl(url); | |
| const sheet = ss.getSheets()[0]; | |
| const values = sheet.getDataRange().getValues(); | |
| const results = []; | |
| const headers = ["ID", "D", "T"]; | |
| // Skip the first row (headers) | |
| for (let r = 1; r < values.length; r++) { | |
| for (let c = 0; c < 3; c++) { | |
| let word = values[r][c]; | |
| if (word && word.toString().trim() !== "") { | |
| results.push({ | |
| word: word.toString().trim(), | |
| category: headers[c] | |
| }); | |
| } | |
| } | |
| } | |
| return results; | |
| } | |
| /** | |
| * Builds the two-slide sequence for the reveal effect | |
| */ | |
| function buildSlides(deck, data) { | |
| // Clear existing | |
| const slides = deck.getSlides(); | |
| for (let i = slides.length - 1; i > 0; i--) slides[i].remove(); | |
| const pWidth = deck.getPageWidth(); | |
| const pHeight = deck.getPageHeight(); | |
| data.forEach((item, index) => { | |
| // --- SLIDE 1: WORD ONLY --- | |
| let s1 = (index === 0) ? deck.getSlides()[0] : deck.appendSlide(SlidesApp.PredefinedLayout.BLANK); | |
| s1.getShapes().forEach(s => s.remove()); | |
| createTextBox(s1, item.word, CONFIG.STYLES.WORD_FONT_SIZE, false, CONFIG.STYLES.TEXT_COLOR, pWidth, pHeight, 0); | |
| // --- SLIDE 2: HEADER + WORD --- | |
| let s2 = deck.appendSlide(SlidesApp.PredefinedLayout.BLANK); | |
| // Add Header (ID/D/T) at the top | |
| createTextBox(s2, item.category, CONFIG.STYLES.HEADER_FONT_SIZE, true, CONFIG.STYLES.HEADER_COLOR, pWidth, pHeight, -100); | |
| // Add Word in the middle | |
| createTextBox(s2, item.word, CONFIG.STYLES.WORD_FONT_SIZE, false, CONFIG.STYLES.TEXT_COLOR, pWidth, pHeight, 40); | |
| // Optional: Add a back button to s1 to skip back to the PREVIOUS word's Slide 1 | |
| if (index > 0) { | |
| const btn = s1.insertShape(SlidesApp.ShapeType.RECTANGLE, 20, pHeight - 50, 80, 30); | |
| btn.getFill().setSolidFill("#eeeeee"); | |
| btn.getText().setText("◀ BACK").getTextStyle().setFontSize(10); | |
| // Link to the first slide of the previous pair | |
| btn.setLinkSlide(deck.getSlides()[(index - 1) * 2]); | |
| } | |
| }); | |
| } | |
| /** | |
| * Helper to center text boxes | |
| */ | |
| function createTextBox(slide, text, size, isBold, color, pWidth, pHeight, yOffset) { | |
| const w = 700, h = 150; | |
| const box = slide.insertShape(SlidesApp.ShapeType.TEXT_BOX, (pWidth/2)-(w/2), (pHeight/2)-(h/2) + yOffset, w, h); | |
| const range = box.getText(); | |
| range.setText(text); | |
| range.getTextStyle() | |
| .setFontSize(size) | |
| .setBold(isBold) | |
| .setForegroundColor(color) | |
| .setFontFamily(CONFIG.STYLES.FONT_FAMILY); | |
| range.getParagraphStyle().setParagraphAlignment(SlidesApp.ParagraphAlignment.CENTER); | |
| box.setContentAlignment(SlidesApp.ContentAlignment.MIDDLE); | |
| } | |
| function shuffle(array) { | |
| for (let i = array.length - 1; i > 0; i--) { | |
| const j = Math.floor(Math.random() * (i + 1)); | |
| [array[i], array[j]] = [array[j], array[i]]; | |
| } | |
| return array; | |
| } | |
| function clearAll() { | |
| const deck = SlidesApp.getActivePresentation(); | |
| deck.getSlides().forEach((s, i) => { | |
| if (i > 0) s.remove(); | |
| else s.getShapes().forEach(sh => sh.remove()); | |
| }); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment