| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| import React, { useEffect, useState } from "react"; |
| import Layout from "@theme/Layout"; |
| import CodeBlock from "@theme/CodeBlock"; |
| import Link from "@docusaurus/Link"; |
| import { useLocation } from "@docusaurus/router"; |
| import styles from "./styles.module.css"; |
| |
| // Renders inline `code` spans inside an otherwise plain doc comment so the |
| // reference table stays readable without pulling in a full markdown renderer. |
| function renderComment(text) { |
| if (!text) return null; |
| const parts = text.split(/(`[^`]+`)/g); |
| return parts.map((part, i) => { |
| if (part.startsWith("`") && part.endsWith("`") && part.length > 1) { |
| return <code key={i}>{part.slice(1, -1)}</code>; |
| } |
| return <span key={i}>{part}</span>; |
| }); |
| } |
| |
| // One group's worth of options, rendered as a table. Default/example values |
| // (extracted from the config source) surface as chips next to the key. |
| function ConfigTable({ rows }) { |
| return ( |
| <div className={styles.tableWrap}> |
| <table className={styles.table}> |
| <thead> |
| <tr> |
| <th>Key</th> |
| <th>Type</th> |
| <th>Required</th> |
| <th>Description</th> |
| </tr> |
| </thead> |
| <tbody> |
| {rows.map((c) => ( |
| <tr key={c.name} className={c.deprecated ? styles.deprecatedRow : ""}> |
| <td> |
| <code>{c.name}</code> |
| {c.deprecated && <span className={styles.badge}>deprecated</span>} |
| {c.default != null && ( |
| <span className={styles.chip}> |
| default <code>{c.default}</code> |
| </span> |
| )} |
| {c.default == null && c.example != null && ( |
| <span className={styles.chip}> |
| e.g. <code>{c.example}</code> |
| </span> |
| )} |
| </td> |
| <td>{c.type}</td> |
| <td>{c.required ? "yes" : "no"}</td> |
| <td className={styles.desc}> |
| {renderComment(c.comments)} |
| {c.deprecated && c.deprecated.note && ( |
| <div className={styles.deprecatedNote}> |
| Deprecated |
| {c.deprecated.since ? ` since ${c.deprecated.since}` : ""}:{" "} |
| {renderComment(c.deprecated.note)} |
| </div> |
| )} |
| </td> |
| </tr> |
| ))} |
| </tbody> |
| </table> |
| </div> |
| ); |
| } |
| |
| // The deep-link routes (`/services/<scheme>/<binding>`) share this component |
| // with the bare service page; the trailing segment, when it names one of the |
| // available bindings, selects the initial tab. |
| function bindingFromPath(pathname, scheme, available) { |
| const segments = pathname.split("/").filter(Boolean); |
| const idx = segments.lastIndexOf(scheme); |
| const tail = idx >= 0 ? segments[idx + 1] : undefined; |
| return tail && available.includes(tail) ? tail : undefined; |
| } |
| |
| export default function ServicePage({ data }) { |
| const { bindings, service } = data; |
| const location = useLocation(); |
| |
| const labelOf = Object.fromEntries(bindings.map((b) => [b.id, b.label])); |
| const available = service.examples.map((e) => e.binding); |
| const initial = |
| bindingFromPath(location.pathname, service.scheme, available) || |
| available[0]; |
| |
| const [active, setActive] = useState(initial); |
| useEffect(() => { |
| setActive(initial); |
| // Re-sync when navigating between deep-link pages on the client. |
| }, [location.pathname]); |
| |
| const example = service.examples.find((e) => e.binding === active); |
| |
| const title = `${service.scheme} | Services`; |
| const description = `Configuration reference and copy-paste setup for the OpenDAL ${service.scheme} service.`; |
| |
| return ( |
| <Layout title={title} description={description}> |
| <div className={styles.page}> |
| <nav className={styles.breadcrumb} aria-label="Breadcrumb"> |
| <Link to="/services">Services</Link> |
| <span aria-hidden="true"> / </span> |
| <span>{service.scheme}</span> |
| </nav> |
| |
| <header className={styles.header}> |
| <h1 className={styles.title}>{service.scheme}</h1> |
| <p className={styles.subtitle}> |
| {service.configs.length} configuration option |
| {service.configs.length === 1 ? "" : "s"} ยท available in{" "} |
| {available.map((id) => labelOf[id] || id).join(", ")} |
| </p> |
| </header> |
| |
| {example && ( |
| <section className={styles.section}> |
| <div |
| className={styles.tabs} |
| role="tablist" |
| aria-label="Choose a binding" |
| > |
| {service.examples.map((e) => ( |
| <button |
| key={e.binding} |
| type="button" |
| role="tab" |
| aria-selected={e.binding === active} |
| className={`${styles.tab} ${ |
| e.binding === active ? styles.tabActive : "" |
| }`} |
| onClick={() => setActive(e.binding)} |
| > |
| {labelOf[e.binding] || e.binding} |
| </button> |
| ))} |
| </div> |
| |
| <CodeBlock language={example.language} title="Quick start"> |
| {example.minimal} |
| </CodeBlock> |
| |
| <details className={styles.details}> |
| <summary>All configuration options (copy & trim)</summary> |
| <CodeBlock language={example.language} title="Full reference"> |
| {example.full} |
| </CodeBlock> |
| </details> |
| |
| <p className={styles.hint}> |
| Every option is passed as a string key; OpenDAL parses it into the |
| right type. Some services may require building the binding with the |
| matching <code>services-*</code> feature enabled. |
| </p> |
| </section> |
| )} |
| |
| <section className={styles.section}> |
| <h2>Configuration reference</h2> |
| {service.configs.length === 0 ? ( |
| <p>This service takes no configuration options.</p> |
| ) : ( |
| (service.groups.length ? service.groups : ["General"]).map( |
| (group, i) => { |
| const rows = service.configs.filter( |
| (c) => (c.group || "General") === group |
| ); |
| if (rows.length === 0) return null; |
| // First group expanded; later (advanced) groups collapsed so the |
| // page opens focused on the essentials. |
| return ( |
| <details |
| key={group} |
| className={styles.group} |
| open={i === 0} |
| > |
| <summary className={styles.groupSummary}> |
| {group} |
| <span className={styles.groupCount}>{rows.length}</span> |
| </summary> |
| <ConfigTable rows={rows} /> |
| </details> |
| ); |
| } |
| ) |
| )} |
| </section> |
| </div> |
| </Layout> |
| ); |
| } |