UNPKG

@t1mmen/srtd

Version:

Supabase Repeatable Template Definitions (srtd): 🪄 Live-reloading SQL templates for Supabase DX. Make your database changes reviewable and migrations maintainable! 🚀

140 lines • 7.96 kB
// src/components/watch.tsx import path from 'node:path'; import { Spinner } from '@inkjs/ui'; import figures from 'figures'; import { Box, Text, useInput } from 'ink'; import React, { useMemo } from 'react'; import Branding from '../components/Branding.js'; import Quittable from '../components/Quittable.js'; import { StatBadge } from '../components/StatBadge.js'; import { TimeSince } from '../components/TimeSince.js'; import { COLOR_ACCENT, COLOR_ERROR, COLOR_SUCCESS, COLOR_WARNING, } from '../components/customTheme.js'; import { useFullscreen } from '../hooks/useFullscreen.js'; import { useTemplateManager } from '../hooks/useTemplateManager.js'; import { store } from '../utils/store.js'; const MAX_FILES = 10; const MAX_CHANGES = 15; const PATH_DISPLAY_LENGTH = 15; function formatTemplateDisplay(templatePath, templateDir) { const parts = templatePath.split(path.sep); const filename = parts.pop() || ''; const dirPath = parts.join(path.sep); if (dirPath && templateDir && dirPath.includes(templateDir)) { const relativePath = dirPath.substring(dirPath.indexOf(templateDir) + templateDir.length + 1); if (relativePath) { const truncatedPath = relativePath.slice(-PATH_DISPLAY_LENGTH); return `${truncatedPath}/${filename}`; } } return filename; } const TemplateRow = React.memo(({ template, isLatest, templateDir, }) => { const displayName = formatTemplateDisplay(template.path, templateDir ?? ''); const needsBuild = !template.buildState.lastBuildDate || template.currentHash !== template.buildState.lastBuildHash; const color = template.buildState.lastAppliedError ? COLOR_ERROR : COLOR_SUCCESS; return (React.createElement(Box, { marginLeft: 2, gap: 1 }, React.createElement(Box, { width: 2, justifyContent: "center" }, React.createElement(Text, { color: color }, template.buildState.lastAppliedError ? figures.cross : isLatest ? figures.play : figures.tick)), React.createElement(Box, { width: 35 }, React.createElement(Text, null, displayName)), React.createElement(Box, { minWidth: 18 }, React.createElement(Text, { dimColor: true }, "applied ", React.createElement(TimeSince, { date: template.buildState.lastAppliedDate }))), React.createElement(Box, null, React.createElement(Text, { dimColor: true }, template.wip ? (React.createElement(React.Fragment, null, "wip")) : needsBuild ? (React.createElement(React.Fragment, null, "needs build")) : (React.createElement(React.Fragment, null, "built ", React.createElement(TimeSince, { date: template.buildState.lastBuildDate }), " ago")))))); }); TemplateRow.displayName = 'TemplateRow'; const UpdateLog = React.memo(({ updates, templateDir, }) => { const sortedUpdates = useMemo(() => { return [...updates] .sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()) // Newest first .slice(0, MAX_CHANGES) // Take most recent .reverse(); // Display oldest to newest }, [updates]); const formatError = (error) => { if (error instanceof Error) return error.message; if (typeof error === 'string') return error; if (typeof error === 'object' && error && 'error' in error) { return String(error.error); } return String(error); }; return (React.createElement(Box, { flexDirection: "column", marginTop: 1 }, React.createElement(Text, { bold: true }, "Changelog:"), !sortedUpdates.length && React.createElement(Spinner, { label: "No updates yet, watching..." }), sortedUpdates.map(update => (React.createElement(Box, { key: `${update.template.path}-${update.timestamp}`, marginLeft: 2 }, React.createElement(Text, { color: update.type === 'error' ? COLOR_ERROR : update.type === 'applied' ? COLOR_SUCCESS : COLOR_ACCENT }, update.type === 'error' ? figures.cross : update.type === 'applied' ? figures.play : figures.info, ' ', formatTemplateDisplay(update.template.path, templateDir ?? ''), ":", ' ', update.type === 'error' ? formatError(update.error) : update.type === 'applied' ? 'applied successfully' : 'changed')))))); }); UpdateLog.displayName = 'UpdateLog'; export default function Watch() { useFullscreen(); const { templates, updates, stats, isLoading, errors, latestPath, templateDir } = useTemplateManager(); const [showUpdates, setShowUpdates] = React.useState(store.get('showWatchLogs')); const activeTemplates = useMemo(() => templates.slice(-MAX_FILES), [templates]); const hasErrors = errors.size > 0; useInput(input => { if (input === 'u') { const show = !showUpdates; setShowUpdates(show); store.set('showWatchLogs', show); } }); return (React.createElement(Box, { flexDirection: "column" }, React.createElement(Branding, { subtitle: "\uD83D\uDC40 Watch Mode" }), React.createElement(Box, { marginBottom: 1 }, React.createElement(StatBadge, { label: "Total", value: stats.total, color: COLOR_SUCCESS }), stats.needsBuild > 0 && (React.createElement(StatBadge, { label: "Needs Build", value: stats.needsBuild, color: COLOR_WARNING })), stats.recentlyChanged > 0 && (React.createElement(StatBadge, { label: "Recent Changes", value: stats.recentlyChanged, color: COLOR_ACCENT })), hasErrors && React.createElement(StatBadge, { label: "Errors", value: stats.errors, color: COLOR_ERROR })), isLoading ? (React.createElement(Text, null, "\uD83D\uDD0D Finding templates...")) : (React.createElement(Box, { flexDirection: "column" }, React.createElement(Text, { bold: true }, "Recently modified templates:"), activeTemplates.length === 0 && React.createElement(Text, { dimColor: true }, "No templates found"), activeTemplates.map(template => (React.createElement(TemplateRow, { key: template.path, template: template, isLatest: template.path === latestPath, templateDir: templateDir }))), showUpdates && React.createElement(UpdateLog, { updates: updates, templateDir: templateDir }), hasErrors && (React.createElement(Box, { flexDirection: "column", marginTop: 1 }, React.createElement(Text, { bold: true, color: COLOR_ERROR }, "Errors:"), Array.from(errors.entries()).map(([path, error]) => (React.createElement(Box, { key: path, marginLeft: 2, marginTop: 1 }, React.createElement(Text, { color: COLOR_ERROR, wrap: "wrap" }, formatTemplateDisplay(path, templateDir ?? ''), ": ", String(error))))))))), React.createElement(Box, { marginY: 1, flexDirection: "row", gap: 1 }, React.createElement(Quittable, null), React.createElement(React.Fragment, null, React.createElement(Box, { marginY: 1 }, React.createElement(Text, { dimColor: true }, "\u2022")), React.createElement(Box, { marginY: 1 }, React.createElement(Text, { dimColor: true }, "Press "), React.createElement(Text, { underline: showUpdates }, "u"), React.createElement(Text, { dimColor: true }, " to toggle updates")))))); } //# sourceMappingURL=watch.js.map