From 1884a12091b2f2796ac5461fe82e545e797f61bc Mon Sep 17 00:00:00 2001 From: leonorlyn Date: Mon, 30 Jun 2025 16:32:36 -0700 Subject: [PATCH 01/70] generate the flashcard --- FLASHCARD_USAGE.md | 47 ++++++++++++++ src/code.js | 122 ++++++++++++++++++++++++++++++++++++ src/components/settings.jsx | 10 --- src/components/text.jsx | 36 ++++++++++- src/manifest.json | 3 +- webpack.config.js | 12 +++- 6 files changed, 214 insertions(+), 16 deletions(-) create mode 100644 FLASHCARD_USAGE.md create mode 100644 src/code.js diff --git a/FLASHCARD_USAGE.md b/FLASHCARD_USAGE.md new file mode 100644 index 0000000..836228e --- /dev/null +++ b/FLASHCARD_USAGE.md @@ -0,0 +1,47 @@ +# FlashCard 插入功能使用指南 + +## 功能说明 + +该功能允许您在Adobe Express中创建和插入闪卡(FlashCard)。每张闪卡包含: +- 一个白色背景的矩形框 +- 居中显示的问题文本 (Q: ...) +- 居中显示的答案文本 (A: ...) + +## 使用步骤 + +1. **手动输入内容**: + - 在"Manual Enter"标签页中 + - 填写"Question"和"Answer"文本框 + - 可以点击"Add Card +"按钮添加更多卡片 + +2. **插入闪卡**: + - 确保至少有一张卡片填写了问题或答案 + - 点击"Insert Flash Cards"按钮 + - 闪卡将自动插入到Adobe Express画布中 + +## 闪卡规格 + +- **尺寸**: 400px × 250px +- **背景**: 白色 (#FFFFFF) +- **边框**: 灰色 (#CCCCCC),2px宽度 +- **间距**: 卡片之间垂直间距50px +- **位置**: 从左上角(50, 50)开始垂直排列 + +## 文本格式 + +- **问题**: 以"Q: "开头,显示在卡片上半部分 +- **答案**: 以"A: "开头,显示在卡片下半部分 +- **字体**: 使用系统默认字体 + +## 注意事项 + +1. 空白的卡片(问题和答案都为空)将被自动过滤,不会插入 +2. 只填写问题或只填写答案的卡片也会正常插入 +3. 闪卡将按照输入顺序垂直排列在画布上 +4. 确保Adobe Express插件已正确加载 + +## 技术实现 + +- 使用Adobe Express Document API +- 通过Document Sandbox创建几何形状和文本元素 +- 支持响应式布局和样式设置 \ No newline at end of file diff --git a/src/code.js b/src/code.js new file mode 100644 index 0000000..43b8cba --- /dev/null +++ b/src/code.js @@ -0,0 +1,122 @@ +import addOnSandboxSdk from "add-on-sdk-document-sandbox"; +import { editor, colorUtils } from "express-document-sdk"; + +const { runtime } = addOnSandboxSdk.instance; + +function start() { + runtime.exposeApi({ + createFlashcards(cards) { + try { + console.log("Starting to create flashcards:", cards); + + // Get current page + const insertionParent = editor.context.insertionParent; + + // Flashcard configuration + // todo: make this dynamic + const cardWidth = 1200; + const cardHeight = 800; + const spacing = 200; + + cards.forEach((card, index) => { + console.log(`Creating card ${index + 1}:`, card); + + // Create flashcard group + const cardGroup = editor.createGroup(); + + // Create background rectangle + const cardRect = editor.createRectangle(); + cardRect.width = cardWidth; + cardRect.height = cardHeight; + cardRect.translation = { x: 0, y: 0 }; + + // Set white background + // todo: make this color dynamic + const bgColor = colorUtils.fromHex("#FFF9E6"); + const bgFill = editor.makeColorFill(bgColor); + cardRect.fill = bgFill; + + // Set border + const strokeColor = colorUtils.fromHex("#CCCCCC"); + const stroke = editor.makeStroke({ + color: strokeColor, + width: 2 + }); + cardRect.stroke = stroke; + + // Add rectangle to group + cardGroup.children.append(cardRect); + + // Create question text (centered, smaller font) + if (card.question && card.question.trim()) { + const questionText = editor.createText(card.question); + + // Use setPositionInParent method to center text + questionText.setPositionInParent( + { x: cardWidth / 2, y: cardHeight / 4 }, // Card center position, question at top 1/4 + { x: 0.5, y: 0.5 } // Text center as reference point + ); + + // Set smaller font size + try { + questionText.fullContent.applyCharacterStyles( + { fontSize: 16 }, // Set to 16pt font + { start: 0, length: card.question.length } + ); + } catch (styleError) { + console.log("Failed to set question text style:", styleError); + } + + cardGroup.children.append(questionText); + console.log(`Added question text: ${card.question}`); + } + + // Create answer text (centered, smaller font) + if (card.answer && card.answer.trim()) { + const answerText = editor.createText(card.answer); + + // Use setPositionInParent method to center text + answerText.setPositionInParent( + { x: cardWidth / 2, y: cardHeight * 3 / 4 }, // Card center position, answer at bottom 3/4 + { x: 0.5, y: 0.5 } // Text center as reference point + ); + + // Set smaller font size + try { + answerText.fullContent.applyCharacterStyles( + { fontSize: 16 }, // Set to 16pt font + { start: 0, length: card.answer.length } + ); + } catch (styleError) { + console.log("Failed to set answer text style:", styleError); + } + + cardGroup.children.append(answerText); + console.log(`Added answer text: ${card.answer}`); + } + + // Set flashcard group position (vertical layout) + cardGroup.translation = { + x: 100, + y: 100 + index * (cardHeight + spacing) + }; + + // Insert to document + insertionParent.children.append(cardGroup); + + console.log(`Successfully created card ${index + 1}`); + }); + + console.log(`Successfully created ${cards.length} flashcards`); + + } catch (error) { + console.error("Error creating flashcards:", error); + console.error("Error details:", error.message); + console.error("Error stack:", error.stack); + throw error; + } + } + }); +} + +start(); \ No newline at end of file diff --git a/src/components/settings.jsx b/src/components/settings.jsx index 367cf84..a3fd962 100644 --- a/src/components/settings.jsx +++ b/src/components/settings.jsx @@ -128,16 +128,6 @@ const TYPE_OPTIONS = [ key: "qa_same_side", img: "./icon/qa-same-side.png", label: "Question and answer\non the same side" - }, - { - key: "one_per_page", - img: "./icon/one-per-page.png", - label: "One per page" - }, - { - key: "multi_per_page", - img: "./icon/multi-per-page.png", - label: "Multi per page" } ]; diff --git a/src/components/text.jsx b/src/components/text.jsx index f5eb37b..1189248 100644 --- a/src/components/text.jsx +++ b/src/components/text.jsx @@ -109,8 +109,7 @@ export default function Text({ addOnSdk }) { const [entryTab, setEntryTab] = useState("Manual Enter"); const [cards, setCards] = useState([ - { question: "", answer: "" }, - { question: "", answer: "" } + { question: "test question", answer: "test answer" } ]); const updateCard = (idx, field, value) => @@ -118,8 +117,39 @@ export default function Text({ addOnSdk }) { const addCard = () => setCards(prev => [...prev, { question: "", answer: "" }]); - const insertCards = () => console.log("insert cards"); //add a real function + const insertCards = async () => { + if (!addOnSdk) { + console.log("Adobe SDK not available"); + return; + } + + try { + // Filter out empty cards + const validCards = cards.filter(card => + card.question.trim() !== "" || card.answer.trim() !== "" + ); + + if (validCards.length === 0) { + console.log("Please fill in at least one card with question or answer"); + return; + } + + // Get Document API + const { runtime } = addOnSdk.instance; + const sandboxProxy = await runtime.apiProxy("documentSandbox"); + + // Create flashcards + await sandboxProxy.createFlashcards(validCards); + + console.log(`Successfully inserted ${validCards.length} flashcards`); + + } catch (error) { + console.error("Failed to insert flashcards:", error); + console.log("Error occurred while inserting flashcards, please check console"); + } + }; + // question form lena: do we still want the preview? async function showDialog() { if (!addOnSdk) return; diff --git a/src/manifest.json b/src/manifest.json index b6429d7..ad17fbb 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -16,7 +16,8 @@ { "type": "panel", "id": "panel1", - "main": "index.html" + "main": "index.html", + "documentSandbox": "code.js" } ] } diff --git a/webpack.config.js b/webpack.config.js index 8c257f6..3eb0592 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -19,7 +19,10 @@ process.env.NODE_ENV = "development"; // Webpack wire up. module.exports = { mode: "development", - entry: "./src/components/index.jsx", + entry: { + index: "./src/components/index.jsx", + code: "./src/code.js" + }, devtool: "source-map", experiments: { outputModule: true, @@ -27,14 +30,19 @@ module.exports = { output: { path: path.resolve(__dirname, "dist"), module: true, - filename: "index.js", + filename: "[name].js", }, externalsType: "module", externalsPresets: { web: true }, + externals: { + "add-on-sdk-document-sandbox": "add-on-sdk-document-sandbox", + "express-document-sdk": "express-document-sdk" + }, plugins: [ new HtmlWebpackPlugin({ template: "src/index.html", scriptLoading: "module", + chunks: ["index"] }), new CopyWebpackPlugin({ patterns: [{ from: "src/*.json", to: "[name][ext]" }], From 73a275b9f1456b5cd300076e0a7b5a3cf3d43329 Mon Sep 17 00:00:00 2001 From: leonorlyn Date: Mon, 30 Jun 2025 18:14:18 -0700 Subject: [PATCH 02/70] add color and size custom --- src/code.js | 145 ++++++++++++++++++------ src/components/App.jsx | 51 ++++++++- src/components/settings.jsx | 219 +++++++++++++++++++++++++++++++++--- src/components/text.jsx | 166 ++++++++++++++++++++++++--- 4 files changed, 506 insertions(+), 75 deletions(-) diff --git a/src/code.js b/src/code.js index 43b8cba..27dbcae 100644 --- a/src/code.js +++ b/src/code.js @@ -5,22 +5,77 @@ const { runtime } = addOnSandboxSdk.instance; function start() { runtime.exposeApi({ - createFlashcards(cards) { + createFlashcards: async (cards, settings = {}) => { try { - console.log("Starting to create flashcards:", cards); + console.log("Starting to create flashcards with settings:", settings); + + // Get settings with defaults + const sizePercentage = getCardSizePercentage(settings.cardSize || "medium"); + const cardColor = settings.cardColor || "#FFFFFF"; + + console.log("Resolved card size percentage:", sizePercentage); + console.log("Resolved card color:", cardColor); + + // Maximum 4 cards limitation + if (cards.length > 4) { + throw new Error("Maximum 4 flashcards allowed per page"); + } - // Get current page + // Get current page and calculate canvas size const insertionParent = editor.context.insertionParent; + const page = editor.documentRoot.pages.first; + const artboard = page.artboards.first; + + // Get canvas dimensions + const canvasWidth = artboard.width; + const canvasHeight = artboard.height; + + console.log(`Canvas size: ${canvasWidth} x ${canvasHeight}`); + + // Calculate grid layout based on percentage + const padding = 50; // Fixed padding + const gap = 30; // Gap between cards + + // Calculate available space for 2x2 grid + const availableWidth = canvasWidth - (2 * padding) - gap; + const availableHeight = canvasHeight - (2 * padding) - gap; + + // Calculate base card size (half of available space for 2x2 grid) + const baseCardWidth = availableWidth / 2; + const baseCardHeight = availableHeight / 2; + + // Apply percentage scaling + const cardWidth = Math.max(150, baseCardWidth * (sizePercentage / 100)); + const cardHeight = Math.max(100, baseCardHeight * (sizePercentage / 100)); + + console.log(`Base card size: ${baseCardWidth} x ${baseCardHeight}`); + console.log(`Scaled card size (${sizePercentage}%): ${cardWidth} x ${cardHeight}`); - // Flashcard configuration - // todo: make this dynamic - const cardWidth = 1200; - const cardHeight = 800; - const spacing = 200; + // Define 2x2 grid positions + const positions = [ + { row: 0, col: 0 }, // Top-left + { row: 0, col: 1 }, // Top-right + { row: 1, col: 0 }, // Bottom-left + { row: 1, col: 1 } // Bottom-right + ]; cards.forEach((card, index) => { + if (index >= 4) return; // Safety check + console.log(`Creating card ${index + 1}:`, card); + const position = positions[index]; + + // Calculate card position - center cards in available space + const totalGridWidth = 2 * cardWidth + gap; + const totalGridHeight = 2 * cardHeight + gap; + + const startX = (canvasWidth - totalGridWidth) / 2; + const startY = (canvasHeight - totalGridHeight) / 2; + + const cardX = startX + position.col * (cardWidth + gap); + const cardY = startY + position.row * (cardHeight + gap); + // Create flashcard group const cardGroup = editor.createGroup(); @@ -30,11 +85,18 @@ function start() { cardRect.height = cardHeight; cardRect.translation = { x: 0, y: 0 }; - // Set white background - // todo: make this color dynamic - const bgColor = colorUtils.fromHex("#FFF9E6"); - const bgFill = editor.makeColorFill(bgColor); - cardRect.fill = bgFill; + // Set custom background color + try { + const bgColor = colorUtils.fromHex(cardColor); + const bgFill = editor.makeColorFill(bgColor); + cardRect.fill = bgFill; + console.log(`Applied color: ${cardColor}`); + } catch (colorError) { + console.log("Invalid color, using default:", colorError); + const bgColor = colorUtils.fromHex("#FFFFFF"); + const bgFill = editor.makeColorFill(bgColor); + cardRect.fill = bgFill; + } // Set border const strokeColor = colorUtils.fromHex("#CCCCCC"); @@ -47,20 +109,23 @@ function start() { // Add rectangle to group cardGroup.children.append(cardRect); - // Create question text (centered, smaller font) + // Calculate responsive font size based on card size + const fontSize = Math.max(10, Math.min(24, cardWidth / 20)); + + // Create question text (centered, responsive font) if (card.question && card.question.trim()) { const questionText = editor.createText(card.question); - // Use setPositionInParent method to center text + // Position at top 1/4 of card questionText.setPositionInParent( - { x: cardWidth / 2, y: cardHeight / 4 }, // Card center position, question at top 1/4 - { x: 0.5, y: 0.5 } // Text center as reference point + { x: cardWidth / 2, y: cardHeight / 4 }, + { x: 0.5, y: 0.5 } ); - // Set smaller font size + // Set responsive font size try { questionText.fullContent.applyCharacterStyles( - { fontSize: 16 }, // Set to 16pt font + { fontSize: fontSize }, { start: 0, length: card.question.length } ); } catch (styleError) { @@ -68,23 +133,23 @@ function start() { } cardGroup.children.append(questionText); - console.log(`Added question text: ${card.question}`); + console.log(`Added question text: ${card.question}, font size: ${fontSize}`); } - // Create answer text (centered, smaller font) + // Create answer text (centered, responsive font) if (card.answer && card.answer.trim()) { const answerText = editor.createText(card.answer); - // Use setPositionInParent method to center text + // Position at bottom 3/4 of card answerText.setPositionInParent( - { x: cardWidth / 2, y: cardHeight * 3 / 4 }, // Card center position, answer at bottom 3/4 - { x: 0.5, y: 0.5 } // Text center as reference point + { x: cardWidth / 2, y: cardHeight * 3 / 4 }, + { x: 0.5, y: 0.5 } ); - // Set smaller font size + // Set responsive font size try { answerText.fullContent.applyCharacterStyles( - { fontSize: 16 }, // Set to 16pt font + { fontSize: fontSize }, { start: 0, length: card.answer.length } ); } catch (styleError) { @@ -92,31 +157,41 @@ function start() { } cardGroup.children.append(answerText); - console.log(`Added answer text: ${card.answer}`); + console.log(`Added answer text: ${card.answer}, font size: ${fontSize}`); } - // Set flashcard group position (vertical layout) + // Set flashcard group position in grid cardGroup.translation = { - x: 100, - y: 100 + index * (cardHeight + spacing) + x: cardX, + y: cardY }; // Insert to document insertionParent.children.append(cardGroup); - console.log(`Successfully created card ${index + 1}`); + console.log(`Successfully created card ${index + 1} at position (${cardX}, ${cardY})`); }); - console.log(`Successfully created ${cards.length} flashcards`); + console.log(`Successfully created ${cards.length} flashcards in 2x2 grid layout`); } catch (error) { - console.error("Error creating flashcards:", error); - console.error("Error details:", error.message); - console.error("Error stack:", error.stack); + console.error("Failed to insert flashcards:", error); throw error; } } }); } +// 新的百分比获取函数 +function getCardSizePercentage(sizeKey) { + const sizes = { + xs: 50, + small: 65, + medium: 80, + large: 95, + xl: 110 + }; + return sizes[sizeKey] || 80; // 默认80% +} + start(); \ No newline at end of file diff --git a/src/components/App.jsx b/src/components/App.jsx index a64630f..066a1e1 100644 --- a/src/components/App.jsx +++ b/src/components/App.jsx @@ -34,7 +34,31 @@ const styles = { }; const App = ({ addOnSdk }) => { - const [activeTab, setActiveTab] = useState("Settings"); + // Main navigation state + const [currentView, setCurrentView] = useState("Settings"); + + // Settings state - lifted from Settings component + const [flashcardSettings, setFlashcardSettings] = useState({ + subject: "Science", + audience: "Primary School", + typeKey: "qa_two_side", + cardSize: "medium", + cardColor: "#FFFFFF" + }); + + // Text component state - lifted from Text component + const [entryTab, setEntryTab] = useState("Manual Enter"); + const [cards, setCards] = useState([ + { question: "test question", answer: "test answer" }, + { question: "test question", answer: "test answer" }, + { question: "test question", answer: "test answer" }, + { question: "test question", answer: "test answer" }, + ]); + + // Settings change handler + const handleSettingsChange = (newSettings) => { + setFlashcardSettings(newSettings); + }; return (
@@ -44,9 +68,9 @@ const App = ({ addOnSdk }) => { key={t} style={{ ...styles.tabButton, - ...(activeTab === t ? styles.tabButtonActive : {}), + ...(currentView === t ? styles.tabButtonActive : {}), }} - onClick={() => setActiveTab(t)} + onClick={() => setCurrentView(t)} > {t} @@ -54,9 +78,24 @@ const App = ({ addOnSdk }) => {
- {activeTab == 'Settings' && } - {activeTab == 'Template' &&