this repo has no description

Added background to SVG

+223 -58
+222 -58
components/WeeklySchedule.tsx
··· 2 2 import format from "https://deno.land/x/date_fns@v2.22.1/format/index.js"; 3 3 import { Event } from "../utils/calendarUtils.ts"; 4 4 5 + // Define theme interface for styling options 6 + interface Theme { 7 + fontFamily: string; 8 + headerColor: string; 9 + dateRangeColor: string; 10 + dayColor: string; 11 + eventBgColor: string; 12 + eventTextColor: string; 13 + noEventColor: string; 14 + backgroundImagePath: string; // Path to background image 15 + eventIconPath?: string; // Path to event SVG icon (optional) 16 + } 17 + 18 + // Default theme 19 + const defaultTheme: Theme = { 20 + fontFamily: "Lazydog", 21 + headerColor: "#ffffff", 22 + dateRangeColor: "#ffffff", 23 + dayColor: "#ffffff", 24 + eventBgColor: "#e6d195", //twitch: eebd37 discord: f3af52 25 + eventTextColor: "#ffffff", 26 + noEventColor: "#ffffff", 27 + backgroundImagePath: "./static/background.png", 28 + eventIconPath: "./static/discord-icon.svg", 29 + }; 30 + 31 + // Theme collection - can be expanded as needed 32 + const themes: Record<string, Theme> = { 33 + "light": defaultTheme, 34 + "dark": { 35 + fontFamily: "Lazydog", 36 + headerColor: "#ffffff", 37 + dateRangeColor: "#cccccc", 38 + dayColor: "#ffffff", 39 + eventBgColor: "#333333", 40 + eventTextColor: "#ffffff", 41 + noEventColor: "#777777", 42 + backgroundImagePath: "./static/background.png", 43 + eventIconPath: "./static/discord-icon.svg", 44 + }, 45 + // Add more themes as needed 46 + }; 47 + 5 48 export default function WeeklySchedule( 6 49 events: Event[], 7 50 startDate: Date, 8 51 endDate: Date, 52 + backgroundImageBase64?: string, 53 + theme: Theme = defaultTheme, 9 54 ) { 10 55 const days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]; 56 + const aspectRatio = 2700 / 4500; // Based on your original background image size 57 + const displayWidth = 375; // Your defined width 58 + const displayHeight = displayWidth / aspectRatio; 59 + 60 + // Calculate relative positions 61 + const headerPosition = 0.10; 62 + const dateRangePosition = 0.15; 63 + const scheduleStartPosition = 0.21; 64 + const scheduleHeight = 0.73; 65 + 66 + // Format the date range 67 + const startDateFormatted = format(startDate, "dd.MM.", []); 68 + const endDateFormatted = format(endDate, "dd.MM.", []); 69 + const dateRangeText = `${startDateFormatted} - ${endDateFormatted}`; 11 70 12 71 return ( 13 72 <div 14 73 style={{ 15 74 display: "flex", 16 75 flexDirection: "column", 17 - alignItems: "stretch", 18 - width: "100%", 19 - height: "auto", 20 - padding: "20px", 21 - backgroundColor: "#f0f0f0", 76 + alignItems: "center", 77 + width: `${displayWidth}px`, 78 + height: `${displayHeight}px`, 79 + position: "relative", 22 80 }} 23 81 > 24 - {days.map((day, index) => { 25 - const event = events.find((e) => 26 - format(e.start, "eee", []) === day 27 - ); 28 - return ( 29 - <div 30 - key={index} 31 - style={{ 32 - display: "flex", 33 - flexDirection: "row", 34 - alignItems: "center", 35 - justifyContent: "space-between", 36 - width: "100%", 37 - height: "auto", 38 - backgroundColor: "#fff", 39 - border: "1px solid #ccc", 40 - borderRadius: "8px", 41 - padding: "10px", 42 - marginBottom: "8px", 43 - }} 44 - > 82 + {/* Background Image */} 83 + {backgroundImageBase64 && ( 84 + <img 85 + src={`data:image/png;base64,${backgroundImageBase64}`} 86 + style={{ 87 + position: "absolute", 88 + top: 0, 89 + left: 0, 90 + width: "100%", 91 + height: "100%", 92 + objectFit: "cover", 93 + zIndex: -1, 94 + }} 95 + /> 96 + )} 97 + 98 + {/* Header Title */} 99 + <div 100 + style={{ 101 + position: "absolute", 102 + top: `${headerPosition * 100}%`, 103 + width: "79%", 104 + textAlign: "center", 105 + fontFamily: theme.fontFamily, 106 + fontSize: "24px", 107 + fontWeight: "bold", 108 + color: theme.headerColor, 109 + }} 110 + > 111 + Stream Schedule 112 + </div> 113 + 114 + {/* Date Range */} 115 + <div 116 + style={{ 117 + position: "absolute", 118 + top: `${dateRangePosition * 100}%`, 119 + width: "79%", 120 + textAlign: "center", 121 + fontFamily: theme.fontFamily, 122 + fontSize: "18px", 123 + color: theme.dateRangeColor, 124 + }} 125 + > 126 + {dateRangeText} 127 + </div> 128 + 129 + {/* Weekly Schedule */} 130 + <div 131 + style={{ 132 + position: "absolute", 133 + top: `${scheduleStartPosition * 100}%`, 134 + width: "79%", 135 + height: `${scheduleHeight * 100}%`, 136 + display: "flex", 137 + flexDirection: "column", 138 + justifyContent: "space-between", 139 + }} 140 + > 141 + {days.map((day, index) => { 142 + const event = events.find((e) => 143 + format(e.start, "eee", []) === day 144 + ); 145 + return ( 45 146 <div 147 + key={index} 46 148 style={{ 47 - fontWeight: "bold", 48 - marginRight: "12px", 49 - textAlign: "left", // Ensure day is left-aligned 50 - flex: "1", // Take some space, adjust as needed 149 + display: "flex", 150 + flexDirection: "row", 151 + alignItems: "center", 152 + justifyContent: "space-between", 153 + width: "100%", 154 + height: `${100 / days.length - 2}%`, 155 + backgroundColor: theme.eventBgColor, 156 + padding: "10px", 157 + borderRadius: "10px", 158 + marginBottom: "8px", 159 + fontFamily: theme.fontFamily, 51 160 }} 52 161 > 53 - {day} 54 - </div> 55 - {event && ( 162 + <div 163 + style={{ 164 + fontWeight: "bold", 165 + marginRight: "12px", 166 + textAlign: "left", 167 + flex: "1", 168 + fontSize: "1.2em", 169 + color: theme.dayColor, 170 + }} 171 + > 172 + {day} 173 + </div> 174 + 175 + {event && ( 176 + <> 177 + <div 178 + style={{ 179 + display: "flex", 180 + flexDirection: "column", 181 + textAlign: "center", 182 + flex: 2, 183 + marginRight: "12px", 184 + wordWrap: "break-word", 185 + fontSize: "1.2em", 186 + color: theme.eventTextColor, 187 + }} 188 + > 189 + <div>{event.summary}</div> 190 + </div> 191 + </> 192 + )} 193 + 56 194 <div 57 195 style={{ 58 196 display: "flex", 59 - flexDirection: "column", // Still keep column layout for summary and time if needed later 60 - textAlign: "center", // Center-align the summary text 61 - flex: 2, // Summary takes more space in the center 62 - marginRight: "12px", // Add right margin to separate from time 63 - wordWrap: "break-word", // Enable word wrapping for long summaries 64 - fontSize: "0.9em", // Slightly smaller font for summary (optional, adjust or remove if not desired) 197 + textAlign: "right", 198 + flex: "1", 199 + whiteSpace: "nowrap", 200 + color: event 201 + ? theme.eventTextColor 202 + : theme.noEventColor, 203 + fontSize: "1.2em", 65 204 }} 66 205 > 67 - <div>{event.summary}</div>{" "} 68 - {/* Summary - word wrapping now enabled */} 206 + {event && format(event.start, "ha", [])} 207 + {!event && "No event"} 69 208 </div> 70 - )} 71 - <div 72 - style={{ 73 - display: "flex", 74 - textAlign: "right", // Right-align the time 75 - flex: "1", // Take some space, adjust as needed 76 - whiteSpace: "nowrap", // Keep time in one line 77 - }} 78 - > 79 - {event && format(event.start, "ha", [])} 80 - {!event && "No event"} 81 209 </div> 82 - </div> 83 - ); 84 - })} 210 + ); 211 + })} 212 + </div> 85 213 </div> 86 214 ); 87 215 } ··· 90 218 events: Event[], 91 219 startDate: Date, 92 220 endDate: Date, 221 + themeName: string = "light", 93 222 ) { 223 + // Get the theme 224 + const theme = themes[themeName] || defaultTheme; 225 + 226 + // Load font 94 227 const fontPath = "./static/fonts/Lazydog.ttf"; 95 228 const fontData = await Deno.readFile(fontPath); 96 229 230 + // Load background image 231 + let backgroundImageBase64 = undefined; 232 + try { 233 + const backgroundData = await Deno.readFile(theme.backgroundImagePath); 234 + // Use a safer way to convert binary data to base64 235 + backgroundImageBase64 = encodeBase64(backgroundData); 236 + } catch (error) { 237 + console.error( 238 + `Error loading background image: ${(error as Error).message}`, 239 + ); 240 + } 241 + 97 242 const svg = await satori( 98 - // deno-lint-ignore no-explicit-any 99 - WeeklySchedule(events, startDate, endDate) as any, 243 + WeeklySchedule( 244 + events, 245 + startDate, 246 + endDate, 247 + backgroundImageBase64, 248 + theme, 249 + // deno-lint-ignore no-explicit-any 250 + ) as any, 100 251 { 101 252 width: 375, 102 - // height: 800, 253 + height: 625, 103 254 fonts: [ 104 255 { 105 256 name: "Lazydog", ··· 111 262 112 263 return svg; 113 264 } 265 + 266 + // Helper function to safely encode binary data to base64 267 + function encodeBase64(data: Uint8Array): string { 268 + const chunks = []; 269 + const chunkSize = 1024; 270 + 271 + for (let i = 0; i < data.length; i += chunkSize) { 272 + const chunk = data.slice(i, i + chunkSize); 273 + chunks.push(String.fromCharCode.apply(null, [...chunk])); 274 + } 275 + 276 + return btoa(chunks.join("")); 277 + }
static/background.png

This is a binary file and will not be displayed.

+1
static/discord-icon.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50" width="50px" height="50px"><path d="M 18.90625 7 C 18.90625 7 12.539063 7.4375 8.375 10.78125 C 8.355469 10.789063 8.332031 10.800781 8.3125 10.8125 C 7.589844 11.480469 7.046875 12.515625 6.375 14 C 5.703125 15.484375 4.992188 17.394531 4.34375 19.53125 C 3.050781 23.808594 2 29.058594 2 34 C 1.996094 34.175781 2.039063 34.347656 2.125 34.5 C 3.585938 37.066406 6.273438 38.617188 8.78125 39.59375 C 11.289063 40.570313 13.605469 40.960938 14.78125 41 C 15.113281 41.011719 15.429688 40.859375 15.625 40.59375 L 18.0625 37.21875 C 20.027344 37.683594 22.332031 38 25 38 C 27.667969 38 29.972656 37.683594 31.9375 37.21875 L 34.375 40.59375 C 34.570313 40.859375 34.886719 41.011719 35.21875 41 C 36.394531 40.960938 38.710938 40.570313 41.21875 39.59375 C 43.726563 38.617188 46.414063 37.066406 47.875 34.5 C 47.960938 34.347656 48.003906 34.175781 48 34 C 48 29.058594 46.949219 23.808594 45.65625 19.53125 C 45.007813 17.394531 44.296875 15.484375 43.625 14 C 42.953125 12.515625 42.410156 11.480469 41.6875 10.8125 C 41.667969 10.800781 41.644531 10.789063 41.625 10.78125 C 37.460938 7.4375 31.09375 7 31.09375 7 C 31.019531 6.992188 30.949219 6.992188 30.875 7 C 30.527344 7.046875 30.234375 7.273438 30.09375 7.59375 C 30.09375 7.59375 29.753906 8.339844 29.53125 9.40625 C 27.582031 9.09375 25.941406 9 25 9 C 24.058594 9 22.417969 9.09375 20.46875 9.40625 C 20.246094 8.339844 19.90625 7.59375 19.90625 7.59375 C 19.734375 7.203125 19.332031 6.964844 18.90625 7 Z M 18.28125 9.15625 C 18.355469 9.359375 18.40625 9.550781 18.46875 9.78125 C 16.214844 10.304688 13.746094 11.160156 11.4375 12.59375 C 11.074219 12.746094 10.835938 13.097656 10.824219 13.492188 C 10.816406 13.882813 11.039063 14.246094 11.390625 14.417969 C 11.746094 14.585938 12.167969 14.535156 12.46875 14.28125 C 17.101563 11.410156 22.996094 11 25 11 C 27.003906 11 32.898438 11.410156 37.53125 14.28125 C 37.832031 14.535156 38.253906 14.585938 38.609375 14.417969 C 38.960938 14.246094 39.183594 13.882813 39.175781 13.492188 C 39.164063 13.097656 38.925781 12.746094 38.5625 12.59375 C 36.253906 11.160156 33.785156 10.304688 31.53125 9.78125 C 31.59375 9.550781 31.644531 9.359375 31.71875 9.15625 C 32.859375 9.296875 37.292969 9.894531 40.3125 12.28125 C 40.507813 12.460938 41.1875 13.460938 41.8125 14.84375 C 42.4375 16.226563 43.09375 18.027344 43.71875 20.09375 C 44.9375 24.125 45.921875 29.097656 45.96875 33.65625 C 44.832031 35.496094 42.699219 36.863281 40.5 37.71875 C 38.5 38.496094 36.632813 38.84375 35.65625 38.9375 L 33.96875 36.65625 C 34.828125 36.378906 35.601563 36.078125 36.28125 35.78125 C 38.804688 34.671875 40.15625 33.5 40.15625 33.5 C 40.570313 33.128906 40.605469 32.492188 40.234375 32.078125 C 39.863281 31.664063 39.226563 31.628906 38.8125 32 C 38.8125 32 37.765625 32.957031 35.46875 33.96875 C 34.625 34.339844 33.601563 34.707031 32.4375 35.03125 C 32.167969 35 31.898438 35.078125 31.6875 35.25 C 29.824219 35.703125 27.609375 36 25 36 C 22.371094 36 20.152344 35.675781 18.28125 35.21875 C 18.070313 35.078125 17.8125 35.019531 17.5625 35.0625 C 16.394531 34.738281 15.378906 34.339844 14.53125 33.96875 C 12.234375 32.957031 11.1875 32 11.1875 32 C 10.960938 31.789063 10.648438 31.699219 10.34375 31.75 C 9.957031 31.808594 9.636719 32.085938 9.53125 32.464844 C 9.421875 32.839844 9.546875 33.246094 9.84375 33.5 C 9.84375 33.5 11.195313 34.671875 13.71875 35.78125 C 14.398438 36.078125 15.171875 36.378906 16.03125 36.65625 L 14.34375 38.9375 C 13.367188 38.84375 11.5 38.496094 9.5 37.71875 C 7.300781 36.863281 5.167969 35.496094 4.03125 33.65625 C 4.078125 29.097656 5.0625 24.125 6.28125 20.09375 C 6.90625 18.027344 7.5625 16.226563 8.1875 14.84375 C 8.8125 13.460938 9.492188 12.460938 9.6875 12.28125 C 12.707031 9.894531 17.140625 9.296875 18.28125 9.15625 Z M 18.5 21 C 15.949219 21 14 23.316406 14 26 C 14 28.683594 15.949219 31 18.5 31 C 21.050781 31 23 28.683594 23 26 C 23 23.316406 21.050781 21 18.5 21 Z M 31.5 21 C 28.949219 21 27 23.316406 27 26 C 27 28.683594 28.949219 31 31.5 31 C 34.050781 31 36 28.683594 36 26 C 36 23.316406 34.050781 21 31.5 21 Z M 18.5 23 C 19.816406 23 21 24.265625 21 26 C 21 27.734375 19.816406 29 18.5 29 C 17.183594 29 16 27.734375 16 26 C 16 24.265625 17.183594 23 18.5 23 Z M 31.5 23 C 32.816406 23 34 24.265625 34 26 C 34 27.734375 32.816406 29 31.5 29 C 30.183594 29 29 27.734375 29 26 C 29 24.265625 30.183594 23 31.5 23 Z"/></svg>