mirror of https://github.com/Budibase/budibase.git
122 changed files with 1924 additions and 588 deletions
@ -0,0 +1,4 @@ |
|||
{ |
|||
"baseUrl": "http://localhost:4001/_builder/", |
|||
"video": false |
|||
} |
|||
@ -0,0 +1,5 @@ |
|||
{ |
|||
"name": "Using fixtures to represent data", |
|||
"email": "hello@cypress.io", |
|||
"body": "Fixtures are a great way to mock data for responses to routes" |
|||
} |
|||
@ -0,0 +1,5 @@ |
|||
{ |
|||
"id": 8739, |
|||
"name": "Jane", |
|||
"email": "jane@example.com" |
|||
} |
|||
@ -0,0 +1,232 @@ |
|||
[ |
|||
{ |
|||
"id": 1, |
|||
"name": "Leanne Graham", |
|||
"username": "Bret", |
|||
"email": "Sincere@april.biz", |
|||
"address": { |
|||
"street": "Kulas Light", |
|||
"suite": "Apt. 556", |
|||
"city": "Gwenborough", |
|||
"zipcode": "92998-3874", |
|||
"geo": { |
|||
"lat": "-37.3159", |
|||
"lng": "81.1496" |
|||
} |
|||
}, |
|||
"phone": "1-770-736-8031 x56442", |
|||
"website": "hildegard.org", |
|||
"company": { |
|||
"name": "Romaguera-Crona", |
|||
"catchPhrase": "Multi-layered client-server neural-net", |
|||
"bs": "harness real-time e-markets" |
|||
} |
|||
}, |
|||
{ |
|||
"id": 2, |
|||
"name": "Ervin Howell", |
|||
"username": "Antonette", |
|||
"email": "Shanna@melissa.tv", |
|||
"address": { |
|||
"street": "Victor Plains", |
|||
"suite": "Suite 879", |
|||
"city": "Wisokyburgh", |
|||
"zipcode": "90566-7771", |
|||
"geo": { |
|||
"lat": "-43.9509", |
|||
"lng": "-34.4618" |
|||
} |
|||
}, |
|||
"phone": "010-692-6593 x09125", |
|||
"website": "anastasia.net", |
|||
"company": { |
|||
"name": "Deckow-Crist", |
|||
"catchPhrase": "Proactive didactic contingency", |
|||
"bs": "synergize scalable supply-chains" |
|||
} |
|||
}, |
|||
{ |
|||
"id": 3, |
|||
"name": "Clementine Bauch", |
|||
"username": "Samantha", |
|||
"email": "Nathan@yesenia.net", |
|||
"address": { |
|||
"street": "Douglas Extension", |
|||
"suite": "Suite 847", |
|||
"city": "McKenziehaven", |
|||
"zipcode": "59590-4157", |
|||
"geo": { |
|||
"lat": "-68.6102", |
|||
"lng": "-47.0653" |
|||
} |
|||
}, |
|||
"phone": "1-463-123-4447", |
|||
"website": "ramiro.info", |
|||
"company": { |
|||
"name": "Romaguera-Jacobson", |
|||
"catchPhrase": "Face to face bifurcated interface", |
|||
"bs": "e-enable strategic applications" |
|||
} |
|||
}, |
|||
{ |
|||
"id": 4, |
|||
"name": "Patricia Lebsack", |
|||
"username": "Karianne", |
|||
"email": "Julianne.OConner@kory.org", |
|||
"address": { |
|||
"street": "Hoeger Mall", |
|||
"suite": "Apt. 692", |
|||
"city": "South Elvis", |
|||
"zipcode": "53919-4257", |
|||
"geo": { |
|||
"lat": "29.4572", |
|||
"lng": "-164.2990" |
|||
} |
|||
}, |
|||
"phone": "493-170-9623 x156", |
|||
"website": "kale.biz", |
|||
"company": { |
|||
"name": "Robel-Corkery", |
|||
"catchPhrase": "Multi-tiered zero tolerance productivity", |
|||
"bs": "transition cutting-edge web services" |
|||
} |
|||
}, |
|||
{ |
|||
"id": 5, |
|||
"name": "Chelsey Dietrich", |
|||
"username": "Kamren", |
|||
"email": "Lucio_Hettinger@annie.ca", |
|||
"address": { |
|||
"street": "Skiles Walks", |
|||
"suite": "Suite 351", |
|||
"city": "Roscoeview", |
|||
"zipcode": "33263", |
|||
"geo": { |
|||
"lat": "-31.8129", |
|||
"lng": "62.5342" |
|||
} |
|||
}, |
|||
"phone": "(254)954-1289", |
|||
"website": "demarco.info", |
|||
"company": { |
|||
"name": "Keebler LLC", |
|||
"catchPhrase": "User-centric fault-tolerant solution", |
|||
"bs": "revolutionize end-to-end systems" |
|||
} |
|||
}, |
|||
{ |
|||
"id": 6, |
|||
"name": "Mrs. Dennis Schulist", |
|||
"username": "Leopoldo_Corkery", |
|||
"email": "Karley_Dach@jasper.info", |
|||
"address": { |
|||
"street": "Norberto Crossing", |
|||
"suite": "Apt. 950", |
|||
"city": "South Christy", |
|||
"zipcode": "23505-1337", |
|||
"geo": { |
|||
"lat": "-71.4197", |
|||
"lng": "71.7478" |
|||
} |
|||
}, |
|||
"phone": "1-477-935-8478 x6430", |
|||
"website": "ola.org", |
|||
"company": { |
|||
"name": "Considine-Lockman", |
|||
"catchPhrase": "Synchronised bottom-line interface", |
|||
"bs": "e-enable innovative applications" |
|||
} |
|||
}, |
|||
{ |
|||
"id": 7, |
|||
"name": "Kurtis Weissnat", |
|||
"username": "Elwyn.Skiles", |
|||
"email": "Telly.Hoeger@billy.biz", |
|||
"address": { |
|||
"street": "Rex Trail", |
|||
"suite": "Suite 280", |
|||
"city": "Howemouth", |
|||
"zipcode": "58804-1099", |
|||
"geo": { |
|||
"lat": "24.8918", |
|||
"lng": "21.8984" |
|||
} |
|||
}, |
|||
"phone": "210.067.6132", |
|||
"website": "elvis.io", |
|||
"company": { |
|||
"name": "Johns Group", |
|||
"catchPhrase": "Configurable multimedia task-force", |
|||
"bs": "generate enterprise e-tailers" |
|||
} |
|||
}, |
|||
{ |
|||
"id": 8, |
|||
"name": "Nicholas Runolfsdottir V", |
|||
"username": "Maxime_Nienow", |
|||
"email": "Sherwood@rosamond.me", |
|||
"address": { |
|||
"street": "Ellsworth Summit", |
|||
"suite": "Suite 729", |
|||
"city": "Aliyaview", |
|||
"zipcode": "45169", |
|||
"geo": { |
|||
"lat": "-14.3990", |
|||
"lng": "-120.7677" |
|||
} |
|||
}, |
|||
"phone": "586.493.6943 x140", |
|||
"website": "jacynthe.com", |
|||
"company": { |
|||
"name": "Abernathy Group", |
|||
"catchPhrase": "Implemented secondary concept", |
|||
"bs": "e-enable extensible e-tailers" |
|||
} |
|||
}, |
|||
{ |
|||
"id": 9, |
|||
"name": "Glenna Reichert", |
|||
"username": "Delphine", |
|||
"email": "Chaim_McDermott@dana.io", |
|||
"address": { |
|||
"street": "Dayna Park", |
|||
"suite": "Suite 449", |
|||
"city": "Bartholomebury", |
|||
"zipcode": "76495-3109", |
|||
"geo": { |
|||
"lat": "24.6463", |
|||
"lng": "-168.8889" |
|||
} |
|||
}, |
|||
"phone": "(775)976-6794 x41206", |
|||
"website": "conrad.com", |
|||
"company": { |
|||
"name": "Yost and Sons", |
|||
"catchPhrase": "Switchable contextually-based project", |
|||
"bs": "aggregate real-time technologies" |
|||
} |
|||
}, |
|||
{ |
|||
"id": 10, |
|||
"name": "Clementina DuBuque", |
|||
"username": "Moriah.Stanton", |
|||
"email": "Rey.Padberg@karina.biz", |
|||
"address": { |
|||
"street": "Kattie Turnpike", |
|||
"suite": "Suite 198", |
|||
"city": "Lebsackbury", |
|||
"zipcode": "31428-2261", |
|||
"geo": { |
|||
"lat": "-38.2386", |
|||
"lng": "57.2232" |
|||
} |
|||
}, |
|||
"phone": "024-648-3804", |
|||
"website": "ambrose.net", |
|||
"company": { |
|||
"name": "Hoeger LLC", |
|||
"catchPhrase": "Centralized empowering task-force", |
|||
"bs": "target end-to-end models" |
|||
} |
|||
} |
|||
] |
|||
@ -0,0 +1,17 @@ |
|||
context('Create an Application', () => { |
|||
|
|||
beforeEach(() => { |
|||
cy.visit('localhost:4001/_builder') |
|||
}) |
|||
|
|||
// https://on.cypress.io/interacting-with-elements
|
|||
|
|||
it('should create a new application', () => { |
|||
// https://on.cypress.io/type
|
|||
cy.createApp('My Cool App', 'This is a description') |
|||
|
|||
cy.visit('localhost:4001/_builder') |
|||
|
|||
cy.contains('My Cool App').should('exist') |
|||
}) |
|||
}) |
|||
@ -0,0 +1,53 @@ |
|||
context('Create Components', () => { |
|||
|
|||
before(() => { |
|||
cy.visit('localhost:4001/_builder') |
|||
// https://on.cypress.io/type
|
|||
cy.createApp('Model App', 'Model App Description') |
|||
cy.createModel('dog', 'name', 'age') |
|||
cy.addRecord('bob', '15') |
|||
}) |
|||
|
|||
// https://on.cypress.io/interacting-with-elements
|
|||
it('should add a container', () => { |
|||
cy.contains('frontend').click() |
|||
cy.get('.switcher > :nth-child(2)').click() |
|||
|
|||
cy.contains('Container').click() |
|||
}) |
|||
it('should add a headline', () => { |
|||
cy.addHeadlineComponent('An Amazing headline!') |
|||
|
|||
getIframeBody().contains('An Amazing headline!') |
|||
}) |
|||
it('change the font size of the headline', () => { |
|||
cy.contains('Typography').click() |
|||
cy.get('input[name="font-size"]') |
|||
.type('60px') |
|||
cy.contains('Design').click() |
|||
|
|||
getIframeBody().contains('An Amazing headline!').should('have.css', 'font-size', '60px') |
|||
}) |
|||
}) |
|||
|
|||
const getIframeDocument = () => { |
|||
return cy |
|||
.get('iframe') |
|||
// Cypress yields jQuery element, which has the real
|
|||
// DOM element under property "0".
|
|||
// From the real DOM iframe element we can get
|
|||
// the "document" element, it is stored in "contentDocument" property
|
|||
// Cypress "its" command can access deep properties using dot notation
|
|||
// https://on.cypress.io/its
|
|||
.its('0.contentDocument').should('exist') |
|||
} |
|||
|
|||
const getIframeBody = () => { |
|||
// get the document
|
|||
return getIframeDocument() |
|||
// automatically retries until body is loaded
|
|||
.its('body').should('not.be.undefined') |
|||
// wraps "body" DOM element to allow
|
|||
// chaining more Cypress commands, like ".find(...)"
|
|||
.then(cy.wrap) |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
context('Create a Model', () => { |
|||
|
|||
before(() => { |
|||
cy.visit('localhost:4001/_builder') |
|||
// https://on.cypress.io/type
|
|||
cy.createApp('Model App', 'Model App Description') |
|||
}) |
|||
|
|||
// https://on.cypress.io/interacting-with-elements
|
|||
it('should create a new model', () => { |
|||
|
|||
cy.createModel('dog', 'name', 'age') |
|||
|
|||
// Check if model exists
|
|||
cy.get('.title').should('have.text', 'dog') |
|||
}) |
|||
it('should add a record', () => { |
|||
cy.addRecord('bob', '15') |
|||
|
|||
cy.contains('bob').should('have.text', 'bob') |
|||
}) |
|||
}) |
|||
@ -0,0 +1,19 @@ |
|||
context('Create a User', () => { |
|||
|
|||
before(() => { |
|||
cy.visit('localhost:4001/_builder') |
|||
// https://on.cypress.io/type
|
|||
cy.createApp('User App', 'This app is used to test user creation') |
|||
}) |
|||
|
|||
// https://on.cypress.io/interacting-with-elements
|
|||
it('should create a user', () => { |
|||
// Close Model modal that shows up after creating an app
|
|||
cy.get('.close').click() |
|||
|
|||
cy.createUser('bbuser', 'test', 'ADMIN') |
|||
|
|||
// Check to make sure user was created!
|
|||
cy.contains('bbuser').should('have.text', 'bbuser') |
|||
}) |
|||
}) |
|||
@ -0,0 +1,46 @@ |
|||
context('Create a workflow', () => { |
|||
|
|||
before(() => { |
|||
cy.visit('localhost:4001/_builder') |
|||
|
|||
cy.createApp('Workflow Test App', 'This app is used to test that workflows do in fact work!') |
|||
}) |
|||
|
|||
// https://on.cypress.io/interacting-with-elements
|
|||
it('should create a workflow', () => { |
|||
cy.createModel('dog', 'name', 'age') |
|||
cy.createUser('bbuser', 'test', 'ADMIN') |
|||
|
|||
|
|||
cy.contains('workflow').click() |
|||
cy.get('.new-workflow-button').click() |
|||
cy.get('input').type('Add Record') |
|||
cy.contains('Save').click() |
|||
|
|||
// Add trigger
|
|||
cy.get('[data-cy=add-workflow-component]').click() |
|||
cy.get('[data-cy=RECORD_SAVED]').click() |
|||
cy.get('.budibase__input').select('dog') |
|||
|
|||
// Create action
|
|||
cy.get('[data-cy=SAVE_RECORD]').click() |
|||
cy.get(':nth-child(2) > .budibase__input').type('goodboy') |
|||
cy.get(':nth-child(3) > .budibase__input').type('11') |
|||
|
|||
// Save
|
|||
cy.get('[data-cy=save-workflow-setup]').click() |
|||
cy.get('.workflow-button').click() |
|||
|
|||
// Activate Workflow
|
|||
cy.get('[data-cy=activate-workflow]').click() |
|||
|
|||
}) |
|||
it('should add record when a new record is added', () => { |
|||
cy.contains('backend').click() |
|||
|
|||
cy.addRecord('bob', '15') |
|||
|
|||
cy.contains('goodboy').should('have.text', 'goodboy') |
|||
|
|||
}) |
|||
}) |
|||
@ -0,0 +1,22 @@ |
|||
/// <reference types="cypress" />
|
|||
// ***********************************************************
|
|||
// This example plugins/index.js can be used to load plugins
|
|||
//
|
|||
// You can change the location of this file or turn off loading
|
|||
// the plugins file with the 'pluginsFile' configuration option.
|
|||
//
|
|||
// You can read more here:
|
|||
// https://on.cypress.io/plugins-guide
|
|||
// ***********************************************************
|
|||
|
|||
// This function is called when a project is opened or re-opened (e.g. due to
|
|||
// the project's config changing)
|
|||
|
|||
/** |
|||
* @type {Cypress.PluginConfig} |
|||
*/ |
|||
// eslint-disable-next-line no-unused-vars
|
|||
module.exports = (on, config) => { |
|||
// `on` is used to hook into various events Cypress emits
|
|||
// `config` is the resolved Cypress config
|
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
// What this script does:
|
|||
// 1. Removes the old test folder if it exists (.budibase-cypress)
|
|||
// 2. Initialises using `.budibase-cypress`
|
|||
// 3. Runs the server using said folder
|
|||
|
|||
const rimraf = require("rimraf") |
|||
const { join } = require("path") |
|||
const homedir = join(require("os").homedir(), ".budibase-cypress") |
|||
const init = require("../../cli/src/commands/init/initHandler") |
|||
const run = require("../../cli/src/commands/run/runHandler") |
|||
|
|||
rimraf.sync(homedir) |
|||
|
|||
init({ dir: homedir, clientId: "cypress-test" }).then(() => { |
|||
delete require.cache[require.resolve("../../server/src/environment")] |
|||
run({ dir: homedir }) |
|||
}) |
|||
@ -0,0 +1,105 @@ |
|||
// ***********************************************
|
|||
// This example commands.js shows you how to
|
|||
// create various custom commands and overwrite
|
|||
// existing commands.
|
|||
//
|
|||
// For more comprehensive examples of custom
|
|||
// commands please read more here:
|
|||
// https://on.cypress.io/custom-commands
|
|||
// ***********************************************
|
|||
//
|
|||
//
|
|||
// -- This is a parent command --
|
|||
// Cypress.Commands.add("login", (email, password) => { ... })
|
|||
//
|
|||
//
|
|||
// -- This is a child command --
|
|||
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
|
|||
//
|
|||
//
|
|||
// -- This is a dual command --
|
|||
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
|
|||
//
|
|||
//
|
|||
// -- This will overwrite an existing command --
|
|||
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
|
|||
|
|||
Cypress.Commands.add("createApp", (name, description) => { |
|||
cy.get(".banner-button") |
|||
.click() |
|||
.get('input[name="name"]') |
|||
.type(name) |
|||
.should("have.value", name) |
|||
|
|||
cy.get('textarea[name="description"]') |
|||
.type(description) |
|||
.should("have.value", description) |
|||
|
|||
cy.contains("Save").click() |
|||
}) |
|||
Cypress.Commands.add("createModel", (modelName, firstField, secondField) => { |
|||
// Enter model name
|
|||
cy.get("[data-cy=Name]") |
|||
.click() |
|||
.type(modelName) |
|||
|
|||
// Add 'name' field
|
|||
cy.get("[data-cy=add-new-model-field]").click() |
|||
cy.get("[data-cy=Name]") |
|||
.click() |
|||
.type(firstField) |
|||
cy.contains("Save").click() |
|||
|
|||
// Add 'age' field
|
|||
cy.get("[data-cy=add-new-model-field]").click() |
|||
|
|||
cy.get("[data-cy=Name]") |
|||
.click() |
|||
.type(secondField) |
|||
cy.get("select").select("number") |
|||
cy.contains("Save").click() |
|||
cy.contains(secondField).should("exist") |
|||
|
|||
// Save model
|
|||
cy.contains("Save").click() |
|||
}) |
|||
Cypress.Commands.add("addRecord", (firstField, secondField) => { |
|||
cy.contains("Create new record").click() |
|||
|
|||
cy.get("[data-cy=name-input]") |
|||
.click() |
|||
.type(firstField) |
|||
cy.get("[data-cy=age-input]") |
|||
.click() |
|||
.type(secondField) |
|||
|
|||
// Save
|
|||
cy.contains("Save").click() |
|||
}) |
|||
|
|||
Cypress.Commands.add("createUser", (username, password, level) => { |
|||
// Create User
|
|||
cy.get(".nav-group-header > .ri-add-line").click() |
|||
|
|||
cy.get("[data-cy=username]").type(username) |
|||
cy.get("[data-cy=password]").type(password) |
|||
cy.get("[data-cy=accessLevel]").select(level) |
|||
|
|||
// Save
|
|||
cy.contains("Save").click() |
|||
}) |
|||
|
|||
Cypress.Commands.add("addHeadlineComponent", text => { |
|||
cy.get(".switcher > :nth-child(2)").click() |
|||
|
|||
cy.get("[data-cy=Text]").click() |
|||
cy.get("[data-cy=Headline]").click() |
|||
cy.get(".tabs > :nth-child(2)").click() |
|||
cy.get('input[type="text"]').type(text) |
|||
cy.contains("Design").click() |
|||
}) |
|||
Cypress.Commands.add("addButtonComponent", () => { |
|||
cy.get(".switcher > :nth-child(2)").click() |
|||
|
|||
cy.get("[data-cy=Button]").click() |
|||
}) |
|||
@ -0,0 +1,3 @@ |
|||
Cypress.Cookies.defaults({ |
|||
whitelist: "builder:token", |
|||
}) |
|||
@ -0,0 +1,21 @@ |
|||
// ***********************************************************
|
|||
// This example support/index.js is processed and
|
|||
// loaded automatically before your test files.
|
|||
//
|
|||
// This is a great place to put global configuration and
|
|||
// behavior that modifies Cypress.
|
|||
//
|
|||
// You can change the location of this file or turn off
|
|||
// automatically serving support files with the
|
|||
// 'supportFile' configuration option.
|
|||
//
|
|||
// You can read more here:
|
|||
// https://on.cypress.io/configuration
|
|||
// ***********************************************************
|
|||
|
|||
// Import commands.js using ES2015 syntax:
|
|||
import "./cookies" |
|||
import "./commands" |
|||
|
|||
// Alternatively you can use CommonJS syntax:
|
|||
// require('./commands')
|
|||
@ -0,0 +1,165 @@ |
|||
<script> |
|||
import { tick } from "svelte" |
|||
import Textbox from "components/common/Textbox.svelte" |
|||
import Button from "components/common/Button.svelte" |
|||
import Select from "components/common/Select.svelte" |
|||
import ActionButton from "components/common/ActionButton.svelte" |
|||
import getIcon from "components/common/icon" |
|||
import FieldView from "./FieldView.svelte" |
|||
import api from "builderStore/api" |
|||
import { store, backendUiStore } from "builderStore" |
|||
import { pipe } from "components/common/core" |
|||
import ErrorsBox from "components/common/ErrorsBox.svelte" |
|||
|
|||
export let model = { schema: {} } |
|||
export let onClosed |
|||
|
|||
let showFieldView = false |
|||
let fieldToEdit |
|||
|
|||
$: modelFields = model.schema ? Object.entries(model.schema) : [] |
|||
|
|||
function editField() {} |
|||
|
|||
function deleteField() {} |
|||
|
|||
function onFinishedFieldEdit() {} |
|||
|
|||
async function saveModel() { |
|||
const SAVE_MODEL_URL = `/api/models` |
|||
const response = await api.post(SAVE_MODEL_URL, model) |
|||
const newModel = await response.json() |
|||
backendUiStore.actions.models.create(newModel) |
|||
onClosed() |
|||
} |
|||
</script> |
|||
|
|||
<div class="heading"> |
|||
{#if !showFieldView} |
|||
<i class="ri-list-settings-line button--toggled" /> |
|||
<h3 class="budibase__title--3">Create / Edit Model</h3> |
|||
{:else} |
|||
<i class="ri-file-list-line button--toggled" /> |
|||
<h3 class="budibase__title--3">Create / Edit Field</h3> |
|||
{/if} |
|||
</div> |
|||
{#if !showFieldView} |
|||
<div class="padding"> |
|||
{#if $store.errors && $store.errors.length > 0} |
|||
<ErrorsBox errors={$store.errors} /> |
|||
{/if} |
|||
<div class="textbox"> |
|||
<Textbox label="Name" bind:text={model.name} /> |
|||
</div> |
|||
<div class="table-controls"> |
|||
<span class="label">Fields</span> |
|||
<div |
|||
data-cy="add-new-model-field" |
|||
class="hoverable new-field" |
|||
on:click={() => (showFieldView = true)}> |
|||
Add new field |
|||
</div> |
|||
</div> |
|||
|
|||
<table class="uk-table fields-table budibase__table"> |
|||
<thead> |
|||
<tr> |
|||
<th>Edit</th> |
|||
<th>Name</th> |
|||
<th>Type</th> |
|||
<th /> |
|||
</tr> |
|||
</thead> |
|||
<tbody> |
|||
{#each modelFields as [key, meta]} |
|||
<tr> |
|||
<td> |
|||
<i class="ri-more-line" on:click={() => editField(meta)} /> |
|||
</td> |
|||
<td> |
|||
<div>{key}</div> |
|||
</td> |
|||
<td>{meta.type}</td> |
|||
<td> |
|||
<i |
|||
class="ri-delete-bin-6-line hoverable" |
|||
on:click={() => deleteField(meta)} /> |
|||
</td> |
|||
</tr> |
|||
{/each} |
|||
</tbody> |
|||
</table> |
|||
<footer> |
|||
<ActionButton color="secondary" on:click={saveModel}>Save</ActionButton> |
|||
</footer> |
|||
</div> |
|||
{:else} |
|||
<FieldView |
|||
field={fieldToEdit} |
|||
onFinished={onFinishedFieldEdit} |
|||
schema={model.schema} |
|||
goBack={() => (showFieldView = false)} /> |
|||
{/if} |
|||
|
|||
<style> |
|||
.padding { |
|||
padding-top: 40px; |
|||
} |
|||
|
|||
.label { |
|||
font-size: 14px; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
.textbox { |
|||
margin: 0px 40px 0px 40px; |
|||
font-size: 14px; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
.new-field { |
|||
font-size: 16px; |
|||
font-weight: bold; |
|||
color: var(--blue); |
|||
} |
|||
|
|||
.fields-table { |
|||
margin: 8px 40px 0px 40px; |
|||
border-collapse: collapse; |
|||
width: 88%; |
|||
} |
|||
|
|||
tbody > tr:hover { |
|||
background-color: var(--grey-light); |
|||
} |
|||
|
|||
.table-controls { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
margin: 0px 40px; |
|||
} |
|||
|
|||
.ri-more-line:hover { |
|||
cursor: pointer; |
|||
} |
|||
|
|||
.heading { |
|||
padding: 40px 40px 0 40px; |
|||
display: flex; |
|||
align-items: center; |
|||
} |
|||
|
|||
h3 { |
|||
margin: 0 0 0 10px; |
|||
color: var(--ink); |
|||
} |
|||
|
|||
footer { |
|||
background-color: var(--grey-light); |
|||
margin-top: 40px; |
|||
padding: 20px 40px 20px 40px; |
|||
display: flex; |
|||
justify-content: flex-end; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,143 @@ |
|||
<script> |
|||
import { getContext, onMount } from "svelte" |
|||
import { store, backendUiStore } from "builderStore" |
|||
import HierarchyRow from "./HierarchyRow.svelte" |
|||
import NavItem from "./NavItem.svelte" |
|||
import getIcon from "components/common/icon" |
|||
import api from "builderStore/api" |
|||
import { |
|||
CreateEditModelModal, |
|||
CreateEditViewModal, |
|||
} from "components/database/ModelDataTable/modals" |
|||
|
|||
const { open, close } = getContext("simple-modal") |
|||
|
|||
function editModel() { |
|||
open( |
|||
CreateEditModelModal, |
|||
{ |
|||
model: node, |
|||
onClosed: close, |
|||
}, |
|||
{ styleContent: { padding: "0" } } |
|||
) |
|||
} |
|||
|
|||
function newModel() { |
|||
open( |
|||
CreateEditModelModal, |
|||
{ |
|||
onClosed: close, |
|||
}, |
|||
{ styleContent: { padding: "0" } } |
|||
) |
|||
} |
|||
|
|||
function newView() { |
|||
open( |
|||
CreateEditViewModal, |
|||
{ |
|||
onClosed: close, |
|||
}, |
|||
{ styleContent: { padding: "0" } } |
|||
) |
|||
} |
|||
|
|||
function selectModel(model) { |
|||
backendUiStore.update(state => { |
|||
state.selectedModel = model |
|||
state.selectedView = `all_${model._id}` |
|||
return state |
|||
}) |
|||
} |
|||
|
|||
async function deleteModel(modelToDelete) { |
|||
const DELETE_MODEL_URL = `/api/models/${node._id}/${node._rev}` |
|||
const response = await api.delete(DELETE_MODEL_URL) |
|||
backendUiStore.update(state => { |
|||
state.models = state.models.filter( |
|||
model => model._id !== modelToDelete._id |
|||
) |
|||
state.selectedView = {} |
|||
return state |
|||
}) |
|||
} |
|||
|
|||
function selectView(view) { |
|||
backendUiStore.update(state => { |
|||
state.selectedView = view.name |
|||
return state |
|||
}) |
|||
} |
|||
</script> |
|||
|
|||
<div class="items-root"> |
|||
<div class="hierarchy"> |
|||
<div class="components-list-container"> |
|||
<div class="nav-group-header"> |
|||
<div class="hierarchy-title">Models</div> |
|||
<div class="uk-inline"> |
|||
<i class="ri-add-line hoverable" on:click={newModel} /> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="hierarchy-items-container"> |
|||
{#each $backendUiStore.models as model} |
|||
<HierarchyRow onSelect={selectModel} node={model} type="model" /> |
|||
{/each} |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="hierarchy"> |
|||
<div class="components-list-container"> |
|||
<div class="nav-group-header"> |
|||
<div class="hierarchy-title">Views</div> |
|||
<div class="uk-inline"> |
|||
<i class="ri-add-line hoverable" on:click={newView} /> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="hierarchy-items-container"> |
|||
{#each $backendUiStore.views as view} |
|||
<HierarchyRow onSelect={selectView} node={view} type="view" /> |
|||
{/each} |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<style> |
|||
.items-root { |
|||
display: flex; |
|||
flex-direction: column; |
|||
max-height: 100%; |
|||
height: 100%; |
|||
background-color: var(--white); |
|||
} |
|||
|
|||
.nav-group-header { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
padding: 20px 20px 10px 20px; |
|||
} |
|||
|
|||
.hierarchy-title { |
|||
align-items: center; |
|||
font-size: 18px; |
|||
font-weight: 700; |
|||
text-rendering: optimizeLegibility; |
|||
color: var(--ink); |
|||
} |
|||
|
|||
.hierarchy { |
|||
display: flex; |
|||
flex-direction: column; |
|||
} |
|||
|
|||
.hierarchy-items-container { |
|||
flex: 1 1 auto; |
|||
overflow-y: auto; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,25 @@ |
|||
<script> |
|||
import {buildStyle} from "./helpers.js" |
|||
import {fade} from "svelte/transition" |
|||
|
|||
export let backgroundSize = "10px" |
|||
export let borderRadius = "" |
|||
export let height = "" |
|||
export let width = "" |
|||
export let margin = "" |
|||
|
|||
$: style = buildStyle({backgroundSize, borderRadius, height, width, margin}) |
|||
|
|||
</script> |
|||
|
|||
<style> |
|||
div { |
|||
background-image: url('data:image/svg+xml;utf8, <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2 2"><path fill="white" d="M1,0H2V1H1V0ZM0,1H1V2H0V1Z"/><path fill="gray" d="M0,0H1V1H0V0ZM1,1H2V2H1V1Z"/></svg>'); |
|||
height: fit-content; |
|||
width: fit-content; |
|||
} |
|||
</style> |
|||
|
|||
<div in:fade {style}> |
|||
<slot /> |
|||
</div> |
|||
@ -0,0 +1,53 @@ |
|||
<script> |
|||
import {createEventDispatcher} from "svelte" |
|||
import { fade } from 'svelte/transition'; |
|||
import CheckedBackground from "./CheckedBackground.svelte" |
|||
|
|||
export let hovered = false |
|||
export let color = "#fff" |
|||
|
|||
const dispatch = createEventDispatcher() |
|||
|
|||
</script> |
|||
|
|||
<style> |
|||
.swatch { |
|||
position: relative; |
|||
cursor: pointer; |
|||
border-radius: 6px; |
|||
border: 1px solid #dedada; |
|||
height: 20px; |
|||
width: 20px; |
|||
} |
|||
|
|||
.space { |
|||
padding: 3px 5px; |
|||
} |
|||
|
|||
.remove-icon { |
|||
position: absolute; |
|||
right: 0; |
|||
top: -5px; |
|||
right: -4px; |
|||
width:10px; |
|||
height: 10px; |
|||
border-radius: 50%; |
|||
background-color: #800000; |
|||
color: #fff; |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
} |
|||
</style> |
|||
|
|||
<div class="space"> |
|||
<CheckedBackground borderRadius="6px"> |
|||
<div in:fade class="swatch" style={`background: ${color};`} on:click|self on:mouseover={() => hovered = true} on:mouseleave={() => hovered = false}> |
|||
{#if hovered} |
|||
<div in:fade class="remove-icon" on:click|self={()=> dispatch("removeswatch")}> |
|||
<span on:click|self={()=> dispatch("removeswatch")}>×</span> |
|||
</div> |
|||
{/if} |
|||
</div> |
|||
</CheckedBackground> |
|||
</div> |
|||
@ -1,3 +1,4 @@ |
|||
<script> |
|||
import { params } from "@sveltech/routify" |
|||
store.setCurrentPage($params.page) |
|||
</script> |
|||
|
|||
@ -0,0 +1,12 @@ |
|||
export const getAppId = docCookie => { |
|||
const cookie = |
|||
docCookie.split(";").find(c => c.trim().startsWith("budibase:token")) || |
|||
docCookie.split(";").find(c => c.trim().startsWith("builder:token")) |
|||
|
|||
const base64Token = cookie.substring(lengthOfKey) |
|||
|
|||
const user = JSON.parse(atob(base64Token.split(".")[1])) |
|||
return user.appId |
|||
} |
|||
|
|||
const lengthOfKey = "budibase:token=".length |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue