mirror of
https://github.com/glassflame/glassflame.github.io.git
synced 2024-11-25 17:44:26 +00:00
Another iteration!
This commit is contained in:
parent
45c3afd7d4
commit
93b91c5b6c
11 changed files with 481 additions and 178 deletions
|
@ -7,7 +7,7 @@
|
||||||
<inspection_tool class="HtmlUnknownTag" enabled="true" level="WARNING" enabled_by_default="true">
|
<inspection_tool class="HtmlUnknownTag" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
<option name="myValues">
|
<option name="myValues">
|
||||||
<value>
|
<value>
|
||||||
<list size="9">
|
<list size="11">
|
||||||
<item index="0" class="java.lang.String" itemvalue="nobr" />
|
<item index="0" class="java.lang.String" itemvalue="nobr" />
|
||||||
<item index="1" class="java.lang.String" itemvalue="noembed" />
|
<item index="1" class="java.lang.String" itemvalue="noembed" />
|
||||||
<item index="2" class="java.lang.String" itemvalue="comment" />
|
<item index="2" class="java.lang.String" itemvalue="comment" />
|
||||||
|
@ -17,6 +17,8 @@
|
||||||
<item index="6" class="java.lang.String" itemvalue="x-card" />
|
<item index="6" class="java.lang.String" itemvalue="x-card" />
|
||||||
<item index="7" class="java.lang.String" itemvalue="x-hashtag" />
|
<item index="7" class="java.lang.String" itemvalue="x-hashtag" />
|
||||||
<item index="8" class="java.lang.String" itemvalue="x-node-file" />
|
<item index="8" class="java.lang.String" itemvalue="x-node-file" />
|
||||||
|
<item index="9" class="java.lang.String" itemvalue="x-canvas" />
|
||||||
|
<item index="10" class="java.lang.String" itemvalue="x-display" />
|
||||||
</list>
|
</list>
|
||||||
</value>
|
</value>
|
||||||
</option>
|
</option>
|
||||||
|
|
118
index.html
118
index.html
|
@ -8,6 +8,24 @@
|
||||||
@import "style/light.css";
|
@import "style/light.css";
|
||||||
@import "style/dark.css";
|
@import "style/dark.css";
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--color-background: #ffffff;
|
||||||
|
--color-foreground: #000000;
|
||||||
|
--color-accent: #ff7f00;
|
||||||
|
|
||||||
|
--color-gray: #7E7E7E;
|
||||||
|
--color-red: #FB464C;
|
||||||
|
--color-orange: #E9973F;
|
||||||
|
--color-yellow: #E0DE71;
|
||||||
|
--color-green: #44CF6E;
|
||||||
|
--color-blue: #53DFDD;
|
||||||
|
--color-purple: #A882FF;
|
||||||
|
|
||||||
|
--edge-width: 2px;
|
||||||
|
--node-group-border-width: 2px;
|
||||||
|
--node-file-border-width: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
||||||
|
@ -17,47 +35,101 @@
|
||||||
</style>
|
</style>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||||
<script type="module" src="src/index.mjs"></script>
|
<script type="module" src="src/index.mjs"></script>
|
||||||
<template id="template-node-file">
|
<template id="template-display">
|
||||||
|
<slot name="display-container"></slot>
|
||||||
|
</template>
|
||||||
|
<template id="template-node-group">
|
||||||
<style>
|
<style>
|
||||||
.node {
|
.node-group {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
--color-node: var(--color-gray);
|
||||||
|
outline: var(--node-group-border-width) solid var(--color-node);
|
||||||
|
background-color: color-mix(in srgb, var(--color-node) 20%, var(--color-background));
|
||||||
|
border-radius: 0 8px 8px 8px;
|
||||||
|
padding: 12px;
|
||||||
|
|
||||||
|
overflow-x: visible;
|
||||||
|
overflow-y: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.node-group-label {
|
||||||
|
position: relative;
|
||||||
|
bottom: 14px;
|
||||||
|
left: -12px;
|
||||||
|
transform: translateY(-100%);
|
||||||
|
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
outline: var(--node-group-border-width) solid var(--color-node);
|
||||||
|
background-color: color-mix(in srgb, var(--color-node) 20%, var(--color-background));
|
||||||
|
border-radius: 8px 8px 0 0;
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-group-label h1 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<section class="node-group">
|
||||||
|
<aside class="node-group-label">
|
||||||
|
<h1><slot name="node-label">{Group label}</slot></h1>
|
||||||
|
</aside>
|
||||||
|
<slot name="node-contents">{Node contents}</slot>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
<template id="template-node-file">
|
||||||
|
<style>
|
||||||
.node-file {
|
.node-file {
|
||||||
outline-width: 2px;
|
position: absolute;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
--color-node: var(--color-gray);
|
||||||
|
outline: var(--node-file-border-width) solid var(--color-node);
|
||||||
|
background-color: color-mix(in srgb, var(--color-node) 10%, var(--color-background));
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
|
|
||||||
overflow-x: clip;
|
overflow-x: clip;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
.node-empty {
|
|
||||||
outline-style: dashed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.node-full {
|
|
||||||
outline-style: solid;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
<article class="node node-file">
|
<article class="node-file">
|
||||||
<h1>
|
<h1>
|
||||||
<slot name="node-title">Node title</slot>
|
<slot name="node-title">{Node title}</slot>
|
||||||
</h1>
|
</h1>
|
||||||
<slot name="node-contents">Node contents</slot>
|
<slot name="node-contents">{Node contents}</slot>
|
||||||
</article>
|
</article>
|
||||||
</template>
|
</template>
|
||||||
<template id="template-markdown">
|
<template id="template-node-text">
|
||||||
<slot name="markdown-contents"></slot>
|
|
||||||
</template>
|
|
||||||
<template id="template-canvas">
|
|
||||||
<style>
|
<style>
|
||||||
.canvas {
|
.node-text {
|
||||||
|
position: absolute;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
--color-node: var(--color-gray);
|
||||||
|
outline: var(--node-file-border-width) solid var(--color-node);
|
||||||
|
background-color: color-mix(in srgb, var(--color-node) 10%, var(--color-background));
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
|
||||||
|
overflow-x: clip;
|
||||||
|
overflow-y: scroll;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<div class="canvas"></div>
|
<article class="node-text">
|
||||||
|
<slot name="node-contents">{Node contents}</slot>
|
||||||
|
</article>
|
||||||
|
</template>
|
||||||
|
<template id="template-edge">
|
||||||
|
<slot name="edge-svg">{Edge SVG}</slot>
|
||||||
|
</template>
|
||||||
|
<template id="template-markdown">
|
||||||
|
<slot name="markdown-contents">{Markdown text}</slot>
|
||||||
|
</template>
|
||||||
|
<template id="template-canvas">
|
||||||
|
<slot name="canvas-contents">{Canvas}</slot>
|
||||||
</template>
|
</template>
|
||||||
<template id="template-wikilink">
|
<template id="template-wikilink">
|
||||||
<style>
|
<style>
|
||||||
|
@ -67,7 +139,7 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<a class="wikilink"><slot name="wikilink-text">Wikilink text</slot></a>
|
<a class="wikilink"><slot name="wikilink-text">{Wikilink text}</slot></a>
|
||||||
</template>
|
</template>
|
||||||
<template id="template-hashtag">
|
<template id="template-hashtag">
|
||||||
<style>
|
<style>
|
||||||
|
@ -76,10 +148,10 @@
|
||||||
color: var(--color-accent);
|
color: var(--color-accent);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<span class="hashtag"><slot name="hashtag-text">#Hashtag</slot></span>
|
<span class="hashtag"><slot name="hashtag-text">{#Hashtag}</slot></span>
|
||||||
</template>
|
</template>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<x-canvas contents=""/>
|
<x-display root vref="https://raw.githubusercontent.com/Steffo99/appunti-magistrali/main/" wref="8 - Crittografia applicata/1 - Concetti/3 - Casualità ed entropia/★ mappa concettuale.canvas"/>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
|
@ -1,25 +1,45 @@
|
||||||
|
import { fileDetails } from "../utils/file.mjs";
|
||||||
|
|
||||||
|
|
||||||
export class CanvasElement extends HTMLElement {
|
export class CanvasElement extends HTMLElement {
|
||||||
|
/**
|
||||||
|
* Return the closest {@link CanvasElement} ancestor in the tree.
|
||||||
|
*
|
||||||
|
* @param initial {HTMLElement} The element to start the search from.
|
||||||
|
*/
|
||||||
|
static findFirstCanvasAncestor(initial) {
|
||||||
|
let current = initial
|
||||||
|
while(current) {
|
||||||
|
if(current instanceof ShadowRoot) {
|
||||||
|
current = current.host
|
||||||
|
}
|
||||||
|
if(current instanceof CanvasElement) {
|
||||||
|
return current
|
||||||
|
}
|
||||||
|
current = current.parentNode
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
static getTemplate() {
|
static getTemplate() {
|
||||||
return document.getElementById("template-canvas")
|
return document.getElementById("template-canvas")
|
||||||
}
|
}
|
||||||
|
|
||||||
parsedJSON
|
parsedJSON
|
||||||
|
|
||||||
canvasElement
|
contentsSlotted
|
||||||
nodeElements = []
|
nodeElements = {}
|
||||||
edgeElements = []
|
edgeElements = {}
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super()
|
|
||||||
this.parsedJSON = JSON.parse(this.getAttribute("contents"))
|
|
||||||
}
|
|
||||||
|
|
||||||
// noinspection JSUnusedGlobalSymbols
|
// noinspection JSUnusedGlobalSymbols
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
const instanceDocument = CanvasElement.getTemplate().content.cloneNode(true)
|
const instanceDocument = CanvasElement.getTemplate().content.cloneNode(true)
|
||||||
const shadow = this.attachShadow({ mode: "open" })
|
const shadow = this.attachShadow({ mode: "open" })
|
||||||
|
|
||||||
this.canvasElement = instanceDocument.querySelector(".canvas")
|
this.parsedJSON = JSON.parse(this.getAttribute("contents"))
|
||||||
|
|
||||||
|
this.contentsSlotted = document.createElement("div")
|
||||||
|
this.contentsSlotted.slot = "canvas-contents"
|
||||||
|
|
||||||
let minX = { x: Infinity, width: 0 }
|
let minX = { x: Infinity, width: 0 }
|
||||||
let minY = { y: Infinity, height: 0 }
|
let minY = { y: Infinity, height: 0 }
|
||||||
|
@ -27,41 +47,95 @@ export class CanvasElement extends HTMLElement {
|
||||||
let maxY = { y: -Infinity, height: 0 }
|
let maxY = { y: -Infinity, height: 0 }
|
||||||
|
|
||||||
for(const node of this.parsedJSON["nodes"]) {
|
for(const node of this.parsedJSON["nodes"]) {
|
||||||
if(node["type"] === "file") {
|
if(node["x"] < minX["x"]) minX = node
|
||||||
if(node["x"] < minX["x"]) minX = node
|
if(node["y"] < minY["y"]) minY = node
|
||||||
if(node["y"] < minY["y"]) minY = node
|
if(node["x"] + node["width"] > maxX["x"] + node["width"]) maxX = node
|
||||||
if(node["x"] + node["width"] > maxX["x"] + node["width"]) maxX = node
|
if(node["y"] + node["height"] > maxY["y"] + node["height"]) maxY = node
|
||||||
if(node["y"] + node["height"] > maxY["y"] + node["height"]) maxY = node
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
console.warn("Encountered node of unimplemented type: ", node["type"])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.debug("minX:", minX, "| minY:", minY, "| maxX:", maxX, "| maxY:", maxY)
|
|
||||||
|
|
||||||
for(const node of this.parsedJSON["nodes"]) {
|
for(const node of this.parsedJSON["nodes"]) {
|
||||||
if(node["type"] === "file") {
|
const element = document.createElement(`x-node-${node["type"]}`)
|
||||||
const element = document.createElement("x-node-file")
|
|
||||||
element.setAttribute("file", node["file"])
|
|
||||||
element.setAttribute("id", node["id"])
|
|
||||||
element.setAttribute("x", node["x"] - minX["x"])
|
|
||||||
element.setAttribute("y", node["y"] - minY["y"])
|
|
||||||
element.setAttribute("width", node["width"])
|
|
||||||
element.setAttribute("height", node["height"])
|
|
||||||
element.setAttribute("color", node["color"])
|
|
||||||
|
|
||||||
this.nodeElements.push(element)
|
element.setAttribute("id", node["id"])
|
||||||
this.canvasElement.appendChild(element)
|
element.setAttribute("x", node["x"] - minX["x"])
|
||||||
}
|
element.setAttribute("y", node["y"] - minY["y"])
|
||||||
else {
|
element.setAttribute("width", node["width"])
|
||||||
console.warn("Encountered node of unimplemented type: ", node["type"])
|
element.setAttribute("height", node["height"])
|
||||||
|
element.setAttribute("color", node["color"])
|
||||||
|
|
||||||
|
switch(node["type"]) {
|
||||||
|
case "text":
|
||||||
|
element.setAttribute("text", node["text"])
|
||||||
|
break
|
||||||
|
|
||||||
|
case "file":
|
||||||
|
element.setAttribute("file", node["file"])
|
||||||
|
element.setAttribute("fileName", fileDetails(node["file"])[0])
|
||||||
|
break
|
||||||
|
|
||||||
|
case "group":
|
||||||
|
element.setAttribute("label", node["label"])
|
||||||
|
break
|
||||||
|
|
||||||
|
default:
|
||||||
|
console.warn("Encountered node of unimplemented type: ", node["type"])
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.nodeElements[node["id"]] = element
|
||||||
|
this.contentsSlotted.appendChild(element)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.canvasElement.style["width"] = `${maxX["x"] + maxX["width"] - minX["x"]}px`
|
for(const edge of this.parsedJSON["edges"]) {
|
||||||
this.canvasElement.style["height"] = `${maxY["y"] + maxY["height"] - minY["y"]}px`
|
const element = document.createElement("x-edge")
|
||||||
|
element.setAttribute("id", edge["id"])
|
||||||
|
element.setAttribute("node-from", edge["fromNode"])
|
||||||
|
element.setAttribute("node-from-side", edge["fromSide"])
|
||||||
|
element.setAttribute("node-to", edge["toNode"])
|
||||||
|
element.setAttribute("node-to-side", edge["toSide"])
|
||||||
|
element.setAttribute("color", edge["color"])
|
||||||
|
element.setAttribute("arrows", edge["toEnd"])
|
||||||
|
|
||||||
|
this.edgeElements[edge["id"]] = element
|
||||||
|
this.contentsSlotted.appendChild(element)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(Object.values(this.nodeElements))
|
||||||
|
|
||||||
|
this.contentsSlotted.style["width"] = `${maxX["x"] + maxX["width"] - minX["x"]}px`
|
||||||
|
this.contentsSlotted.style["height"] = `${maxY["y"] + maxY["height"] - minY["y"]}px`
|
||||||
|
|
||||||
|
this.appendChild(this.contentsSlotted)
|
||||||
|
|
||||||
shadow.appendChild(instanceDocument)
|
shadow.appendChild(instanceDocument)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Element representing the generic skeleton of an Obsidian Canvas item.
|
||||||
|
*/
|
||||||
|
export class CanvasItemElement extends HTMLElement {
|
||||||
|
colorToHex() {
|
||||||
|
const color = this.getAttribute("color")
|
||||||
|
|
||||||
|
if(color?.startsWith("#")) {
|
||||||
|
// This is an hex color
|
||||||
|
return color
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// TODO: Check which colors correspond to what
|
||||||
|
return {
|
||||||
|
[undefined]: "var(--color-gray)",
|
||||||
|
"undefined": "var(--color-gray)",
|
||||||
|
"0": "var(--color-gray)",
|
||||||
|
"1": "var(--color-red)",
|
||||||
|
"2": "var(--color-orange)",
|
||||||
|
"3": "var(--color-yellow)",
|
||||||
|
"4": "var(--color-green)",
|
||||||
|
"5": "var(--color-blue)",
|
||||||
|
"6": "var(--color-purple)",
|
||||||
|
}[color]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
82
src/elements/display.mjs
Normal file
82
src/elements/display.mjs
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
import { fileDetails } from "../utils/file.mjs";
|
||||||
|
import { CanvasElement } from "./canvas.mjs";
|
||||||
|
import { MarkdownElement } from "./markdown.mjs";
|
||||||
|
import { FetchError } from "./node.mjs";
|
||||||
|
|
||||||
|
|
||||||
|
export class DisplayElement extends HTMLElement {
|
||||||
|
/**
|
||||||
|
* Return the closest {@link DisplayElement} ancestor in the tree.
|
||||||
|
*
|
||||||
|
* @param initial {HTMLElement} The element to start the search from.
|
||||||
|
*/
|
||||||
|
static findFirstDisplayAncestor(initial) {
|
||||||
|
let current = initial
|
||||||
|
while(current) {
|
||||||
|
if(current instanceof ShadowRoot) {
|
||||||
|
current = current.host
|
||||||
|
}
|
||||||
|
if(current instanceof DisplayElement) {
|
||||||
|
return current
|
||||||
|
}
|
||||||
|
current = current.parentNode
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
static getTemplate() {
|
||||||
|
return document.getElementById("template-display")
|
||||||
|
}
|
||||||
|
|
||||||
|
containerSlotted
|
||||||
|
loadButton
|
||||||
|
|
||||||
|
// noinspection JSUnusedGlobalSymbols
|
||||||
|
connectedCallback() {
|
||||||
|
const instanceDocument = DisplayElement.getTemplate().content.cloneNode(true)
|
||||||
|
const shadow = this.attachShadow({ mode: "open" })
|
||||||
|
|
||||||
|
this.containerSlotted = document.createElement("div")
|
||||||
|
this.containerSlotted.slot = "display-container"
|
||||||
|
this.loadButton = document.createElement("button")
|
||||||
|
this.loadButton.innerText = "Load"
|
||||||
|
this.loadButton.addEventListener("click", this.load.bind(this))
|
||||||
|
this.containerSlotted.appendChild(this.loadButton)
|
||||||
|
this.appendChild(this.containerSlotted)
|
||||||
|
|
||||||
|
shadow.appendChild(instanceDocument)
|
||||||
|
}
|
||||||
|
|
||||||
|
data
|
||||||
|
|
||||||
|
async fetchData() {
|
||||||
|
const vref = this.getAttribute("vref")
|
||||||
|
const wref = this.getAttribute("wref")
|
||||||
|
const url = new URL(wref, vref)
|
||||||
|
|
||||||
|
const response = await fetch(url, {})
|
||||||
|
|
||||||
|
if(!response.ok) throw new FetchError(response, "Fetch response is not ok")
|
||||||
|
|
||||||
|
this.data = await response.text()
|
||||||
|
}
|
||||||
|
|
||||||
|
async load() {
|
||||||
|
this.loadButton.disabled = true
|
||||||
|
|
||||||
|
await this.fetchData()
|
||||||
|
|
||||||
|
this.containerSlotted.remove()
|
||||||
|
this.containerSlotted = null
|
||||||
|
|
||||||
|
const [, fileExtension] = fileDetails(this.getAttribute("wref"))
|
||||||
|
|
||||||
|
this.containerSlotted = document.createElement({
|
||||||
|
"md": customElements.getName(MarkdownElement),
|
||||||
|
"canvas": this.getAttribute("root") !== undefined ? customElements.getName(CanvasElement) : "div",
|
||||||
|
}[fileExtension] ?? "div")
|
||||||
|
this.containerSlotted.slot = "display-container"
|
||||||
|
this.containerSlotted.setAttribute("contents", this.data)
|
||||||
|
this.appendChild(this.containerSlotted)
|
||||||
|
}
|
||||||
|
}
|
54
src/elements/edge.mjs
Normal file
54
src/elements/edge.mjs
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
import { CanvasElement, CanvasItemElement } from "./canvas.mjs";
|
||||||
|
|
||||||
|
|
||||||
|
export class EdgeElement extends CanvasItemElement {
|
||||||
|
static getTemplate() {
|
||||||
|
return document.getElementById("template-edge")
|
||||||
|
}
|
||||||
|
|
||||||
|
svgSlotted
|
||||||
|
lineElement
|
||||||
|
|
||||||
|
// noinspection JSUnusedGlobalSymbols
|
||||||
|
connectedCallback() {
|
||||||
|
const instanceDocument = EdgeElement.getTemplate().content.cloneNode(true)
|
||||||
|
const shadow = this.attachShadow({ mode: "open" })
|
||||||
|
|
||||||
|
const canvas = CanvasElement.findFirstCanvasAncestor(this)
|
||||||
|
|
||||||
|
const fromNode = canvas.nodeElements[this.getAttribute("node-from")]
|
||||||
|
const fromSide = this.getAttribute("node-from-side")
|
||||||
|
const [x1, y1] = fromNode.getCenterCoordinatesOfSide(fromSide)
|
||||||
|
|
||||||
|
const toNode = canvas.nodeElements[this.getAttribute("node-to")]
|
||||||
|
const toSide = this.getAttribute("node-to-side")
|
||||||
|
const [x2, y2] = toNode.getCenterCoordinatesOfSide(toSide)
|
||||||
|
|
||||||
|
const minX = Math.min(x1, x2)
|
||||||
|
const minY = Math.min(y1, y2)
|
||||||
|
const diffX = Math.abs(x1 - x2)
|
||||||
|
const diffY = Math.abs(y1 - y2)
|
||||||
|
|
||||||
|
const arrows = this.getAttribute("arrows") ?? "end"
|
||||||
|
|
||||||
|
this.svgSlotted = document.createElementNS("http://www.w3.org/2000/svg", "svg")
|
||||||
|
this.svgSlotted.slot = "edge-svg"
|
||||||
|
this.svgSlotted.style.setProperty("position", "absolute")
|
||||||
|
this.svgSlotted.style.setProperty("left", "0")
|
||||||
|
this.svgSlotted.style.setProperty("top", "0")
|
||||||
|
this.svgSlotted.style.setProperty("overflow", "visible")
|
||||||
|
|
||||||
|
this.lineElement = document.createElementNS("http://www.w3.org/2000/svg", "line")
|
||||||
|
this.lineElement.setAttribute("x1", x1)
|
||||||
|
this.lineElement.setAttribute("y1", y1)
|
||||||
|
this.lineElement.setAttribute("x2", x2)
|
||||||
|
this.lineElement.setAttribute("y2", y2)
|
||||||
|
this.lineElement.style.setProperty("stroke", this.colorToHex())
|
||||||
|
this.lineElement.style.setProperty("stroke-width", "var(--edge-width)")
|
||||||
|
|
||||||
|
this.svgSlotted.appendChild(this.lineElement)
|
||||||
|
this.appendChild(this.svgSlotted)
|
||||||
|
|
||||||
|
shadow.appendChild(instanceDocument)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,5 @@
|
||||||
export {NodeFileElement} from "./node.mjs"
|
export {NodeFileElement, NodeGroupElement, NodeTextElement} from "./node.mjs"
|
||||||
export {MarkdownElement, HashtagElement, WikilinkElement} from "./markdown.mjs"
|
export {MarkdownElement, HashtagElement, WikilinkElement} from "./markdown.mjs"
|
||||||
export {CanvasElement} from "./canvas.mjs"
|
export {CanvasElement} from "./canvas.mjs"
|
||||||
|
export {DisplayElement} from "./display.mjs"
|
||||||
|
export {EdgeElement} from "./edge.mjs"
|
|
@ -1,38 +1,7 @@
|
||||||
import { configFromWindow } from "../config.mjs";
|
import { CanvasItemElement } from "./canvas.mjs";
|
||||||
|
import { DisplayElement } from "./display.mjs";
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Element representing the generic skeleton of an Obsidian Canvas node.
|
|
||||||
*/
|
|
||||||
export class NodeElement extends HTMLElement {
|
|
||||||
x
|
|
||||||
y
|
|
||||||
width
|
|
||||||
height
|
|
||||||
color
|
|
||||||
|
|
||||||
// noinspection JSUnusedGlobalSymbols
|
|
||||||
connectedCallback() {
|
|
||||||
this.id = this.getAttribute("id")
|
|
||||||
this.x = this.getAttribute("x")
|
|
||||||
this.y = this.getAttribute("y")
|
|
||||||
this.width = this.getAttribute("width")
|
|
||||||
this.height = this.getAttribute("height")
|
|
||||||
this.color = this.getAttribute("color")
|
|
||||||
}
|
|
||||||
|
|
||||||
colorToHex() {
|
|
||||||
if(this?.color?.startsWith("#")) {
|
|
||||||
// This is an hex color
|
|
||||||
return this.color
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// TODO: Check which colors correspond to what
|
|
||||||
return {}[this.color]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Error in the fetching of a file.
|
* Error in the fetching of a file.
|
||||||
*/
|
*/
|
||||||
|
@ -48,114 +17,130 @@ export class FetchError extends Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Element representing the skeleton of an Obsidian Canvas node pointing to a file.
|
export class NodeElement extends CanvasItemElement {
|
||||||
*
|
getCenterCoordinatesOfSide(side) {
|
||||||
* Requires the following attributes:
|
switch(side) {
|
||||||
* - `file`: wref to the target file
|
case "top":
|
||||||
* - `id`: id unique to the node
|
return [
|
||||||
* - `x`: horizontal translation
|
Number(this.getAttribute("x")) + Number(this.getAttribute("width")) / 2,
|
||||||
* - `y`: vertical translation
|
Number(this.getAttribute("y")),
|
||||||
* - `width`: width of the card in px
|
]
|
||||||
* - `height`: height of the card in px
|
case "bottom":
|
||||||
* - (optional) `color`: custom Obsidian color of the card
|
return [
|
||||||
*/
|
Number(this.getAttribute("x")) + Number(this.getAttribute("width")) / 2,
|
||||||
|
Number(this.getAttribute("y")) + Number(this.getAttribute("height")),
|
||||||
|
]
|
||||||
|
case "left":
|
||||||
|
return [
|
||||||
|
Number(this.getAttribute("x")),
|
||||||
|
Number(this.getAttribute("y")) + Number(this.getAttribute("height")) / 2,
|
||||||
|
]
|
||||||
|
case "right":
|
||||||
|
return [
|
||||||
|
Number(this.getAttribute("x")) + Number(this.getAttribute("width")),
|
||||||
|
Number(this.getAttribute("y")) + Number(this.getAttribute("height")) / 2,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export class NodeGroupElement extends NodeElement {
|
||||||
|
static getTemplate() {
|
||||||
|
return document.getElementById("template-node-group")
|
||||||
|
}
|
||||||
|
|
||||||
|
instanceElement
|
||||||
|
labelSlotted
|
||||||
|
|
||||||
|
// noinspection JSUnusedGlobalSymbols
|
||||||
|
connectedCallback() {
|
||||||
|
const instanceDocument = NodeGroupElement.getTemplate().content.cloneNode(true)
|
||||||
|
const shadow = this.attachShadow({ mode: "open" })
|
||||||
|
|
||||||
|
this.instanceElement = instanceDocument.querySelector(".node-group")
|
||||||
|
|
||||||
|
this.instanceElement.style.setProperty("left", `${this.getAttribute("x")}px`)
|
||||||
|
this.instanceElement.style.setProperty("top", `${this.getAttribute("y")}px`)
|
||||||
|
this.instanceElement.style.setProperty("width", `${this.getAttribute("width")}px`)
|
||||||
|
this.instanceElement.style.setProperty("height", `${this.getAttribute("height")}px`)
|
||||||
|
this.instanceElement.style.setProperty("--color-node", this.colorToHex())
|
||||||
|
|
||||||
|
this.labelSlotted = document.createElement("span")
|
||||||
|
this.labelSlotted.slot = "node-label"
|
||||||
|
this.labelSlotted.innerText = this.getAttribute("label")
|
||||||
|
this.appendChild(this.labelSlotted)
|
||||||
|
|
||||||
|
shadow.appendChild(instanceDocument)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export class NodeFileElement extends NodeElement {
|
export class NodeFileElement extends NodeElement {
|
||||||
static type = "file"
|
|
||||||
|
|
||||||
file
|
|
||||||
fileName
|
|
||||||
fileExtension
|
|
||||||
|
|
||||||
fileLeaf() {
|
|
||||||
return this.file.split("/").at(-1)
|
|
||||||
}
|
|
||||||
|
|
||||||
fileDetails() {
|
|
||||||
const split = this.fileLeaf().split(".")
|
|
||||||
const name = split.slice(0, -1)
|
|
||||||
const extension = split.at(-1)
|
|
||||||
return [name, extension]
|
|
||||||
}
|
|
||||||
|
|
||||||
static getTemplate() {
|
static getTemplate() {
|
||||||
return document.getElementById("template-node-file")
|
return document.getElementById("template-node-file")
|
||||||
}
|
}
|
||||||
|
|
||||||
instanceElement
|
instanceElement
|
||||||
nameSlotted
|
nameSlotted
|
||||||
placeholderSlotted
|
|
||||||
contentsSlotted
|
contentsSlotted
|
||||||
loadButton
|
|
||||||
|
|
||||||
// noinspection JSUnusedGlobalSymbols
|
// noinspection JSUnusedGlobalSymbols
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
super.connectedCallback()
|
|
||||||
this.file = this.getAttribute("file")
|
|
||||||
const [fileName, fileExtension] = this.fileDetails()
|
|
||||||
this.fileName = fileName
|
|
||||||
this.fileExtension = fileExtension
|
|
||||||
|
|
||||||
const instanceDocument = NodeFileElement.getTemplate().content.cloneNode(true)
|
const instanceDocument = NodeFileElement.getTemplate().content.cloneNode(true)
|
||||||
const shadow = this.attachShadow({ mode: "open" })
|
const shadow = this.attachShadow({ mode: "open" })
|
||||||
|
|
||||||
this.instanceElement = instanceDocument.querySelector(".node.node-file")
|
this.instanceElement = instanceDocument.querySelector(".node-file")
|
||||||
|
|
||||||
this.instanceElement.style["left"] = `${this.x}px`
|
this.instanceElement.style.setProperty("left", `${this.getAttribute("x")}px`)
|
||||||
this.instanceElement.style["top"] = `${this.y}px`
|
this.instanceElement.style.setProperty("top", `${this.getAttribute("y")}px`)
|
||||||
this.instanceElement.style["width"] = `${this.width}px`
|
this.instanceElement.style.setProperty("width", `${this.getAttribute("width")}px`)
|
||||||
this.instanceElement.style["height"] = `${this.height}px`
|
this.instanceElement.style.setProperty("height", `${this.getAttribute("height")}px`)
|
||||||
this.instanceElement.style["--node-color"] = this.colorToHex()
|
this.instanceElement.style.setProperty("--color-node", this.colorToHex())
|
||||||
|
|
||||||
this.instanceElement.classList.add("node-empty")
|
|
||||||
this.instanceElement.classList.remove("node-full")
|
|
||||||
|
|
||||||
this.nameSlotted = document.createElement("span")
|
this.nameSlotted = document.createElement("span")
|
||||||
this.nameSlotted.slot = "node-title"
|
this.nameSlotted.slot = "node-title"
|
||||||
this.nameSlotted.innerText = this.fileName
|
this.nameSlotted.innerText = this.getAttribute("fileName")
|
||||||
this.appendChild(this.nameSlotted)
|
this.appendChild(this.nameSlotted)
|
||||||
|
|
||||||
this.placeholderSlotted = document.createElement("div")
|
this.contentsSlotted = document.createElement(customElements.getName(DisplayElement))
|
||||||
this.placeholderSlotted.slot = "node-contents"
|
this.contentsSlotted.slot = "node-contents"
|
||||||
this.loadButton = document.createElement("button")
|
|
||||||
this.loadButton.innerText = "Load"
|
const firstDisplayAncestor = DisplayElement.findFirstDisplayAncestor(this)
|
||||||
this.loadButton.addEventListener("click", this.fillNode.bind(this))
|
this.contentsSlotted.setAttribute("vref", firstDisplayAncestor.getAttribute("vref"))
|
||||||
this.placeholderSlotted.appendChild(this.loadButton)
|
this.contentsSlotted.setAttribute("wref", this.getAttribute("file"))
|
||||||
this.appendChild(this.placeholderSlotted)
|
this.appendChild(this.contentsSlotted)
|
||||||
|
|
||||||
shadow.appendChild(instanceDocument)
|
shadow.appendChild(instanceDocument)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
contents
|
export class NodeTextElement extends NodeElement {
|
||||||
|
static getTemplate() {
|
||||||
async fetchContents() {
|
return document.getElementById("template-node-text")
|
||||||
console.info("Fetching:", this.file)
|
|
||||||
|
|
||||||
const url = new URL(this.file, configFromWindow()["vault"])
|
|
||||||
const response = await fetch(url, {})
|
|
||||||
|
|
||||||
if(!response.ok) throw new FetchError(response, "Fetch response is not ok")
|
|
||||||
|
|
||||||
this.contents = await response.text()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fillNode() {
|
instanceElement
|
||||||
this.loadButton.disabled = true
|
contentsSlotted
|
||||||
|
|
||||||
this.placeholderSlotted.remove()
|
// noinspection JSUnusedGlobalSymbols
|
||||||
this.placeholderSlotted = null
|
connectedCallback() {
|
||||||
|
const instanceDocument = NodeTextElement.getTemplate().content.cloneNode(true)
|
||||||
|
const shadow = this.attachShadow({ mode: "open" })
|
||||||
|
|
||||||
await this.fetchContents()
|
this.instanceElement = instanceDocument.querySelector(".node-text")
|
||||||
|
|
||||||
this.instanceElement.classList.remove("node-empty")
|
this.instanceElement.style.setProperty("left", `${this.getAttribute("x")}px`)
|
||||||
this.instanceElement.classList.add("node-full")
|
this.instanceElement.style.setProperty("top", `${this.getAttribute("y")}px`)
|
||||||
|
this.instanceElement.style.setProperty("width", `${this.getAttribute("width")}px`)
|
||||||
|
this.instanceElement.style.setProperty("height", `${this.getAttribute("height")}px`)
|
||||||
|
this.instanceElement.style.setProperty("--color-node", this.colorToHex())
|
||||||
|
|
||||||
this.contentsSlotted = document.createElement({
|
this.contentsSlotted = document.createElement("x-markdown")
|
||||||
"md": "x-markdown",
|
|
||||||
"canvas": "x-canvas",
|
|
||||||
}[this.fileExtension] ?? "div")
|
|
||||||
this.contentsSlotted.slot = "node-contents"
|
this.contentsSlotted.slot = "node-contents"
|
||||||
this.contentsSlotted.setAttribute("contents", this.contents)
|
this.contentsSlotted.setAttribute("contents", this.getAttribute("text"))
|
||||||
this.appendChild(this.contentsSlotted)
|
|
||||||
|
shadow.appendChild(instanceDocument)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
import { CanvasElement, HashtagElement, MarkdownElement, NodeFileElement, WikilinkElement } from "./elements/index.mjs";
|
import { CanvasElement, HashtagElement, MarkdownElement, NodeFileElement, WikilinkElement, DisplayElement, EdgeElement, NodeGroupElement, NodeTextElement } from "./elements/index.mjs";
|
||||||
|
|
||||||
customElements.define("x-node-file", NodeFileElement)
|
customElements.define("x-node-file", NodeFileElement)
|
||||||
|
customElements.define("x-node-text", NodeTextElement)
|
||||||
|
customElements.define("x-node-group", NodeGroupElement)
|
||||||
customElements.define("x-markdown", MarkdownElement)
|
customElements.define("x-markdown", MarkdownElement)
|
||||||
customElements.define("x-wikilink", WikilinkElement)
|
customElements.define("x-wikilink", WikilinkElement)
|
||||||
customElements.define("x-hashtag", HashtagElement)
|
customElements.define("x-hashtag", HashtagElement)
|
||||||
customElements.define("x-canvas", CanvasElement)
|
customElements.define("x-canvas", CanvasElement)
|
||||||
|
customElements.define("x-display", DisplayElement)
|
||||||
|
customElements.define("x-edge", EdgeElement)
|
||||||
|
|
12
src/utils/file.mjs
Normal file
12
src/utils/file.mjs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
/**
|
||||||
|
* Parse a file path to get the file name and extension.
|
||||||
|
*
|
||||||
|
* @param file The file path to parse.
|
||||||
|
* @returns {[String, String]} The file's name and extension without the dot respectively.
|
||||||
|
*/
|
||||||
|
export function fileDetails(file) {
|
||||||
|
const split = file.split("/").at(-1).split(".")
|
||||||
|
const name = split.slice(0, -1)
|
||||||
|
const extension = split.at(-1)
|
||||||
|
return [name, extension]
|
||||||
|
}
|
|
@ -1,7 +1,15 @@
|
||||||
@media (prefers-color-scheme: dark) {
|
@media screen and (prefers-color-scheme: dark) {
|
||||||
:root {
|
:root:root {
|
||||||
--color-background: #1e1e1e;
|
--color-background: #1e1e1e;
|
||||||
--color-foreground: #ffffff;
|
--color-foreground: #ffffff;
|
||||||
--color-accent: #ff7f00;
|
--color-accent: #ff7f00;
|
||||||
|
|
||||||
|
--color-gray: #7E7E7E;
|
||||||
|
--color-red: #FB464C;
|
||||||
|
--color-orange: #E9973F;
|
||||||
|
--color-yellow: #E0DE71;
|
||||||
|
--color-green: #44CF6E;
|
||||||
|
--color-blue: #53DFDD;
|
||||||
|
--color-purple: #A882FF;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,15 @@
|
||||||
@media (prefers-color-scheme: light) {
|
@media screen and (prefers-color-scheme: light) {
|
||||||
:root {
|
:root:root {
|
||||||
--color-background: #ffffff;
|
--color-background: #ffffff;
|
||||||
--color-foreground: #1e1e1e;
|
--color-foreground: #1e1e1e;
|
||||||
--color-accent: #ff7f00;
|
--color-accent: #ff7f00;
|
||||||
|
|
||||||
|
--color-gray: #7E7E7E;
|
||||||
|
--color-red: #FB464C;
|
||||||
|
--color-orange: #E9973F;
|
||||||
|
--color-yellow: #E0DE71;
|
||||||
|
--color-green: #44CF6E;
|
||||||
|
--color-blue: #53DFDD;
|
||||||
|
--color-purple: #A882FF;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue