Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(gateway): visualize dag-cbor and dag-json #315

Merged
merged 4 commits into from
May 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ The following emojis are used to highlight certain changes:
- New human-friendly error messages.
- Updated, higher-definition icons in directory listings.
- Customizable menu items next to "About IPFS" and "Install IPFS".
- Valid DAG-CBOR and DAG-JSON blocks now provide a preview, where links can be followed.

## [0.8.0] - 2023-04-05
### Added
Expand Down
7 changes: 4 additions & 3 deletions gateway/assets/assets.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,9 @@ type MenuItem struct {
}

type GlobalData struct {
Menu []MenuItem
Menu []MenuItem
GatewayURL string
DNSLink bool
}

type DagTemplateData struct {
Expand All @@ -95,6 +97,7 @@ type DagTemplateData struct {
CID string
CodecName string
CodecHex string
Node *ParsedNode
}

type ErrorTemplateData struct {
Expand All @@ -106,8 +109,6 @@ type ErrorTemplateData struct {

type DirectoryTemplateData struct {
GlobalData
GatewayURL string
DNSLink bool
Listing []DirectoryItem
Size string
Path string
Expand Down
35 changes: 33 additions & 2 deletions gateway/assets/dag.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@
<main id="main">
<header>
<div>
<strong>CID: <code translate="no">{{.CID}}</code></strong>
<strong>CID: <code translate="no">{{ .CID }}</code></strong>
</div>
<div>
<strong>Codec</strong>: <code translate="no">{{.CodecName}} ({{.CodecHex}})</code>
<strong>Codec</strong>: <code translate="no">{{ .CodecName }} ({{ .CodecHex }})</code>
</div>
</header>
<section class="container">
Expand All @@ -28,6 +28,37 @@
<li><a href="?format=dag-cbor" rel="nofollow">Valid DAG-CBOR</a> (specs at <a href="https://ipld.io/specs/codecs/dag-cbor/spec/" target="_blank" rel="noopener noreferrer">IPLD</a> and <a href="https://www.iana.org/assignments/media-types/application/vnd.ipld.dag-cbor" target="_blank" rel="noopener noreferrer">IANA</a>)</li>
</ul>
</section>
{{ with .Node }}
<section class="full-width">
<header>
<strong><span translate="no" style="text-transform: uppercase;">{{ $.CodecName }}</span> Preview</strong>
</header>
{{ template "node" (args $ .) }}
</section>
{{ end }}
</main>
</body>
</html>

{{ define "node" }}
{{ $root := index . 0 }}
{{ $node := index . 1 }}
{{ if len $node.Values }}
<div class="grid dag">
{{ range $index, $key := $node.Keys }}
{{ template "node" (args $root $key) }}
{{ template "node" (args $root (index $node.Values $index)) }}
{{ end }}
</div>
{{ else }}
<div translate="no">
{{ with $node.CID }}<a class="ipfs-hash" href={{ if $root.DNSLink }}"https://cid.ipfs.tech/#{{ . | urlEscape}}" target="_blank" rel="noreferrer noopener"{{ else }}"{{ $root.GatewayURL }}/ipfs/{{ . | urlEscape}}"{{ end }}>{{ end }}
{{ if $node.Long }}
<pre>{{- $node.Value -}}</pre>
{{ else }}
<code class="nowrap">{{- $node.Value -}}</code>
{{ end }}
{{ with $node.CID }}</a>{{ end }}
</div>
{{ end }}
{{ end }}
112 changes: 112 additions & 0 deletions gateway/assets/node.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package assets

import (
"encoding/hex"
"fmt"

"github.com/ipld/go-ipld-prime/datamodel"
cidlink "github.com/ipld/go-ipld-prime/linking/cid"
)

type ParsedNode struct {
Keys []*ParsedNode
Values []*ParsedNode
Value string
CID string
Long bool
}

func ParseNode(node datamodel.Node) (*ParsedNode, error) {
dag := &ParsedNode{}

switch node.Kind() {
case datamodel.Kind_Map:
it := node.MapIterator()

for !it.Done() {
k, v, err := it.Next()
if err != nil {
return nil, err
}

kd, err := ParseNode(k)
if err != nil {
return nil, err
}

vd, err := ParseNode(v)
if err != nil {
return nil, err
}

dag.Keys = append(dag.Keys, kd)
dag.Values = append(dag.Values, vd)
}
case datamodel.Kind_List:
it := node.ListIterator()
for !it.Done() {
k, v, err := it.Next()
if err != nil {
return nil, err
}

vd, err := ParseNode(v)
if err != nil {
return nil, err
}

dag.Keys = append(dag.Keys, &ParsedNode{Value: fmt.Sprintf("%d", k)})
dag.Values = append(dag.Values, vd)
}
case datamodel.Kind_Bool:
v, err := node.AsBool()
if err != nil {
return nil, err
}
dag.Value = fmt.Sprintf("%t", v)
case datamodel.Kind_Int:
v, err := node.AsInt()
if err != nil {
return nil, err
}
dag.Value = fmt.Sprintf("%d", v)
case datamodel.Kind_Float:
v, err := node.AsFloat()
if err != nil {
return nil, err
}
dag.Value = fmt.Sprintf("%f", v)
case datamodel.Kind_String:
v, err := node.AsString()
if err != nil {
return nil, err
}
dag.Value = v
case datamodel.Kind_Bytes:
v, err := node.AsBytes()
if err != nil {
return nil, err
}
dag.Long = true
dag.Value = hex.Dump(v)
case datamodel.Kind_Link:
lnk, err := node.AsLink()
if err != nil {
return nil, err
}
dag.Value = lnk.String()

cl, isCid := lnk.(cidlink.Link)
if isCid {
dag.CID = cl.Cid.String()
}
case datamodel.Kind_Invalid:
dag.Value = "INVALID"
case datamodel.Kind_Null:
dag.Value = "NULL"
default:
dag.Value = "UNKNOWN"
}

return dag, nil
}
45 changes: 41 additions & 4 deletions gateway/assets/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -144,13 +144,22 @@ main section:not(:last-child) {
border-bottom: 1px solid var(--dark-white);
}

main section header {
background-color: var(--near-white);
}

.grid {
display: grid;
overflow-x: auto;
}

.grid .grid {
overflow-x: visible;
}

.grid > div {
padding: .7em;
border-top: 1px solid var(--dark-white);
border-bottom: 1px solid var(--dark-white);
}

.grid.dir {
Expand All @@ -165,8 +174,8 @@ main section:not(:last-child) {
padding-right: 1em;
}

.grid.dir > div:nth-child(-n+4) {
border-top: 0;
.grid.dir > div:nth-last-child(-n+4) {
border-bottom: 0;
}

.grid.dir > div:nth-of-type(8n+5),
Expand All @@ -176,6 +185,35 @@ main section:not(:last-child) {
background-color: var(--near-white);
}

.grid.dag {
grid-template-columns: max-content 1fr;
}

.grid.dag pre {
margin: 0;
}

.grid.dag .grid {
padding: 0;
}

.grid.dag > div:nth-last-child(-n+2) {
border-bottom: 0;
}

.grid.dag > div {
background: white
}

.grid.dag > div:nth-child(4n),
.grid.dag > div:nth-child(4n+3) {
background-color: var(--near-white);
}

section > .grid.dag > div:nth-of-type(2n+1) {
padding-left: 1em;
}

.type-icon,
.type-icon > * {
width: 1.15em
Expand Down Expand Up @@ -222,4 +260,3 @@ main section:not(:last-child) {
display: none;
}
}

6 changes: 6 additions & 0 deletions gateway/assets/templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,15 @@ func iconFromExt(filename string) string {
return "ipfs-_blank" // Default is blank icon.
}

// args is a helper function to allow sending more than one object to a template.
func args(args ...interface{}) []interface{} {
return args
}

var funcMap = template.FuncMap{
"iconFromExt": iconFromExt,
"urlEscape": urlEscape,
"args": args,
}

func readFile(fs fs.FS, filename string) ([]byte, error) {
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading