export const theme = { bg: "#312652", fg: "#f0f0ff", primary: "#fabe32", }; export function meta({ file }: { file: MediaFile }) { if (file.path === "/") return { title: "clo's files" }; return { title: file.basename + " - clo's files" }; } // TODO: use rules.ts export const extensionToViewer: { [key: string]: (props: { file: MediaFile; siblingFiles?: MediaFile[]; }) => any; } = { ".html": IframeView, ".txt": TextView, ".md": TextView, ".mp4": VideoView, ".mkv": VideoView, ".webm": VideoView, ".avi": VideoView, ".mov": VideoView, ".mp3": AudioView, ".flac": AudioView, ".wav": AudioView, ".ogg": AudioView, ".m4a": AudioView, ".jpg": ImageView, ".jpeg": ImageView, ".png": ImageView, ".gif": ImageView, ".webp": ImageView, ".avif": ImageView, ".heic": ImageView, ".svg": ImageView, ".chat": ChatView, ".json": CodeView, ".jsonc": CodeView, ".toml": CodeView, ".ts": CodeView, ".js": CodeView, ".tsx": CodeView, ".jsx": CodeView, ".css": CodeView, ".py": CodeView, ".lua": CodeView, ".sh": CodeView, ".bat": CodeView, ".ps1": CodeView, ".cmd": CodeView, ".yaml": CodeView, ".yml": CodeView, ".xml": CodeView, ".zig": CodeView, ".astro": CodeView, ".mdx": CodeView, ".php": CodeView, ".diff": CodeView, ".patch": CodeView, }; export default function MediaList({ file, hasCotyledonCookie, }: { file: MediaFile; hasCotyledonCookie: boolean; }) { addScript("./clofi.client.ts"); const dirs: MediaFile[] = []; let dir: MediaFile | null = file; assert(dir); do { dirs.unshift(dir); dir = dir?.getParent(); } while (dir); const parts = file.path.split("/"); const isCotyledon = parseInt(parts[1]) < 2025; return (
{dirs.map((entry, i) => { const isLast = i === dirs.length - 1; return ( ); })}
); } const specialCaseViewers: Record = { "/2024/for everyone": ForEveryone, }; export function MediaPanel({ file, isLast, activeFilename, hasCotyledonCookie, }: { file: MediaFile; isLast: boolean; activeFilename: string | null; hasCotyledonCookie: boolean; }) { const showsActions = file.path !== "/2024/for everyone"; const label = file.path === "/" ? ( "clo's files" ) : ( <> {file.kind === MediaFileKind.directory ? ( {file.basename} / ) : ( <> {file.basename}
{showsActions && ( )} )} ); let View = specialCaseViewers[file.path] ?? (extensionToViewer[file.extension.toLowerCase()] as any) ?? DownloadView; if (View === VideoView && file.size > 500_000_000) { View = DownloadViewTooBig; } const mobileBtn = file.path !== "/" && ( ); const canvases: Record = { "/2017": "2017", "/2018": "2018", "/2019": "2019", "/2020": "2020", "/2021": "2021", "/2022": "2022", "/2023": "2023", "/2024": "2024", }; if (canvases[file.path]) { addScript(`file-viewer/canvas_${canvases[file.path]}.client.ts`); } return (
{canvases[file.path] && ( )} {file.kind === MediaFileKind.directory && (
{label}
)}
{mobileBtn} {
} {label}
{file.kind === MediaFileKind.directory ? ( ) : (
)}
); } const readmeFile = "readme.txt"; const priorityFiles = ["readme.txt", "index.html"]; function sorterFromDirSort(dirsort: string) { const array: string[] = JSON.parse(dirsort); if (array.length === 1 && array[0] === "a-z") { return sortAlphabetically; } return (a: MediaFile, b: MediaFile) => { const aIndex = array.indexOf(a.basename); const bIndex = array.indexOf(b.basename); if (bIndex == -1 && aIndex >= 0) return -1; if (aIndex == -1 && bIndex >= 0) return 1; if (aIndex >= 0 && bIndex >= 0) { return aIndex - bIndex; } return sortDefault(a, b); }; } function sortNumerically(a: MediaFile, b: MediaFile) { const n1 = parseInt(a.basenameWithoutExt); const n2 = parseInt(a.basenameWithoutExt); return n1 - n2; } function sortDefault(a: MediaFile, b: MediaFile) { // First check if either file is in the priority list const aIndex = priorityFiles.indexOf(a.basename.toLowerCase()); const bIndex = priorityFiles.indexOf(b.basename.toLowerCase()); if (aIndex !== -1 && bIndex !== -1) { // Both are priority files, sort by priority order return aIndex - bIndex; } else if (aIndex !== -1) { // Only a is a priority file return -1; } else if (bIndex !== -1) { // Only b is a priority file return 1; } // Then sort directories before files if (a.kind !== b.kind) { return a.kind === MediaFileKind.directory ? -1 : 1; } // Finally sort by date (newest first), then by name (a-z) if dates are the same const dateComparison = b.date.getTime() - a.date.getTime(); if (dateComparison !== 0) { return dateComparison; } // If dates are the same, sort alphabetically by name return a.basename.localeCompare(b.basename); } function sortAlphabetically(a: MediaFile, b: MediaFile) { // First check if either file is in the priority list const aIndex = priorityFiles.indexOf(a.basename.toLowerCase()); const bIndex = priorityFiles.indexOf(b.basename.toLowerCase()); if (aIndex !== -1 && bIndex !== -1) { // Both are priority files, sort by priority order return aIndex - bIndex; } else if (aIndex !== -1) { // Only a is a priority file return -1; } else if (bIndex !== -1) { // Only b is a priority file return 1; } // If dates are the same, sort alphabetically by name return a.basename.localeCompare(b.basename); } function isNumericallyOrdered(files: MediaFile[]) { return !files.some((x) => Number.isNaN(parseInt(x.basenameWithoutExt))); } function sortChronologicalStartToEnd(a: MediaFile, b: MediaFile) { const aIndex = priorityFiles.indexOf(a.basename.toLowerCase()); const bIndex = priorityFiles.indexOf(b.basename.toLowerCase()); if (aIndex !== -1 && bIndex !== -1) { return aIndex - bIndex; } else if (aIndex !== -1) { return -1; } else if (bIndex !== -1) { return 1; } const dateComparison = a.date.getTime() - b.date.getTime(); if (dateComparison !== 0) { return dateComparison; } return a.basename.localeCompare(b.basename); } const sortOverrides: Record number> = { "/2024": sortChronologicalStartToEnd, "/2023": sortChronologicalStartToEnd, "/2022": sortChronologicalStartToEnd, "/2021": sortChronologicalStartToEnd, "/2020": sortChronologicalStartToEnd, "/2019": sortChronologicalStartToEnd, "/2018": sortChronologicalStartToEnd, "/2017": sortChronologicalStartToEnd, }; function DirView({ dir, activeFilename, isLast, hasCotyledonCookie, }: { dir: MediaFile; activeFilename: string | null; isLast: boolean; hasCotyledonCookie: boolean; }) { if (dir.path === "/") { return ( ); } const isCotyledon = parseInt(dir.path.split("/")?.[1]) < 2025; const unsortedFiles = dir .getPublicChildren() .filter((f) => !f.basenameWithoutExt.startsWith("_unlisted")); const sorter = dir.dirsort ? sorterFromDirSort(dir.dirsort) : isNumericallyOrdered(unsortedFiles) ? sortNumerically : isCotyledon ? sortChronologicalStartToEnd : (sortOverrides[dir.path] ?? sortDefault); const sortedFiles = unsortedFiles.sort(sorter); const readme = sortedFiles.find((f) => f.basename === readmeFile); if (readme && isLast) activeFilename ||= readmeFile; const main = (
    {sortedFiles.map((file) => { return ( ); })}
); if (readme && isLast) { return (
{main}
); } return main; } const unknownDate = new Date("1970-01-03"); const unknownDateWithKnownYear = new Date("1970-02-20"); function ListItem({ file, active, noDate, }: { file: MediaFile; active: boolean; noDate?: boolean; }) { const dateTime = file.date; let shortDate = dateTime < unknownDateWithKnownYear ? ( dateTime < unknownDate ? ( "??.??.??" ) : <>xx.xx.{21 + Math.floor(dateTime.getTime() / 86400000)} ) : ( `${(dateTime.getMonth() + 1).toString().padStart(2, "0")}.${ dateTime .getDate() .toString() .padStart(2, "0") }.${dateTime.getFullYear().toString().slice(2)}` ); let basenameWithoutExt = file.basenameWithoutExt; let meta = file.kind === MediaFileKind.directory ? formatSize(file.size) : (file.duration ?? 0) > 0 ? formatDuration(file.duration!) : null; if (meta && dirname(file.path) !== "/") { meta = `(${meta})`; } const isReadme = file.basename === readmeFile; return (
  • {file.basename === readmeFile ? <>{!noDate &&
    } : <>{!noDate && {shortDate}}} {meta}
    {path.dirname(file.path) !== "/" && ( )} {basenameWithoutExt} {file.extension} {file.kind === MediaFileKind.directory ? / : ( "" )} {meta}
  • ); } function fileIcon(file: MediaFile, dirOpen?: boolean) { if (file.kind === MediaFileKind.directory) { return dirOpen ? "dir-open" : "dir"; } if (file.basename === "readme.txt") { return "readme"; } if (file.path === "/2024/for everyone") { return "snow"; } const ext = path.extname(file.basename).toLowerCase(); if ( ext === ".mp4" || ext === ".mkv" || ext === ".webm" || ext === ".avi" || ext === ".mov" ) { return "video"; } if ( ext === ".mp3" || ext === ".flac" || ext === ".wav" || ext === ".ogg" || ext === ".m4a" ) { return "audio"; } if ( ext === ".jpg" || ext === ".jpeg" || ext === ".png" || ext === ".gif" || ext === ".webp" || ext === ".avif" || ext === ".heic" || ext === ".svg" ) { return "image"; } if (ext === ".comp" || ext === ".fuse" || ext === ".setting") return "fusion"; if (ext === ".txt" || ext === ".md") return "text"; if (ext === ".html") return "webpage"; if (ext === ".blend") return "blend"; if ( ext === ".zip" || ext === ".rar" || ext === ".7z" || ext === ".tar" || ext === ".gz" || ext === ".bz2" || ext === ".xz" ) { return "archive"; } if (ext === ".lnk") return "link"; if (ext === ".chat") return "chat"; if ( ext === ".ts" || ext === ".js" || ext === ".tsx" || ext === ".jsx" || ext === ".css" || ext === ".py" || ext === ".lua" || ext === ".sh" || ext === ".bat" || ext === ".ps1" || ext === ".cmd" || ext === ".php" ) { return "code"; } if (ext === ".json" || ext === ".toml" || ext === ".yaml" || ext === ".yml") { return "json"; } return "file"; } function RootDirView({ dir, activeFilename, isLast, hasCotyledonCookie, }: { dir: MediaFile; activeFilename: string | null; isLast: boolean; hasCotyledonCookie: boolean; }) { const children = dir.getPublicChildren(); let readme: MediaFile | null = null; const groups = { // years 2025 and onwards years: [] as MediaFile[], // named categories categories: [] as MediaFile[], // years 2017 to 2024 cotyledon: [] as MediaFile[], }; const colorMap = { years: "#a2ff91", categories: "#9c91ff", cotyledon: "#ff91ca", }; for (const child of children) { const basename = child.basename; if (basename === readmeFile) { readme = child; continue; } const year = basename.match(/^(\d{4})/); if (year) { const n = parseInt(year[1]); if (n >= 2025) { groups.years.push(child); } else { groups.cotyledon.push(child); } } else { groups.categories.push(child); } } if (readme && isLast) activeFilename ||= readmeFile; const main = (
    {readme && (
    )} {Object.entries(groups).map(([key, files]) => { if (key === "cotyledon" && !hasCotyledonCookie) { return null; } if (key === "years" || key === "cotyledon") { files.sort((a, b) => { return b.basename.localeCompare(a.basename); }); } else { files.sort((a, b) => { return a.basename.localeCompare(b.basename); }); } return (

    {key}

      {files.map((file) => ( ))}
    ); })}
    ); if (readme && isLast) { return (
    cotyledon )} /> {main} {!hasCotyledonCookie && ( <> cotyledon )}
    ); } return main; } function TextView({ file, siblingFiles = [], }: { file: MediaFile; siblingFiles?: MediaFile[]; }) { const contents = file.contents; if (file.path.startsWith("/2021/phoenix-write/maps")) { const basename = file.basename; if (basename.includes("map") && basename.endsWith(".txt")) { return (
    
          );
        }
      }
    
      return (
        
     f.kind === MediaFileKind.file),
            ),
          }}
        >
    ); } function ChatView({ file }: { file: MediaFile }) { const contents = file.contents; return (
    ); } function CodeView({ file }: { file: MediaFile }) { const highlightedContents = file.contents; if (!highlightedContents) { if (file.size > 1_000_000) { return ; } return ; } return (
      );
    }
    
    function VideoView({ file }: { file: MediaFile }) {
      addScript("@/tags/hls-polyfill.client.ts");
      const dimensions = file.parseDimensions() ?? { width: 1920, height: 1080 };
      return (
        <>
          
        
      );
    }
    
    function AudioView({
      file,
      onlyAudio = false,
    }: {
      file: MediaFile;
      onlyAudio?: boolean;
    }) {
      const parent = file.getParent();
      let lyricsFile: MediaFile | null = null;
      if (parent && !onlyAudio) {
        const siblings = parent.getPublicChildren();
        // show lyrics only if
        // - this media file is the only audio file
        // - there is lyrics.txt in the same directory
        const audioFiles = siblings.filter(
          (f) =>
            f.kind === MediaFileKind.file &&
            extensionToViewer[path.extname(f.basename)] === AudioView,
        );
        if (
          audioFiles.length === 1 &&
          siblings.find((f) => f.basename === "lyrics.txt")
        ) {
          lyricsFile = siblings.find((f) => f.basename === "lyrics.txt")!;
        }
      }
      return (
        <>
          
          {lyricsFile && (
            

    lyrics

    )} ); } function ImageView({ file }: { file: MediaFile }) { return ( {file.basename} ); } function IframeView({ file }: { file: MediaFile }) { return ( <>