mirror of
https://github.com/wavetermdev/waveterm.git
synced 2024-12-21 16:38:23 +01:00
quick media viewer (using new hmac read-file urls) (#451)
This commit is contained in:
parent
697f7c0758
commit
0f2f6c9cc0
7
src/plugins/media/media.less
Normal file
7
src/plugins/media/media.less
Normal file
@ -0,0 +1,7 @@
|
||||
.media-renderer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-top: var(--termpad);
|
||||
}
|
49
src/plugins/media/media.tsx
Normal file
49
src/plugins/media/media.tsx
Normal file
@ -0,0 +1,49 @@
|
||||
// Copyright 2024, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import * as React from "react";
|
||||
import * as mobx from "mobx";
|
||||
import * as mobxReact from "mobx-react";
|
||||
import { GlobalModel } from "@/models";
|
||||
|
||||
import "./media.less";
|
||||
|
||||
@mobxReact.observer
|
||||
class SimpleMediaRenderer extends React.Component<
|
||||
{ data: ExtBlob; context: RendererContext; opts: RendererOpts; savedHeight: number; lineState: LineStateType },
|
||||
{}
|
||||
> {
|
||||
objUrl: string = null;
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.objUrl != null) {
|
||||
URL.revokeObjectURL(this.objUrl);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
let dataBlob = this.props.data;
|
||||
if (dataBlob == null || dataBlob.notFound) {
|
||||
return (
|
||||
<div className="media-renderer" style={{ fontSize: this.props.opts.termFontSize }}>
|
||||
<div className="load-error-text">
|
||||
ERROR: file {dataBlob && dataBlob.name ? JSON.stringify(dataBlob.name) : ""} not found
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
let videoUrl = GlobalModel.getBaseHostPort() + this.props.lineState["wave:fileurl"];
|
||||
const opts = this.props.opts;
|
||||
const maxHeight = opts.maxSize.height - 10;
|
||||
const maxWidth = opts.maxSize.width - 10;
|
||||
return (
|
||||
<div className="media-renderer">
|
||||
<video width="320" height="240" controls>
|
||||
<source src={videoUrl} />
|
||||
</video>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export { SimpleMediaRenderer };
|
@ -32,7 +32,7 @@ class SimplePdfRenderer extends React.Component<
|
||||
);
|
||||
}
|
||||
if (this.objUrl == null) {
|
||||
const pdfBlob = new File([dataBlob], "test.pdf", { type: "application/pdf" });
|
||||
const pdfBlob = new File([dataBlob], dataBlob.name ?? "file.pdf", { type: "application/pdf" });
|
||||
this.objUrl = URL.createObjectURL(pdfBlob);
|
||||
}
|
||||
const opts = this.props.opts;
|
||||
|
@ -8,6 +8,7 @@ import { SimpleMustacheRenderer } from "./mustache/mustache";
|
||||
import { CSVRenderer } from "./csv/csv";
|
||||
import { OpenAIRenderer, OpenAIRendererModel } from "./openai/openai";
|
||||
import { SimplePdfRenderer } from "./pdf/pdf";
|
||||
import { SimpleMediaRenderer } from "./media/media";
|
||||
import { isBlank } from "@/util/util";
|
||||
import { sprintf } from "sprintf-js";
|
||||
|
||||
@ -89,6 +90,16 @@ const PluginConfigs: RendererPluginType[] = [
|
||||
mimeTypes: ["application/pdf"],
|
||||
simpleComponent: SimplePdfRenderer,
|
||||
},
|
||||
{
|
||||
name: "media",
|
||||
rendererType: "simple",
|
||||
heightType: "pixels",
|
||||
dataType: "blob",
|
||||
collapseType: "hide",
|
||||
globalCss: null,
|
||||
mimeTypes: ["video/*", "audio/*"],
|
||||
simpleComponent: SimpleMediaRenderer,
|
||||
},
|
||||
];
|
||||
|
||||
class PluginModelClass {
|
||||
|
@ -37,6 +37,7 @@ import (
|
||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/comp"
|
||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/dbutil"
|
||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/pcloud"
|
||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/promptenc"
|
||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/releasechecker"
|
||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/remote"
|
||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/remote/openai"
|
||||
@ -280,6 +281,7 @@ func init() {
|
||||
registerCmdFn("mdview", MarkdownViewCommand)
|
||||
registerCmdFn("markdownview", MarkdownViewCommand)
|
||||
registerCmdFn("pdfview", PdfViewCommand)
|
||||
registerCmdFn("mediaview", MediaViewCommand)
|
||||
|
||||
registerCmdFn("csvview", CSVViewCommand)
|
||||
}
|
||||
@ -4916,6 +4918,58 @@ func PdfViewCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbu
|
||||
return update, nil
|
||||
}
|
||||
|
||||
func MakeReadFileUrl(screenId string, lineId string, filePath string) (string, error) {
|
||||
qvals := make(url.Values)
|
||||
qvals.Set("screenid", screenId)
|
||||
qvals.Set("lineid", lineId)
|
||||
qvals.Set("path", filePath)
|
||||
qvals.Set("nonce", uuid.New().String())
|
||||
hmacStr, err := promptenc.ComputeUrlHmac([]byte(scbase.WaveAuthKey), "/api/read-file", qvals)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error computing hmac-url: %v", err)
|
||||
}
|
||||
qvals.Set("hmac", hmacStr)
|
||||
return "/api/read-file?" + qvals.Encode(), nil
|
||||
}
|
||||
|
||||
func MediaViewCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) {
|
||||
if len(pk.Args) == 0 {
|
||||
return nil, fmt.Errorf("%s requires an argument (file name)", GetCmdStr(pk))
|
||||
}
|
||||
// TODO more error checking on filename format?
|
||||
if pk.Args[0] == "" {
|
||||
return nil, fmt.Errorf("%s argument cannot be empty", GetCmdStr(pk))
|
||||
}
|
||||
fileName := pk.Args[0]
|
||||
ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen|R_RemoteConnected)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
outputStr := fmt.Sprintf("%s %q", GetCmdStr(pk), fileName)
|
||||
cmd, err := makeStaticCmd(ctx, GetCmdStr(pk), ids, pk.GetRawStr(), []byte(outputStr))
|
||||
if err != nil {
|
||||
// TODO tricky error since the command was a success, but we can't show the output
|
||||
return nil, err
|
||||
}
|
||||
// compute hmac read-file URL
|
||||
readFileUrl, err := MakeReadFileUrl(ids.ScreenId, cmd.LineId, fileName)
|
||||
if err != nil {
|
||||
// TODO tricky error since the command was a success, but we can't show the output
|
||||
return nil, fmt.Errorf("error making read-file url: %v", err)
|
||||
}
|
||||
// set the line state
|
||||
lineState := make(map[string]any)
|
||||
lineState[sstore.LineState_FileUrl] = readFileUrl
|
||||
lineState[sstore.LineState_File] = fileName
|
||||
update, err := addLineForCmd(ctx, "/"+GetCmdStr(pk), false, ids, cmd, "media", lineState)
|
||||
if err != nil {
|
||||
// TODO tricky error since the command was a success, but we can't show the output
|
||||
return nil, err
|
||||
}
|
||||
update.AddUpdate(sstore.InteractiveUpdate(pk.Interactive))
|
||||
return update, nil
|
||||
}
|
||||
|
||||
func MarkdownViewCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) {
|
||||
if len(pk.Args) == 0 {
|
||||
return nil, fmt.Errorf("%s requires an argument (file name)", GetCmdStr(pk))
|
||||
|
@ -36,6 +36,7 @@ var BareMetaCmds = []BareMetaCmdDecl{
|
||||
{"mdview", "markdownview"},
|
||||
{"csvview", "csvview"},
|
||||
{"pdfview", "pdfview"},
|
||||
{"mediaview", "mediaview"},
|
||||
}
|
||||
|
||||
const (
|
||||
|
@ -63,6 +63,7 @@ const (
|
||||
const (
|
||||
LineState_Source = "prompt:source"
|
||||
LineState_File = "prompt:file"
|
||||
LineState_FileUrl = "wave:fileurl"
|
||||
LineState_Min = "wave:min"
|
||||
LineState_Template = "template"
|
||||
LineState_Mode = "mode"
|
||||
|
Loading…
Reference in New Issue
Block a user