diff --git a/web/src/pages/admin/theme/admin-chrome.css b/web/src/pages/admin/theme/admin-chrome.css index cf3dc8a9a..a47aff090 100644 --- a/web/src/pages/admin/theme/admin-chrome.css +++ b/web/src/pages/admin/theme/admin-chrome.css @@ -974,7 +974,9 @@ @media (prefers-reduced-motion: reduce) { [data-admin-root] *, body.admin-overlays * { - transition: none !important; - animation: none !important; + transition-duration: 0.01ms !important; + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; } } + diff --git a/web/src/pages/plugins/plugin-documentation-modals.tsx b/web/src/pages/plugins/plugin-documentation-modals.tsx index dc28f7c4e..735cf27d3 100644 --- a/web/src/pages/plugins/plugin-documentation-modals.tsx +++ b/web/src/pages/plugins/plugin-documentation-modals.tsx @@ -12,11 +12,28 @@ import "./plugins.css"; type UploadPluginModalProps = { open: boolean; onClose: () => void; - onUpload: (file: File) => void; + onUpload: (file: File) => void | Promise; }; +const MINIMAL_MANIFEST_SNIPPET = `{ + "apiVersion": "yingce.plugin/v1", + "id": "my-custom-plugin", + "name": "我的自定义插件", + "version": "1.0.0", + "author": "Developer", + "description": "通过统一清单扩展平台功能", + "permissions": ["generation.run", "media.read"], + "contributes": { + "providers": [], + "workflows": [] + } +}`; + export function UploadPluginModal({ open, onClose, onUpload }: UploadPluginModalProps) { + const [activeTab, setActiveTab] = useState<"install" | "guide">("install"); const [isDraggingPlugin, setIsDraggingPlugin] = useState(false); + const [uploading, setUploading] = useState(false); + const [copied, setCopied] = useState(false); const dragDepth = useRef(0); const handlePluginDragEnter = (event: React.DragEvent) => { @@ -40,22 +57,74 @@ export function UploadPluginModal({ open, onClose, onUpload }: UploadPluginModal setIsDraggingPlugin(false); }; + const handleExecuteUpload = async (file: File) => { + setUploading(true); + try { + await onUpload(file); + } finally { + setUploading(false); + } + }; + + const copySnippet = () => { + void navigator.clipboard?.writeText(MINIMAL_MANIFEST_SNIPPET).then(() => { + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }); + }; + + const isCompact = activeTab === "install"; + return ( -
-
- -
- -
+ +
+ 初次制作插件? + +
+ + ) : ( +
+
+ + 以下为开发者技术规范与清单编写参考(只读文档)。安装插件请切换至「安装插件包」。 +
+
+ +
+
+ )}
); } diff --git a/web/src/pages/plugins/plugins.css b/web/src/pages/plugins/plugins.css index 6e7b3a7f1..aa4d373ee 100644 --- a/web/src/pages/plugins/plugins.css +++ b/web/src/pages/plugins/plugins.css @@ -485,7 +485,135 @@ } .workspace-modal.plugin-upload-modal { - width: min(1480px, calc(100vw - var(--space-4))) !important; + width: min(1120px, calc(100vw - var(--space-4))) !important; +} + +.workspace-modal.plugin-upload-modal.plugin-upload-modal-compact { + width: min(620px, calc(100vw - var(--space-4))) !important; +} + +.plugin-upload-tabs-bar { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: var(--space-4); + padding-bottom: var(--space-3); + border-bottom: 1px solid var(--workspace-border); +} + +.plugin-upload-tabs-nav { + display: inline-flex; + align-items: center; + padding: 3px; + border-radius: var(--r-md); + background: color-mix(in srgb, var(--foreground) 6%, transparent); + gap: 2px; +} + +.plugin-upload-tab-btn { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 5px 12px; + border: none; + border-radius: var(--r-sm); + background: transparent; + color: color-mix(in srgb, var(--foreground) 60%, transparent); + font-size: var(--fs-label); + font-weight: 550; + cursor: pointer; + transition: all 0.18s ease; +} + +.plugin-upload-tab-btn:hover { + color: var(--foreground); +} + +.plugin-upload-tab-btn.is-active { + background: var(--workspace-surface-strong, #ffffff); + color: var(--workspace-accent, #1677ff); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); +} + +.plugin-upload-badge { + display: inline-flex; + align-items: center; + gap: 5px; + font-size: var(--fs-caption); + color: color-mix(in srgb, var(--foreground) 50%, transparent); +} + +.plugin-upload-copy-btn { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 4px 10px; + border: 1px solid var(--workspace-border); + border-radius: var(--r-sm); + background: transparent; + color: color-mix(in srgb, var(--foreground) 75%, transparent); + font-size: var(--fs-caption); + cursor: pointer; + transition: all 0.15s ease; +} + +.plugin-upload-copy-btn:hover { + border-color: var(--workspace-accent); + color: var(--workspace-accent); +} + +.plugin-upload-install-view { + display: flex; + flex-direction: column; + gap: var(--space-4); +} + +.plugin-upload-dropzone-shell.is-uploading { + pointer-events: none; + opacity: 0.85; +} + +.plugin-upload-switch-guide-hint { + display: flex; + align-items: center; + justify-content: center; + gap: 6px; + font-size: var(--fs-label); + color: color-mix(in srgb, var(--foreground) 45%, transparent); + margin-top: var(--space-1); +} + +.plugin-upload-link-btn { + background: transparent; + border: none; + padding: 0; + color: var(--workspace-accent, #1677ff); + cursor: pointer; + text-decoration: underline; + text-underline-offset: 3px; + font-size: inherit; +} + +.plugin-upload-link-btn:hover { + opacity: 0.8; +} + +.plugin-upload-guide-view { + display: flex; + flex-direction: column; + gap: var(--space-3); +} + +.plugin-upload-guide-banner { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 12px; + border-radius: var(--r-md); + background: color-mix(in srgb, var(--workspace-accent) 8%, transparent); + border: 1px solid color-mix(in srgb, var(--workspace-accent) 20%, transparent); + font-size: var(--fs-label); + color: color-mix(in srgb, var(--foreground) 75%, transparent); } .plugin-upload-layout {