blob: fc2e3b92fc4c424ee11610157c98475398c343cb [file]
/*
* 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 &amp; 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>
);
}