+ `
+
+ // Find `recent-errors-title` by ID and add an onClick event listener to it
+ this.shadow.getElementById("recent-errors-title").addEventListener("click", () => {
+ this.showDetailsModalRecentErrors = !this.showDetailsModalRecentErrors
+ this.createDetailsModal(task)
+ })
+
+ // Find button with id `close-modal` and add an onClick event listener to it
+ this.shadow.getElementById("close-modal").addEventListener("click", () => {
+ // Find the element identified by `ticket-detail-modal` and change it's display to `none`
+ this.shadow.querySelector(".ticket-detail-modal").style.display = "none"
+ })
}
}
-window.customElements.define("outerbase-plugin-table", OuterbasePluginTable_$PLUGIN_ID)
-window.customElements.define("outerbase-plugin-configuration", OuterbasePluginTableConfiguration_$PLUGIN_ID)
\ No newline at end of file
+window.customElements.define('outerbase-plugin-table', OuterbasePluginTable_$PLUGIN_ID)
+// window.customElements.define('outerbase-plugin-table-$PLUGIN_ID', OuterbasePluginTable_$PLUGIN_ID)
diff --git a/columns/_empty.js b/columns/_empty.js
index ea7fdca..9d4dbbe 100644
--- a/columns/_empty.js
+++ b/columns/_empty.js
@@ -1,4 +1,4 @@
-var observableAttributes = [
+var observableAttributes_$PLUGIN_ID = [
// The value of the cell that the plugin is being rendered in
"cellValue",
// The configuration object that the user specified when installing the plugin
@@ -7,12 +7,12 @@ var observableAttributes = [
"metadata"
]
-var OuterbaseEvent = {
+var OuterbaseEvent_$PLUGIN_ID = {
// The user has triggered an action to save updates
onSave: "onSave",
}
-var OuterbaseColumnEvent = {
+var OuterbaseColumnEvent_$PLUGIN_ID = {
// The user has began editing the selected cell
onEdit: "onEdit",
// Stops editing a cells editor popup view and accept the changes
@@ -49,7 +49,7 @@ class OuterbasePluginConfig_$PLUGIN_ID {
}
}
-var triggerEvent = (fromClass, data) => {
+var triggerEvent_$PLUGIN_ID = (fromClass, data) => {
const event = new CustomEvent("custom-change", {
detail: data,
bubbles: true,
@@ -59,7 +59,7 @@ var triggerEvent = (fromClass, data) => {
fromClass.dispatchEvent(event);
}
-var decodeAttributeByName = (fromClass, name) => {
+var decodeAttributeByName_$PLUGIN_ID = (fromClass, name) => {
const encodedJSON = fromClass.getAttribute(name);
const decodedJSON = encodedJSON
?.replace(/"/g, '"')
@@ -116,7 +116,7 @@ templateCell_$PLUGIN_ID.innerHTML = `
class OuterbasePluginCell_$PLUGIN_ID extends HTMLElement {
static get observedAttributes() {
- return privileges
+ return observableAttributes_$PLUGIN_ID
}
config = new OuterbasePluginConfig_$PLUGIN_ID({})
@@ -129,7 +129,7 @@ class OuterbasePluginCell_$PLUGIN_ID extends HTMLElement {
}
connectedCallback() {
- this.config = new OuterbasePluginConfig_$PLUGIN_ID(decodeAttributeByName(this, "configuration"))
+ this.config = new OuterbasePluginConfig_$PLUGIN_ID(decodeAttributeByName_$PLUGIN_ID(this, "configuration"))
this.render()
}
@@ -170,7 +170,7 @@ templateEditor_$PLUGIN_ID.innerHTML = `
class OuterbasePluginCellEditor_$PLUGIN_ID extends HTMLElement {
static get observedAttributes() {
- return privileges
+ return observableAttributes_$PLUGIN_ID
}
config = new OuterbasePluginConfig_$PLUGIN_ID({})
@@ -183,8 +183,8 @@ class OuterbasePluginCellEditor_$PLUGIN_ID extends HTMLElement {
}
connectedCallback() {
- this.config = new OuterbasePluginConfig_$PLUGIN_ID(decodeAttributeByName(this, "configuration"))
- this.config.cellValue = decodeAttributeByName(this, "cellValue")
+ this.config = new OuterbasePluginConfig_$PLUGIN_ID(decodeAttributeByName_$PLUGIN_ID(this, "configuration"))
+ this.config.cellValue = decodeAttributeByName_$PLUGIN_ID(this, "cellValue")
this.render()
}
@@ -234,7 +234,7 @@ templateConfiguration.innerHTML = `
class OuterbasePluginConfiguration_$PLUGIN_ID extends HTMLElement {
static get observedAttributes() {
- return privileges
+ return observableAttributes_$PLUGIN_ID
}
config = new OuterbasePluginConfig_$PLUGIN_ID({})
@@ -247,8 +247,8 @@ class OuterbasePluginConfiguration_$PLUGIN_ID extends HTMLElement {
}
connectedCallback() {
- this.config = new OuterbasePluginConfig_$PLUGIN_ID(decodeAttributeByName(this, "configuration"))
- this.config.cellValue = decodeAttributeByName(this, "cellValue")
+ this.config = new OuterbasePluginConfig_$PLUGIN_ID(decodeAttributeByName_$PLUGIN_ID(this, "configuration"))
+ this.config.cellValue = decodeAttributeByName_$PLUGIN_ID(this, "cellValue")
this.render()
}
@@ -262,7 +262,7 @@ class OuterbasePluginConfiguration_$PLUGIN_ID extends HTMLElement {
var saveButton = this.shadow.getElementById("saveButton");
saveButton.addEventListener("click", () => {
- triggerEvent(this, {
+ triggerEvent_$PLUGIN_ID(this, {
action: OuterbaseEvent.onSave,
value: {}
})
diff --git a/columns/column-config.js b/columns/column-config.js
new file mode 100644
index 0000000..60052c9
--- /dev/null
+++ b/columns/column-config.js
@@ -0,0 +1,403 @@
+var observableAttributes = [
+ // The value of the cell that the plugin is being rendered in
+ "cellvalue",
+ // The value of the row that the plugin is being rendered in
+ "rowvalue",
+ // The value of the table that the plugin is being rendered in
+ "tablevalue",
+ // The schema of the table that the plugin is being rendered in
+ "tableschemavalue",
+ // The schema of the database that the plugin is being rendered in
+ "databaseschemavalue",
+ // The configuration object that the user specified when installing the plugin
+ "configuration",
+ // Additional information about the view such as count, page and offset.
+ "metadata"
+]
+
+var privileges = [
+ 'cellValue',
+ 'configuration',
+]
+
+var templateCell_$PLUGIN_ID = document.createElement('template')
+templateCell_$PLUGIN_ID.innerHTML = `
+
+
+
+`
+
+// This is the configuration object that Outerbase passes to your plugin.
+// Define all of the configuration options that your plugin requires here.
+class OuterbasePluginConfig_$PLUGIN_ID {
+ constructor(object) {
+ // No custom properties needed in this plugin.
+ }
+}
+
+class OuterbasePluginCell_$PLUGIN_ID extends HTMLElement {
+ static get observedAttributes() {
+ return privileges
+ }
+
+ config = new OuterbasePluginConfig_$PLUGIN_ID({})
+
+ constructor() {
+ super()
+
+ // The shadow DOM is a separate DOM tree that is attached to the element.
+ // This allows us to encapsulate our styles and markup. It also prevents
+ // styles from the parent page from leaking into our plugin.
+ this.shadow = this.attachShadow({ mode: 'open' })
+ this.shadow.appendChild(templateCell_$PLUGIN_ID.content.cloneNode(true))
+ }
+
+ // This function is called when the UI is made available into the DOM. Put any
+ // logic that you want to run when the element is first stood up here, such as
+ // event listeners, default values to display, etc.
+ connectedCallback() {
+ // Parse the configuration object from the `configuration` attribute
+ // and store it in the `config` property.
+ this.config = new OuterbasePluginConfig_$PLUGIN_ID(
+ JSON.parse(this.getAttribute('configuration'))
+ )
+
+ // Set default value based on input
+ this.shadow.querySelector('#image-value').value = this.getAttribute('cellvalue')
+
+ var imageInput = this.shadow.getElementById("image-value");
+ var viewImageButton = this.shadow.getElementById("view-image");
+
+ if (imageInput && viewImageButton) {
+ imageInput.addEventListener("focus", () => {
+ // Tell Outerbase to start editing the cell
+ this.callCustomEvent({
+ action: 'onstopedit',
+ value: true
+ })
+ });
+
+ imageInput.addEventListener("blur", () => {
+ // Tell Outerbase to update the cells raw value
+ this.callCustomEvent({
+ action: 'cellvalue',
+ value: imageInput.value
+ })
+
+ // Then stop editing the cell and close the editor view
+ this.callCustomEvent({
+ action: 'onstopedit',
+ value: true
+ })
+ });
+
+ viewImageButton.addEventListener("click", () => {
+ this.callCustomEvent({
+ action: 'onedit',
+ value: true
+ })
+ });
+ }
+ }
+
+ callCustomEvent(data) {
+ const event = new CustomEvent('custom-change', {
+ detail: data,
+ bubbles: true, // If you want the event to bubble up through the DOM
+ composed: true // Allows the event to pass through shadow DOM boundaries
+ });
+
+ this.dispatchEvent(event);
+ }
+}
+
+class OuterbasePluginEditor_$PLUGIN_ID extends HTMLElement {
+ static get observedAttributes() {
+ return privileges
+ }
+
+ constructor() {
+ super()
+
+ // The shadow DOM is a separate DOM tree that is attached to the element.
+ // This allows us to encapsulate our styles and markup. It also prevents
+ // styles from the parent page from leaking into our plugin.
+ this.shadow = this.attachShadow({ mode: 'open' })
+ this.shadow.appendChild(templateEditor_$PLUGIN_ID.content.cloneNode(true))
+
+ // Parse the configuration object from the `configuration` attribute
+ // and store it in the `config` property.
+ this.config = new OuterbasePluginConfig_$PLUGIN_ID(
+ JSON.parse(this.getAttribute('configuration'))
+ )
+ }
+
+ // This function is called when the UI is made available into the DOM. Put any
+ // logic that you want to run when the element is first stood up here, such as
+ // event listeners, default values to display, etc.
+ connectedCallback() {
+ var imageView = this.shadow.getElementById("image");
+ var backgroundImageView = this.shadow.getElementById("background-image");
+
+ if (imageView && backgroundImageView) {
+ imageView.src = this.getAttribute('cellvalue')
+ backgroundImageView.style.backgroundImage = `url(${this.getAttribute('cellvalue')})`
+ }
+ }
+}
+
+/**
+ * ******************
+ * Configuration View
+ * ******************
+ *
+ * ░░░░░░░░░░░░░░░░░
+ * ░░░░░▀▄░░░▄▀░░░░░
+ * ░░░░▄█▀███▀█▄░░░░
+ * ░░░█▀███████▀█░░░
+ * ░░░█░█▀▀▀▀▀█░█░░░
+ * ░░░░░░▀▀░▀▀░░░░░░
+ * ░░░░░░░░░░░░░░░░░
+ *
+ * When a user either installs a plugin onto a table resource for the first time
+ * or they configure an existing installation, this is the view that is presented
+ * to the user. For many plugin applications it's essential to capture information
+ * that is required to allow your plugin to work correctly and this is the best
+ * place to do it.
+ *
+ * It is a requirement that a save button that triggers the `OuterbaseEvent.onSave`
+ * event exists so Outerbase can complete the installation or preference update
+ * action.
+ */
+var templateConfiguration = document.createElement("template")
+templateConfiguration.innerHTML = `
+
+
+
+
+
+
+
+`
+// Can the above div just be a self closing container:
+
+class OuterbasePluginConfiguration_$PLUGIN_ID extends HTMLElement {
+ static get observedAttributes() {
+ return observableAttributes
+ }
+
+ config = new OuterbasePluginConfig_$PLUGIN_ID({})
+
+ constructor() {
+ super()
+
+ this.shadow = this.attachShadow({ mode: "open" })
+ this.shadow.appendChild(templateConfiguration.content.cloneNode(true))
+ }
+
+ connectedCallback() {
+ this.render()
+ }
+
+ attributeChangedCallback(name, oldValue, newValue) {
+ this.config = new OuterbasePluginConfig_$PLUGIN_ID(decodeAttributeByName(this, "configuration"))
+ this.config.tableValue = decodeAttributeByName(this, "tableValue")
+ this.config.theme = decodeAttributeByName(this, "metadata").theme
+
+ var element = this.shadow.getElementById("theme-container");
+ element.classList.remove("dark")
+ element.classList.add(this.config.theme);
+
+ this.render()
+ }
+
+ render() {
+ let sample = this.config.tableValue.length ? this.config.tableValue[0] : {}
+ let keys = Object.keys(sample)
+
+ if (!keys || keys.length === 0 || !this.shadow.querySelector('#configuration-container')) return
+
+ this.shadow.querySelector('#configuration-container').innerHTML = `
+
+
Image URL Prefix (optional)
+
+
+
+
+
+
+ `
+
+ var saveButton = this.shadow.getElementById("saveButton");
+ saveButton.addEventListener("click", () => {
+ triggerEvent(this, {
+ action: OuterbaseEvent.onSave,
+ value: { "test": "me aht" }
+ })
+ });
+ }
+}
+
+// DO NOT change the name of this variable or the classes defined in this file.
+// Changing the name of this variable will cause your plugin to not work properly
+// when installed in Outerbase.
+window.customElements.define('outerbase-plugin-cell-$PLUGIN_ID', OuterbasePluginCell_$PLUGIN_ID)
+window.customElements.define('outerbase-plugin-editor-$PLUGIN_ID', OuterbasePluginEditor_$PLUGIN_ID)
+window.customElements.define('outerbase-plugin-configuration-$PLUGIN_ID', OuterbasePluginConfiguration_$PLUGIN_ID)
diff --git a/columns/hackathon/boolean.js b/columns/hackathon/boolean.js
new file mode 100644
index 0000000..68500f5
--- /dev/null
+++ b/columns/hackathon/boolean.js
@@ -0,0 +1,255 @@
+var privileges_$PLUGIN_ID = [
+ 'cellValue',
+ 'configuration',
+]
+
+var observableAttributes = [
+ // The value of the cell that the plugin is being rendered in
+ "cellValue",
+ // The configuration object that the user specified when installing the plugin
+ "configuration",
+ // Additional information about the view such as count, page and offset.
+ "metadata"
+]
+
+var OuterbaseEvent = {
+ // The user has triggered an action to save updates
+ onSave: "onSave",
+}
+
+var OuterbaseColumnEvent = {
+ // The user has began editing the selected cell
+ onEdit: "onEdit",
+ // Stops editing a cells editor popup view and accept the changes
+ onStopEdit: "onStopEdit",
+ // Stops editing a cells editor popup view and prevent persisting the changes
+ onCancelEdit: "onCancelEdit",
+ // Updates the cells value with the provided value
+ updateCell: "updateCell",
+}
+
+/**
+ * ******************
+ * Custom Definitions
+ * ******************
+ *
+ * ░░░░░░░░░░░░░░░░░
+ * ░░░░▄▄████▄▄░░░░░
+ * ░░░██████████░░░░
+ * ░░░██▄▄██▄▄██░░░░
+ * ░░░░▄▀▄▀▀▄▀▄░░░░░
+ * ░░░▀░░░░░░░░▀░░░░
+ * ░░░░░░░░░░░░░░░░░
+ *
+ * Define your custom classes here. We do recommend the usage of our `OuterbasePluginConfig_$PLUGIN_ID`
+ * class for you to manage properties between the other classes below, however, it's strictly optional.
+ * However, this would be a good class to contain the properties you need to store when a user installs
+ * or configures your plugin.
+ */
+class OuterbasePluginConfig_$PLUGIN_ID {
+ theme = "light"
+
+ constructor(object) {
+ this.theme = object?.theme ? object.theme : "light";
+ }
+}
+
+var triggerEvent = (fromClass, data) => {
+ const event = new CustomEvent("custom-change", {
+ detail: data,
+ bubbles: true,
+ composed: true
+ });
+
+ fromClass.dispatchEvent(event);
+}
+
+var decodeAttributeByName_$PLUGIN_ID = (fromClass, name) => {
+ const encodedJSON = fromClass.getAttribute(name);
+ const decodedJSON = encodedJSON
+ ?.replace(/"/g, '"')
+ ?.replace(/'/g, "'");
+ return decodedJSON ? JSON.parse(decodedJSON) : {};
+}
+
+/**
+ * **********
+ * Cell View
+ * **********
+ *
+ * ░░░░░░░░░░░░░░░░░░
+ * ░░░░░▄▄████▄▄░░░░░
+ * ░░░▄██████████▄░░░
+ * ░▄██▄██▄██▄██▄██▄░
+ * ░░░▀█▀░░▀▀░░▀█▀░░░
+ * ░░░░░░░░░░░░░░░░░░
+ * ░░░░░░░░░░░░░░░░░░
+ *
+ * TBD
+ */
+var templateCell_$PLUGIN_ID = document.createElement('template')
+templateCell_$PLUGIN_ID.innerHTML = `
+
+
+
+
+
+
+
+`
+
+class OuterbasePluginCell_$PLUGIN_ID extends HTMLElement {
+ static get observedAttributes() {
+ return privileges_$PLUGIN_ID
+ }
+
+ config = new OuterbasePluginConfig_$PLUGIN_ID({})
+
+ constructor() {
+ super()
+
+ this.shadow = this.attachShadow({ mode: "open" })
+ this.shadow.appendChild(templateCell_$PLUGIN_ID.content.cloneNode(true))
+ }
+
+ connectedCallback() {
+ this.config = new OuterbasePluginConfig_$PLUGIN_ID(decodeAttributeByName_$PLUGIN_ID(this, "configuration"))
+ this.render()
+
+ // If user clicks on `.indicators` then update the cell value to go from "true" > "false" > "null" depending on current value
+ this.shadow.querySelector("svg").addEventListener("click", () => {
+ this.render();
+ });
+ }
+
+ attributeChangedCallback(name, oldValue, newValue) {
+ this.config = new OuterbasePluginConfig_$PLUGIN_ID(decodeAttributeByName_$PLUGIN_ID(this, "configuration"))
+
+ let metadata = decodeAttributeByName_$PLUGIN_ID(this, "metadata")
+ this.config.theme = metadata?.theme
+
+ var element = this.shadow.querySelector(".theme-container")
+ element.classList.remove("dark")
+ element.classList.add(this.config.theme);
+ }
+
+ render() {
+ // let cellValue = this.getAttribute('cellvalue')
+
+ // if (cellValue.length === 0) {
+ // cellValue = "NULL"
+ // }
+
+ // this.shadow.querySelector('span').innerText = cellValue
+
+ console.log('Render')
+ // let currentValue = this.getAttribute("cellvalue").toLowerCase();
+ let currentValue = this.shadow.querySelector('span').innerText.toUpperCase();
+ let newValue = "TRUE"
+
+ // this.shadow.querySelector("#indicator-true").classList.remove("indicator-selected")
+ // this.shadow.querySelector("#indicator-false").classList.remove("indicator-selected")
+ // this.shadow.querySelector("#indicator-empty").classList.remove("indicator-selected")
+
+ if (currentValue === "TRUE") {
+ newValue = "FALSE"
+ // this.shadow.querySelector("#indicator-false").classList.add("indicator-selected")
+ } else if (currentValue === "FALSE") {
+ newValue = "NULL"
+ // this.shadow.querySelector("#indicator-empty").classList.add("indicator-selected")
+ } else {
+ newValue = "TRUE"
+ // this.shadow.querySelector("#indicator-true").classList.add("indicator-selected")
+ }
+
+ // Set value of span
+ this.shadow.querySelector('span').innerText = newValue
+
+ // triggerEvent(this, {
+ // type: OuterbaseColumnEvent.updateCell,
+ // data: newValue
+ // })
+ }
+}
+
+
+// DO NOT change the name of this variable or the classes defined in this file.
+// Changing the name of this variable will cause your plugin to not work properly
+// when installed in Outerbase.
+// window.customElements.define('outerbase-plugin-cell', OuterbasePluginCell_$PLUGIN_ID)
+window.customElements.define('outerbase-plugin-cell-$PLUGIN_ID', OuterbasePluginCell_$PLUGIN_ID)
\ No newline at end of file
diff --git a/columns/hackathon/date.js b/columns/hackathon/date.js
new file mode 100644
index 0000000..f15e52b
--- /dev/null
+++ b/columns/hackathon/date.js
@@ -0,0 +1,300 @@
+var observableAttributes_$PLUGIN_ID = [
+ // The value of the cell that the plugin is being rendered in
+ "cellValue",
+ // The configuration object that the user specified when installing the plugin
+ "configuration",
+ // Additional information about the view such as count, page and offset.
+ "metadata"
+]
+
+/**
+ * ******************
+ * Custom Definitions
+ * ******************
+ *
+ * ░░░░░░░░░░░░░░░░░
+ * ░░░░▄▄████▄▄░░░░░
+ * ░░░██████████░░░░
+ * ░░░██▄▄██▄▄██░░░░
+ * ░░░░▄▀▄▀▀▄▀▄░░░░░
+ * ░░░▀░░░░░░░░▀░░░░
+ * ░░░░░░░░░░░░░░░░░
+ *
+ * Define your custom classes here. We do recommend the usage of our `OuterbasePluginConfig_$PLUGIN_ID`
+ * class for you to manage properties between the other classes below, however, it's strictly optional.
+ * However, this would be a good class to contain the properties you need to store when a user installs
+ * or configures your plugin.
+ */
+class OuterbasePluginConfig_$PLUGIN_ID {
+ theme = "light"
+
+ constructor(object) {
+ this.theme = object?.theme ? object.theme : "light";
+ }
+}
+
+var triggerEvent_$PLUGIN_ID = (fromClass, data) => {
+ const event = new CustomEvent("custom-change", {
+ detail: data,
+ bubbles: true,
+ composed: true
+ });
+
+ fromClass.dispatchEvent(event);
+}
+
+var decodeAttributeByName_$PLUGIN_ID = (fromClass, name) => {
+ const encodedJSON = fromClass.getAttribute(name);
+ const decodedJSON = encodedJSON
+ ?.replace(/"/g, '"')
+ ?.replace(/'/g, "'");
+ return decodedJSON ? JSON.parse(decodedJSON) : {};
+}
+
+/**
+ * **********
+ * Cell View
+ * **********
+ *
+ * ░░░░░░░░░░░░░░░░░░
+ * ░░░░░▄▄████▄▄░░░░░
+ * ░░░▄██████████▄░░░
+ * ░▄██▄██▄██▄██▄██▄░
+ * ░░░▀█▀░░▀▀░░▀█▀░░░
+ * ░░░░░░░░░░░░░░░░░░
+ * ░░░░░░░░░░░░░░░░░░
+ *
+ * TBD
+ */
+var templateCell_$PLUGIN_ID = document.createElement('template')
+templateCell_$PLUGIN_ID.innerHTML = `
+
+
+
+ Jan 3 202410:03 PM
+
+
+`
+
+class OuterbasePluginCell_$PLUGIN_ID extends HTMLElement {
+ static get observedAttributes() {
+ return observableAttributes_$PLUGIN_ID
+ }
+
+ config = new OuterbasePluginConfig_$PLUGIN_ID({})
+
+ constructor() {
+ super()
+
+ this.shadow = this.attachShadow({ mode: "open" })
+ this.shadow.appendChild(templateCell_$PLUGIN_ID.content.cloneNode(true))
+ }
+
+ connectedCallback() {
+ this.config = new OuterbasePluginConfig_$PLUGIN_ID(decodeAttributeByName_$PLUGIN_ID(this, "configuration"))
+ this.render()
+
+ // When the SVG is clicked, we want to trigger an event to the parent
+ this.shadow.querySelector('span').addEventListener('click', () => {
+ triggerEvent_$PLUGIN_ID(this, {
+ action: "onedit",
+ value: true,
+ })
+ })
+ }
+
+ attributeChangedCallback(name, oldValue, newValue) {
+ this.config = new OuterbasePluginConfig_$PLUGIN_ID(decodeAttributeByName_$PLUGIN_ID(this, "configuration"))
+
+ let metadata = decodeAttributeByName_$PLUGIN_ID(this, "metadata")
+ this.config.theme = metadata?.theme
+
+ var element = this.shadow.querySelector(".theme-container")
+ element.classList.remove("dark")
+ element.classList.add(this.config.theme);
+ }
+
+ render() {
+ // Get the cellValue
+ const cellValue = this.getAttribute("cellValue")
+
+ // Cast the cellValue into a date
+ const date = new Date(cellValue)
+
+ // The `date` is in UTC, so we need to convert it to the local timezone
+ date.setMinutes(date.getMinutes() - date.getTimezoneOffset())
+
+
+ // Format a date string with `MMM d yyyy`
+ let dateString = date.toLocaleDateString("en-US", {
+ month: "short",
+ day: "numeric",
+ year: "numeric"
+ })
+
+ // Remove comma from dateString
+ dateString = dateString.replace(",", "")
+
+ // Format another date string with `h:mm a`
+ const timeString = date.toLocaleTimeString("en-US", {
+ hour: "numeric",
+ minute: "numeric",
+ hour12: true
+ })
+
+ // Set `#date` to dateString
+ this.shadow.querySelector("#date").textContent = dateString + ','
+
+ // Set `#time` to timeString
+ this.shadow.querySelector("#time").textContent = timeString
+ }
+}
+
+
+// For Configuration view let them choose the SOURCE date (e.g. UTC) and the
+// TARGET date (e.g. Local Timezone) and the format of the date and time.
+
+
+
+
+var templateEditor_$PLUGIN_ID = document.createElement("template");
+templateEditor_$PLUGIN_ID.innerHTML = `
+
+
+
+
+
+`;
+
+class OuterbasePluginEditor_$PLUGIN_ID extends HTMLElement {
+ static get observedAttributes() {
+ return observableAttributes_$PLUGIN_ID;
+ }
+
+ config = new OuterbasePluginConfig_$PLUGIN_ID({})
+
+ constructor() {
+ super()
+
+ this.shadow = this.attachShadow({ mode: "open" })
+ this.shadow.appendChild(templateEditor_$PLUGIN_ID.content.cloneNode(true))
+ }
+
+ attributeChangedCallback(name, oldValue, newValue) {
+ this.config = new OuterbasePluginConfig_$PLUGIN_ID(decodeAttributeByName_$PLUGIN_ID(this, "configuration"))
+ let metadata = decodeAttributeByName_$PLUGIN_ID(this, "metadata")
+ this.config.theme = metadata?.theme
+
+ var element = this.shadow.querySelector(".theme-container")
+ element.classList.remove("dark")
+ element.classList.add(this.config.theme);
+
+ this.render()
+ }
+
+ connectedCallback() {
+ this.config = new OuterbasePluginConfig_$PLUGIN_ID(decodeAttributeByName_$PLUGIN_ID(this, "configuration"))
+ this.render()
+ }
+
+ render() {
+
+ }
+}
+
+
+// DO NOT change the name of this variable or the classes defined in this file.
+// Changing the name of this variable will cause your plugin to not work properly
+// when installed in Outerbase.
+// window.customElements.define('outerbase-plugin-cell', OuterbasePluginCell_$PLUGIN_ID)
+window.customElements.define('outerbase-plugin-cell-$PLUGIN_ID', OuterbasePluginCell_$PLUGIN_ID)
+window.customElements.define('outerbase-plugin-editor-$PLUGIN_ID', OuterbasePluginEditor_$PLUGIN_ID)
\ No newline at end of file
diff --git a/columns/hackathon/enum.js b/columns/hackathon/enum.js
new file mode 100644
index 0000000..3afbd04
--- /dev/null
+++ b/columns/hackathon/enum.js
@@ -0,0 +1,643 @@
+var privileges_$PLUGIN_ID = [
+ 'cellValue',
+ 'configuration',
+]
+
+var observableAttributes_$PLUGIN_ID = [
+ // The value of the cell that the plugin is being rendered in
+ "cellValue",
+ // The configuration object that the user specified when installing the plugin
+ "configuration",
+ // Additional information about the view such as count, page and offset.
+ "metadata"
+]
+
+var OuterbaseEvent_$PLUGIN_ID = {
+ // The user has triggered an action to save updates
+ onSave: "onSave",
+}
+
+var OuterbaseColumnEvent_$PLUGIN_ID = {
+ // The user has began editing the selected cell
+ onEdit: "onEdit",
+ // Stops editing a cells editor popup view and accept the changes
+ onStopEdit: "onStopEdit",
+ // Stops editing a cells editor popup view and prevent persisting the changes
+ onCancelEdit: "onCancelEdit",
+ // Updates the cells value with the provided value
+ updateCell: "updateCell",
+}
+
+/**
+ * ******************
+ * Custom Definitions
+ * ******************
+ *
+ * ░░░░░░░░░░░░░░░░░
+ * ░░░░▄▄████▄▄░░░░░
+ * ░░░██████████░░░░
+ * ░░░██▄▄██▄▄██░░░░
+ * ░░░░▄▀▄▀▀▄▀▄░░░░░
+ * ░░░▀░░░░░░░░▀░░░░
+ * ░░░░░░░░░░░░░░░░░
+ *
+ * Define your custom classes here. We do recommend the usage of our `OuterbasePluginConfig_$PLUGIN_ID`
+ * class for you to manage properties between the other classes below, however, it's strictly optional.
+ * However, this would be a good class to contain the properties you need to store when a user installs
+ * or configures your plugin.
+ */
+class OuterbasePluginConfig_$PLUGIN_ID {
+ theme = "light"
+ enumOptions = []
+
+ constructor(object) {
+ this.theme = object?.theme ? object.theme : "light";
+ this.enumOptions = object?.enumOptions ? object.enumOptions : [];
+ }
+}
+
+var triggerEvent_$PLUGIN_ID = (fromClass, data) => {
+ const event = new CustomEvent("custom-change", {
+ detail: data,
+ bubbles: true,
+ composed: true
+ });
+
+ fromClass.dispatchEvent(event);
+}
+
+var decodeAttributeByName_$PLUGIN_ID = (fromClass, name) => {
+ const encodedJSON = fromClass.getAttribute(name);
+ const decodedJSON = encodedJSON
+ ?.replace(/"/g, '"')
+ ?.replace(/'/g, "'");
+ return decodedJSON ? JSON.parse(decodedJSON) : {};
+}
+
+/**
+ * **********
+ * Cell View
+ * **********
+ *
+ * ░░░░░░░░░░░░░░░░░░
+ * ░░░░░▄▄████▄▄░░░░░
+ * ░░░▄██████████▄░░░
+ * ░▄██▄██▄██▄██▄██▄░
+ * ░░░▀█▀░░▀▀░░▀█▀░░░
+ * ░░░░░░░░░░░░░░░░░░
+ * ░░░░░░░░░░░░░░░░░░
+ *
+ * TBD
+ */
+var templateCell_$PLUGIN_ID = document.createElement('template')
+templateCell_$PLUGIN_ID.innerHTML = `
+
+
+
+ `
+
+ return div
+ }
+}
+
+
+
+
+
+
+
+
+
+
+/**
+ * ******************
+ * Configuration View
+ * ******************
+ *
+ * ░░░░░░░░░░░░░░░░░
+ * ░░░░░▀▄░░░▄▀░░░░░
+ * ░░░░▄█▀███▀█▄░░░░
+ * ░░░█▀███████▀█░░░
+ * ░░░█░█▀▀▀▀▀█░█░░░
+ * ░░░░░░▀▀░▀▀░░░░░░
+ * ░░░░░░░░░░░░░░░░░
+ *
+ * When a user either installs a plugin onto a table resource for the first time
+ * or they configure an existing installation, this is the view that is presented
+ * to the user. For many plugin applications it's essential to capture information
+ * that is required to allow your plugin to work correctly and this is the best
+ * place to do it.
+ *
+ * It is a requirement that a save button that triggers the `OuterbaseEvent.onSave`
+ * event exists so Outerbase can complete the installation or preference update
+ * action.
+ */
+var templateConfiguration = document.createElement("template")
+templateConfiguration.innerHTML = `
+
+
+
+
Select Enum Options
+
+
+
+
+
+
+
+
+
+
+
+
+ Add Option
+
+
+
+
+ Save View
+
+`
+
+class OuterbasePluginConfiguration_$PLUGIN_ID extends HTMLElement {
+ static get observedAttributes() {
+ return privileges
+ }
+
+ config = new OuterbasePluginConfig_$PLUGIN_ID({})
+ newOptions = []
+
+ constructor() {
+ super()
+
+ this.shadow = this.attachShadow({ mode: "open" })
+ this.shadow.appendChild(templateConfiguration.content.cloneNode(true))
+ }
+
+ connectedCallback() {
+ this.config = new OuterbasePluginConfig_$PLUGIN_ID(decodeAttributeByName_$PLUGIN_ID(this, "configuration"))
+ this.config.cellValue = decodeAttributeByName_$PLUGIN_ID(this, "cellValue")
+
+ var saveButton = this.shadow.getElementById("saveButton");
+ saveButton.addEventListener("click", () => {
+ // Combine the existing `enumOptions` with the `newOptions` array
+ this.config.enumOptions = this.config.enumOptions.concat(this.newOptions)
+
+ triggerEvent_$PLUGIN_ID(this, {
+ action: OuterbaseEvent_$PLUGIN_ID.onSave,
+ value: this.config
+ })
+ });
+
+ // Listen to when the `add-new-option` button is clicked
+ this.shadow.querySelector('#add-new-option').addEventListener('click', () => {
+ let value = this.shadow.querySelector('#current-new-option').value
+ this.newOptions.push(value)
+ this.shadow.querySelector('#current-new-option').value = ""
+
+ this.render();
+ })
+
+ this.fetchDistinctValues()
+ this.render()
+ }
+
+ // attributeChangedCallback(name, oldValue, newValue) {
+ // this.config = new OuterbasePluginConfig_$PLUGIN_ID(decodeAttributeByName_$PLUGIN_ID(this, "configuration"))
+ // let metadata = decodeAttributeByName_$PLUGIN_ID(this, "metadata")
+ // this.config.theme = metadata?.theme
+
+ // var element = this.shadow.querySelector(".theme-container")
+ // element.classList.remove("dark")
+ // element.classList.add(this.config.theme);
+
+ // this.render()
+ // }
+
+ async fetchDistinctValues() {
+ try {
+ // Based on the information provided to our plugin, we need to identify
+ // what the column constraints are and what database table it is linked to.
+ // This will allow us to construct a SQL query to fetch the value from the
+ // linked table.
+ const column = this.getAttribute('columnName')
+ const table = JSON.parse(this.getAttribute('tableSchemaValue')).name
+ const schema = JSON.parse(this.getAttribute('tableSchemaValue')).schema ?? "public"
+
+ // Necessary information is graciously stored by Outerbase in the `localStorage`
+ // for us to make the necessary network request to fetch the value from the linked table.
+ const session = JSON.parse(localStorage.getItem('session'))
+ const workspaceId = localStorage.getItem('workspace_id')
+ const sourceId = localStorage.getItem('source_id')
+
+ // SELECT DISTINCT column_name FROM table_name;
+
+ // When a cached value does not exist for this `schema.table.column.value`, fetch the value
+ // from the database and store it in the cache for future re-use.
+ await fetch(`https://app.dev.outerbase.com/api/v1/workspace/${workspaceId}/source/${sourceId}/query/raw`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'x-auth-token': session?.state?.session?.token
+ },
+ body: JSON.stringify({
+ query: `SELECT DISTINCT ${column} FROM ${schema}.${table};`,
+ options: {}
+ })
+ }).then(response => response.json()).then(data => {
+ const items = data.response?.items ?? []
+
+ // Condense the above `items` array with the structure above into an array of strings
+ let itemsArray = items.map(item => item[column])
+ this.config.enumOptions = itemsArray
+
+ this.render();
+ })
+ } catch (error) {
+ console.error(error)
+ }
+ }
+
+ render() {
+ const items = this.config.enumOptions
+ let select = this.shadow.querySelector('#options')
+ select.innerHTML = ''
+
+ items.forEach(item => {
+ const div = document.createElement('div')
+ div.innerHTML = `
+
+ `
+ select.appendChild(div)
+ })
+
+ // Listen to the Remove buttons and remove from list when clicked
+ select.querySelectorAll('.remove-option').forEach((removeButton, index) => {
+ removeButton.addEventListener('click', () => {
+ if (index < items.length) {
+ this.config.enumOptions.splice(index, 1)
+ } else {
+ this.newOptions.splice(index - items.length, 1)
+ }
+
+ this.render()
+ })
+ })
+ }
+}
+
+
+// DO NOT change the name of this variable or the classes defined in this file.
+// Changing the name of this variable will cause your plugin to not work properly
+// when installed in Outerbase.
+// window.customElements.define('outerbase-plugin-cell', OuterbasePluginCell_$PLUGIN_ID)
+// window.customElements.define('outerbase-plugin-editor', OuterbasePluginEditor_$PLUGIN_ID)
+// window.customElements.define('outerbase-plugin-configuration', OuterbasePluginConfiguration_$PLUGIN_ID)
+
+// window.customElements.define('outerbase-plugin-cell-$PLUGIN_ID', OuterbasePluginCell_$PLUGIN_ID)
+// window.customElements.define('outerbase-plugin-editor-$PLUGIN_ID', OuterbasePluginEditor_$PLUGIN_ID)
+// window.customElements.define('outerbase-plugin-configuration-$PLUGIN_ID', OuterbasePluginConfiguration_$PLUGIN_ID)
diff --git a/columns/hackathon/expandable-text.js b/columns/hackathon/expandable-text.js
new file mode 100644
index 0000000..b15348d
--- /dev/null
+++ b/columns/hackathon/expandable-text.js
@@ -0,0 +1,510 @@
+var observableAttributes_$PLUGIN_ID = [
+ // The value of the cell that the plugin is being rendered in
+ "cellValue",
+ // The configuration object that the user specified when installing the plugin
+ "configuration",
+ // Additional information about the view such as count, page and offset.
+ "metadata"
+]
+
+var OuterbaseEvent = {
+ // The user has triggered an action to save updates
+ onSave: "onSave",
+}
+
+var OuterbaseColumnEvent = {
+ // The user has began editing the selected cell
+ onEdit: "onEdit",
+ // Stops editing a cells editor popup view and accept the changes
+ onStopEdit: "onStopEdit",
+ // Stops editing a cells editor popup view and prevent persisting the changes
+ onCancelEdit: "onCancelEdit",
+ // Updates the cells value with the provided value
+ updateCell: "updateCell",
+}
+
+/**
+ * ******************
+ * Custom Definitions
+ * ******************
+ *
+ * ░░░░░░░░░░░░░░░░░
+ * ░░░░▄▄████▄▄░░░░░
+ * ░░░██████████░░░░
+ * ░░░██▄▄██▄▄██░░░░
+ * ░░░░▄▀▄▀▀▄▀▄░░░░░
+ * ░░░▀░░░░░░░░▀░░░░
+ * ░░░░░░░░░░░░░░░░░
+ *
+ * Define your custom classes here. We do recommend the usage of our `OuterbasePluginConfig_$PLUGIN_ID`
+ * class for you to manage properties between the other classes below, however, it's strictly optional.
+ * However, this would be a good class to contain the properties you need to store when a user installs
+ * or configures your plugin.
+ */
+class OuterbasePluginConfig_$PLUGIN_ID {
+ cellValue = undefined
+
+ constructor(object) {
+
+ }
+}
+
+var triggerEvent_$PLUGIN_ID = (fromClass, data) => {
+ const event = new CustomEvent("plugin-change", {
+ detail: data,
+ bubbles: true,
+ composed: true
+ });
+
+ fromClass.dispatchEvent(event);
+}
+
+var decodeAttributeByName_$PLUGIN_ID = (fromClass, name) => {
+ const encodedJSON = fromClass.getAttribute(name);
+ const decodedJSON = encodedJSON
+ ?.replace(/"/g, '"')
+ ?.replace(/'/g, "'")
+ ?.replace(/`/g, "`");
+ return decodedJSON ? JSON.parse(decodedJSON) : {};
+}
+
+/**
+ * **********
+ * Cell View
+ * **********
+ *
+ * ░░░░░░░░░░░░░░░░░░
+ * ░░░░░▄▄████▄▄░░░░░
+ * ░░░▄██████████▄░░░
+ * ░▄██▄██▄██▄██▄██▄░
+ * ░░░▀█▀░░▀▀░░▀█▀░░░
+ * ░░░░░░░░░░░░░░░░░░
+ * ░░░░░░░░░░░░░░░░░░
+ *
+ * TBD
+ */
+
+// #container {
+// height: 100%;
+// min-height: 34px;
+// width: calc(100% - 16px);
+// padding: 0 8px;
+// position: relative;
+// display: flex;
+// align-items: center;
+// gap: 0px;
+// }
+
+// #container {
+// height: 100%;
+// min-height: 34px;
+// position: absolute;
+// top: 0;
+// left: 12px;
+// right: 8px;
+// bottom: 0;
+// display: flex;
+// align-items: center;
+// gap: 0px;
+// transform: translateY(-1px);
+// }
+var templateCell_$PLUGIN_ID = document.createElement('template')
+templateCell_$PLUGIN_ID.innerHTML = `
+
+
+
+
+
+
+`
+
+class OuterbasePluginCell_$PLUGIN_ID extends HTMLElement {
+ static get observedAttributes() {
+ return privileges
+ }
+
+ config = new OuterbasePluginConfig_$PLUGIN_ID({})
+
+ constructor() {
+ super()
+
+ this.shadow = this.attachShadow({ mode: "open" })
+ this.shadow.appendChild(templateCell_$PLUGIN_ID.content.cloneNode(true))
+ }
+
+ connectedCallback() {
+ this.config = new OuterbasePluginConfig_$PLUGIN_ID(decodeAttributeByName_$PLUGIN_ID(this, "configuration"))
+ this.render()
+
+ // When the SVG is clicked, we want to trigger an event to the parent
+ this.shadow.querySelector('svg').addEventListener('click', () => {
+ triggerEvent_$PLUGIN_ID(this, {
+ action: "onedit",
+ value: true,
+ })
+ })
+
+ // Listen to paste event on input
+ this.shadow.querySelector('input').addEventListener('paste', (event) => {
+ event.preventDefault()
+ let text = event.clipboardData.getData('text/plain')
+ document.execCommand('insertText', false, text)
+
+ // Escape single and double quotes from `text`
+ // text = JSON.stringify(text)
+ // ?.replace(/"/g, '"')
+ // .replace(/'/g, ''')
+
+ // // Remove quotes around the text
+ // text = text.substring(1, text.length - 1)
+
+ // Send the event to the parent
+ triggerEvent_$PLUGIN_ID(this, {
+ action: "updatecell",
+ value: text,
+ })
+ })
+
+ // Detect when input value changes
+ this.shadow.querySelector('input').addEventListener('input', (event) => {
+ let cellValue = event.target.value
+
+ // Escape quotes from cellValue
+ // cellValue = JSON.stringify(cellValue)
+ // ?.replace(/"/g, '"')
+ // .replace(/'/g, ''') //cellValue.replace(/"/g, '\\"')//.replace(/'/g, "\\'").replace(/`/g, "\\`").replace(/\\/g, "\\\\")
+
+ // Set the input value to the cell value
+ this.setAttribute('cellvalue', cellValue)
+ this.shadow.querySelector('input').value = cellValue
+
+ // Send the event to the parent
+ triggerEvent_$PLUGIN_ID(this, {
+ action: "updatecell",
+ value: cellValue,
+ })
+ })
+ }
+
+ render() {
+ let cellValue = this.getAttribute('cellvalue')
+
+ if (cellValue.length === 0 || (cellValue && cellValue.toLowerCase() === "null")) {
+ this.shadow.querySelector('input').placeholder = "NULL"
+ } else {
+ this.shadow.querySelector('input').value = cellValue
+ }
+ }
+}
+
+
+
+
+
+
+
+
+
+
+var templateEditor_$PLUGIN_ID = document.createElement("template");
+templateEditor_$PLUGIN_ID.innerHTML = `
+
+
+
+
+
+
NULL
+
+
+
+
+`;
+
+class OuterbasePluginEditor_$PLUGIN_ID extends HTMLElement {
+ static get observedAttributes() {
+ return observableAttributes_$PLUGIN_ID;
+ }
+
+ config = new OuterbasePluginConfig_$PLUGIN_ID({})
+ tableSchema = {}
+ metadata = {}
+
+ constructor() {
+ super()
+
+ this.shadow = this.attachShadow({ mode: "open" })
+ this.shadow.appendChild(templateEditor_$PLUGIN_ID.content.cloneNode(true))
+ }
+
+ attributeChangedCallback(name, oldValue, newValue) {
+ this.config = new OuterbasePluginConfig_$PLUGIN_ID(decodeAttributeByName_$PLUGIN_ID(this, "configuration"))
+ let metadata = decodeAttributeByName_$PLUGIN_ID(this, "metadata")
+ this.config.theme = metadata?.theme
+
+ var element = this.shadow.querySelector(".theme-container")
+ element.classList.remove("dark")
+ element.classList.add(this.config.theme);
+
+ this.render()
+ }
+
+ connectedCallback() {
+ this.config = new OuterbasePluginConfig_$PLUGIN_ID(decodeAttributeByName_$PLUGIN_ID(this, "configuration"))
+ this.tableSchema = decodeAttributeByName_$PLUGIN_ID(this, "tableschemavalue")
+ this.metadata = decodeAttributeByName_$PLUGIN_ID(this, "metadata")
+ const columnName = this.getAttribute('columnname')
+ this.render()
+
+ const availableColumns = this.tableSchema.columns
+
+ // Get the column object from the table schema
+ const column = availableColumns?.find(column => column.name === columnName)
+ this.maximumCharacterCount = column?.character_maximum_length || null
+ this.updateCharacterCount()
+
+ // Listen to input changes in textarea
+ this.shadow.querySelector('textarea').addEventListener('input', (event) => {
+ const cellValue = event.target.value
+
+ if (cellValue.length === 0 || (cellValue && cellValue.toLowerCase() === "null")) {
+ this.shadow.querySelector('#null-placeholder').style.display = "block"
+ } else {
+ this.shadow.querySelector('#null-placeholder').style.display = "none"
+ }
+
+ this.updateCharacterCount()
+ })
+
+ // Listen to `update-button` and `cancel-button` clicks
+ this.shadow.querySelector('#update-button').addEventListener('click', () => {
+ // Get value of textarea
+ let value = this.shadow.querySelector('textarea').value
+
+ triggerEvent_$PLUGIN_ID(this, {
+ action: "updatecell",
+ value,
+ })
+
+ triggerEvent_$PLUGIN_ID(this, {
+ action: "onstopedit"
+ })
+
+ // Close the editor after event has saved changes
+ setTimeout(() => {
+ triggerEvent_$PLUGIN_ID(this, {
+ action: "onstopedit"
+ })
+ }, 500);
+ })
+
+ this.shadow.querySelector('#cancel-button').addEventListener('click', () => {
+ triggerEvent_$PLUGIN_ID(this, {
+ action: "oncanceledit",
+ value: true,
+ })
+ })
+ }
+
+ render() {
+ // Get the `cellValue` and populate it in the `textarea`
+ let cellValue = this.getAttribute('cellvalue')
+ this.shadow.querySelector('textarea').value = cellValue
+
+ if (cellValue.length === 0 || (cellValue && cellValue.toLowerCase() === "null")) {
+ // this.shadow.querySelector('textarea').placeholder = "NULL"
+ this.shadow.querySelector('textarea').value = ""
+ this.shadow.querySelector('#null-placeholder').style.display = "block"
+ } else {
+ this.shadow.querySelector('textarea').value = cellValue
+ this.shadow.querySelector('#null-placeholder').style.display = "none"
+ }
+
+ // If `this.metadata.editable` is false, hide the button
+ if (this.metadata.editable === false) {
+ this.shadow.querySelector('#footer').style.display = "none"
+
+ // Set textarea to readonly
+ this.shadow.querySelector('textarea').readOnly = true
+ } else {
+ this.shadow.querySelector('#footer').style.display = "flex"
+
+ // Set textarea to readonly
+ this.shadow.querySelector('textarea').readOnly = false
+ }
+ }
+
+ updateCharacterCount() {
+ const currentCharacterLength = this.shadow.querySelector('textarea').value.length
+
+ if (this.maximumCharacterCount) {
+ const formattedCharacterLength = Number(currentCharacterLength).toLocaleString();
+ const formattedMaxNumber = Number(this.maximumCharacterCount).toLocaleString();
+ this.shadow.querySelector('#character-count').textContent = `${formattedCharacterLength}/${formattedMaxNumber}`;
+ } else {
+ this.shadow.querySelector('#character-count').textContent = ``;
+ }
+
+ // If the character length exceeds the maximum character count, show the text in red
+ if (currentCharacterLength > this.maximumCharacterCount) {
+ this.shadow.querySelector('#character-count').style.color = "#F0384E";
+ this.shadow.querySelector('#character-count').style.opacity = 1;
+ } else {
+ this.shadow.querySelector('#character-count').style.color = "var(--ob-text-color)";
+ this.shadow.querySelector('#character-count').style.opacity = 0.5;
+ }
+ }
+}
+
+// DO NOT change the name of this variable or the classes defined in this file.
+// Changing the name of this variable will cause your plugin to not work properly
+// when installed in Outerbase.
+// window.customElements.define('outerbase-plugin-cell', OuterbasePluginCell_$PLUGIN_ID)
+// window.customElements.define('outerbase-plugin-editor', OuterbasePluginEditor_$PLUGIN_ID)
+
+window.customElements.define('outerbase-plugin-cell-$PLUGIN_ID', OuterbasePluginCell_$PLUGIN_ID)
+window.customElements.define('outerbase-plugin-editor-$PLUGIN_ID', OuterbasePluginEditor_$PLUGIN_ID)
\ No newline at end of file
diff --git a/columns/hackathon/foreign-key.js b/columns/hackathon/foreign-key.js
new file mode 100644
index 0000000..c76153e
--- /dev/null
+++ b/columns/hackathon/foreign-key.js
@@ -0,0 +1,531 @@
+var privileges_$PLUGIN_ID = [
+ "cellValue",
+ "rowValue",
+ "tableValue",
+ "tableSchemaValue",
+ "databaseSchemaValue",
+ "configuration",
+ "metadata"
+]
+
+var OuterbaseEvent_$PLUGIN_ID = {
+ // The user has triggered an action to save updates
+ onSave: "onSave",
+}
+
+var OuterbaseColumnEvent_$PLUGIN_ID = {
+ // The user has began editing the selected cell
+ onEdit: "onEdit",
+ // Stops editing a cells editor popup view and accept the changes
+ onStopEdit: "onStopEdit",
+ // Stops editing a cells editor popup view and prevent persisting the changes
+ onCancelEdit: "onCancelEdit",
+ // Updates the cells value with the provided value
+ updateCell: "updateCell",
+}
+
+/**
+ * ******************
+ * Custom Definitions
+ * ******************
+ *
+ * ░░░░░░░░░░░░░░░░░
+ * ░░░░▄▄████▄▄░░░░░
+ * ░░░██████████░░░░
+ * ░░░██▄▄██▄▄██░░░░
+ * ░░░░▄▀▄▀▀▄▀▄░░░░░
+ * ░░░▀░░░░░░░░▀░░░░
+ * ░░░░░░░░░░░░░░░░░
+ *
+ * Define your custom classes here. We do recommend the usage of our `OuterbasePluginConfig_$PLUGIN_ID`
+ * class for you to manage properties between the other classes below, however, it's strictly optional.
+ * However, this would be a good class to contain the properties you need to store when a user installs
+ * or configures your plugin.
+ */
+class OuterbasePluginConfig_$PLUGIN_ID {
+ theme = "light"
+ preferredColumn = ""
+
+ constructor(object) {
+ this.theme = object?.theme ? object.theme : "light";
+ this.preferredColumn = object?.preferredColumn ? object.preferredColumn : "";
+ }
+}
+
+var triggerEvent_$PLUGIN_ID = (fromClass, data) => {
+ const event = new CustomEvent("custom-change", {
+ detail: data,
+ bubbles: true,
+ composed: true
+ });
+
+ fromClass.dispatchEvent(event);
+}
+
+var decodeAttributeByName_$PLUGIN_ID = (fromClass, name) => {
+ const encodedJSON = fromClass.getAttribute(name);
+ const decodedJSON = encodedJSON
+ ?.replace(/"/g, '"')
+ ?.replace(/'/g, "'");
+ return decodedJSON ? JSON.parse(decodedJSON) : {};
+}
+
+/**
+ * **********
+ * Cell View
+ * **********
+ *
+ * ░░░░░░░░░░░░░░░░░░
+ * ░░░░░▄▄████▄▄░░░░░
+ * ░░░▄██████████▄░░░
+ * ░▄██▄██▄██▄██▄██▄░
+ * ░░░▀█▀░░▀▀░░▀█▀░░░
+ * ░░░░░░░░░░░░░░░░░░
+ * ░░░░░░░░░░░░░░░░░░
+ *
+ * TBD
+ */
+var templateCell_$PLUGIN_ID = document.createElement('template')
+templateCell_$PLUGIN_ID.innerHTML = `
+
+
+
+
+
+
+
+
+
+
+
+`
+
+class OuterbasePluginCell_$PLUGIN_ID extends HTMLElement {
+ static get observedAttributes() {
+ return privileges_$PLUGIN_ID
+ }
+
+ config = new OuterbasePluginConfig_$PLUGIN_ID({})
+
+ constructor() {
+ super()
+
+ this.shadow = this.attachShadow({ mode: "open" })
+ this.shadow.appendChild(templateCell_$PLUGIN_ID.content.cloneNode(true))
+ }
+
+ connectedCallback() {
+ this.config = new OuterbasePluginConfig_$PLUGIN_ID(decodeAttributeByName_$PLUGIN_ID(this, "configuration"))
+ this.render()
+ }
+
+ storeValueInCache(key, value) {
+ const cacheName = 'pluginForeignKeyCache'
+ const currentCache = JSON.parse(localStorage.getItem(cacheName)) ?? {}
+
+ if (currentCache[key] === value) {
+ return
+ }
+
+ // Check how many keys are in the cache
+ const keys = Object.keys(currentCache)
+
+ if (keys.length > 500) {
+ // Remove the first 100 keys
+ for (let i = 0; i < 100; i++) {
+ delete currentCache[keys[i]]
+ }
+ }
+
+ currentCache[key] = value
+ localStorage.setItem(cacheName, JSON.stringify(currentCache))
+ }
+
+ getValueFromCache(key) {
+ const cacheName = 'pluginForeignKeyCache'
+ const currentCache = JSON.parse(localStorage.getItem(cacheName)) ?? {}
+
+ return currentCache[key]
+ }
+
+ async attributeChangedCallback(name, oldValue, newValue) {
+ this.config = new OuterbasePluginConfig_$PLUGIN_ID(decodeAttributeByName_$PLUGIN_ID(this, "configuration"))
+
+ let metadata = decodeAttributeByName_$PLUGIN_ID(this, "metadata")
+ this.config.theme = metadata?.theme
+
+ // console.log('FK Cell Config: ', this.config)
+ // console.log('FK Cell Metadata: ', metadata)
+
+ var element = this.shadow.querySelector(".theme-container")
+ element.classList.remove("dark")
+ element.classList.add(this.config.theme);
+
+ this.render()
+ }
+
+ async render() {
+ let cellValue = this.getAttribute('cellvalue')
+
+ if (cellValue.length === 0) {
+ cellValue = "NULL"
+ }
+
+ this.shadow.querySelector('#label').innerText = cellValue
+
+ try {
+ // Based on the information provided to our plugin, we need to identify
+ // what the column constraints are and what database table it is linked to.
+ // This will allow us to construct a SQL query to fetch the value from the
+ // linked table.
+ const column = this.getAttribute('columnName')
+ const table = JSON.parse(this.getAttribute('tableSchemaValue')).name
+ const schema = JSON.parse(this.getAttribute('tableSchemaValue')).schema ?? "public"
+ const databaseSchemaValue = JSON.parse(this.getAttribute('databaseSchemaValue'))
+ const schemaColumns = databaseSchemaValue?.[schema]
+ const schemaTable = schemaColumns.find(t => t.name === table)
+ const constraints = schemaTable?.constraints
+
+ if (constraints.length === 0) {
+ this.shadow.querySelector('#loader').style.display = 'none'
+ return
+ }
+
+ let fkName = ""
+ let fkTable = ""
+ let fkSchema = ""
+ let cellValue = this.getAttribute('cellValue')
+
+ // Loop through `constraints` and find where type === `FOREIGN KEY`
+ for (const constraint of constraints) {
+ if (constraint.type === "FOREIGN KEY" && constraint.table === table && constraint.column === column) {
+ const fkColumn = constraint.columns?.[0]
+ fkName = fkColumn.name
+ fkTable = fkColumn.table
+ fkSchema = fkColumn.schema ?? "public"
+ }
+ }
+
+ // Necessary information is graciously stored by Outerbase in the `localStorage`
+ // for us to make the necessary network request to fetch the value from the linked table.
+ const session = JSON.parse(localStorage.getItem('session'))
+ const workspaceId = localStorage.getItem('workspace_id')
+ const sourceId = localStorage.getItem('source_id')
+
+ // Create a unique cache key based on the `schema.table.column.value`
+ const cacheKey = `${fkSchema}.${fkTable}.${fkName}.${cellValue}`
+
+ // If the `cacheKey` already exists in the cache, use the cached value instead
+ // of making another network request to fetch it.
+ if (this.getValueFromCache(cacheKey)) {
+ this.shadow.querySelector('#label').innerText = this.getValueFromCache(cacheKey)
+ this.shadow.querySelector('#loader').style.display = 'none'
+ return
+ }
+
+ // When a cached value does not exist for this `schema.table.column.value`, fetch the value
+ // from the database and store it in the cache for future re-use.
+ await fetch(`https://app.dev.outerbase.com/api/v1/workspace/${workspaceId}/source/${sourceId}/query/raw`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'x-auth-token': session?.state?.session?.token
+ },
+ body: JSON.stringify({
+ query: `SELECT * FROM ${fkSchema}.${fkTable} WHERE ${fkName} = '${cellValue}'`,
+ options: {}
+ })
+ }).then(response => response.json()).then(data => {
+ const item = data.response?.items?.[0] ?? {}
+ const bestCandidate = this.detectGoodColumnCandidate(item)
+ this.shadow.querySelector('#label').innerText = bestCandidate
+
+ // Set cache
+ this.storeValueInCache(cacheKey, bestCandidate)
+ this.shadow.querySelector('#loader').style.display = 'none'
+ })
+ } catch (error) {
+ console.error(error)
+ this.shadow.querySelector('#loader').style.display = 'none'
+ }
+ }
+
+ detectGoodColumnCandidate(column) {
+ const preferredColumn = this.config.preferredColumn?.length > 0 ? column[this.config.preferredColumn] : null
+ let firstStringFound = Object.entries(column).find(([key, value]) => typeof value === 'string')
+ let bestMatch = null
+ let bestMatchEmail = null
+ let bestMatchPhone = null
+ let bestMatchAddress = null
+
+ for (const [key, value] of Object.entries(column)) {
+ if (key.includes('name') && !bestMatch) {
+ bestMatch = value
+ } else if (key.includes('email') && !bestMatchEmail) {
+ bestMatchEmail = value
+ } else if (key.includes('phone') && !bestMatchPhone) {
+ bestMatchPhone = value
+ } else if (key.includes('address') && !bestMatchAddress) {
+ bestMatchAddress = value
+ }
+ }
+
+ return preferredColumn ? preferredColumn : bestMatch ?? bestMatchEmail ?? bestMatchPhone ?? bestMatchAddress ?? firstStringFound
+ }
+}
+
+
+
+
+
+
+/**
+ * ******************
+ * Configuration View
+ * ******************
+ *
+ * ░░░░░░░░░░░░░░░░░
+ * ░░░░░▀▄░░░▄▀░░░░░
+ * ░░░░▄█▀███▀█▄░░░░
+ * ░░░█▀███████▀█░░░
+ * ░░░█░█▀▀▀▀▀█░█░░░
+ * ░░░░░░▀▀░▀▀░░░░░░
+ * ░░░░░░░░░░░░░░░░░
+ *
+ * When a user either installs a plugin onto a table resource for the first time
+ * or they configure an existing installation, this is the view that is presented
+ * to the user. For many plugin applications it's essential to capture information
+ * that is required to allow your plugin to work correctly and this is the best
+ * place to do it.
+ *
+ * It is a requirement that a save button that triggers the `OuterbaseEvent.onSave`
+ * event exists so Outerbase can complete the installation or preference update
+ * action.
+ */
+var templateConfiguration = document.createElement("template")
+templateConfiguration.innerHTML = `
+
+
+
+
Select Foreign Key Column
+
+
+
+
+
+`
+
+class OuterbasePluginConfiguration_$PLUGIN_ID extends HTMLElement {
+ static get observedAttributes() {
+ return privileges
+ }
+
+ config = new OuterbasePluginConfig_$PLUGIN_ID({})
+
+ constructor() {
+ super()
+
+ this.shadow = this.attachShadow({ mode: "open" })
+ this.shadow.appendChild(templateConfiguration.content.cloneNode(true))
+ }
+
+ connectedCallback() {
+ this.config = new OuterbasePluginConfig_$PLUGIN_ID(decodeAttributeByName_$PLUGIN_ID(this, "configuration"))
+ this.config.cellValue = decodeAttributeByName_$PLUGIN_ID(this, "cellValue")
+
+ var saveButton = this.shadow.getElementById("saveButton");
+ saveButton.addEventListener("click", () => {
+ // Clear FK cache
+ const cacheName = 'pluginForeignKeyCache'
+ localStorage.removeItem(cacheName)
+
+ triggerEvent_$PLUGIN_ID(this, {
+ action: OuterbaseEvent_$PLUGIN_ID.onSave,
+ value: this.config
+ })
+ });
+
+ // var saveButton = this.shadow.getElementById("saveButton");
+ // saveButton.addEventListener("click", () => {
+ // triggerEvent(this, {
+ // action: OuterbaseEvent.onSave,
+ // value: this.config.toJSON()
+ // })
+ // });
+
+ // Listen to when the select option changes and store the selected option
+ this.shadow.querySelector('select').addEventListener('change', (event) => {
+ this.config.preferredColumn = event.target.value
+ })
+
+ this.fetchConstraintMetadata()
+ this.render()
+ }
+
+ async fetchConstraintMetadata() {
+ try {
+ // Based on the information provided to our plugin, we need to identify
+ // what the column constraints are and what database table it is linked to.
+ // This will allow us to construct a SQL query to fetch the value from the
+ // linked table.
+ const column = this.getAttribute('columnName')
+ const table = JSON.parse(this.getAttribute('tableSchemaValue')).name
+ const schema = JSON.parse(this.getAttribute('tableSchemaValue')).schema ?? "public"
+ const databaseSchemaValue = JSON.parse(this.getAttribute('databaseSchemaValue'))
+ const schemaColumns = databaseSchemaValue?.[schema]
+ const schemaTable = schemaColumns.find(t => t.name === table)
+ const constraints = schemaTable?.constraints
+
+ // if (constraints.length === 0) {
+ // this.shadow.querySelector('#loader').style.display = 'none'
+ // return
+ // }
+
+ let fkName = ""
+ let fkTable = ""
+ let fkSchema = ""
+ // let cellValue = this.getAttribute('cellValue')
+
+ // Loop through `constraints` and find where type === `FOREIGN KEY`
+ for (const constraint of constraints) {
+ if (constraint.type === "FOREIGN KEY" && constraint.table === table && constraint.column === column) {
+ const fkColumn = constraint.columns?.[0]
+ fkName = fkColumn.name
+ fkTable = fkColumn.table
+ fkSchema = fkColumn.schema ?? "public"
+ }
+ }
+
+ // Necessary information is graciously stored by Outerbase in the `localStorage`
+ // for us to make the necessary network request to fetch the value from the linked table.
+ const session = JSON.parse(localStorage.getItem('session'))
+ const workspaceId = localStorage.getItem('workspace_id')
+ const sourceId = localStorage.getItem('source_id')
+
+ // Create a unique cache key based on the `schema.table.column.value`
+ // const cacheKey = `${fkSchema}.${fkTable}.${fkName}.${cellValue}`
+
+ // // If the `cacheKey` already exists in the cache, use the cached value instead
+ // // of making another network request to fetch it.
+ // if (this.getValueFromCache(cacheKey)) {
+ // this.shadow.querySelector('#label').innerText = this.getValueFromCache(cacheKey)
+ // this.shadow.querySelector('#loader').style.display = 'none'
+ // return
+ // }
+
+ // When a cached value does not exist for this `schema.table.column.value`, fetch the value
+ // from the database and store it in the cache for future re-use.
+ await fetch(`https://app.dev.outerbase.com/api/v1/workspace/${workspaceId}/source/${sourceId}/query/raw`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'x-auth-token': session?.state?.session?.token
+ },
+ body: JSON.stringify({
+ query: `SELECT * FROM ${fkSchema}.${fkTable} LIMIT 1`,
+ options: {}
+ })
+ }).then(response => response.json()).then(data => {
+ const item = data.response?.items?.[0] ?? {}
+ const keys = Object.keys(item)
+ // console.log('First Row Keys: ', keys)
+
+ // Add a new `option` in the `select` for each keys object
+ let select = this.shadow.querySelector('select')
+ keys.forEach(key => {
+ let option = document.createElement('option')
+ option.value = key
+ option.text = key
+ select.appendChild(option)
+ })
+ })
+ } catch (error) {
+ console.error(error)
+ }
+ }
+
+ render() {
+
+ }
+}
+
+// DO NOT change the name of this variable or the classes defined in this file.
+// Changing the name of this variable will cause your plugin to not work properly
+// when installed in Outerbase.
+// window.customElements.define('outerbase-plugin-cell', OuterbasePluginCell_$PLUGIN_ID)
+// window.customElements.define('outerbase-plugin-configuration', OuterbasePluginConfiguration_$PLUGIN_ID)
+
+window.customElements.define('outerbase-plugin-cell-$PLUGIN_ID', OuterbasePluginCell_$PLUGIN_ID)
+window.customElements.define('outerbase-plugin-configuration-$PLUGIN_ID', OuterbasePluginConfiguration_$PLUGIN_ID)
diff --git a/columns/hackathon/image.js b/columns/hackathon/image.js
new file mode 100644
index 0000000..1bff1a4
--- /dev/null
+++ b/columns/hackathon/image.js
@@ -0,0 +1,328 @@
+var observableAttributes = [
+ // The value of the cell that the plugin is being rendered in
+ "cellValue",
+ // The configuration object that the user specified when installing the plugin
+ "configuration",
+ // Additional information about the view such as count, page and offset.
+ "metadata"
+]
+
+var OuterbaseEvent = {
+ // The user has triggered an action to save updates
+ onSave: "onSave",
+}
+
+var OuterbaseColumnEvent = {
+ // The user has began editing the selected cell
+ onEdit: "onEdit",
+ // Stops editing a cells editor popup view and accept the changes
+ onStopEdit: "onStopEdit",
+ // Stops editing a cells editor popup view and prevent persisting the changes
+ onCancelEdit: "onCancelEdit",
+ // Updates the cells value with the provided value
+ updateCell: "updateCell",
+}
+
+/**
+ * ******************
+ * Custom Definitions
+ * ******************
+ *
+ * ░░░░░░░░░░░░░░░░░
+ * ░░░░▄▄████▄▄░░░░░
+ * ░░░██████████░░░░
+ * ░░░██▄▄██▄▄██░░░░
+ * ░░░░▄▀▄▀▀▄▀▄░░░░░
+ * ░░░▀░░░░░░░░▀░░░░
+ * ░░░░░░░░░░░░░░░░░
+ *
+ * Define your custom classes here. We do recommend the usage of our `OuterbasePluginConfig_$PLUGIN_ID`
+ * class for you to manage properties between the other classes below, however, it's strictly optional.
+ * However, this would be a good class to contain the properties you need to store when a user installs
+ * or configures your plugin.
+ */
+class OuterbasePluginConfig_$PLUGIN_ID {
+ cellValue = undefined
+
+ constructor(object) {
+
+ }
+}
+
+var triggerEvent = (fromClass, data) => {
+ const event = new CustomEvent("custom-change", {
+ detail: data,
+ bubbles: true,
+ composed: true
+ });
+
+ fromClass.dispatchEvent(event);
+}
+
+var decodeAttributeByName = (fromClass, name) => {
+ const encodedJSON = fromClass.getAttribute(name);
+ const decodedJSON = encodedJSON
+ ?.replace(/"/g, '"')
+ ?.replace(/'/g, "'");
+ return decodedJSON ? JSON.parse(decodedJSON) : {};
+}
+
+/**
+ * **********
+ * Cell View
+ * **********
+ *
+ * ░░░░░░░░░░░░░░░░░░
+ * ░░░░░▄▄████▄▄░░░░░
+ * ░░░▄██████████▄░░░
+ * ░▄██▄██▄██▄██▄██▄░
+ * ░░░▀█▀░░▀▀░░▀█▀░░░
+ * ░░░░░░░░░░░░░░░░░░
+ * ░░░░░░░░░░░░░░░░░░
+ *
+ * TBD
+ */
+var templateCell_$PLUGIN_ID = document.createElement('template')
+templateCell_$PLUGIN_ID.innerHTML = `
+
+
+
+
+
+
+
+
Overlay
+
+`
+
+class OuterbasePluginCell_$PLUGIN_ID extends HTMLElement {
+ static get observedAttributes() {
+ return privileges
+ }
+
+ config = new OuterbasePluginConfig_$PLUGIN_ID({})
+
+ constructor() {
+ super()
+
+ this.shadow = this.attachShadow({ mode: "open" })
+ this.shadow.appendChild(templateCell_$PLUGIN_ID.content.cloneNode(true))
+ }
+
+ connectedCallback() {
+ this.config = new OuterbasePluginConfig_$PLUGIN_ID(decodeAttributeByName(this, "configuration"))
+ this.render()
+
+ // this.shadow.querySelector("img").addEventListener("click", () => {
+ // // Make `overlay` visible
+ // this.shadow.querySelector("#overlay").style.opacity = 1;
+ // this.shadow.querySelector("#overlay").style.display = 'block';
+ // })
+
+ var viewImageButton = this.shadow.querySelector("img");
+ viewImageButton.addEventListener("click", () => {
+ let url = `${this.getAttribute('cellvalue')}`
+ window.open(url, '_blank')
+ });
+ }
+
+ render() {
+ this.shadow.querySelector("img").src = this.getAttribute('cellvalue')
+ }
+
+ callCustomEvent(data) {
+ const event = new CustomEvent('custom-change', {
+ detail: data,
+ bubbles: true, // If you want the event to bubble up through the DOM
+ composed: true // Allows the event to pass through shadow DOM boundaries
+ });
+
+ this.dispatchEvent(event);
+ }
+}
+
+/**
+ * ****************
+ * Cell Editor View
+ * ****************
+ *
+ * ░░░░░░░░░░░░░░░░░░
+ * ░░░░░▄▄████▄▄░░░░░
+ * ░░░▄██████████▄░░░
+ * ░▄██▄██▄██▄██▄██▄░
+ * ░░░▀█▀░░▀▀░░▀█▀░░░
+ * ░░░░░░░░░░░░░░░░░░
+ * ░░░░░░░░░░░░░░░░░░
+ *
+ * An optional view that pops below the cell for an expanded viewing area
+ * of additional UI data.
+ */
+var templateEditor_$PLUGIN_ID = document.createElement("template")
+templateEditor_$PLUGIN_ID.innerHTML = `
+
+
+
+
Editor View
+
+`
+
+class OuterbasePluginCellEditor_$PLUGIN_ID extends HTMLElement {
+ static get observedAttributes() {
+ return privileges
+ }
+
+ config = new OuterbasePluginConfig_$PLUGIN_ID({})
+
+ constructor() {
+ super()
+
+ this.shadow = this.attachShadow({ mode: "open" })
+ this.shadow.appendChild(templateEditor_$PLUGIN_ID.content.cloneNode(true))
+ }
+
+ connectedCallback() {
+ this.config = new OuterbasePluginConfig_$PLUGIN_ID(decodeAttributeByName(this, "configuration"))
+ this.config.cellValue = decodeAttributeByName(this, "cellValue")
+ this.render()
+ }
+
+ render() {
+
+ }
+}
+
+/**
+ * ******************
+ * Configuration View
+ * ******************
+ *
+ * ░░░░░░░░░░░░░░░░░
+ * ░░░░░▀▄░░░▄▀░░░░░
+ * ░░░░▄█▀███▀█▄░░░░
+ * ░░░█▀███████▀█░░░
+ * ░░░█░█▀▀▀▀▀█░█░░░
+ * ░░░░░░▀▀░▀▀░░░░░░
+ * ░░░░░░░░░░░░░░░░░
+ *
+ * When a user either installs a plugin onto a table resource for the first time
+ * or they configure an existing installation, this is the view that is presented
+ * to the user. For many plugin applications it's essential to capture information
+ * that is required to allow your plugin to work correctly and this is the best
+ * place to do it.
+ *
+ * It is a requirement that a save button that triggers the `OuterbaseEvent.onSave`
+ * event exists so Outerbase can complete the installation or preference update
+ * action.
+ */
+var templateConfiguration = document.createElement("template")
+templateConfiguration.innerHTML = `
+
+
+
+
+
+`
+
+// For Configuration view, let them optionally provide a PREFIX URL
+// to attach to all URL's in the column. If none is provided, just
+// try using the value of the cell.
+
+class OuterbasePluginConfiguration_$PLUGIN_ID extends HTMLElement {
+ static get observedAttributes() {
+ return privileges
+ }
+
+ config = new OuterbasePluginConfig_$PLUGIN_ID({})
+
+ constructor() {
+ super()
+
+ this.shadow = this.attachShadow({ mode: "open" })
+ this.shadow.appendChild(templateConfiguration.content.cloneNode(true))
+ }
+
+ connectedCallback() {
+ this.config = new OuterbasePluginConfig_$PLUGIN_ID(decodeAttributeByName(this, "configuration"))
+ this.config.cellValue = decodeAttributeByName(this, "cellValue")
+ this.render()
+ }
+
+ render() {
+ this.shadow.querySelector("#container").innerHTML = `
+
+
Hello, Configuration World!
+
+
+ `
+
+ var saveButton = this.shadow.getElementById("saveButton");
+ saveButton.addEventListener("click", () => {
+ triggerEvent(this, {
+ action: OuterbaseEvent.onSave,
+ value: {}
+ })
+ });
+ }
+}
+
+// DO NOT change the name of this variable or the classes defined in this file.
+// Changing the name of this variable will cause your plugin to not work properly
+// when installed in Outerbase.
+window.customElements.define('outerbase-plugin-cell-$PLUGIN_ID', OuterbasePluginCell_$PLUGIN_ID)
+// window.customElements.define('outerbase-plugin-cell-editor', OuterbasePluginCellEditor_$PLUGIN_ID)
+// window.customElements.define('outerbase-plugin-configuration-$PLUGIN_ID', OuterbasePluginConfiguration_$PLUGIN_ID)
diff --git a/columns/hackathon/link.js b/columns/hackathon/link.js
new file mode 100644
index 0000000..f25a77a
--- /dev/null
+++ b/columns/hackathon/link.js
@@ -0,0 +1,285 @@
+var observableAttributes_$PLUGIN_ID = [
+ // The value of the cell that the plugin is being rendered in
+ "cellValue",
+ // The configuration object that the user specified when installing the plugin
+ "configuration",
+ // Additional information about the view such as count, page and offset.
+ "metadata"
+]
+
+var OuterbaseEvent_$PLUGIN_ID = {
+ // The user has triggered an action to save updates
+ onSave: "onSave",
+}
+
+var OuterbaseColumnEvent_$PLUGIN_ID = {
+ // The user has began editing the selected cell
+ onEdit: "onEdit",
+ // Stops editing a cells editor popup view and accept the changes
+ onStopEdit: "onStopEdit",
+ // Stops editing a cells editor popup view and prevent persisting the changes
+ onCancelEdit: "onCancelEdit",
+ // Updates the cells value with the provided value
+ updateCell: "updateCell",
+}
+
+var triggerEvent_$PLUGIN_ID = (fromClass, data) => {
+ const event = new CustomEvent("custom-change", {
+ detail: data,
+ bubbles: true,
+ composed: true
+ });
+
+ fromClass.dispatchEvent(event);
+}
+
+/**
+ * ******************
+ * Custom Definitions
+ * ******************
+ *
+ * ░░░░░░░░░░░░░░░░░
+ * ░░░░▄▄████▄▄░░░░░
+ * ░░░██████████░░░░
+ * ░░░██▄▄██▄▄██░░░░
+ * ░░░░▄▀▄▀▀▄▀▄░░░░░
+ * ░░░▀░░░░░░░░▀░░░░
+ * ░░░░░░░░░░░░░░░░░
+ *
+ * Define your custom classes here. We do recommend the usage of our `OuterbasePluginConfig_$PLUGIN_ID`
+ * class for you to manage properties between the other classes below, however, it's strictly optional.
+ * However, this would be a good class to contain the properties you need to store when a user installs
+ * or configures your plugin.
+ */
+class OuterbasePluginConfig_$PLUGIN_ID {
+ theme = "light"
+ baseURL = ""
+
+ constructor(object) {
+ this.theme = object?.theme ? object.theme : "light";
+ this.baseURL = object?.baseUrl ? object.baseUrl : "";
+ }
+}
+
+var triggerEvent = (fromClass, data) => {
+ const event = new CustomEvent("custom-change", {
+ detail: data,
+ bubbles: true,
+ composed: true
+ });
+
+ fromClass.dispatchEvent(event);
+}
+
+var decodeAttributeByName_$PLUGIN_ID = (fromClass, name) => {
+ const encodedJSON = fromClass.getAttribute(name);
+ const decodedJSON = encodedJSON
+ ?.replace(/"/g, '"')
+ ?.replace(/'/g, "'");
+ return decodedJSON ? JSON.parse(decodedJSON) : {};
+}
+
+/**
+ * **********
+ * Cell View
+ * **********
+ *
+ * ░░░░░░░░░░░░░░░░░░
+ * ░░░░░▄▄████▄▄░░░░░
+ * ░░░▄██████████▄░░░
+ * ░▄██▄██▄██▄██▄██▄░
+ * ░░░▀█▀░░▀▀░░▀█▀░░░
+ * ░░░░░░░░░░░░░░░░░░
+ * ░░░░░░░░░░░░░░░░░░
+ *
+ * TBD
+ */
+var templateCell_$PLUGIN_ID = document.createElement('template')
+templateCell_$PLUGIN_ID.innerHTML = `
+
+
+
+
+
+`
+
+class OuterbasePluginCell_$PLUGIN_ID extends HTMLElement {
+ static get observedAttributes() {
+ return observableAttributes_$PLUGIN_ID
+ }
+
+ config = new OuterbasePluginConfig_$PLUGIN_ID({})
+
+ constructor() {
+ super()
+
+ this.shadow = this.attachShadow({ mode: "open" })
+ this.shadow.appendChild(templateCell_$PLUGIN_ID.content.cloneNode(true))
+ }
+
+ connectedCallback() {
+ this.config = new OuterbasePluginConfig_$PLUGIN_ID(decodeAttributeByName_$PLUGIN_ID(this, "configuration"))
+ this.render()
+
+ // When a user clicks the span open a new tab with the link
+ this.shadow.querySelector('span').addEventListener('click', () => {
+ let url = `${this.config.baseURL}${this.getAttribute('cellvalue')}`
+ window.open(url, '_blank')
+ })
+ }
+
+ attributeChangedCallback(name, oldValue, newValue) {
+ this.config = new OuterbasePluginConfig_$PLUGIN_ID(decodeAttributeByName_$PLUGIN_ID(this, "configuration"))
+
+ let metadata = decodeAttributeByName_$PLUGIN_ID(this, "metadata")
+ this.config.theme = metadata?.theme
+
+ var element = this.shadow.querySelector(".theme-container")
+ element.classList.remove("dark")
+ element.classList.add(this.config.theme);
+ }
+
+ render() {
+ let cellValue = this.getAttribute('cellvalue')
+
+ if (cellValue.length === 0) {
+ cellValue = "NULL"
+ }
+
+ this.shadow.querySelector('span').innerText = cellValue
+ }
+}
+
+
+
+
+
+
+// For Configuration view, let them optionally provide a PREFIX URL
+// to attach to all URL's in the column. If none is provided, just
+// try using the value of the cell.
+
+
+/**
+ * ******************
+ * Configuration View
+ * ******************
+ *
+ * ░░░░░░░░░░░░░░░░░
+ * ░░░░░▀▄░░░▄▀░░░░░
+ * ░░░░▄█▀███▀█▄░░░░
+ * ░░░█▀███████▀█░░░
+ * ░░░█░█▀▀▀▀▀█░█░░░
+ * ░░░░░░▀▀░▀▀░░░░░░
+ * ░░░░░░░░░░░░░░░░░
+ *
+ * When a user either installs a plugin onto a table resource for the first time
+ * or they configure an existing installation, this is the view that is presented
+ * to the user. For many plugin applications it's essential to capture information
+ * that is required to allow your plugin to work correctly and this is the best
+ * place to do it.
+ *
+ * It is a requirement that a save button that triggers the `OuterbaseEvent.onSave`
+ * event exists so Outerbase can complete the installation or preference update
+ * action.
+ */
+var templateConfiguration = document.createElement("template")
+templateConfiguration.innerHTML = `
+
+
+
+
Select URL Options
+
+
+
Base URL: (optional)
+
+
+
+ Save View
+
+`
+
+class OuterbasePluginConfiguration_$PLUGIN_ID extends HTMLElement {
+ static get observedAttributes() {
+ return observableAttributes_$PLUGIN_ID
+ }
+
+ config = new OuterbasePluginConfig_$PLUGIN_ID({})
+
+ constructor() {
+ super()
+
+ this.shadow = this.attachShadow({ mode: "open" })
+ this.shadow.appendChild(templateConfiguration.content.cloneNode(true))
+ }
+
+ connectedCallback() {
+ this.config = new OuterbasePluginConfig_$PLUGIN_ID(decodeAttributeByName_$PLUGIN_ID(this, "configuration"))
+ this.config.cellValue = decodeAttributeByName_$PLUGIN_ID(this, "cellValue")
+
+ var saveButton = this.shadow.getElementById("saveButton");
+ saveButton.addEventListener("click", () => {
+ this.config.baseURL = this.shadow.getElementById("prefixValue").value
+
+ triggerEvent_$PLUGIN_ID(this, {
+ action: OuterbaseEvent_$PLUGIN_ID.onSave,
+ value: this.config
+ })
+ });
+
+ this.render()
+ }
+
+ render() {
+
+ }
+}
+
+// DO NOT change the name of this variable or the classes defined in this file.
+// Changing the name of this variable will cause your plugin to not work properly
+// when installed in Outerbase.
+// window.customElements.define('outerbase-plugin-cell', OuterbasePluginCell_$PLUGIN_ID)
+// window.customElements.define('outerbase-plugin-configuration', OuterbasePluginConfiguration_$PLUGIN_ID)
+
+
+window.customElements.define('outerbase-plugin-cell-$PLUGIN_ID', OuterbasePluginCell_$PLUGIN_ID)
+window.customElements.define('outerbase-plugin-configuration-$PLUGIN_ID', OuterbasePluginConfiguration_$PLUGIN_ID)
diff --git a/columns/hackathon/privacy.js b/columns/hackathon/privacy.js
new file mode 100644
index 0000000..3146926
--- /dev/null
+++ b/columns/hackathon/privacy.js
@@ -0,0 +1,170 @@
+var observableAttributes_$PLUGIN_ID = [
+ // The value of the cell that the plugin is being rendered in
+ "cellValue",
+ // The configuration object that the user specified when installing the plugin
+ "configuration",
+ // Additional information about the view such as count, page and offset.
+ "metadata"
+]
+
+var OuterbaseEvent = {
+ // The user has triggered an action to save updates
+ onSave: "onSave",
+}
+
+var OuterbaseColumnEvent = {
+ // The user has began editing the selected cell
+ onEdit: "onEdit",
+ // Stops editing a cells editor popup view and accept the changes
+ onStopEdit: "onStopEdit",
+ // Stops editing a cells editor popup view and prevent persisting the changes
+ onCancelEdit: "onCancelEdit",
+ // Updates the cells value with the provided value
+ updateCell: "updateCell",
+}
+
+/**
+ * ******************
+ * Custom Definitions
+ * ******************
+ *
+ * ░░░░░░░░░░░░░░░░░
+ * ░░░░▄▄████▄▄░░░░░
+ * ░░░██████████░░░░
+ * ░░░██▄▄██▄▄██░░░░
+ * ░░░░▄▀▄▀▀▄▀▄░░░░░
+ * ░░░▀░░░░░░░░▀░░░░
+ * ░░░░░░░░░░░░░░░░░
+ *
+ * Define your custom classes here. We do recommend the usage of our `OuterbasePluginConfig_$PLUGIN_ID`
+ * class for you to manage properties between the other classes below, however, it's strictly optional.
+ * However, this would be a good class to contain the properties you need to store when a user installs
+ * or configures your plugin.
+ */
+class OuterbasePluginConfig_$PLUGIN_ID {
+ cellValue = undefined
+
+ constructor(object) {
+
+ }
+}
+
+var triggerEvent_$PLUGIN_ID = (fromClass, data) => {
+ const event = new CustomEvent("custom-change", {
+ detail: data,
+ bubbles: true,
+ composed: true
+ });
+
+ fromClass.dispatchEvent(event);
+}
+
+var decodeAttributeByName_$PLUGIN_ID = (fromClass, name) => {
+ const encodedJSON = fromClass.getAttribute(name);
+ const decodedJSON = encodedJSON
+ ?.replace(/"/g, '"')
+ ?.replace(/'/g, "'")
+ ?.replace(/`/g, "`");
+ return decodedJSON ? JSON.parse(decodedJSON) : {};
+}
+
+/**
+ * **********
+ * Cell View
+ * **********
+ *
+ * ░░░░░░░░░░░░░░░░░░
+ * ░░░░░▄▄████▄▄░░░░░
+ * ░░░▄██████████▄░░░
+ * ░▄██▄██▄██▄██▄██▄░
+ * ░░░▀█▀░░▀▀░░▀█▀░░░
+ * ░░░░░░░░░░░░░░░░░░
+ * ░░░░░░░░░░░░░░░░░░
+ *
+ * TBD
+ */
+var templateCell_$PLUGIN_ID = document.createElement('template')
+templateCell_$PLUGIN_ID.innerHTML = `
+
+
+
+
+
+`
+
+class OuterbasePluginCell_$PLUGIN_ID extends HTMLElement {
+ static get observedAttributes() {
+ return observableAttributes_$PLUGIN_ID
+ }
+
+ config = new OuterbasePluginConfig_$PLUGIN_ID({})
+
+ constructor() {
+ super()
+
+ this.shadow = this.attachShadow({ mode: "open" })
+ this.shadow.appendChild(templateCell_$PLUGIN_ID.content.cloneNode(true))
+ }
+
+ connectedCallback() {
+ this.config = new OuterbasePluginConfig_$PLUGIN_ID(decodeAttributeByName_$PLUGIN_ID(this, "configuration"))
+ this.render()
+ }
+
+ attributeChangedCallback(name, oldValue, newValue) {
+ this.config = new OuterbasePluginConfig_$PLUGIN_ID(decodeAttributeByName_$PLUGIN_ID(this, "configuration"))
+
+ let metadata = decodeAttributeByName_$PLUGIN_ID(this, "metadata")
+ this.config.theme = metadata?.theme
+
+ var element = this.shadow.querySelector(".theme-container")
+ element.classList.remove("dark")
+ element.classList.add(this.config.theme);
+ }
+
+ render() {
+ let cellValue = this.getAttribute('cellvalue')
+
+ if (cellValue.length === 0) {
+ cellValue = "NULL"
+ }
+
+ this.shadow.querySelector('input').value = cellValue
+ }
+}
+
+// DO NOT change the name of this variable or the classes defined in this file.
+// Changing the name of this variable will cause your plugin to not work properly
+// when installed in Outerbase.
+// window.customElements.define('outerbase-plugin-cell', OuterbasePluginCell_$PLUGIN_ID)
+window.customElements.define('outerbase-plugin-cell-$PLUGIN_ID', OuterbasePluginCell_$PLUGIN_ID)
\ No newline at end of file
diff --git a/columns/hackathon/value-range.js b/columns/hackathon/value-range.js
new file mode 100644
index 0000000..99f275b
--- /dev/null
+++ b/columns/hackathon/value-range.js
@@ -0,0 +1,547 @@
+var observableAttributes_$PLUGIN_ID = [
+ // The value of the cell that the plugin is being rendered in
+ "cellValue",
+ // The configuration object that the user specified when installing the plugin
+ "configuration",
+ // Additional information about the view such as count, page and offset.
+ "metadata"
+]
+
+var OuterbaseEvent_$PLUGIN_ID = {
+ // The user has triggered an action to save updates
+ onSave: "onSave",
+}
+
+var OuterbaseColumnEvent_$PLUGIN_ID = {
+ // The user has began editing the selected cell
+ onEdit: "onEdit",
+ // Stops editing a cells editor popup view and accept the changes
+ onStopEdit: "onStopEdit",
+ // Stops editing a cells editor popup view and prevent persisting the changes
+ onCancelEdit: "onCancelEdit",
+ // Updates the cells value with the provided value
+ updateCell: "updateCell",
+}
+
+/**
+ * ******************
+ * Custom Definitions
+ * ******************
+ *
+ * ░░░░░░░░░░░░░░░░░
+ * ░░░░▄▄████▄▄░░░░░
+ * ░░░██████████░░░░
+ * ░░░██▄▄██▄▄██░░░░
+ * ░░░░▄▀▄▀▀▄▀▄░░░░░
+ * ░░░▀░░░░░░░░▀░░░░
+ * ░░░░░░░░░░░░░░░░░
+ *
+ * Define your custom classes here. We do recommend the usage of our `OuterbasePluginConfig_$PLUGIN_ID`
+ * class for you to manage properties between the other classes below, however, it's strictly optional.
+ * However, this would be a good class to contain the properties you need to store when a user installs
+ * or configures your plugin.
+ */
+class OuterbasePluginConfig_$PLUGIN_ID {
+ theme = "light"
+ rangeOptions = []
+
+ constructor(object) {
+ this.theme = object?.theme ? object.theme : "light";
+ this.rangeOptions = object?.rangeOptions ? object.rangeOptions : [];
+ }
+}
+
+var triggerEvent_$PLUGIN_ID = (fromClass, data) => {
+ const event = new CustomEvent("custom-change", {
+ detail: data,
+ bubbles: true,
+ composed: true
+ });
+
+ fromClass.dispatchEvent(event);
+}
+
+var decodeAttributeByName_$PLUGIN_ID = (fromClass, name) => {
+ const encodedJSON = fromClass.getAttribute(name);
+ const decodedJSON = encodedJSON
+ ?.replace(/"/g, '"')
+ ?.replace(/'/g, "'");
+ return decodedJSON ? JSON.parse(decodedJSON) : {};
+}
+
+/**
+ * **********
+ * Cell View
+ * **********
+ *
+ * ░░░░░░░░░░░░░░░░░░
+ * ░░░░░▄▄████▄▄░░░░░
+ * ░░░▄██████████▄░░░
+ * ░▄██▄██▄██▄██▄██▄░
+ * ░░░▀█▀░░▀▀░░▀█▀░░░
+ * ░░░░░░░░░░░░░░░░░░
+ * ░░░░░░░░░░░░░░░░░░
+ *
+ * TBD
+ */
+var templateCell_$PLUGIN_ID = document.createElement('template')
+templateCell_$PLUGIN_ID.innerHTML = `
+
+
+
+
+
+
+`
+
+class OuterbasePluginCell_$PLUGIN_ID extends HTMLElement {
+ static get observedAttributes() {
+ return observableAttributes_$PLUGIN_ID
+ }
+
+ config = new OuterbasePluginConfig_$PLUGIN_ID({})
+
+ constructor() {
+ super()
+
+ this.shadow = this.attachShadow({ mode: "open" })
+ this.shadow.appendChild(templateCell_$PLUGIN_ID.content.cloneNode(true))
+ }
+
+ connectedCallback() {
+ this.config = new OuterbasePluginConfig_$PLUGIN_ID(decodeAttributeByName_$PLUGIN_ID(this, "configuration"))
+ this.render()
+ }
+
+ attributeChangedCallback(name, oldValue, newValue) {
+ this.config = new OuterbasePluginConfig_$PLUGIN_ID(decodeAttributeByName_$PLUGIN_ID(this, "configuration"))
+
+ let metadata = decodeAttributeByName_$PLUGIN_ID(this, "metadata")
+ this.config.theme = metadata?.theme
+
+ var element = this.shadow.querySelector(".theme-container")
+ element.classList.remove("dark")
+ element.classList.add(this.config.theme);
+ }
+
+ render() {
+ let cellValue = this.getAttribute('cellvalue')
+
+ if (cellValue.length === 0) {
+ cellValue = "NULL"
+ }
+
+ this.shadow.querySelector('span').innerText = cellValue
+
+ // Get the indicator element
+ const indicator = this.shadow.querySelector("#indicator")
+
+ if (cellValue === "NULL") {
+ indicator.style.backgroundColor = "transparent"
+ return
+ }
+
+ let number = Number(cellValue)
+ let rangeOptions = this.config.rangeOptions
+
+ for (let i = 0; i < rangeOptions.length; i++) {
+ let range = rangeOptions[i]
+ let value = Number(range.value)
+
+ if (range.operator === ">") {
+ if (number > value) {
+ indicator.style.backgroundColor = range.color
+ return
+ }
+ } else if (range.operator === ">=") {
+ if (number >= value) {
+ indicator.style.backgroundColor = range.color
+ return
+ }
+ } else if (range.operator === "<") {
+ if (number < value) {
+ indicator.style.backgroundColor = range.color
+ return
+ }
+ } else if (range.operator === "<=") {
+ if (number <= value) {
+ indicator.style.backgroundColor = range.color
+ return
+ }
+ } else if (range.operator === "=") {
+ if (number === value) {
+ indicator.style.backgroundColor = range.color
+ return
+ }
+ }
+ }
+
+ // If the indicator is less than 5, set the color to red
+ // let number = parseInt(cellValue)
+ // if (number <= 5) {
+ // indicator.style.backgroundColor = "#fafafa"
+ // } else if (number > 5 && number < 10) {
+ // indicator.style.backgroundColor = "#a8a29e"
+ // } else {
+ // indicator.style.backgroundColor = "#44403c"
+ // }
+
+ }
+}
+
+
+
+
+
+
+
+// SQL to get range of integer values:
+// ----
+// SELECT MIN(column_name) AS MinValue, MAX(column_name) AS MaxValue
+// FROM table_name;
+// ----
+// Put the above in a configuration view so we can quickly sample the data
+// and provide a range of values to the user for their behalf.
+// May also allow them to put a MIN and MAX in as well, or define
+// values to indicator colors themselves.
+
+/**
+ * ******************
+ * Configuration View
+ * ******************
+ *
+ * ░░░░░░░░░░░░░░░░░
+ * ░░░░░▀▄░░░▄▀░░░░░
+ * ░░░░▄█▀███▀█▄░░░░
+ * ░░░█▀███████▀█░░░
+ * ░░░█░█▀▀▀▀▀█░█░░░
+ * ░░░░░░▀▀░▀▀░░░░░░
+ * ░░░░░░░░░░░░░░░░░
+ *
+ * When a user either installs a plugin onto a table resource for the first time
+ * or they configure an existing installation, this is the view that is presented
+ * to the user. For many plugin applications it's essential to capture information
+ * that is required to allow your plugin to work correctly and this is the best
+ * place to do it.
+ *
+ * It is a requirement that a save button that triggers the `OuterbaseEvent.onSave`
+ * event exists so Outerbase can complete the installation or preference update
+ * action.
+ */
+var templateConfiguration = document.createElement("template")
+templateConfiguration.innerHTML = `
+
+
+
+
Select Range Options
+
+
+ Min Value:
+
+
+ Max Value:
+
+
+
+
+
+
+
+
+
+ Add Range
+
+ Save View
+
+`
+
+class OuterbasePluginConfiguration_$PLUGIN_ID extends HTMLElement {
+ static get observedAttributes() {
+ return observableAttributes_$PLUGIN_ID
+ }
+
+ config = new OuterbasePluginConfig_$PLUGIN_ID({})
+ minValue = 0
+ maxValue = 50
+ rangeOptions = [
+ // { color: "#FF0000", operator: ">", value: 5 },
+ // { color: "#00FF00", operator: "<=", value: 5 }
+ ]
+
+ constructor() {
+ super()
+
+ this.shadow = this.attachShadow({ mode: "open" })
+ this.shadow.appendChild(templateConfiguration.content.cloneNode(true))
+ }
+
+ connectedCallback() {
+ this.config = new OuterbasePluginConfig_$PLUGIN_ID(decodeAttributeByName_$PLUGIN_ID(this, "configuration"))
+ this.config.cellValue = decodeAttributeByName_$PLUGIN_ID(this, "cellValue")
+
+ var saveButton = this.shadow.getElementById("saveButton");
+ saveButton.addEventListener("click", () => {
+ this.config.rangeOptions = this.rangeOptions
+
+ triggerEvent_$PLUGIN_ID(this, {
+ action: OuterbaseEvent_$PLUGIN_ID.onSave,
+ value: this.config
+ })
+ });
+
+ // When the `addRange` button is clicked add a new entry to this.rangeOptions
+ var addRangeButton = this.shadow.getElementById("addRange")
+ addRangeButton.addEventListener("click", () => {
+ this.rangeOptions.push({ color: "#000000", operator: ">", value: 0 })
+ this.render()
+ })
+
+ // If user clicks `valueRangeApply` button, update the minValue and maxValue
+ var applyButton = this.shadow.getElementById("valueRangeApply")
+ applyButton.addEventListener("click", () => {
+ this.minValue = Number(this.shadow.getElementById("minValue").value)
+ this.maxValue = Number(this.shadow.getElementById("maxValue").value)
+ this.smartRangeLayout()
+ })
+
+ this.fetchMinMaxValues()
+ this.render()
+ }
+
+ smartRangeLayout() {
+ console.log('Min: ', this.minValue)
+ console.log('Max: ', this.maxValue)
+
+ // Based on the minValue and maxValue can we automatically create a range of values
+ // for the user to select from. Try to figure out how many values to create and add
+ // them to the `rangeOptions` array with default values.
+ const range = this.maxValue - this.minValue
+ const step = range / 5
+
+ const defaultColors = ['#f5f5f5', '#d4d4d4', '#a3a3a3', '#525252', '#262626']
+
+ this.rangeOptions = []
+
+ for (let i = 0; i < 5; i++) {
+ this.rangeOptions.push({ color: defaultColors[i], operator: ">", value: this.minValue + (step * i) })
+ }
+
+ // Revere the array
+ this.rangeOptions.reverse()
+
+ this.render()
+ }
+
+ async fetchMinMaxValues() {
+ // Testing locally with this
+ // this.minValue = 10
+ // this.maxValue = 100
+ // this.smartRangeLayout()
+ // return
+
+
+ try {
+ // Based on the information provided to our plugin, we need to identify
+ // what the column constraints are and what database table it is linked to.
+ // This will allow us to construct a SQL query to fetch the value from the
+ // linked table.
+ const column = this.getAttribute('columnName')
+ const table = JSON.parse(this.getAttribute('tableSchemaValue')).name
+ const schema = JSON.parse(this.getAttribute('tableSchemaValue')).schema ?? "public"
+
+ // Necessary information is graciously stored by Outerbase in the `localStorage`
+ // for us to make the necessary network request to fetch the value from the linked table.
+ const session = JSON.parse(localStorage.getItem('session'))
+ const workspaceId = localStorage.getItem('workspace_id')
+ const sourceId = localStorage.getItem('source_id')
+
+ // SELECT DISTINCT column_name FROM table_name;
+
+ // When a cached value does not exist for this `schema.table.column.value`, fetch the value
+ // from the database and store it in the cache for future re-use.
+ // SELECT MIN(column_name) AS MinValue, MAX(column_name) AS MaxValue FROM table_name;
+
+ await fetch(`https://app.dev.outerbase.com/api/v1/workspace/${workspaceId}/source/${sourceId}/query/raw`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'x-auth-token': session?.state?.session?.token
+ },
+ body: JSON.stringify({
+ query: `SELECT MIN(${column}) AS minValue, MAX(${column}) AS maxValue FROM ${schema}.${table};`,
+ options: {}
+ })
+ }).then(response => response.json()).then(data => {
+ const items = data.response?.items ?? []
+
+ if (items.length) {
+ const first = items[0]
+ this.minValue = Number(first.minValue)
+ this.maxValue = Number(first.maxValue)
+ }
+
+ this.smartRangeLayout();
+ })
+ } catch (error) {
+ console.error(error)
+ }
+ }
+
+ render() {
+ // Clear all the range definitions
+ const rangeDefinitions = this.shadow.getElementById("range-definitions")
+ rangeDefinitions.innerHTML = ""
+
+ this.shadow.getElementById("minValue").value = this.minValue
+ this.shadow.getElementById("maxValue").value = this.maxValue
+
+ // const rangeDefinitions = this.shadow.getElementById("range-definitions")
+ this.rangeOptions.forEach((item, index) => {
+ const rangeItem = this.createRangeItem(index, item.color, item.operator, item.value)
+ rangeDefinitions.appendChild(rangeItem)
+ })
+
+ // Detect a change in range item color field and update the indicator
+ const rangeItems = this.shadow.querySelectorAll(".range-option")
+ rangeItems.forEach(item => {
+ item.querySelector("input[type='text']").addEventListener("change", event => {
+ const indicator = item.querySelector(".indicator")
+ indicator.style.backgroundColor = event.target.value
+
+ // Update this value in the `this.rangeOptions` array, get the index from the `data-item-id` attribute
+ const index = item.getAttribute("data-item-id")
+ if (!index) return
+ this.rangeOptions[index].color = event.target.value
+ })
+ })
+
+ // Detect a change in range item operator field and update the indicator
+ rangeItems.forEach(item => {
+ item.querySelector("select").addEventListener("change", event => {
+ // Update this value in the `this.rangeOptions` array, get the index from the `data-item-id` attribute
+ const index = item.getAttribute("data-item-id")
+ if (!index) return
+ this.rangeOptions[index].operator = event.target.value
+ })
+ })
+
+ // Detect a change in range item value field and update the indicator
+ rangeItems.forEach(item => {
+ item.querySelector("input[type='number']").addEventListener("change", event => {
+ // Update this value in the `this.rangeOptions` array, get the index from the `data-item-id` attribute
+ const index = item.getAttribute("data-item-id")
+ if (!index) return
+ this.rangeOptions[index].value = event.target.value
+ })
+ })
+
+ // Detect a click on the remove button and remove the range item
+ rangeItems.forEach(item => {
+ item.querySelector("button").addEventListener("click", event => {
+ const index = item.getAttribute("data-item-id")
+ if (!index) return
+ this.rangeOptions.splice(index, 1)
+ this.render()
+ })
+ })
+ }
+
+ createRangeItem(index, color, operator, value) {
+ const rangeItem = document.createElement("div")
+
+ rangeItem.innerHTML = `
+
+
+
+
+
+
+
+ `
+
+ return rangeItem
+ }
+}
+
+// DO NOT change the name of this variable or the classes defined in this file.
+// Changing the name of this variable will cause your plugin to not work properly
+// when installed in Outerbase.
+// window.customElements.define('outerbase-plugin-cell', OuterbasePluginCell_$PLUGIN_ID)
+// window.customElements.define('outerbase-plugin-configuration', OuterbasePluginConfiguration_$PLUGIN_ID)
+
+window.customElements.define('outerbase-plugin-cell-$PLUGIN_ID', OuterbasePluginCell_$PLUGIN_ID)
+window.customElements.define('outerbase-plugin-configuration-$PLUGIN_ID', OuterbasePluginConfiguration_$PLUGIN_ID)
diff --git a/columns/html-preview.js b/columns/html-preview.js
new file mode 100644
index 0000000..1ff21f2
--- /dev/null
+++ b/columns/html-preview.js
@@ -0,0 +1,325 @@
+var privileges_$PLUGIN_ID = [
+ 'cellValue',
+ 'configuration',
+]
+
+var OuterbaseEvent_$PLUGIN_ID = {
+ // The user has triggered an action to save updates
+ onSave: "onSave",
+}
+
+var triggerEvent_$PLUGIN_ID = (fromClass, data) => {
+ const event = new CustomEvent("custom-change", {
+ detail: data,
+ bubbles: true,
+ composed: true
+ });
+
+ fromClass.dispatchEvent(event);
+}
+
+var decodeAttributeByName_$PLUGIN_ID = (fromClass, name) => {
+ const encodedJSON = fromClass.getAttribute(name);
+ const decodedJSON = encodedJSON
+ ?.replace(/"/g, '"')
+ ?.replace(/'/g, "'");
+ return decodedJSON ? JSON.parse(decodedJSON) : {};
+}
+
+var templateCell_$PLUGIN_ID = document.createElement('template')
+templateCell_$PLUGIN_ID.innerHTML = `
+
+
+
+`
+
+// This is the configuration object that Outerbase passes to your plugin.
+// Define all of the configuration options that your plugin requires here.
+class OuterbasePluginConfig_$PLUGIN_ID {
+ theme = "light";
+
+ constructor(object) {
+ this.theme = object.theme ? object.theme : "light";
+ }
+}
+
+class OuterbasePluginCell_$PLUGIN_ID extends HTMLElement {
+ static get observedAttributes() {
+ return privileges_$PLUGIN_ID
+ }
+
+ config = new OuterbasePluginConfig_$PLUGIN_ID({})
+
+ constructor() {
+ super()
+
+ // The shadow DOM is a separate DOM tree that is attached to the element.
+ // This allows us to encapsulate our styles and markup. It also prevents
+ // styles from the parent page from leaking into our plugin.
+ this.shadow = this.attachShadow({ mode: 'open' })
+ this.shadow.appendChild(templateCell_$PLUGIN_ID.content.cloneNode(true))
+ }
+
+ attributeChangedCallback(name, oldValue, newValue) {
+ this.config = new OuterbasePluginConfig_$PLUGIN_ID(decodeAttributeByName_$PLUGIN_ID(this, "configuration"))
+
+ let metadata = decodeAttributeByName_$PLUGIN_ID(this, "metadata")
+ this.config.theme = metadata?.theme
+
+ var element = this.shadow.querySelector(".theme-container")
+ element.classList.remove("dark")
+ element.classList.add(this.config.theme);
+ }
+
+ // This function is called when the UI is made available into the DOM. Put any
+ // logic that you want to run when the element is first stood up here, such as
+ // event listeners, default values to display, etc.
+ connectedCallback() {
+ // Parse the configuration object from the `configuration` attribute
+ // and store it in the `config` property.
+ this.config = new OuterbasePluginConfig_$PLUGIN_ID(
+ JSON.parse(this.getAttribute('configuration'))
+ )
+
+ // Set default value based on input
+ this.shadow.querySelector('#html-value').value = this.getAttribute('cellvalue')
+
+ var imageInput = this.shadow.getElementById("html-value");
+ var viewImageButton = this.shadow.getElementById("action-button");
+
+ if (imageInput && viewImageButton) {
+ imageInput.addEventListener("focus", () => {
+ // Tell Outerbase to start editing the cell
+ this.callCustomEvent({
+ action: 'onstopedit',
+ value: true
+ })
+ });
+
+ imageInput.addEventListener("blur", () => {
+ // Tell Outerbase to update the cells raw value
+ this.callCustomEvent({
+ action: 'cellvalue',
+ value: imageInput.value
+ })
+
+ // Then stop editing the cell and close the editor view
+ this.callCustomEvent({
+ action: 'onstopedit',
+ value: true
+ })
+ });
+
+ viewImageButton.addEventListener("click", () => {
+ this.callCustomEvent({
+ action: 'onedit',
+ value: true
+ })
+ });
+ }
+ }
+
+ callCustomEvent(data) {
+ const event = new CustomEvent('custom-change', {
+ detail: data,
+ bubbles: true, // If you want the event to bubble up through the DOM
+ composed: true // Allows the event to pass through shadow DOM boundaries
+ });
+
+ this.dispatchEvent(event);
+ }
+}
+
+class OuterbasePluginEditor_$PLUGIN_ID extends HTMLElement {
+ static get observedAttributes() {
+ return privileges
+ }
+
+ static NO_VALUE_HEADER = "Nothing to preview";
+ static BAD_VALUE_HEADER = "Failed to load preview";
+
+ config = new OuterbasePluginConfig_$PLUGIN_ID({});
+
+ constructor() {
+ super()
+
+ // The shadow DOM is a separate DOM tree that is attached to the element.
+ // This allows us to encapsulate our styles and markup. It also prevents
+ // styles from the parent page from leaking into our plugin.
+ this.shadow = this.attachShadow({ mode: 'open' })
+ this.shadow.appendChild(templateEditor_$PLUGIN_ID.content.cloneNode(true))
+
+ // Parse the configuration object from the `configuration` attribute
+ // and store it in the `config` property.
+ this.config = new OuterbasePluginConfig_$PLUGIN_ID(
+ JSON.parse(this.getAttribute('configuration'))
+ )
+ }
+
+ // This function is called when the UI is made available into the DOM. Put any
+ // logic that you want to run when the element is first stood up here, such as
+ // event listeners, default values to display, etc.
+ connectedCallback() {
+ const value = this.getAttribute("cellValue");
+
+ this.shadow.querySelector("#container").style.padding = "0px";
+ this.shadow.querySelector("#header").style.display = "block";
+ this.shadow.querySelector("#hr").style.display = "block";
+
+ if (this.isEmpty(value)) {
+ this.shadow.querySelector("#header").innerHTML = OuterbasePluginEditor_$PLUGIN_ID.NO_VALUE_HEADER;
+ return;
+ }
+
+ var error = this.isInvalidHTML(value);
+ if (error) {
+ this.shadow.querySelector("#container").style.padding = "20px";
+ this.shadow.querySelector("#header").innerHTML = OuterbasePluginEditor_$PLUGIN_ID.BAD_VALUE_HEADER;
+ this.shadow.querySelector("#error").style.display = "block";
+ this.shadow.querySelector("#error").innerHTML = error.innerHTML;
+ this.shadow.querySelector("#content").innerText = value;
+ return;
+ }
+
+ this.shadow.querySelector("#header").style.display = "none";
+ this.shadow.querySelector("#hr").style.display = "none";
+ this.shadow.querySelector("#content").innerHTML = value;
+ }
+
+ isEmpty(data) {
+ return !data || data.length == 0;
+ }
+
+ isInvalidHTML(data) {
+ const parser = new DOMParser();
+ const doc = parser.parseFromString(data, "application/xml");
+ return doc.querySelector("parsererror");
+ }
+}
+
+// DO NOT change the name of this variable or the classes defined in this file.
+// Changing the name of this variable will cause your plugin to not work properly
+// when installed in Outerbase.
+window.customElements.define('outerbase-plugin-cell-$PLUGIN_ID', OuterbasePluginCell_$PLUGIN_ID)
+window.customElements.define('outerbase-plugin-editor-$PLUGIN_ID', OuterbasePluginEditor_$PLUGIN_ID)
diff --git a/columns/image-viewer.js b/columns/image-viewer.js
index 9abd19c..fb5fa42 100644
--- a/columns/image-viewer.js
+++ b/columns/image-viewer.js
@@ -1,8 +1,31 @@
-var privileges = [
+var privileges_$PLUGIN_ID = [
'cellValue',
'configuration',
]
+var OuterbaseEvent_$PLUGIN_ID = {
+ // The user has triggered an action to save updates
+ onSave: "onSave",
+}
+
+var triggerEvent_$PLUGIN_ID = (fromClass, data) => {
+ const event = new CustomEvent("custom-change", {
+ detail: data,
+ bubbles: true,
+ composed: true
+ });
+
+ fromClass.dispatchEvent(event);
+}
+
+var decodeAttributeByName_$PLUGIN_ID = (fromClass, name) => {
+ const encodedJSON = fromClass.getAttribute(name);
+ const decodedJSON = encodedJSON
+ ?.replace(/"/g, '"')
+ ?.replace(/'/g, "'");
+ return decodedJSON ? JSON.parse(decodedJSON) : {};
+}
+
var templateCell_$PLUGIN_ID = document.createElement('template')
templateCell_$PLUGIN_ID.innerHTML = `
-
`
// This is the configuration object that Outerbase passes to your plugin.
// Define all of the configuration options that your plugin requires here.
class OuterbasePluginConfig_$PLUGIN_ID {
+ baseURL = ""
+ theme = "light"
+
constructor(object) {
- // No custom properties needed in this plugin.
+ this.baseURL = object?.baseUrl ?? ""
+ this.theme = object?.theme ? object.theme : "light";
}
}
class OuterbasePluginCell_$PLUGIN_ID extends HTMLElement {
static get observedAttributes() {
- return privileges
+ return privileges_$PLUGIN_ID
}
config = new OuterbasePluginConfig_$PLUGIN_ID({})
@@ -94,6 +206,17 @@ class OuterbasePluginCell_$PLUGIN_ID extends HTMLElement {
this.shadow.appendChild(templateCell_$PLUGIN_ID.content.cloneNode(true))
}
+ attributeChangedCallback(name, oldValue, newValue) {
+ this.config = new OuterbasePluginConfig_$PLUGIN_ID(decodeAttributeByName_$PLUGIN_ID(this, "configuration"))
+
+ let metadata = decodeAttributeByName_$PLUGIN_ID(this, "metadata")
+ this.config.theme = metadata?.theme
+
+ var element = this.shadow.querySelector(".theme-container")
+ element.classList.remove("dark")
+ element.classList.add(this.config.theme);
+ }
+
// This function is called when the UI is made available into the DOM. Put any
// logic that you want to run when the element is first stood up here, such as
// event listeners, default values to display, etc.
@@ -108,7 +231,7 @@ class OuterbasePluginCell_$PLUGIN_ID extends HTMLElement {
this.shadow.querySelector('#image-value').value = this.getAttribute('cellvalue')
var imageInput = this.shadow.getElementById("image-value");
- var viewImageButton = this.shadow.getElementById("view-image");
+ var viewImageButton = this.shadow.getElementById("action-button");
if (imageInput && viewImageButton) {
imageInput.addEventListener("focus", () => {
@@ -182,10 +305,185 @@ class OuterbasePluginEditor_$PLUGIN_ID extends HTMLElement {
var backgroundImageView = this.shadow.getElementById("background-image");
if (imageView && backgroundImageView) {
- imageView.src = this.getAttribute('cellvalue')
- backgroundImageView.style.backgroundImage = `url(${this.getAttribute('cellvalue')})`
+ const source = this.config.baseURL ? `${this.config.baseURL}${this.getAttribute('cellvalue')}` : this.getAttribute('cellvalue')
+ imageView.src = source
+ backgroundImageView.style.backgroundImage = `url(${source})`
+
+ this.getImageSize(source).then(size => {
+ const sizeInKilobytes = this.bytesToKilobytes(size);
+
+ // Update image details
+ this.shadow.getElementById("image-details").innerHTML = `
+
${this.extractImageName(source)}
+
+
${sizeInKilobytes.toFixed(1)} KB
+ `
+ this.shadow.getElementById("image-details").style.display = "flex";
+ });
}
}
+
+ getImageSize(url) {
+ return fetch(url, { method: 'GET' }) // Use HEAD request to get headers without downloading the whole image
+ .then(response => {
+ const contentLength = response.headers.get('content-length');
+ if (contentLength) {
+ return parseInt(contentLength, 10);
+ } else {
+ // If content-length header is not available, fetch the whole image and compute its size
+ return response.blob().then(data => data.size);
+ }
+ }).catch(() => {
+ // If the request fails, return 0
+ return 0;
+ });
+ }
+
+ extractImageName(url) {
+ return url.split('/').pop();
+ }
+
+ bytesToKilobytes(bytes) {
+ return bytes / 1024;
+ }
+}
+
+
+/**
+ * ******************
+ * Configuration View
+ * ******************
+ *
+ * ░░░░░░░░░░░░░░░░░
+ * ░░░░░▀▄░░░▄▀░░░░░
+ * ░░░░▄█▀███▀█▄░░░░
+ * ░░░█▀███████▀█░░░
+ * ░░░█░█▀▀▀▀▀█░█░░░
+ * ░░░░░░▀▀░▀▀░░░░░░
+ * ░░░░░░░░░░░░░░░░░
+ *
+ * When a user either installs a plugin onto a table resource for the first time
+ * or they configure an existing installation, this is the view that is presented
+ * to the user. For many plugin applications it's essential to capture information
+ * that is required to allow your plugin to work correctly and this is the best
+ * place to do it.
+ *
+ * It is a requirement that a save button that triggers the `OuterbaseEvent.onSave`
+ * event exists so Outerbase can complete the installation or preference update
+ * action.
+ */
+var templateConfiguration_$PLUGIN_ID = document.createElement("template")
+var templateConfigurationInnerHTML_$PLUGIN_ID = `
+
+
+
+`;
+
+/**
+ * 1. Use `` in the plugin directly
+ * 2. Listen for the `event.detail.code` change on each keystroke
+ * 3. Send the change to Starboard to update the cell value
+ * 4. Pass in starboard
+ */
+
+// This is the configuration object that Outerbase passes to your plugin.
+// Define all of the configuration options that your plugin requires here.
+class OuterbasePluginConfig_$PLUGIN_ID {
+ baseURL = "";
+ theme = "light";
+
+ constructor(object) {
+ this.baseURL = object?.baseUrl ?? "";
+ this.theme = object?.theme ? object.theme : "light";
+ }
+}
+
+class OuterbasePluginCell_$PLUGIN_ID extends HTMLElement {
+ static get observedAttributes() {
+ return privileges_$PLUGIN_ID;
+ }
+
+ config = new OuterbasePluginConfig_$PLUGIN_ID({});
+
+ constructor() {
+ super();
+
+ this.attributeChangedCallback = this.attributeChangedCallback.bind(this);
+ this.connectedCallback = this.connectedCallback.bind(this);
+ this.callCustomEvent = this.callCustomEvent.bind(this);
+
+ // The shadow DOM is a separate DOM tree that is attached to the element.
+ // This allows us to encapsulate our styles and markup. It also prevents
+ // styles from the parent page from leaking into our plugin.
+ this.attachShadow({ mode: "open" });
+ this.shadowRoot.appendChild(
+ templateCell_$PLUGIN_ID.content.cloneNode(true)
+ );
+ }
+
+ attributeChangedCallback(name, oldValue, newValue) {
+ this.config = new OuterbasePluginConfig_$PLUGIN_ID(
+ decodeAttributeByName_$PLUGIN_ID(this, "configuration")
+ );
+
+ let metadata = decodeAttributeByName_$PLUGIN_ID(this, "metadata");
+ this.config.theme = metadata?.theme;
+
+ var element = this.shadowRoot.querySelector(".theme-container");
+ element.classList.remove("dark");
+ element.classList.add(this.config.theme);
+ }
+
+ // This function is called when the UI is made available into the DOM. Put any
+ // logic that you want to run when the element is first stood up here, such as
+ // event listeners, default values to display, etc.
+ connectedCallback() {
+ // Parse the configuration object from the `configuration` attribute
+ // and store it in the `config` property.
+ this.config = new OuterbasePluginConfig_$PLUGIN_ID(
+ JSON.parse(this.getAttribute("configuration"))
+ );
+
+ // Set default value based on input
+ this.shadowRoot.querySelector("#image-value").value =
+ this.getAttribute("cellvalue");
+
+ var imageInput = this.shadowRoot.getElementById("image-value");
+ var viewImageButton = this.shadowRoot.getElementById("action-button");
+
+ if (imageInput && viewImageButton) {
+ const emit = this.callCustomEvent.bind(this);
+ // TODO This listener should be removed?
+ viewImageButton.addEventListener("click", () => {
+ emit({
+ action: "onedit",
+ value: true,
+ });
+ });
+ }
+ }
+
+ callCustomEvent(data) {
+ const event = new CustomEvent("plugin-change", {
+ detail: data,
+ bubbles: true, // If you want the event to bubble up through the DOM
+ composed: true, // Allows the event to pass through shadow DOM boundaries
+ });
+
+ this.dispatchEvent(event);
+ }
+}
+
+class OuterbasePluginEditor_$PLUGIN_ID extends HTMLElement {
+ static get observedAttributes() {
+ return privileges_$PLUGIN_ID;
+ }
+
+ constructor() {
+ super();
+
+ // this.attributeChangedCallback = this.attributeChangedCallback.bind(this);
+ this.connectedCallback = this.connectedCallback.bind(this);
+
+ // The shadow DOM is a separate DOM tree that is attached to the element.
+ // This allows us to encapsulate our styles and markup. It also prevents
+ // styles from the parent page from leaking into our plugin.
+ this.attachShadow({ mode: "open" });
+ this.shadowRoot.appendChild(
+ templateEditor_$PLUGIN_ID.content.cloneNode(true)
+ );
+
+ // Parse the configuration object from the `configuration` attribute
+ // and store it in the `config` property.
+ this.config = new OuterbasePluginConfig_$PLUGIN_ID(
+ JSON.parse(this.getAttribute("configuration"))
+ );
+ }
+
+ // This function is called when the UI is made available into the DOM. Put any
+ // logic that you want to run when the element is first stood up here, such as
+ // event listeners, default values to display, etc.
+ connectedCallback() {
+ if (this.config.theme === "light") {
+ this.shadowRoot.querySelector("#container").style.backgroundColor =
+ "white";
+ } else {
+ this.shadowRoot.querySelector("#container").style.backgroundColor =
+ "black";
+ }
+
+ this.editor = this.shadowRoot.getElementById("editor");
+ this.editor.setAttribute("code", this.getAttribute("cellvalue"));
+ this.editor.addEventListener("editor-change", (event) => {
+ event.stopPropagation();
+ const {
+ detail: { value: jsonString },
+ } = event;
+
+ try {
+ const value = JSON.parse(jsonString);
+ this.dispatchEvent(
+ new CustomEvent("plugin-change", {
+ bubbles: true,
+ composed: true,
+ detail: {
+ action: "updatecell",
+ value,
+ },
+ })
+ );
+ } catch (err) {
+ console.error(err);
+ }
+ });
+ }
+
+ attributeChangedCallback(name, oldValue, newValue) {
+ this.config = new OuterbasePluginConfig_$PLUGIN_ID(decodeAttributeByName_$PLUGIN_ID(this, "configuration"))
+ let metadata = decodeAttributeByName_$PLUGIN_ID(this, "metadata")
+ this.config.theme = metadata?.theme
+
+ // Set the `mode` attribute of id `editor` to value of `theme`
+ var editor = this.shadowRoot.getElementById("editor")
+ if (editor) {
+ editor.setAttribute("mode", this.config.theme)
+ }
+ }
+}
+
+// DO NOT change the name of this variable or the classes defined in this file.
+// Changing the name of this variable will cause your plugin to not work properly
+// when installed in Outerbase.
+window.customElements.define(
+ "outerbase-plugin-cell-$PLUGIN_ID",
+ OuterbasePluginCell_$PLUGIN_ID
+);
+window.customElements.define(
+ "outerbase-plugin-editor-$PLUGIN_ID",
+ OuterbasePluginEditor_$PLUGIN_ID
+);
diff --git a/index.html b/index.html
index 9b251d8..5c6792e 100644
--- a/index.html
+++ b/index.html
@@ -189,7 +189,10 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/tables/dealership.js b/tables/dealership.js
index 5b7f50e..e75dc25 100644
--- a/tables/dealership.js
+++ b/tables/dealership.js
@@ -1,4 +1,4 @@
-var observableAttributes = [
+var observableAttributes_$PLUGIN_ID = [
// The value of the cell that the plugin is being rendered in
"cellvalue",
// The value of the row that the plugin is being rendered in
@@ -15,14 +15,14 @@ var observableAttributes = [
"metadata"
]
-var OuterbaseEvent = {
+var OuterbaseEvent_$PLUGIN_ID = {
// The user has triggered an action to save updates
onSave: "onSave",
// The user has triggered an action to configure the plugin
configurePlugin: "configurePlugin",
}
-var OuterbaseColumnEvent = {
+var OuterbaseColumnEvent_$PLUGIN_ID = {
// The user has began editing the selected cell
onEdit: "onEdit",
// Stops editing a cells editor popup view and accept the changes
@@ -33,7 +33,7 @@ var OuterbaseColumnEvent = {
updateCell: "updateCell",
}
-var OuterbaseTableEvent = {
+var OuterbaseTableEvent_$PLUGIN_ID = {
// Updates the value of a row with the provided JSON value
updateRow: "updateRow",
// Deletes an entire row with the provided JSON value
@@ -59,12 +59,12 @@ var OuterbaseTableEvent = {
* ░░░▀░░░░░░░░▀░░░░
* ░░░░░░░░░░░░░░░░░
*
- * Define your custom classes here. We do recommend the usage of our `OuterbasePluginConfig_$PLUGIN_ID`
+ * Define your custom classes here. We do recommend the usage of our `OuterbasePluginModel_$PLUGIN_ID`
* class for you to manage properties between the other classes below, however, it's strictly optional.
* However, this would be a good class to contain the properties you need to store when a user installs
* or configures your plugin.
*/
-class OuterbasePluginConfig_$PLUGIN_ID {
+class OuterbasePluginModel_$PLUGIN_ID {
// Inputs from Outerbase for us to retain
tableValue = undefined
count = 0
@@ -103,7 +103,7 @@ class OuterbasePluginConfig_$PLUGIN_ID {
}
}
-var triggerEvent = (fromClass, data) => {
+var triggerEvent_$PLUGIN_ID = (fromClass, data) => {
const event = new CustomEvent("custom-change", {
detail: data,
bubbles: true,
@@ -113,7 +113,7 @@ var triggerEvent = (fromClass, data) => {
fromClass.dispatchEvent(event);
}
-var decodeAttributeByName = (fromClass, name) => {
+var decodeAttributeByName_$PLUGIN_ID = (fromClass, name) => {
const encodedJSON = fromClass.getAttribute(name);
const decodedJSON = encodedJSON
?.replace(/"/g, '"')
@@ -135,8 +135,8 @@ var decodeAttributeByName = (fromClass, name) => {
* ░░░░░░░░░░░░░░░░░░
* ░░░░░░░░░░░░░░░░░░
*/
-var templateTable = document.createElement("template")
-templateTable.innerHTML = `
+var templateTable_$PLUGIN_ID = document.createElement("template")
+var templateTableInnerHTML_$PLUGIN_ID = `
-
-
`
+
+ const configurePluginButtons = this.shadow.querySelectorAll('.select-column-link');
+ configurePluginButtons.forEach((btn, index) => {
+ btn.addEventListener('click', () => {
+ triggerEvent_$PLUGIN_ID(this, {
+ action: OuterbaseEvent_$PLUGIN_ID.configurePlugin
+ })
+ });
+ });
+
+ var previousPageButton = this.shadow.getElementById("previousPageButton");
+ previousPageButton?.addEventListener("click", () => {
+ triggerEvent_$PLUGIN_ID(this, {
+ action: OuterbaseTableEvent_$PLUGIN_ID.getPreviousPage,
+ value: {}
+ })
+ });
+
+ var nextPageButton = this.shadow.getElementById("nextPageButton");
+ nextPageButton?.addEventListener("click", () => {
+ triggerEvent_$PLUGIN_ID(this, {
+ action: OuterbaseTableEvent_$PLUGIN_ID.getNextPage,
+ value: {}
+ })
+ });
+ }
+
+ isValidURL(string) {
+ try {
+ new URL(string);
+ } catch (_) {
+ return false;
+ }
+
+ return true;
}
}
-var templateConfiguration = document.createElement('template')
-templateConfiguration.innerHTML = `
+
+/**
+ * ******************
+ * Configuration View
+ * ******************
+ *
+ * ░░░░░░░░░░░░░░░░░
+ * ░░░░░▀▄░░░▄▀░░░░░
+ * ░░░░▄█▀███▀█▄░░░░
+ * ░░░█▀███████▀█░░░
+ * ░░░█░█▀▀▀▀▀█░█░░░
+ * ░░░░░░▀▀░▀▀░░░░░░
+ * ░░░░░░░░░░░░░░░░░
+ *
+ * When a user either installs a plugin onto a table resource for the first time
+ * or they configure an existing installation, this is the view that is presented
+ * to the user. For many plugin applications it's essential to capture information
+ * that is required to allow your plugin to work correctly and this is the best
+ * place to do it.
+ *
+ * It is a requirement that a save button that triggers the `OuterbaseEvent.onSave`
+ * event exists so Outerbase can complete the installation or preference update
+ * action.
+ */
+var templateConfiguration_$PLUGIN_ID = document.createElement("template")
+templateConfiguration_$PLUGIN_ID.innerHTML = `
-
-
+
+
+
+
`
+// Can the above div just be a self closing container:
-class OuterbasePluginTableConfiguration_$PLUGIN_ID extends HTMLElement {
+class OuterbasePluginConfiguration_$PLUGIN_ID extends HTMLElement {
static get observedAttributes() {
- return privileges
+ return observableAttributes_$PLUGIN_ID
}
config = new OuterbasePluginConfig_$PLUGIN_ID({})
- items = []
constructor() {
super()
- // The shadow DOM is a separate DOM tree that is attached to the element.
- // This allows us to encapsulate our styles and markup. It also prevents
- // styles from the parent page from leaking into our plugin.
- this.shadow = this.attachShadow({ mode: 'open' })
- this.shadow.appendChild(templateConfiguration.content.cloneNode(true))
+ this.shadow = this.attachShadow({ mode: "open" })
+ this.shadow.appendChild(templateConfiguration_$PLUGIN_ID.content.cloneNode(true))
}
connectedCallback() {
- // Parse the configuration object from the `configuration` attribute
- // and store it in the `config` property.
- const encodedTableJSON = this.getAttribute('configuration');
- const decodedTableJSON = encodedTableJSON
- ?.replace(/"/g, '"')
- ?.replace(/'/g, "'");
- const configuration = JSON.parse(decodedTableJSON);
-
- this.config = new OuterbasePluginConfig_$PLUGIN_ID(
- configuration
- )
-
- // Set the items property to the value of the `tableValue` attribute.
- if (this.getAttribute('tableValue')) {
- const encodedTableJSON = this.getAttribute('tableValue');
- const decodedTableJSON = encodedTableJSON
- ?.replace(/"/g, '"')
- ?.replace(/'/g, "'");
- this.items = JSON.parse(decodedTableJSON);
- }
+ this.render()
+ }
+
+ attributeChangedCallback(name, oldValue, newValue) {
+ this.config = new OuterbasePluginConfig_$PLUGIN_ID(decodeAttributeByName_$PLUGIN_ID(this, "configuration"))
+ this.config.tableValue = decodeAttributeByName_$PLUGIN_ID(this, "tableValue")
+ this.config.theme = decodeAttributeByName_$PLUGIN_ID(this, "metadata").theme
+
+ var element = this.shadow.getElementById("theme-container");
+ element.classList.remove("dark")
+ element.classList.add(this.config.theme);
- // Manually render dynamic content
this.render()
}
render() {
- let sample = this.items.length ? this.items[0] : {}
+ let sample = this.config.tableValue.length ? this.config.tableValue[0] : {}
let keys = Object.keys(sample)
- this.shadow.querySelector('#container').innerHTML = `
+ if (!keys || keys.length === 0 || !this.shadow.querySelector('#configuration-container')) return
+
+ this.shadow.querySelector('#configuration-container').innerHTML = `
@@ -361,8 +697,8 @@ class OuterbasePluginTableConfiguration_$PLUGIN_ID extends HTMLElement {
var saveButton = this.shadow.getElementById("saveButton");
saveButton.addEventListener("click", () => {
- this.callCustomEvent({
- action: 'onsave',
+ triggerEvent_$PLUGIN_ID(this, {
+ action: OuterbaseEvent_$PLUGIN_ID.onSave,
value: this.config.toJSON()
})
});
@@ -372,6 +708,12 @@ class OuterbasePluginTableConfiguration_$PLUGIN_ID extends HTMLElement {
this.config.imageKey = imageKeySelect.value
this.render()
});
+
+ var optionalImagePrefixInput = this.shadow.querySelector("input");
+ optionalImagePrefixInput.addEventListener("change", () => {
+ this.config.optionalImagePrefix = optionalImagePrefixInput.value
+ this.render()
+ });
var titleKeySelect = this.shadow.getElementById("titleKeySelect");
titleKeySelect.addEventListener("change", () => {
@@ -392,16 +734,18 @@ class OuterbasePluginTableConfiguration_$PLUGIN_ID extends HTMLElement {
});
}
- callCustomEvent(data) {
- const event = new CustomEvent('custom-change', {
- detail: data,
- bubbles: true, // If you want the event to bubble up through the DOM
- composed: true // Allows the event to pass through shadow DOM boundaries
- });
+ isValidURL(string) {
+ try {
+ new URL(string);
+ } catch (_) {
+ return false;
+ }
- this.dispatchEvent(event);
+ return true;
}
}
window.customElements.define('outerbase-plugin-table-$PLUGIN_ID', OuterbasePluginTable_$PLUGIN_ID)
-window.customElements.define('outerbase-plugin-table-configuration-$PLUGIN_ID', OuterbasePluginTableConfiguration_$PLUGIN_ID)
+window.customElements.define('outerbase-plugin-configuration-$PLUGIN_ID', OuterbasePluginConfiguration_$PLUGIN_ID)
+// window.customElements.define('outerbase-plugin-table-gallery', OuterbasePluginTable_$PLUGIN_ID)
+// window.customElements.define('outerbase-plugin-configuration-gallery', OuterbasePluginConfiguration_$PLUGIN_ID)
diff --git a/tables/map-view.js b/tables/map-view.js
new file mode 100644
index 0000000..baee1e6
--- /dev/null
+++ b/tables/map-view.js
@@ -0,0 +1,782 @@
+var observableAttributes_$PLUGIN_ID = [
+ // The value of the cell that the plugin is being rendered in
+ "cellvalue",
+ // The value of the row that the plugin is being rendered in
+ "rowvalue",
+ // The value of the table that the plugin is being rendered in
+ "tablevalue",
+ // The schema of the table that the plugin is being rendered in
+ "tableschemavalue",
+ // The schema of the database that the plugin is being rendered in
+ "databaseschemavalue",
+ // The configuration object that the user specified when installing the plugin
+ "configuration",
+ // Additional information about the view such as count, page and offset.
+ "metadata"
+]
+
+var OuterbaseEvent_$PLUGIN_ID = {
+ // The user has triggered an action to save updates
+ onSave: "onSave",
+ // The user has triggered an action to configure the plugin
+ configurePlugin: "configurePlugin",
+}
+
+var OuterbaseColumnEvent_$PLUGIN_ID = {
+ // The user has began editing the selected cell
+ onEdit: "onEdit",
+ // Stops editing a cells editor popup view and accept the changes
+ onStopEdit: "onStopEdit",
+ // Stops editing a cells editor popup view and prevent persisting the changes
+ onCancelEdit: "onCancelEdit",
+ // Updates the cells value with the provided value
+ updateCell: "updateCell",
+}
+
+var OuterbaseTableEvent_$PLUGIN_ID = {
+ // Updates the value of a row with the provided JSON value
+ updateRow: "updateRow",
+ // Deletes an entire row with the provided JSON value
+ deleteRow: "deleteRow",
+ // Creates a new row with the provided JSON value
+ createRow: "createRow",
+ // Performs an action to get the next page of results, if they exist
+ getNextPage: "getNextPage",
+ // Performs an action to get the previous page of results, if they exist
+ getPreviousPage: "getPreviousPage"
+}
+
+/**
+ * ******************
+ * Custom Definitions
+ * ******************
+ *
+ * ░░░░░░░░░░░░░░░░░
+ * ░░░░▄▄████▄▄░░░░░
+ * ░░░██████████░░░░
+ * ░░░██▄▄██▄▄██░░░░
+ * ░░░░▄▀▄▀▀▄▀▄░░░░░
+ * ░░░▀░░░░░░░░▀░░░░
+ * ░░░░░░░░░░░░░░░░░
+ *
+ * Define your custom classes here. We do recommend the usage of our `OuterbasePluginConfig_$PLUGIN_ID`
+ * class for you to manage properties between the other classes below, however, it's strictly optional.
+ * However, this would be a good class to contain the properties you need to store when a user installs
+ * or configures your plugin.
+ */
+class OuterbasePluginConfig_$PLUGIN_ID {
+ // Inputs from Outerbase for us to retain
+ tableValue = undefined
+ count = 0
+ limit = 0
+ offset = 0
+ page = 0
+ pageCount = 0
+ theme = "light"
+
+ // Inputs from the configuration screen
+ imageKey = undefined
+ optionalImagePrefix = undefined
+ titleKey = undefined
+ descriptionKey = undefined
+ subtitleKey = undefined
+
+ // Variables for us to hold state of user actions
+ deletedRows = []
+
+ constructor(object) {
+ this.imageKey = object?.imageKey
+ this.optionalImagePrefix = object?.optionalImagePrefix
+ this.titleKey = object?.titleKey
+ this.descriptionKey = object?.descriptionKey
+ this.subtitleKey = object?.subtitleKey
+ }
+
+ toJSON() {
+ return {
+ "imageKey": this.imageKey,
+ "optionalImagePrefix": this.optionalImagePrefix,
+ "titleKey": this.titleKey,
+ "descriptionKey": this.descriptionKey,
+ "subtitleKey": this.subtitleKey
+ }
+ }
+}
+
+var triggerEvent_$PLUGIN_ID = (fromClass, data) => {
+ const event = new CustomEvent("custom-change", {
+ detail: data,
+ bubbles: true,
+ composed: true
+ });
+
+ fromClass.dispatchEvent(event);
+}
+
+var decodeAttributeByName_$PLUGIN_ID = (fromClass, name) => {
+ const encodedJSON = fromClass.getAttribute(name);
+ const decodedJSON = encodedJSON
+ ?.replace(/"/g, '"')
+ ?.replace(/'/g, "'");
+ return decodedJSON ? JSON.parse(decodedJSON) : {};
+}
+
+
+/**
+ * **********
+ * Table View
+ * **********
+ *
+ * ░░░░░░░░░░░░░░░░░░
+ * ░░░░░▄▄████▄▄░░░░░
+ * ░░░▄██████████▄░░░
+ * ░▄██▄██▄██▄██▄██▄░
+ * ░░░▀█▀░░▀▀░░▀█▀░░░
+ * ░░░░░░░░░░░░░░░░░░
+ * ░░░░░░░░░░░░░░░░░░
+ */
+var templateTable_$PLUGIN_ID = document.createElement("template")
+templateTable_$PLUGIN_ID.innerHTML = `
+
+
+
+ `
+
+ const infoWindow = new google.maps.InfoWindow({
+ content: content
+ });
+
+
+ clickableMarker.addListener("gmp-click", (event) => {
+
+ if (this.openedInfoWindow !== undefined) {
+ this.openedInfoWindow.close()
+ }
+
+ const position = clickableMarker.position;
+ infoWindow.open(clickableMarker.map, clickableMarker);
+ this.openedInfoWindow = infoWindow;
+ });
+ })
+ }
+
+ var previousPageButton = this.shadow.getElementById("previousPageButton");
+ previousPageButton.addEventListener("click", () => {
+ triggerEvent(this, {
+ action: OuterbaseTableEvent.getPreviousPage,
+ value: {}
+ })
+ });
+
+ var nextPageButton = this.shadow.getElementById("nextPageButton");
+ nextPageButton.addEventListener("click", () => {
+ triggerEvent(this, {
+ action: OuterbaseTableEvent.getNextPage,
+ value: {}
+ })
+ });
+ }
+}
+
+
+/**
+ * ******************
+ * Configuration View
+ * ******************
+ *
+ * ░░░░░░░░░░░░░░░░░
+ * ░░░░░▀▄░░░▄▀░░░░░
+ * ░░░░▄█▀███▀█▄░░░░
+ * ░░░█▀███████▀█░░░
+ * ░░░█░█▀▀▀▀▀█░█░░░
+ * ░░░░░░▀▀░▀▀░░░░░░
+ * ░░░░░░░░░░░░░░░░░
+ *
+ * When a user either installs a plugin onto a table resource for the first time
+ * or they configure an existing installation, this is the view that is presented
+ * to the user. For many plugin applications it's essential to capture information
+ * that is required to allow your plugin to work correctly and this is the best
+ * place to do it.
+ *
+ * It is a requirement that a save button that triggers the `OuterbaseEvent.onSave`
+ * event exists so Outerbase can complete the installation or preference update
+ * action.
+ */
+var templateConfiguration_$PLUGIN_ID = document.createElement("template")
+templateConfiguration_$PLUGIN_ID.innerHTML = `
+
+
+
+
+
+
+
+`
+// Can the above div just be a self closing container:
+
+class OuterbasePluginConfiguration_$PLUGIN_ID extends HTMLElement {
+ static get observedAttributes() {
+ return observableAttributes_$PLUGIN_ID
+ }
+
+ config = new OuterbasePluginConfig_$PLUGIN_ID({})
+
+ constructor() {
+ super()
+
+ this.shadow = this.attachShadow({ mode: "open" })
+ this.shadow.appendChild(templateConfiguration_$PLUGIN_ID.content.cloneNode(true))
+ }
+
+ connectedCallback() {
+ this.render()
+ }
+
+ attributeChangedCallback(name, oldValue, newValue) {
+ this.config = new OuterbasePluginConfig_$PLUGIN_ID(decodeAttributeByName_$PLUGIN_ID(this, "configuration"))
+ this.config.tableValue = decodeAttributeByName_$PLUGIN_ID(this, "tableValue")
+ this.config.theme = decodeAttributeByName_$PLUGIN_ID(this, "metadata").theme
+
+ var element = this.shadow.getElementById("theme-container");
+ element.classList.remove("dark")
+ element.classList.add(this.config.theme);
+
+ this.render()
+ }
+
+ render() {
+ let sample = this.config.tableValue.length ? this.config.tableValue[0] : {}
+ let keys = Object.keys(sample)
+
+ if (!keys || keys.length === 0 || !this.shadow.querySelector('#configuration-container')) return
+
+ this.shadow.querySelector('#configuration-container').innerHTML = `
+