Merge branch 'next' of github.com:Budibase/budibase into lab-day-search

This commit is contained in:
Andrew Kingston 2021-05-11 10:47:52 +01:00
commit e661fe8cf2
463 changed files with 8403 additions and 5216 deletions

View File

@ -92,6 +92,16 @@ then `cd ` into your local copy.
### 3. Install and Build
To develop the Budibase platform you'll need [Docker](https://www.docker.com/) and [Docker Compose](https://docs.docker.com/compose/) installed.
#### Quick method
`yarn setup` will check that all necessary components are installed and setup the repo for usage.
#### Manual method
The following commands can be executed to manually get Budibase up and running (assuming Docker/Docker Compose has been installed).
`yarn` to install project dependencies
`yarn bootstrap` will install all budibase modules and symlink them together using lerna.
@ -112,10 +122,17 @@ To run the budibase server and builder in dev mode (i.e. with live reloading):
1. Open a new console
2. `yarn dev` (from root)
3. Access the builder on http://localhost:4001/_builder/
3. Access the builder on http://localhost:10000/builder
This will enable watch mode for both the builder app, server, client library and any component libraries.
### 5. Cleanup
If you wish to delete all the apps created in development and reset the environment then run the following:
1. `yarn nuke:docker` will wipe all the Budibase services
2. `yarn dev` will restart all the services
## Data Storage
When you are running locally, budibase stores data on disk using [PouchDB](https://pouchdb.com/), as well as some JSON on local files. After setting up budibase, you can find all of this data in the `~/.budibase` directory.

View File

@ -15,6 +15,22 @@
"created_at": "2021-04-14T16:20:04Z",
"repoId": 190729906,
"pullRequestNo": 1383
},
{
"name": "aptkingston",
"id": 9075550,
"comment_id": 830252031,
"created_at": "2021-04-30T17:37:37Z",
"repoId": 190729906,
"pullRequestNo": 1431
},
{
"name": "kevmodrome",
"id": 534488,
"comment_id": 830545461,
"created_at": "2021-05-01T05:27:53Z",
"repoId": 190729906,
"pullRequestNo": 1431
}
]
}

View File

@ -3,6 +3,8 @@
"semi": false,
"singleQuote": false,
"trailingComma": "es5",
"arrowParens": "avoid",
"jsxBracketSameLine": false,
"plugins": ["prettier-plugin-svelte"],
"svelteSortOrder" : "scripts-markup-styles"
"svelteSortOrder" : "options-scripts-markup-styles"
}

View File

@ -59,6 +59,7 @@ services:
container_name: budi-redis-dev
restart: always
image: redis
command: redis-server --requirepass ${REDIS_PASSWORD}
ports:
- "${REDIS_PORT}:6379"
volumes:

View File

@ -23,6 +23,8 @@ services:
LOG_LEVEL: info
SENTRY_DSN: https://a34ae347621946bf8acded18e5b7d4b8@o420233.ingest.sentry.io/5338131
ENABLE_ANALYTICS: "true"
REDIS_URL: redis-service:6379
REDIS_PASSWORD: ${REDIS_PASSWORD}
depends_on:
- worker-service
@ -43,6 +45,8 @@ services:
COUCH_DB_PASSWORD: ${COUCH_DB_PASSWORD}
COUCH_DB_URL: http://${COUCH_DB_USER}:${COUCH_DB_PASSWORD}@couchdb-service:5984
SELF_HOST_KEY: ${HOSTING_KEY}
REDIS_URL: redis-service:6379
REDIS_PASSWORD: ${REDIS_PASSWORD}
depends_on:
- minio-service
- couch-init
@ -100,8 +104,7 @@ services:
redis-service:
restart: always
image: redis
ports:
- "${REDIS_PORT}:6379"
command: redis-server --requirepass ${REDIS_PASSWORD}
volumes:
- redis_data:/data

View File

@ -16,16 +16,16 @@ static_resources:
- name: local_services
domains: ["*"]
routes:
# special case to redirect specifically the route path
# to the builder, if this were a prefix then it would break minio
- match: { path: "/" }
redirect: { path_redirect: "/builder/" }
- match: { prefix: "/db/" }
route:
cluster: couchdb-service
prefix_rewrite: "/"
- match: { prefix: "/cache/" }
route:
cluster: redis-service
prefix_rewrite: "/"
- match: { prefix: "/api/admin/" }
route:
cluster: worker-dev
@ -38,6 +38,13 @@ static_resources:
route:
cluster: server-dev
# the below three cases are needed to make sure
# all traffic prefixed for the builder is passed through
# correctly.
- match: { path: "/" }
route:
cluster: builder-dev
- match: { prefix: "/builder/" }
route:
cluster: builder-dev
@ -47,10 +54,6 @@ static_resources:
cluster: builder-dev
prefix_rewrite: "/builder/"
# special case in dev to redirect no path to builder
- match: { path: "/" }
redirect: { path_redirect: "/builder/" }
# minio is on the default route because this works
# best, minio + AWS SDK doesn't handle path proxy
- match: { prefix: "/" }
@ -89,20 +92,6 @@ static_resources:
address: couchdb-service
port_value: 5984
- name: redis-service
connect_timeout: 0.25s
type: strict_dns
lb_policy: round_robin
load_assignment:
cluster_name: redis-service
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: redis-service
port_value: 6379
- name: server-dev
connect_timeout: 0.25s
type: strict_dns

View File

@ -21,7 +21,6 @@ static_resources:
cluster: app-service
prefix_rewrite: "/"
# special case for presenting our static self hosting page
- match: { path: "/" }
route:
cluster: app-service
@ -41,11 +40,6 @@ static_resources:
cluster: worker-service
prefix_rewrite: "/"
- match: { prefix: "/cache/" }
route:
cluster: redis-service
prefix_rewrite: "/"
- match: { prefix: "/db/" }
route:
cluster: couchdb-service
@ -117,18 +111,3 @@ static_resources:
address: couchdb-service
port_value: 5984
- name: redis-service
connect_timeout: 0.25s
type: strict_dns
lb_policy: round_robin
load_assignment:
cluster_name: redis-service
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: redis-service
port_value: 6379

View File

@ -12,6 +12,7 @@ MINIO_ACCESS_KEY=budibase
MINIO_SECRET_KEY=budibase
COUCH_DB_PASSWORD=budibase
COUCH_DB_USER=budibase
REDIS_PASSWORD=budibase
# This section contains variables that do not need to be altered under normal circumstances
APP_PORT=4002

61
hosting/scripts/setup.js Executable file
View File

@ -0,0 +1,61 @@
#!/usr/bin/env node
const os = require("os")
const exec = require("child_process").exec
const fs = require("fs")
const platform = os.platform()
const windows = platform === "win32"
const mac = platform === "darwin"
const linux = platform === "linux"
function execute(command) {
return new Promise(resolve => {
exec(command, (err, stdout) => resolve(linux ? !!stdout : true))
})
}
async function commandExistsUnix(command) {
const unixCmd = `command -v ${command} 2>/dev/null && { echo >&1 ${command}; exit 0; }`
return execute(command)
}
async function commandExistsWindows(command) {
if (/[\x00-\x1f<>:"|?*]/.test(command)) {
return false
}
return execute(`where ${command}`)
}
function commandExists(command) {
return windows ? commandExistsWindows(command) : commandExistsUnix(command)
}
async function init() {
const docker = commandExists("docker")
const dockerCompose = commandExists("docker-compose")
if (docker && dockerCompose) {
console.log("Docker installed - continuing.")
return
}
if (mac) {
console.log(
"Please install docker by visiting: https://docs.docker.com/docker-for-mac/install/"
)
} else if (windows) {
console.log(
"Please install docker by visiting: https://docs.docker.com/docker-for-windows/install/"
)
} else if (linux) {
console.log("Beginning automated linux installation.")
await execute(`./hosting/scripts/linux/get-docker.sh`)
await execute(`./hosting/scripts/linux/get-docker-compose.sh`)
} else {
console.error(
"Platform unknown - please look online for information about installing docker for our OS."
)
}
console.log("Once installation complete please re-run the setup script.")
process.exit(-1)
}
init()

View File

@ -14,9 +14,10 @@
"prettier-plugin-svelte": "^2.2.0",
"rimraf": "^3.0.2",
"rollup-plugin-replace": "^2.2.0",
"svelte": "^3.30.0"
"svelte": "^3.38.2"
},
"scripts": {
"setup": "./hosting/scripts/setup.js && yarn && yarn bootstrap && yarn build && yarn dev",
"bootstrap": "lerna link && lerna bootstrap",
"build": "lerna run build",
"initialise": "lerna run initialise",

View File

@ -1,18 +1,27 @@
{
"name": "@budibase/auth",
"version": "0.0.1",
"version": "0.18.6",
"description": "Authentication middlewares for budibase builder and apps",
"main": "src/index.js",
"author": "Budibase",
"license": "AGPL-3.0",
"dependencies": {
"aws-sdk": "^2.901.0",
"bcryptjs": "^2.4.3",
"ioredis": "^4.27.1",
"jsonwebtoken": "^8.5.1",
"koa-passport": "^4.1.4",
"node-fetch": "^2.6.1",
"passport-google-auth": "^1.0.2",
"passport-google-oauth": "^2.0.0",
"passport-jwt": "^4.0.0",
"passport-local": "^1.0.0",
"uuid": "^8.3.2"
"sanitize-s3-objectkey": "^0.0.1",
"tar-fs": "^2.1.1",
"uuid": "^8.3.2",
"zlib": "^1.0.5"
},
"devDependencies": {
"ioredis-mock": "^5.5.5"
}
}

View File

@ -123,7 +123,7 @@ const getConfigParams = ({ type, group, user }, otherProps = {}) => {
* @param {Object} scopes - the type, group and userID scopes of the configuration.
* @returns The most granular configuration document based on the scope.
*/
const determineScopedConfig = async function(db, { type, user, group }) {
const getScopedFullConfig = async function (db, { type, user, group }) {
const response = await db.allDocs(
getConfigParams(
{ type, user, group },
@ -132,31 +132,40 @@ const determineScopedConfig = async function(db, { type, user, group }) {
}
)
)
const configs = response.rows.map(row => {
function determineScore(row) {
const config = row.doc
// Config is specific to a user and a group
if (config._id.includes(generateConfigID({ type, user, group }))) {
config.score = 4
return 4
} else if (config._id.includes(generateConfigID({ type, user }))) {
// Config is specific to a user only
config.score = 3
return 3
} else if (config._id.includes(generateConfigID({ type, group }))) {
// Config is specific to a group only
config.score = 2
return 2
} else if (config._id.includes(generateConfigID({ type }))) {
// Config is specific to a type only
config.score = 1
return 1
}
return 0
}
return config
})
// Find the config with the most granular scope based on context
const scopedConfig = configs.sort((a, b) => b.score - a.score)[0]
const scopedConfig = response.rows.sort(
(a, b) => determineScore(a) - determineScore(b)
)[0]
return scopedConfig
return scopedConfig && scopedConfig.doc
}
async function getScopedConfig(db, params) {
const configDoc = await getScopedFullConfig(db, params)
return configDoc && configDoc.config ? configDoc.config : configDoc
}
exports.getScopedConfig = getScopedConfig
exports.generateConfigID = generateConfigID
exports.getConfigParams = getConfigParams
exports.determineScopedConfig = determineScopedConfig
exports.getScopedFullConfig = getScopedFullConfig

View File

@ -1,5 +1,19 @@
function isTest() {
return (
process.env.NODE_ENV === "jest" ||
process.env.NODE_ENV === "cypress" ||
process.env.JEST_WORKER_ID != null
)
}
module.exports = {
JWT_SECRET: process.env.JWT_SECRET,
COUCH_DB_URL: process.env.COUCH_DB_URL,
SALT_ROUNDS: process.env.SALT_ROUNDS,
REDIS_URL: process.env.REDIS_URL,
REDIS_PASSWORD: process.env.REDIS_PASSWORD,
MINIO_ACCESS_KEY: process.env.MINIO_ACCESS_KEY,
MINIO_SECRET_KEY: process.env.MINIO_SECRET_KEY,
MINIO_URL: process.env.MINIO_URL,
isTest,
}

View File

@ -28,6 +28,14 @@ module.exports = {
setDB(pouch)
},
db: require("./db/utils"),
redis: {
Client: require("./redis"),
utils: require("./redis/utils"),
},
objectStore: {
...require("./objectStore"),
...require("./objectStore/utils"),
},
utils: {
...require("./utils"),
...require("./hashing"),

View File

@ -3,11 +3,35 @@ const database = require("../db")
const { getCookie, clearCookie } = require("../utils")
const { StaticDatabases } = require("../db/utils")
module.exports = (noAuthPatterns = []) => {
const regex = new RegExp(noAuthPatterns.join("|"))
const PARAM_REGEX = /\/:(.*?)\//g
function buildNoAuthRegex(patterns) {
return patterns.map(pattern => {
const isObj = typeof pattern === "object" && pattern.route
const method = isObj ? pattern.method : "GET"
let route = isObj ? pattern.route : pattern
const matches = route.match(PARAM_REGEX)
if (matches) {
for (let match of matches) {
route = route.replace(match, "/.*/")
}
}
return { regex: new RegExp(route), method }
})
}
module.exports = (noAuthPatterns = [], opts) => {
const noAuthOptions = noAuthPatterns ? buildNoAuthRegex(noAuthPatterns) : []
return async (ctx, next) => {
// the path is not authenticated
if (regex.test(ctx.request.url)) {
const found = noAuthOptions.find(({ regex, method }) => {
return (
regex.test(ctx.request.url) &&
ctx.request.method.toLowerCase() === method.toLowerCase()
)
})
if (found != null) {
return next()
}
try {
@ -30,10 +54,14 @@ module.exports = (noAuthPatterns = []) => {
if (ctx.isAuthenticated !== true) {
ctx.isAuthenticated = false
}
return next()
} catch (err) {
// allow configuring for public access
if (opts && opts.publicAllowed) {
ctx.isAuthenticated = false
} else {
ctx.throw(err.status || 403, err)
}
}
}
}

View File

@ -0,0 +1,240 @@
const sanitize = require("sanitize-s3-objectkey")
const AWS = require("aws-sdk")
const stream = require("stream")
const fetch = require("node-fetch")
const tar = require("tar-fs")
const zlib = require("zlib")
const { promisify } = require("util")
const { join } = require("path")
const fs = require("fs")
const env = require("../environment")
const { budibaseTempDir, ObjectStoreBuckets } = require("./utils")
const { v4 } = require("uuid")
const streamPipeline = promisify(stream.pipeline)
// use this as a temporary store of buckets that are being created
const STATE = {
bucketCreationPromises: {},
}
const CONTENT_TYPE_MAP = {
html: "text/html",
css: "text/css",
js: "application/javascript",
}
const STRING_CONTENT_TYPES = [
CONTENT_TYPE_MAP.html,
CONTENT_TYPE_MAP.css,
CONTENT_TYPE_MAP.js,
]
function publicPolicy(bucketName) {
return {
Version: "2012-10-17",
Statement: [
{
Effect: "Allow",
Principal: {
AWS: ["*"],
},
Action: "s3:GetObject",
Resource: [`arn:aws:s3:::${bucketName}/*`],
},
],
}
}
const PUBLIC_BUCKETS = [ObjectStoreBuckets.APPS, ObjectStoreBuckets.GLOBAL]
/**
* Gets a connection to the object store using the S3 SDK.
* @param {string} bucket the name of the bucket which blobs will be uploaded/retrieved from.
* @return {Object} an S3 object store object, check S3 Nodejs SDK for usage.
* @constructor
*/
exports.ObjectStore = bucket => {
AWS.config.update({
accessKeyId: env.MINIO_ACCESS_KEY,
secretAccessKey: env.MINIO_SECRET_KEY,
})
const config = {
s3ForcePathStyle: true,
signatureVersion: "v4",
params: {
Bucket: bucket,
},
}
if (env.MINIO_URL) {
config.endpoint = env.MINIO_URL
}
return new AWS.S3(config)
}
/**
* Given an object store and a bucket name this will make sure the bucket exists,
* if it does not exist then it will create it.
*/
exports.makeSureBucketExists = async (client, bucketName) => {
try {
await client
.headBucket({
Bucket: bucketName,
})
.promise()
} catch (err) {
const promises = STATE.bucketCreationPromises
if (promises[bucketName]) {
await promises[bucketName]
} else if (err.statusCode === 404) {
// bucket doesn't exist create it
promises[bucketName] = client
.createBucket({
Bucket: bucketName,
})
.promise()
await promises[bucketName]
delete promises[bucketName]
// public buckets are quite hidden in the system, make sure
// no bucket is set accidentally
if (PUBLIC_BUCKETS.includes(bucketName)) {
await client
.putBucketPolicy({
Bucket: bucketName,
Policy: JSON.stringify(publicPolicy(bucketName)),
})
.promise()
}
} else {
throw err
}
}
}
/**
* Uploads the contents of a file given the required parameters, useful when
* temp files in use (for example file uploaded as an attachment).
*/
exports.upload = async ({ bucket, filename, path, type, metadata }) => {
const extension = [...filename.split(".")].pop()
const fileBytes = fs.readFileSync(path)
const objectStore = exports.ObjectStore(bucket)
await exports.makeSureBucketExists(objectStore, bucket)
const config = {
// windows file paths need to be converted to forward slashes for s3
Key: sanitize(filename).replace(/\\/g, "/"),
Body: fileBytes,
ContentType: type || CONTENT_TYPE_MAP[extension.toLowerCase()],
}
if (metadata) {
config.Metadata = metadata
}
return objectStore.upload(config).promise()
}
/**
* Similar to the upload function but can be used to send a file stream
* through to the object store.
*/
exports.streamUpload = async (bucket, filename, stream) => {
const objectStore = exports.ObjectStore(bucket)
await exports.makeSureBucketExists(objectStore, bucket)
const params = {
Bucket: bucket,
Key: sanitize(filename).replace(/\\/g, "/"),
Body: stream,
}
return objectStore.upload(params).promise()
}
/**
* retrieves the contents of a file from the object store, if it is a known content type it
* will be converted, otherwise it will be returned as a buffer stream.
*/
exports.retrieve = async (bucket, filepath) => {
const objectStore = exports.ObjectStore(bucket)
const params = {
Bucket: bucket,
Key: sanitize(filepath).replace(/\\/g, "/"),
}
const response = await objectStore.getObject(params).promise()
// currently these are all strings
if (STRING_CONTENT_TYPES.includes(response.ContentType)) {
return response.Body.toString("utf8")
} else {
return response.Body
}
}
/**
* Same as retrieval function but puts to a temporary file.
*/
exports.retrieveToTmp = async (bucket, filepath) => {
const data = await exports.retrieve(bucket, filepath)
const outputPath = join(budibaseTempDir(), v4())
fs.writeFileSync(outputPath, data)
return outputPath
}
exports.deleteFolder = async (bucket, folder) => {
const client = exports.ObjectStore(bucket)
const listParams = {
Bucket: bucket,
Prefix: folder,
}
let response = await client.listObjects(listParams).promise()
if (response.Contents.length === 0) {
return
}
const deleteParams = {
Bucket: bucket,
Delete: {
Objects: [],
},
}
response.Contents.forEach(content => {
deleteParams.Delete.Objects.push({ Key: content.Key })
})
response = await client.deleteObjects(deleteParams).promise()
// can only empty 1000 items at once
if (response.Deleted.length === 1000) {
return exports.deleteFolder(bucket, folder)
}
}
exports.uploadDirectory = async (bucket, localPath, bucketPath) => {
let uploads = []
const files = fs.readdirSync(localPath, { withFileTypes: true })
for (let file of files) {
const path = join(bucketPath, file.name)
const local = join(localPath, file.name)
if (file.isDirectory()) {
uploads.push(exports.uploadDirectory(bucket, local, path))
} else {
uploads.push(
exports.streamUpload(bucket, path, fs.createReadStream(local))
)
}
}
await Promise.all(uploads)
}
exports.downloadTarball = async (url, bucket, path) => {
const response = await fetch(url)
if (!response.ok) {
throw new Error(`unexpected response ${response.statusText}`)
}
const tmpPath = join(budibaseTempDir(), path)
await streamPipeline(response.body, zlib.Unzip(), tar.extract(tmpPath))
if (!env.isTest()) {
await exports.uploadDirectory(bucket, tmpPath, path)
}
// return the temporary path incase there is a use for it
return tmpPath
}

View File

@ -0,0 +1,13 @@
const { join } = require("path")
const { tmpdir } = require("os")
exports.ObjectStoreBuckets = {
BACKUPS: "backups",
APPS: "prod-budi-app-assets",
TEMPLATES: "templates",
GLOBAL: "global",
}
exports.budibaseTempDir = function () {
return join(tmpdir(), ".budibase")
}

View File

@ -0,0 +1,152 @@
const env = require("../environment")
// ioredis mock is all in memory
const Redis = env.isTest() ? require("ioredis-mock") : require("ioredis")
const { addDbPrefix, removeDbPrefix, getRedisOptions } = require("./utils")
const CLUSTERED = false
// for testing just generate the client once
let CLIENT = env.isTest() ? new Redis(getRedisOptions()) : null
/**
* Inits the system, will error if unable to connect to redis cluster (may take up to 10 seconds) otherwise
* will return the ioredis client which will be ready to use.
* @return {Promise<object>} The ioredis client.
*/
function init() {
return new Promise((resolve, reject) => {
// testing uses a single in memory client
if (env.isTest()) {
return resolve(CLIENT)
}
// if a connection existed, close it and re-create it
if (CLIENT) {
CLIENT.disconnect()
CLIENT = null
}
const { opts, host, port } = getRedisOptions(CLUSTERED)
if (CLUSTERED) {
CLIENT = new Redis.Cluster([{ host, port }], opts)
} else {
CLIENT = new Redis(opts)
}
CLIENT.on("end", err => {
reject(err)
})
CLIENT.on("error", err => {
reject(err)
})
CLIENT.on("connect", () => {
resolve(CLIENT)
})
})
}
/**
* Utility function, takes a redis stream and converts it to a promisified response -
* this can only be done with redis streams because they will have an end.
* @param stream A redis stream, specifically as this type of stream will have an end.
* @return {Promise<object>} The final output of the stream
*/
function promisifyStream(stream) {
return new Promise((resolve, reject) => {
const outputKeys = new Set()
stream.on("data", keys => {
keys.forEach(key => {
outputKeys.add(key)
})
})
stream.on("error", err => {
reject(err)
})
stream.on("end", async () => {
const keysArray = Array.from(outputKeys)
try {
let getPromises = []
for (let key of keysArray) {
getPromises.push(CLIENT.get(key))
}
const jsonArray = await Promise.all(getPromises)
resolve(
keysArray.map(key => ({
key: removeDbPrefix(key),
value: JSON.parse(jsonArray.shift()),
}))
)
} catch (err) {
reject(err)
}
})
})
}
class RedisWrapper {
constructor(db) {
this._db = db
}
async init() {
this._client = await init()
return this
}
async finish() {
this._client.disconnect()
}
async scan() {
const db = this._db,
client = this._client
let stream
if (CLUSTERED) {
let node = client.nodes("master")
stream = node[0].scanStream({ match: db + "-*", count: 100 })
} else {
stream = client.scanStream({ match: db + "-*", count: 100 })
}
return promisifyStream(stream)
}
async get(key) {
const db = this._db,
client = this._client
let response = await client.get(addDbPrefix(db, key))
// overwrite the prefixed key
if (response != null && response.key) {
response.key = key
}
// if its not an object just return the response
try {
return JSON.parse(response)
} catch (err) {
return response
}
}
async store(key, value, expirySeconds = null) {
const db = this._db,
client = this._client
if (typeof value === "object") {
value = JSON.stringify(value)
}
const prefixedKey = addDbPrefix(db, key)
await client.set(prefixedKey, value)
if (expirySeconds) {
await client.expire(prefixedKey, expirySeconds)
}
}
async delete(key) {
const db = this._db,
client = this._client
await client.del(addDbPrefix(db, key))
}
async clear() {
const db = this._db
let items = await this.scan(db)
await Promise.all(items.map(obj => this.delete(db, obj.key)))
}
}
module.exports = RedisWrapper

View File

@ -0,0 +1,46 @@
const env = require("../environment")
const SLOT_REFRESH_MS = 2000
const CONNECT_TIMEOUT_MS = 10000
const SEPARATOR = "-"
const REDIS_URL = !env.REDIS_URL ? "localhost:6379" : env.REDIS_URL
const REDIS_PASSWORD = !env.REDIS_PASSWORD ? "budibase" : env.REDIS_PASSWORD
exports.Databases = {
PW_RESETS: "pwReset",
INVITATIONS: "invitation",
}
exports.getRedisOptions = (clustered = false) => {
const [host, port] = REDIS_URL.split(":")
const opts = {
connectTimeout: CONNECT_TIMEOUT_MS,
}
if (clustered) {
opts.redisOptions = {}
opts.redisOptions.tls = {}
opts.redisOptions.password = REDIS_PASSWORD
opts.slotsRefreshTimeout = SLOT_REFRESH_MS
opts.dnsLookup = (address, callback) => callback(null, address)
} else {
opts.host = host
opts.port = port
opts.password = REDIS_PASSWORD
}
return { opts, host, port }
}
exports.addDbPrefix = (db, key) => {
return `${db}${SEPARATOR}${key}`
}
exports.removeDbPrefix = key => {
let parts = key.split(SEPARATOR)
if (parts.length >= 2) {
parts.shift()
return parts.join(SEPARATOR)
} else {
// return the only part
return parts[0]
}
}

View File

@ -105,6 +105,12 @@ exports.isClient = ctx => {
return ctx.headers["x-budibase-type"] === "client"
}
/**
* Given an email address this will use a view to search through
* all the users to find one with this email address.
* @param {string} email the email to lookup the user by.
* @return {Promise<object|null>}
*/
exports.getGlobalUserByEmail = async email => {
const db = getDB(StaticDatabases.GLOBAL.name)
try {

View File

@ -36,6 +36,21 @@ asynckit@^0.4.0:
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
integrity sha1-x57Zf380y48robyXkLzDZkdLS3k=
aws-sdk@^2.901.0:
version "2.901.0"
resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.901.0.tgz#96b387778cf2b3537383fba04994e815f1fab4d4"
integrity sha512-prmUjg4mjguamnwaXMdm/g1xtnT9515cjSaV/MhBsMUVzhe66EX7dLwiA7Jo8qUlwFMyCVIcp/2T+6KwJ9sQgQ==
dependencies:
buffer "4.9.2"
events "1.1.1"
ieee754 "1.1.13"
jmespath "0.15.0"
querystring "0.2.0"
sax "1.2.1"
url "0.10.3"
uuid "3.3.2"
xml2js "0.4.19"
aws-sign2@~0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
@ -46,6 +61,16 @@ aws4@^1.8.0:
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59"
integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==
balanced-match@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
base64-js@^1.0.2, base64-js@^1.3.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
base64url@3.x.x:
version "3.0.1"
resolved "https://registry.yarnpkg.com/base64url/-/base64url-3.0.1.tgz#6399d572e2bc3f90a9a8b22d5dbb0a32d33f788d"
@ -63,16 +88,60 @@ bcryptjs@^2.4.3:
resolved "https://registry.yarnpkg.com/bcryptjs/-/bcryptjs-2.4.3.tgz#9ab5627b93e60621ff7cdac5da9733027df1d0cb"
integrity sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms=
bl@^4.0.3:
version "4.1.0"
resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a"
integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==
dependencies:
buffer "^5.5.0"
inherits "^2.0.4"
readable-stream "^3.4.0"
brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
dependencies:
balanced-match "^1.0.0"
concat-map "0.0.1"
buffer-equal-constant-time@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=
buffer@4.9.2:
version "4.9.2"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8"
integrity sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==
dependencies:
base64-js "^1.0.2"
ieee754 "^1.1.4"
isarray "^1.0.0"
buffer@^5.5.0:
version "5.7.1"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0"
integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==
dependencies:
base64-js "^1.3.1"
ieee754 "^1.1.13"
caseless@~0.12.0:
version "0.12.0"
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=
chownr@^1.1.1:
version "1.1.4"
resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==
cluster-key-slot@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz#30474b2a981fb12172695833052bc0d01336d10d"
integrity sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw==
combined-stream@^1.0.6, combined-stream@~1.0.6:
version "1.0.8"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
@ -80,6 +149,11 @@ combined-stream@^1.0.6, combined-stream@~1.0.6:
dependencies:
delayed-stream "~1.0.0"
concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
core-util-is@1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
@ -92,11 +166,23 @@ dashdash@^1.12.0:
dependencies:
assert-plus "^1.0.0"
debug@^4.3.1:
version "4.3.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee"
integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==
dependencies:
ms "2.1.2"
delayed-stream@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk=
denque@^1.1.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/denque/-/denque-1.5.0.tgz#773de0686ff2d8ec2ff92914316a47b73b1c73de"
integrity sha512-CYiCSgIF1p6EUByQPlGkKnP1M9g0ZV3qMIrqMqZqdwazygIA/YP2vrbcyl1h/WppKJTdl1F85cXIle+394iDAQ==
ecc-jsbn@~0.1.1:
version "0.1.2"
resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9"
@ -112,6 +198,18 @@ ecdsa-sig-formatter@1.0.11:
dependencies:
safe-buffer "^5.0.1"
end-of-stream@^1.1.0, end-of-stream@^1.4.1:
version "1.4.4"
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==
dependencies:
once "^1.4.0"
events@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924"
integrity sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=
extend@~3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
@ -137,6 +235,20 @@ fast-json-stable-stringify@^2.0.0:
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
fengari-interop@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/fengari-interop/-/fengari-interop-0.1.2.tgz#f7731dcdd2ff4449073fb7ac3c451a8841ce1e87"
integrity sha512-8iTvaByZVoi+lQJhHH9vC+c/Yaok9CwOqNQZN6JrVpjmWwW4dDkeblBXhnHC+BoI6eF4Cy5NKW3z6ICEjvgywQ==
fengari@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/fengari/-/fengari-0.1.4.tgz#72416693cd9e43bd7d809d7829ddc0578b78b0bb"
integrity sha512-6ujqUuiIYmcgkGz8MGAdERU57EIluGGPSUgGPTsco657EHa+srq0S3/YUl/r9kx1+D+d4rGfYObd+m8K22gB1g==
dependencies:
readline-sync "^1.4.9"
sprintf-js "^1.1.1"
tmp "^0.0.33"
forever-agent@~0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
@ -151,6 +263,11 @@ form-data@~2.3.2:
combined-stream "^1.0.6"
mime-types "^2.1.12"
fs-constants@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
getpass@^0.1.1:
version "0.1.7"
resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa"
@ -216,16 +333,68 @@ http-signature@~1.2.0:
jsprim "^1.2.2"
sshpk "^1.7.0"
ieee754@1.1.13:
version "1.1.13"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84"
integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==
ieee754@^1.1.13, ieee754@^1.1.4:
version "1.2.1"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
inherits@^2.0.3, inherits@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
ioredis-mock@^5.5.5:
version "5.5.5"
resolved "https://registry.yarnpkg.com/ioredis-mock/-/ioredis-mock-5.5.5.tgz#dec9fedd238c6ab9f56c026fc366533144f8a256"
integrity sha512-7SxCAwNtDLC8IFDptqIhOC7ajp3fciVtCrXOEOkpyjPboAGRQkJbnpNPy1NYORoWi+0/iOtUPUQckSKtSQj4DA==
dependencies:
fengari "^0.1.4"
fengari-interop "^0.1.2"
lodash "^4.17.21"
minimatch "^3.0.4"
standard-as-callback "^2.1.0"
ioredis@^4.27.1:
version "4.27.1"
resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-4.27.1.tgz#4ef947b455a1b995baa4b0d7e2c4e4f75f746421"
integrity sha512-PaFNFeBbOcEYHXAdrJuy7uesJcyvzStTM1aYMchTuky+VgKqDbXhnTJHaDsjAwcTwPx8Asatx+l2DW8zZ2xlsQ==
dependencies:
cluster-key-slot "^1.1.0"
debug "^4.3.1"
denque "^1.1.0"
lodash.defaults "^4.2.0"
lodash.flatten "^4.4.0"
p-map "^2.1.0"
redis-commands "1.7.0"
redis-errors "^1.2.0"
redis-parser "^3.0.0"
standard-as-callback "^2.1.0"
is-typedarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=
isarray@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
isstream@~0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
jmespath@0.15.0:
version "0.15.0"
resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.15.0.tgz#a3f222a9aae9f966f5d27c796510e28091764217"
integrity sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=
jsbn@~0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
@ -296,6 +465,16 @@ koa-passport@^4.1.4:
dependencies:
passport "^0.4.0"
lodash.defaults@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c"
integrity sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=
lodash.flatten@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f"
integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=
lodash.includes@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f"
@ -336,7 +515,7 @@ lodash.once@^4.0.0:
resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=
lodash@^4.14.0:
lodash@^4.14.0, lodash@^4.17.21:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@ -358,11 +537,33 @@ mime@^1.4.1:
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
minimatch@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
dependencies:
brace-expansion "^1.1.7"
mkdirp-classic@^0.5.2:
version "0.5.3"
resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==
ms@2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
ms@^2.1.1:
version "2.1.3"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
node-fetch@^2.6.1:
version "2.6.1"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
node-forge@^0.7.1:
version "0.7.6"
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.6.tgz#fdf3b418aee1f94f0ef642cd63486c77ca9724ac"
@ -378,6 +579,23 @@ oauth@0.9.x:
resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.9.15.tgz#bd1fefaf686c96b75475aed5196412ff60cfb9c1"
integrity sha1-vR/vr2hslrdUda7VGWQS/2DPucE=
once@^1.3.1, once@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
dependencies:
wrappy "1"
os-tmpdir@~1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=
p-map@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175"
integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==
passport-google-auth@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/passport-google-auth/-/passport-google-auth-1.0.2.tgz#8b300b5aa442ef433de1d832ed3112877d0b2938"
@ -471,6 +689,19 @@ psl@^1.1.28:
resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24"
integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==
pump@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64"
integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==
dependencies:
end-of-stream "^1.1.0"
once "^1.3.1"
punycode@1.3.2:
version "1.3.2"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d"
integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=
punycode@^2.1.0, punycode@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
@ -481,6 +712,42 @@ qs@~6.5.2:
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
querystring@0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620"
integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=
readable-stream@^3.1.1, readable-stream@^3.4.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==
dependencies:
inherits "^2.0.3"
string_decoder "^1.1.1"
util-deprecate "^1.0.1"
readline-sync@^1.4.9:
version "1.4.10"
resolved "https://registry.yarnpkg.com/readline-sync/-/readline-sync-1.4.10.tgz#41df7fbb4b6312d673011594145705bf56d8873b"
integrity sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw==
redis-commands@1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.7.0.tgz#15a6fea2d58281e27b1cd1acfb4b293e278c3a89"
integrity sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ==
redis-errors@^1.0.0, redis-errors@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad"
integrity sha1-62LSrbFeTq9GEMBK/hUpOEJQq60=
redis-parser@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-3.0.0.tgz#b66d828cdcafe6b4b8a428a7def4c6bcac31c8b4"
integrity sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ=
dependencies:
redis-errors "^1.0.0"
request@^2.72.0, request@^2.74.0:
version "2.88.2"
resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3"
@ -507,7 +774,7 @@ request@^2.72.0, request@^2.74.0:
tunnel-agent "^0.6.0"
uuid "^3.3.2"
safe-buffer@^5.0.1, safe-buffer@^5.1.2:
safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0:
version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
@ -517,11 +784,31 @@ safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
sanitize-s3-objectkey@^0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/sanitize-s3-objectkey/-/sanitize-s3-objectkey-0.0.1.tgz#efa9887cd45275b40234fb4bb12fc5754fe64e7e"
integrity sha512-ZTk7aqLxy4sD40GWcYWoLfbe05XLmkKvh6vGKe13ADlei24xlezcvjgKy1qRArlaIbIMYaqK7PCalvZtulZlaQ==
sax@1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a"
integrity sha1-e45lYZCyKOgaZq6nSEgNgozS03o=
sax@>=0.6.0:
version "1.2.4"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
semver@^5.6.0:
version "5.7.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
sprintf-js@^1.1.1:
version "1.1.2"
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673"
integrity sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==
sshpk@^1.7.0:
version "1.16.1"
resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877"
@ -537,11 +824,51 @@ sshpk@^1.7.0:
safer-buffer "^2.0.2"
tweetnacl "~0.14.0"
standard-as-callback@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-2.1.0.tgz#8953fc05359868a77b5b9739a665c5977bb7df45"
integrity sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==
string-template@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/string-template/-/string-template-1.0.0.tgz#9e9f2233dc00f218718ec379a28a5673ecca8b96"
integrity sha1-np8iM9wA8hhxjsN5oopWc+zKi5Y=
string_decoder@^1.1.1:
version "1.3.0"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==
dependencies:
safe-buffer "~5.2.0"
tar-fs@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784"
integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==
dependencies:
chownr "^1.1.1"
mkdirp-classic "^0.5.2"
pump "^3.0.0"
tar-stream "^2.1.4"
tar-stream@^2.1.4:
version "2.2.0"
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287"
integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==
dependencies:
bl "^4.0.3"
end-of-stream "^1.4.1"
fs-constants "^1.0.0"
inherits "^2.0.3"
readable-stream "^3.1.1"
tmp@^0.0.33:
version "0.0.33"
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==
dependencies:
os-tmpdir "~1.0.2"
tough-cookie@~2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2"
@ -574,11 +901,29 @@ uri-js@^4.2.2:
dependencies:
punycode "^2.1.0"
url@0.10.3:
version "0.10.3"
resolved "https://registry.yarnpkg.com/url/-/url-0.10.3.tgz#021e4d9c7705f21bbf37d03ceb58767402774c64"
integrity sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=
dependencies:
punycode "1.3.2"
querystring "0.2.0"
util-deprecate@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
utils-merge@1.x.x:
version "1.0.1"
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
uuid@3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"
integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==
uuid@^3.3.2:
version "3.4.0"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
@ -597,3 +942,26 @@ verror@1.10.0:
assert-plus "^1.0.0"
core-util-is "1.0.2"
extsprintf "^1.2.0"
wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
xml2js@0.4.19:
version "0.4.19"
resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7"
integrity sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==
dependencies:
sax ">=0.6.0"
xmlbuilder "~9.0.1"
xmlbuilder@~9.0.1:
version "9.0.7"
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d"
integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=
zlib@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/zlib/-/zlib-1.0.5.tgz#6e7c972fc371c645a6afb03ab14769def114fcc0"
integrity sha1-bnyXL8NxxkWmr7A6sUdp3vEU/MA=

View File

@ -27,7 +27,7 @@
"rollup-plugin-postcss": "^4.0.0",
"rollup-plugin-svelte": "^7.1.0",
"rollup-plugin-terser": "^7.0.2",
"svelte": "^3.37.0"
"svelte": "^3.38.2"
},
"keywords": [
"svelte"

View File

@ -42,12 +42,14 @@
class="spectrum-ActionButton spectrum-ActionButton--size{size}"
{disabled}
on:longPress
on:click|preventDefault>
on:click|preventDefault
>
{#if longPressable}
<svg
class="spectrum-Icon spectrum-UIIcon-CornerTriangle100 spectrum-ActionButton-hold"
focusable="false"
aria-hidden="true">
aria-hidden="true"
>
<use xlink:href="#spectrum-css-icon-CornerTriangle100" />
</svg>
{/if}
@ -56,7 +58,8 @@
class="spectrum-Icon spectrum-Icon--size{size}"
focusable="false"
aria-hidden="true"
aria-label={icon}>
aria-label={icon}
>
<use xlink:href="#spectrum-icon-18-{icon}" />
</svg>
{/if}

View File

@ -8,7 +8,7 @@
// Attaches a spectrum-ActionGroup-item class to buttons inside the div
function group(element) {
const buttons = Array.from(element.getElementsByTagName("button"))
buttons.forEach((button) => {
buttons.forEach(button => {
button.classList.add("spectrum-ActionGroup-item")
})
}

View File

@ -4,6 +4,7 @@
import Menu from "../Menu/Menu.svelte"
export let disabled = false
export let align = "left"
let anchor
let dropdown
@ -31,7 +32,7 @@
<div use:getAnchor on:click={openMenu}>
<slot name="control" />
</div>
<Popover bind:this={dropdown} {anchor} align="left">
<Popover bind:this={dropdown} {anchor} {align}>
<Menu>
<slot />
</Menu>

View File

@ -1,12 +1,53 @@
<script>
import "@spectrum-css/avatar/dist/index-vars.css"
let sizes = new Map([
["XXS", "--spectrum-alias-avatar-size-50"],
["XS", "--spectrum-alias-avatar-size-75"],
["S", "--spectrum-alias-avatar-size-200"],
["M", "--spectrum-alias-avatar-size-300"],
["L", "--spectrum-alias-avatar-size-500"],
["XL", "--spectrum-alias-avatar-size-600"],
["XXL", "--spectrum-alias-avatar-size-700"],
])
export let size = "M"
export let url = ""
export let disabled = false
export let name = "John Doe"
function getInitials(name) {
let parts = name.split(" ")
return parts.map(name => name[0]).join("")
}
</script>
{#if url}
<img
class:is-disabled={disabled}
class="spectrum-Avatar"
src={url}
alt="Avatar"
style="width: var({sizes.get(size)}); height: var({sizes.get(size)});"
/>
{:else}
<div
class:is-disabled={disabled}
style="width: var({sizes.get(size)}); height: var({sizes.get(
size
)}); font-size: calc(var({sizes.get(size)}) / 2)"
>
{getInitials(name)}
</div>
{/if}
<style>
div {
color: white;
display: grid;
place-items: center;
font-weight: 500;
background: #3aab87;
border-radius: 50%;
overflow: hidden;
user-select: none;
}
</style>

View File

@ -23,13 +23,15 @@
class:active
class="spectrum-Button spectrum-Button--size{size.toUpperCase()}"
{disabled}
on:click|preventDefault>
on:click|preventDefault
>
{#if icon}
<svg
class="spectrum-Icon spectrum-Icon--size{size.toUpperCase()}"
focusable="false"
aria-hidden="true"
aria-label={icon}>
aria-label={icon}
>
<use xlink:href="#spectrum-icon-18-{icon}" />
</svg>
{/if}

View File

@ -3,13 +3,17 @@
export let vertical = false
function group(element) {
const buttons = Array.from(element.getElementsByTagName('button'))
const buttons = Array.from(element.getElementsByTagName("button"))
buttons.forEach(button => {
button.classList.add('spectrum-ButtonGroup-item')
button.classList.add("spectrum-ButtonGroup-item")
})
}
</script>
<div use:group class="spectrum-ButtonGroup" class:spectrum-ButtonGroup--vertical={vertical}>
<div
use:group
class="spectrum-ButtonGroup"
class:spectrum-ButtonGroup--vertical={vertical}
>
<slot />
</div>

View File

@ -37,8 +37,10 @@
<section class="drawer" transition:slide>
<header>
<div class="text">
<Heading xs>{title}</Heading>
<Body xxs><slot name="description" /></Body>
<Heading size="XS">{title}</Heading>
<Body size="XXS">
<slot name="description" />
</Body>
</div>
<div class="buttons">
<slot name="buttons" />

View File

@ -28,11 +28,9 @@
border-right: var(--border-light);
overflow: auto;
}
.sidebar::-webkit-scrollbar {
display: none;
}
.main {
font-family: var(--font-sans);
}

View File

@ -35,5 +35,6 @@
{placeholder}
{getOptionLabel}
{getOptionValue}
on:change={onChange} />
on:change={onChange}
/>
</Field>

View File

@ -17,27 +17,31 @@
<label
class="spectrum-Checkbox spectrum-Checkbox--sizeM spectrum-Checkbox--emphasized"
class:is-invalid={!!error}>
class:is-invalid={!!error}
>
<input
checked={value}
{disabled}
on:change={onChange}
type="checkbox"
class="spectrum-Checkbox-input"
{id} />
{id}
/>
<span class="spectrum-Checkbox-box">
<svg
class="spectrum-Icon spectrum-UIIcon-Checkmark100 spectrum-Checkbox-checkmark"
focusable="false"
aria-hidden="true">
aria-hidden="true"
>
<use xlink:href="#spectrum-css-icon-Checkmark100" />
</svg>
<svg
class="spectrum-Icon spectrum-UIIcon-Dash100 spectrum-Checkbox-partialCheckmark"
focusable="false"
aria-hidden="true">
aria-hidden="true"
>
<use xlink:href="#spectrum-css-icon-Dash100" />
</svg>
</span>
<span class="spectrum-Checkbox-label">{text || ''}</span>
<span class="spectrum-Checkbox-label">{text || ""}</span>
</label>

View File

@ -51,12 +51,14 @@
class="spectrum-InputGroup"
class:is-focused={open || focus}
class:is-invalid={!!error}
class:is-disabled={disabled}>
class:is-disabled={disabled}
>
<div
class="spectrum-Textfield spectrum-InputGroup-textfield"
class:is-invalid={!!error}
class:is-disabled={disabled}
class:is-focused={open || focus}>
class:is-focused={open || focus}
>
<input
type="text"
on:focus={() => (focus = true)}
@ -65,18 +67,21 @@
{value}
{disabled}
{placeholder}
class="spectrum-Textfield-input spectrum-InputGroup-input" />
class="spectrum-Textfield-input spectrum-InputGroup-input"
/>
</div>
<button
class="spectrum-Picker spectrum-Picker--sizeM spectrum-InputGroup-button"
tabindex="-1"
aria-haspopup="true"
{disabled}
on:click={() => (open = true)}>
on:click={() => (open = true)}
>
<svg
class="spectrum-Icon spectrum-UIIcon-ChevronDown100 spectrum-Picker-menuIcon spectrum-InputGroup-icon"
focusable="false"
aria-hidden="true">
aria-hidden="true"
>
<use xlink:href="#spectrum-css-icon-Chevron100" />
</svg>
</button>
@ -84,7 +89,8 @@
<div class="overlay" on:mousedown|self={() => (open = false)} />
<div
transition:fly={{ y: -20, duration: 200 }}
class="spectrum-Popover spectrum-Popover--bottom is-open">
class="spectrum-Popover spectrum-Popover--bottom is-open"
>
<ul class="spectrum-Menu" role="listbox">
{#if options && Array.isArray(options)}
{#each options as option}
@ -94,13 +100,16 @@
role="option"
aria-selected="true"
tabindex="0"
on:click={() => selectOption(getOptionValue(option))}>
<span
class="spectrum-Menu-itemLabel">{getOptionLabel(option)}</span>
on:click={() => selectOption(getOptionValue(option))}
>
<span class="spectrum-Menu-itemLabel"
>{getOptionLabel(option)}</span
>
<svg
class="spectrum-Icon spectrum-UIIcon-Checkmark100 spectrum-Menu-checkmark spectrum-Menu-itemIcon"
focusable="false"
aria-hidden="true">
aria-hidden="true"
>
<use xlink:href="#spectrum-css-icon-Checkmark100" />
</svg>
</li>

View File

@ -64,7 +64,8 @@
on:close={onClose}
options={flatpickrOptions}
on:change={handleChange}
element={`#${flatpickrId}`}>
element={`#${flatpickrId}`}
>
<div
id={flatpickrId}
class:is-disabled={disabled}
@ -73,17 +74,20 @@
class:is-focused={open}
aria-readonly="false"
aria-required="false"
aria-haspopup="true">
aria-haspopup="true"
>
<div
on:click={flatpickr?.open}
class="spectrum-Textfield spectrum-InputGroup-textfield"
class:is-disabled={disabled}
class:is-invalid={!!error}>
class:is-invalid={!!error}
>
{#if !!error}
<svg
class="spectrum-Icon spectrum-Icon--sizeM spectrum-Textfield-validationIcon"
focusable="false"
aria-hidden="true">
aria-hidden="true"
>
<use xlink:href="#spectrum-icon-18-Alert" />
</svg>
{/if}
@ -94,7 +98,8 @@
class="spectrum-Textfield-input spectrum-InputGroup-input"
{placeholder}
{id}
{value} />
{value}
/>
</div>
<button
type="button"
@ -102,12 +107,14 @@
tabindex="-1"
{disabled}
class:is-invalid={!!error}
on:click={flatpickr?.open}>
on:click={flatpickr?.open}
>
<svg
class="spectrum-Icon spectrum-Icon--sizeM"
focusable="false"
aria-hidden="true"
aria-label="Calendar">
aria-label="Calendar"
>
<use xlink:href="#spectrum-icon-18-Calendar" />
</svg>
</button>

View File

@ -15,6 +15,8 @@
export let fileSizeLimit = BYTES_IN_MB * 20
export let processFiles = null
export let handleFileTooLarge = null
export let gallery = true
export let error = null
const dispatch = createEventDispatcher()
const imageExtensions = [
@ -52,6 +54,8 @@
const newValue = [...value, ...processedFiles]
dispatch("change", newValue)
selectedImageIdx = newValue.length - 1
} else {
dispatch("change", fileList)
}
}
@ -94,6 +98,7 @@
<div class="container">
{#if selectedImage}
{#if gallery}
<div class="gallery">
<div class="title">
<div class="filename">{selectedImage.name}</div>
@ -119,20 +124,42 @@
<div
class="nav left"
class:visible={selectedImageIdx > 0}
on:click={navigateLeft}>
on:click={navigateLeft}
>
<Icon name="ChevronLeft" />
</div>
<div
class="nav right"
class:visible={selectedImageIdx < fileCount - 1}
on:click={navigateRight}>
on:click={navigateRight}
>
<Icon name="ChevronRight" />
</div>
<div class="footer">File {selectedImageIdx + 1} of {fileCount}</div>
</div>
{:else if value?.length}
{#each value as file}
<div class="gallery">
<div class="title">
<div class="filename">{file.name}</div>
<div class="filesize">
{#if file.size <= BYTES_IN_MB}
{`${file.size / BYTES_IN_KB} KB`}
{:else}{`${file.size / BYTES_IN_MB} MB`}{/if}
</div>
{#if !disabled}
<div class="delete-button" on:click={removeFile}>
<Icon name="Close" />
</div>
{/if}
</div>
</div>
{/each}
{/if}
{/if}
<div
class="spectrum-Dropzone"
class:is-invalid={!!error}
class:disabled
role="region"
tabindex="0"
@ -140,19 +167,22 @@
on:dragleave={handleDragLeave}
on:dragenter={handleDragOver}
on:drop={handleDrop}
class:is-dragged={fileDragged}>
class:is-dragged={fileDragged}
>
<div class="spectrum-IllustratedMessage spectrum-IllustratedMessage--cta">
<input
id={fieldId}
{disabled}
type="file"
multiple
on:change={handleFile} />
on:change={handleFile}
/>
<svg
class="spectrum-IllustratedMessage-illustration"
width="125"
height="60"
viewBox="0 0 199 97.7"><defs>
viewBox="0 0 199 97.7"
><defs>
<style>
.cls-1,
.cls-2 {
@ -170,25 +200,31 @@
</defs>
<path
class="cls-1"
d="M110.53,85.66,100.26,95.89a1.09,1.09,0,0,1-1.52,0L88.47,85.66" />
d="M110.53,85.66,100.26,95.89a1.09,1.09,0,0,1-1.52,0L88.47,85.66"
/>
<line class="cls-1" x1="99.5" y1="95.5" x2="99.5" y2="58.5" />
<path class="cls-1" d="M105.5,73.5h19a2,2,0,0,0,2-2v-43" />
<path
class="cls-1"
d="M126.5,22.5h-19a2,2,0,0,1-2-2V1.5h-31a2,2,0,0,0-2,2v68a2,2,0,0,0,2,2h19" />
d="M126.5,22.5h-19a2,2,0,0,1-2-2V1.5h-31a2,2,0,0,0-2,2v68a2,2,0,0,0,2,2h19"
/>
<line class="cls-1" x1="105.5" y1="1.5" x2="126.5" y2="22.5" />
<path
class="cls-2"
d="M47.93,50.49a5,5,0,1,0-4.83-5A4.93,4.93,0,0,0,47.93,50.49Z" />
d="M47.93,50.49a5,5,0,1,0-4.83-5A4.93,4.93,0,0,0,47.93,50.49Z"
/>
<path
class="cls-2"
d="M36.6,65.93,42.05,60A2.06,2.06,0,0,1,45,60l12.68,13.2" />
d="M36.6,65.93,42.05,60A2.06,2.06,0,0,1,45,60l12.68,13.2"
/>
<path
class="cls-2"
d="M3.14,73.23,22.42,53.76a1.65,1.65,0,0,1,2.38,0l19.05,19.7" />
d="M3.14,73.23,22.42,53.76a1.65,1.65,0,0,1,2.38,0l19.05,19.7"
/>
<path
class="cls-1"
d="M139.5,36.5H196A1.49,1.49,0,0,1,197.5,38V72A1.49,1.49,0,0,1,196,73.5H141A1.49,1.49,0,0,1,139.5,72V32A1.49,1.49,0,0,1,141,30.5H154a2.43,2.43,0,0,1,1.67.66l6,5.66" />
d="M139.5,36.5H196A1.49,1.49,0,0,1,197.5,38V72A1.49,1.49,0,0,1,196,73.5H141A1.49,1.49,0,0,1,139.5,72V32A1.49,1.49,0,0,1,141,30.5H154a2.43,2.43,0,0,1,1.67.66l6,5.66"
/>
<rect
class="cls-1"
x="1.5"
@ -196,16 +232,21 @@
width="58"
height="39"
rx="2"
ry="2" />
ry="2"
/>
</svg>
<h2
class="spectrum-Heading spectrum-Heading--sizeL spectrum-Heading--light spectrum-IllustratedMessage-heading">
class="spectrum-Heading spectrum-Heading--sizeL spectrum-Heading--light spectrum-IllustratedMessage-heading"
>
Drag and drop your file
</h2>
{#if !disabled}
<p
class="spectrum-Body spectrum-Body--sizeS spectrum-IllustratedMessage-description">
<label for={fieldId} class="spectrum-Link">Select a file to upload</label>
class="spectrum-Body spectrum-Body--sizeS spectrum-IllustratedMessage-description"
>
<label for={fieldId} class="spectrum-Link"
>Select a file to upload</label
>
<br />
from your computer
</p>
@ -229,6 +270,9 @@
.spectrum-Dropzone {
user-select: none;
}
.spectrum-Dropzone.is-invalid {
border-color: var(--spectrum-global-color-red-400);
}
input[type="file"] {
display: none;
}
@ -260,7 +304,7 @@
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: stretch;
align-items: center;
}
.filename {
flex: 1 1 auto;
@ -315,6 +359,7 @@
.delete-button {
transition: all 0.3s;
margin-left: 10px;
display: flex;
}
.delete-button i {
font-size: 2em;

View File

@ -78,4 +78,5 @@
{isOptionSelected}
{getOptionLabel}
{getOptionValue}
onSelectOption={toggleOption} />
onSelectOption={toggleOption}
/>

View File

@ -19,6 +19,7 @@
export let getOptionValue = option => option
export let open = false
export let readonly = false
export let quiet = false
const dispatch = createEventDispatcher()
const onClick = e => {
@ -33,11 +34,13 @@
<button
{id}
class="spectrum-Picker spectrum-Picker--sizeM"
class:spectrum-Picker--quiet={quiet}
{disabled}
class:is-invalid={!!error}
class:is-open={open}
aria-haspopup="listbox"
on:mousedown={onClick}>
on:mousedown={onClick}
>
<span class="spectrum-Picker-label" class:is-placeholder={isPlaceholder}>
{fieldText}
</span>
@ -46,14 +49,16 @@
class="spectrum-Icon spectrum-Icon--sizeM spectrum-Picker-validationIcon"
focusable="false"
aria-hidden="true"
aria-label="Folder">
aria-label="Folder"
>
<use xlink:href="#spectrum-icon-18-Alert" />
</svg>
{/if}
<svg
class="spectrum-Icon spectrum-UIIcon-ChevronDown100 spectrum-Picker-menuIcon"
focusable="false"
aria-hidden="true">
aria-hidden="true"
>
<use xlink:href="#spectrum-css-icon-Chevron100" />
</svg>
</button>
@ -61,7 +66,8 @@
<div
use:clickOutside={() => (open = false)}
transition:fly={{ y: -20, duration: 200 }}
class="spectrum-Popover spectrum-Popover--bottom spectrum-Picker-popover is-open">
class="spectrum-Popover spectrum-Popover--bottom spectrum-Picker-popover is-open"
>
<ul class="spectrum-Menu" role="listbox">
{#if placeholderOption}
<li
@ -70,12 +76,14 @@
role="option"
aria-selected="true"
tabindex="0"
on:click={() => onSelectOption(null)}>
on:click={() => onSelectOption(null)}
>
<span class="spectrum-Menu-itemLabel">{placeholderOption}</span>
<svg
class="spectrum-Icon spectrum-UIIcon-Checkmark100 spectrum-Menu-checkmark spectrum-Menu-itemIcon"
focusable="false"
aria-hidden="true">
aria-hidden="true"
>
<use xlink:href="#spectrum-css-icon-Checkmark100" />
</svg>
</li>
@ -88,13 +96,16 @@
role="option"
aria-selected="true"
tabindex="0"
on:click={() => onSelectOption(getOptionValue(option, idx))}>
<span
class="spectrum-Menu-itemLabel">{getOptionLabel(option, idx)}</span>
on:click={() => onSelectOption(getOptionValue(option, idx))}
>
<span class="spectrum-Menu-itemLabel"
>{getOptionLabel(option, idx)}</span
>
<svg
class="spectrum-Icon spectrum-UIIcon-Checkmark100 spectrum-Menu-checkmark spectrum-Menu-itemIcon"
focusable="false"
aria-hidden="true">
aria-hidden="true"
>
<use xlink:href="#spectrum-css-icon-Checkmark100" />
</svg>
</li>

View File

@ -21,14 +21,16 @@
<div
title={getOptionLabel(option)}
class="spectrum-Radio spectrum-FieldGroup-item spectrum-Radio--emphasized"
class:is-invalid={!!error}>
class:is-invalid={!!error}
>
<input
on:change={onChange}
bind:group={value}
value={getOptionValue(option)}
type="radio"
class="spectrum-Radio-input"
{disabled} />
{disabled}
/>
<span class="spectrum-Radio-button" />
<label class="spectrum-Radio-label">{getOptionLabel(option)}</label>
</div>

View File

@ -34,11 +34,13 @@
<div
class="spectrum-Textfield"
class:is-focused={focus}
class:is-disabled={disabled}>
class:is-disabled={disabled}
>
<svg
class="spectrum-Icon spectrum-Icon--sizeM spectrum-Textfield-icon"
focusable="false"
aria-hidden="true">
aria-hidden="true"
>
<use xlink:href="#spectrum-icon-18-Magnify" />
</svg>
<input
@ -46,23 +48,26 @@
on:keyup={updateValueOnEnter}
{disabled}
{id}
value={value || ''}
placeholder={placeholder || ''}
value={value || ""}
placeholder={placeholder || ""}
on:blur={onBlur}
on:focus={onFocus}
on:input
type="search"
class="spectrum-Textfield-input spectrum-Search-input"
autocomplete="off" />
autocomplete="off"
/>
</div>
<button
on:click={() => updateValue('')}
on:click={() => updateValue("")}
type="reset"
class="spectrum-ClearButton spectrum-Search-clearButton">
class="spectrum-ClearButton spectrum-Search-clearButton"
>
<svg
class="spectrum-Icon spectrum-UIIcon-Cross75"
focusable="false"
aria-hidden="true">
aria-hidden="true"
>
<use xlink:href="#spectrum-css-icon-Cross75" />
</svg>
</button>

View File

@ -11,6 +11,7 @@
export let getOptionLabel = option => option
export let getOptionValue = option => option
export let readonly = false
export let quiet = false
const dispatch = createEventDispatcher()
let open = false
@ -43,6 +44,7 @@
<Picker
on:click
bind:open
{quiet}
{id}
{error}
{disabled}
@ -51,7 +53,8 @@
{options}
{getOptionLabel}
{getOptionValue}
isPlaceholder={value == null || value === ''}
isPlaceholder={value == null || value === ""}
placeholderOption={placeholder}
isOptionSelected={option => option === value}
onSelectOption={selectOption} />
onSelectOption={selectOption}
/>

View File

@ -21,7 +21,8 @@
on:change={onChange}
{id}
type="checkbox"
class="spectrum-Switch-input" />
class="spectrum-Switch-input"
/>
<span class="spectrum-Switch-switch" />
<label class="spectrum-Switch-label" for={id}>{text}</label>
</div>

View File

@ -25,23 +25,28 @@
class="spectrum-Textfield spectrum-Textfield--multiline"
class:is-invalid={!!error}
class:is-disabled={disabled}
class:is-focused={focus}>
class:is-focused={focus}
>
{#if error}
<svg
class="spectrum-Icon spectrum-Icon--sizeM spectrum-Textfield-validationIcon"
class="spectrum-Icon spectrum-Icon--sizeM
spectrum-Textfield-validationIcon"
focusable="false"
aria-hidden="true">
aria-hidden="true"
>
<use xlink:href="#spectrum-icon-18-Alert" />
</svg>
{/if}
<!-- prettier-ignore -->
<textarea
bind:this={textarea}
placeholder={placeholder || ''}
placeholder={placeholder || ""}
class="spectrum-Textfield-input"
{disabled}
{id}
on:focus={() => (focus = true)}
on:blur={onChange}>{value || ''}</textarea>
on:blur={onChange}
>{value || ""}</textarea>
</div>
<style>

View File

@ -37,6 +37,7 @@
}
focus = false
updateValue(event.target.value)
dispatch("blur")
}
const updateValueOnEnter = event => {
@ -53,12 +54,14 @@
class="spectrum-Textfield"
class:is-invalid={!!error}
class:is-disabled={disabled}
class:is-focused={focus}>
class:is-focused={focus}
>
{#if error}
<svg
class="spectrum-Icon spectrum-Icon--sizeM spectrum-Textfield-validationIcon"
focusable="false"
aria-hidden="true">
aria-hidden="true"
>
<use xlink:href="#spectrum-icon-18-Alert" />
</svg>
{/if}
@ -68,13 +71,14 @@
{disabled}
{readonly}
{id}
value={value || ''}
placeholder={placeholder || ''}
value={value || ""}
placeholder={placeholder || ""}
on:blur={onBlur}
on:focus={onFocus}
on:input
{type}
class="spectrum-Textfield-input" />
class="spectrum-Textfield-input"
/>
</div>
<style>

View File

@ -25,5 +25,6 @@
{value}
{placeholder}
{enableTime}
on:change={onChange} />
on:change={onChange}
/>
</Field>

View File

@ -11,6 +11,7 @@
export let fileSizeLimit = undefined
export let processFiles = undefined
export let handleFileTooLarge = undefined
export let gallery = true
const dispatch = createEventDispatcher()
const onChange = e => {
@ -27,5 +28,7 @@
{fileSizeLimit}
{processFiles}
{handleFileTooLarge}
on:change={onChange} />
{gallery}
on:change={onChange}
/>
</Field>

View File

@ -8,7 +8,7 @@
export let error = null
</script>
<div class="spectrum-Form-item" class:above={labelPosition === 'above'}>
<div class="spectrum-Form-item" class:above={labelPosition === "above"}>
{#if label}
<FieldLabel forId={id} {label} position={labelPosition} />
{/if}

View File

@ -10,8 +10,9 @@
<label
for={forId}
class={`spectrum-FieldLabel spectrum-FieldLabel--sizeM spectrum-Form-itemLabel ${className}`}>
{label || ''}
class={`spectrum-FieldLabel spectrum-FieldLabel--sizeM spectrum-Form-itemLabel ${className}`}
>
{label || ""}
</label>
<style>

View File

@ -29,5 +29,7 @@
{type}
on:change={onChange}
on:click
on:input />
on:input
on:blur
/>
</Field>

View File

@ -31,5 +31,6 @@
{getOptionLabel}
{getOptionValue}
on:change={onChange}
on:click />
on:click
/>
</Field>

View File

@ -33,5 +33,6 @@
{options}
{getOptionLabel}
{getOptionValue}
on:change={onChange} />
on:change={onChange}
/>
</Field>

View File

@ -23,5 +23,6 @@
{placeholder}
on:change={onChange}
on:click
on:input />
on:input
/>
</Field>

View File

@ -13,6 +13,7 @@
export let options = []
export let getOptionLabel = option => extractProperty(option, "label")
export let getOptionValue = option => extractProperty(option, "value")
export let quiet = false
const dispatch = createEventDispatcher()
const onChange = e => {
@ -29,6 +30,7 @@
<Field {label} {labelPosition} {error}>
<Select
{quiet}
{error}
{disabled}
{readonly}
@ -38,5 +40,6 @@
{getOptionLabel}
{getOptionValue}
on:change={onChange}
on:click />
on:click
/>
</Field>

View File

@ -25,5 +25,6 @@
{disabled}
{value}
{placeholder}
on:change={onChange} />
on:change={onChange}
/>
</Field>

View File

@ -1,14 +1,16 @@
<script>
import "@spectrum-css/fieldlabel/dist/index-vars.css"
export let size = "M"
</script>
<label
class={`spectrum-FieldLabel spectrum-FieldLabel--sizeL spectrum-Form-itemLabel`}>
<label class={`spectrum-FieldLabel spectrum-FieldLabel--size${size}`}>
<slot />
</label>
<style>
label {
padding: 0;
white-space: nowrap;
}
</style>

View File

@ -5,9 +5,11 @@
export let noPadding = false
export let gap = "M"
export let noGap = false
export let alignContent = "normal"
</script>
<div
style="align-content:{alignContent};"
class:horizontal
class="container paddingX-{!noPadding && paddingX} paddingY-{!noPadding &&
paddingY} gap-{!noGap && gap}"
@ -44,6 +46,9 @@
padding-top: var(--spacing-l);
padding-bottom: var(--spacing-l);
}
.gap-XS {
grid-gap: var(--spacing-s);
}
.gap-S {
grid-gap: var(--spectrum-alias-grid-gutter-xsmall);
}

View File

@ -0,0 +1,26 @@
<script>
export let wide = false
</script>
<div class:wide>
<slot />
</div>
<style>
div {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
max-width: 80ch;
margin: 0 auto;
padding: calc(var(--spacing-xl) * 2);
}
.wide {
max-width: none;
margin: 0;
padding: var(--spacing-xl) calc(var(--spacing-xl) * 2)
calc(var(--spacing-xl) * 2) calc(var(--spacing-xl) * 2);
}
</style>

View File

@ -17,4 +17,5 @@
class:spectrum-Link--secondary={secondary}
class:spectrum-Link--overBackground={overBackground}
class:spectrum-Link--quiet={quiet}
class="spectrum-Link spectrum-Link--size{size}"><slot /></a>
class="spectrum-Link spectrum-Link--size{size}"><slot /></a
>

View File

@ -27,7 +27,7 @@
>
{#if icon}
<svg
class="spectrum-Icon spectrum-Icon--sizeM spectrum-Menu-itemIcon"
class="spectrum-Icon spectrum-Icon--sizeS spectrum-Menu-itemIcon"
focusable="false"
aria-hidden="true"
aria-label={icon}
@ -37,3 +37,9 @@
{/if}
<span class="spectrum-Menu-itemLabel"><slot /></span>
</li>
<style>
.spectrum-Menu-itemIcon {
align-self: center;
}
</style>

View File

@ -1,6 +1,7 @@
<script>
export let heading
</script>
<li role="presentation">
<span class="spectrum-Menu-sectionHeading">{heading}</span>
<ul class="spectrum-Menu" role="group">

View File

@ -1 +1 @@
<li class="spectrum-Menu-divider" role="separator"></li>
<li class="spectrum-Menu-divider" role="separator" />

View File

@ -17,7 +17,7 @@
<div on:click={increment}>
Click me
{remaining}
more time{remaining === 1 ? '' : 's'}
more time{remaining === 1 ? "" : "s"}
to close this modal!
</div>

View File

@ -7,9 +7,10 @@
import Context from "../context"
export let fixed = false
export let inline = false
const dispatch = createEventDispatcher()
let visible = !!fixed
let visible = fixed || inline
$: dispatch(visible ? "show" : "hide")
export function show() {
@ -20,7 +21,7 @@
}
export function hide() {
if (!visible || fixed) {
if (!visible || fixed || inline) {
return
}
visible = false
@ -45,18 +46,26 @@
<svelte:window on:keydown={handleKey} />
{#if visible}
<!-- These svelte if statements need to be defined like this. -->
<!-- The modal transitions do not work if nested inside more than one "if" -->
{#if visible && inline}
<div use:focusFirstInput class="spectrum-Modal inline is-open">
<slot />
</div>
{:else if visible}
<Portal target=".modal-container">
<div
class="spectrum-Underlay is-open"
transition:fade={{ duration: 200 }}
on:mousedown|self={hide}>
transition:fade|local={{ duration: 200 }}
on:mousedown|self={hide}
>
<div class="modal-wrapper" on:mousedown|self={hide}>
<div class="modal-inner-wrapper" on:mousedown|self={hide}>
<div
use:focusFirstInput
class="spectrum-Modal is-open"
transition:fly={{ y: 30, duration: 200 }}>
transition:fly|local={{ y: 30, duration: 200 }}
>
<slot />
</div>
</div>
@ -96,6 +105,7 @@
}
.spectrum-Modal {
background: var(--background);
overflow: visible;
max-height: none;
margin: 40px 0;
@ -104,4 +114,7 @@
--spectrum-global-dimension-size-100
);
}
:global(.spectrum--lightest .spectrum-Modal.inline) {
border: var(--border-light);
}
</style>

View File

@ -7,7 +7,7 @@
import Context from "../context"
export let title = undefined
export let size = "small"
export let size = "S"
export let cancelText = "Cancel"
export let confirmText = "Confirm"
export let showCancelButton = true
@ -30,11 +30,16 @@
</script>
<div
class="spectrum-Dialog spectrum-Dialog--{size}"
class="spectrum-Dialog"
class:spectrum-Dialog--small={size === "S"}
class:spectrum-Dialog--medium={size === "M"}
class:spectrum-Dialog--large={size === "L"}
class:spectrum-Dialog--extraLarge={size === "XL"}
style="position: relative;"
role="dialog"
tabindex="-1"
aria-modal="true">
aria-modal="true"
>
<div class="spectrum-Dialog-grid">
<h1 class="spectrum-Dialog-heading spectrum-Dialog-heading--noHeader">
{title}
@ -47,7 +52,8 @@
</section>
{#if showCancelButton || showConfirmButton}
<div
class="spectrum-ButtonGroup spectrum-Dialog-buttonGroup spectrum-Dialog-buttonGroup--noFooter">
class="spectrum-ButtonGroup spectrum-Dialog-buttonGroup spectrum-Dialog-buttonGroup--noFooter"
>
<slot name="footer" />
{#if showCancelButton}
<Button group secondary on:click={hide}>{cancelText}</Button>
@ -58,7 +64,8 @@
cta
{...$$restProps}
disabled={confirmDisabled}
on:click={confirm}>
on:click={confirm}
>
{confirmText}
</Button>
{/if}
@ -73,6 +80,10 @@
</div>
<style>
.spectrum-Dialog--extraLarge {
width: 1000px;
}
.content-grid {
display: grid;
position: relative;

View File

@ -31,7 +31,8 @@
bind:this={modal}
confirmText="Submit"
onConfirm={answerQuiz}
on:show={resetState}>
on:show={resetState}
>
{#if error}
<p class="error">Wrong answer! Try again.</p>
{/if}

View File

@ -3,15 +3,23 @@
import Portal from "svelte-portal"
import { flip } from "svelte/animate"
import { fly } from "svelte/transition"
import { notifications } from '../Stores/notifications'
import { notifications } from "../Stores/notifications"
</script>
<Portal target=".modal-container">
<div class="notifications">
{#each $notifications as { type, icon, message, id } (id)}
<div animate:flip transition:fly={{ y: -30 }} class="spectrum-Toast spectrum-Toast--{type} notification-offset">
<div
animate:flip
transition:fly={{ y: -30 }}
class="spectrum-Toast spectrum-Toast--{type} notification-offset"
>
{#if icon}
<svg class="spectrum-Icon spectrum-Icon--sizeM spectrum-Toast-typeIcon" focusable="false" aria-hidden="true">
<svg
class="spectrum-Icon spectrum-Icon--sizeM spectrum-Toast-typeIcon"
focusable="false"
aria-hidden="true"
>
<use xlink:href="#spectrum-icon-18-{icon}" />
</svg>
{/if}
@ -22,6 +30,7 @@
{/each}
</div>
</Portal>
<style>
.notifications {
position: fixed;
@ -41,4 +50,3 @@
margin-bottom: 10px;
}
</style>

View File

@ -37,7 +37,8 @@
use:clickOutside={hide}
on:keydown={handleEscape}
class="spectrum-Popover is-open"
role="presentation">
role="presentation"
>
<slot />
</div>
</Portal>

View File

@ -33,22 +33,14 @@
>
{#if $$slots}
<div
class:spectrum-FieldLabel--sizeS={s}
class:spectrum-FieldLabel--sizeM={m}
class:spectrum-FieldLabel--sizeL={l}
class:spectrum-FieldLabel--sizeXL={xl}
class="spectrum-FieldLabel spectrum-ProgressBar-label"
class="spectrum-FieldLabel spectrum-ProgressBar-label spectrum-FieldLabel--size{size}"
>
<slot />
</div>
{/if}
{#if value}
<div
class:spectrum-FieldLabel--sizeS={s}
class:spectrum-FieldLabel--sizeM={m}
class:spectrum-FieldLabel--sizeL={l}
class:spectrum-FieldLabel--sizeXL={xl}
class="spectrum-FieldLabel spectrum-ProgressBar-percentage"
class="spectrum-FieldLabel spectrum-ProgressBar-percentage spectrum-FieldLabel--size{size}"
>
{Math.round($progress)}%
</div>

View File

@ -1,29 +1,62 @@
<script>
// WIP! Does not yet work.
import "@spectrum-css/progresscircle/dist/index-vars.css"
import { tweened } from 'svelte/motion';
import { cubicOut } from 'svelte/easing';
export let size = "M"
function convertSize(size) {
switch (size) {
case "S":
return "small"
case "L":
return "large"
default:
return
}
}
export let value = false
export let small;
export let large;
export let minValue = 0
export let maxValue = 100
export let overBackground;
let subMask1Style
let subMask2Style
$: calculateSubMasks(value)
function calculateSubMasks(value) {
if (value) {
let percentage = ((value - minValue) / (maxValue - minValue)) * 100
let angle
if (percentage > 0 && percentage <= 50) {
angle = -180 + (percentage / 50) * 180
subMask1Style = `transform: rotate(${angle}deg);`
subMask2Style = "transform: rotate(-180deg);"
} else if (percentage > 50) {
angle = -180 + ((percentage - 50) / 50) * 180
subMask1Style = "transform: rotate(0deg);"
subMask2Style = `transform: rotate(${angle}deg);`
}
}
}
export let overBackground
</script>
<div class:spectrum-ProgressBar--indeterminate={!value} class:spectrum-ProgressCircle--small={small} class:spectrum-ProgressCircle--large={large} class="spectrum-ProgressCircle">
<div class="spectrum-ProgressCircle-track"></div>
<div
on:click
class:spectrum-ProgressBar--indeterminate={!value}
class:spectrum-ProgressCircle--overBackground={overBackground}
class="spectrum-ProgressCircle spectrum-ProgressCircle--{convertSize(size)}"
>
<div class="spectrum-ProgressCircle-track" />
<div class="spectrum-ProgressCircle-fills">
<div class="spectrum-ProgressCircle-fillMask1">
<div class="spectrum-ProgressCircle-fillSubMask1">
<div class="spectrum-ProgressCircle-fill"></div>
<div class="spectrum-ProgressCircle-fillSubMask1" style={subMask1Style}>
<div class="spectrum-ProgressCircle-fill" />
</div>
</div>
<div class="spectrum-ProgressCircle-fillMask2">
<div class="spectrum-ProgressCircle-fillSubMask2">
<div class="spectrum-ProgressCircle-fill"></div>
<div class="spectrum-ProgressCircle-fillSubMask2" style={subMask2Style}>
<div class="spectrum-ProgressCircle-fill" />
</div>
</div>
</div>
</div>

View File

@ -1,22 +1,36 @@
<script>
import { getContext } from 'svelte'
const multilevel = getContext('sidenav-type');
export let href = "";
export let external = false;
import { getContext } from "svelte"
const multilevel = getContext("sidenav-type")
export let href = ""
export let external = false
export let heading = ""
export let icon = "";
export let selected = false;
export let disabled = false;
export let icon = ""
export let selected = false
export let disabled = false
</script>
<li class="spectrum-SideNav-item" class:is-selected={selected} class:is-disabled={disabled}>
<li
class="spectrum-SideNav-item"
class:is-selected={selected}
class:is-disabled={disabled}
>
{#if heading}
<h2 class="spectrum-SideNav-heading" id="nav-heading-{heading}">{heading}</h2>
<h2 class="spectrum-SideNav-heading" id="nav-heading-{heading}">
{heading}
</h2>
{/if}
<a target={external ? '_blank' : '_self'} {href} class="spectrum-SideNav-itemLink" aria-current="page">
<a
target={external ? "_blank" : ""}
{href}
class="spectrum-SideNav-itemLink"
aria-current="page"
>
{#if icon}
<svg class="spectrum-Icon spectrum-Icon--sizeM spectrum-SideNav-itemIcon" focusable="false" aria-hidden="true">
<svg
class="spectrum-Icon spectrum-Icon--sizeM spectrum-SideNav-itemIcon"
focusable="false"
aria-hidden="true"
>
<use xlink:href="#spectrum-icon-18-{icon}" />
</svg>
{/if}

View File

@ -1,8 +1,8 @@
<script>
import { setContext } from 'svelte'
import { setContext } from "svelte"
import "@spectrum-css/sidenav/dist/index-vars.css"
export let multilevel = false;
setContext('sidenav-type', multilevel)
export let multilevel = false
setContext("sidenav-type", multilevel)
</script>
<nav>

View File

@ -50,12 +50,7 @@ export const createNotificationStore = () => {
}
function id() {
return (
"_" +
Math.random()
.toString(36)
.substr(2, 9)
)
return "_" + Math.random().toString(36).substr(2, 9)
}
export const notifications = createNotificationStore()

View File

@ -1,72 +0,0 @@
<script>
export let extraSmall = false,
small = false,
medium = false,
large = false,
extraLarge = false,
white = false,
grey = false,
black = false,
lh = false
</script>
<p
class="bb-body"
class:extraSmall
class:small
class:medium
class:large
class:extraLarge
class:white
class:grey
class:black
class:lh>
<slot />
</p>
<style>
.bb-body {
font-family: var(--font-sans);
font-weight: 400;
text-rendering: var(--text-render);
color: var(--ink);
font-size: var(--font-size-m);
line-height: 0;
}
.extraSmall {
font-size: var(--font-size-xs);
}
.small {
font-size: var(--font-size-s);
}
.medium {
font-size: var(--font-size-m);
}
.large {
font-size: var(--font-size-l);
}
.extraLarge {
font-size: var(--font-size-xl);
}
.white {
color: white;
}
.grey {
color: var(--grey-6);
}
.black {
color: var(--ink);
}
.lh {
line-height: 1.75;
}
</style>

View File

@ -1,175 +0,0 @@
<script>
import { View } from "svench";
</script>
<h1>Borders</h1>
<p>Budibase has 2 border variables, light and dark.</p>
<p><strong>Light</strong> is for layouts.</p>
<p><strong>Dark</strong> is for components.</p>
<code>border: var(--border-light);</code>
<div class="border-light"></div>
<code>border: var(--border-dark);</code>
<div class="border-dark"></div>
<hr>
<h2>Border Radius</h2>
<p>Budibase has 5 border-radius variables:</p>
<table>
<tr>
<th>Name</th>
<th>Var</th>
<th>rem</th>
<th>px</th>
<th>Visual</th>
</tr>
<tr>
<td>Extra Small</td>
<td>var(--border-radius-xs)</td>
<td>0.125</td>
<td>2</td>
<td><div class="border-radius-xs">Extra small</div></td>
</tr>
<tr>
<td>Small</td>
<td>var(--border-radius-s)</td>
<td>0.35</td>
<td>5.6px</td>
<td><div class="border-radius-s">Small</div></td>
</tr>
<tr>
<td>Medium</td>
<td>var(--border-radius-m)</td>
<td>0.5</td>
<td>8</td>
<td><div class="border-radius-m">Medium</div></td>
</tr>
<tr>
<td>Large</td>
<td>var(--border-radius-l)</td>
<td>1</td>
<td>16</td>
<td><div class="border-radius-l">Large</div></td>
</tr>
<tr>
<td>Extra Large</td>
<td>var(--border-radius-xl)</td>
<td>100</td>
<td>1600</td>
<td><div class="border-radius-xl">Extra large</div></td>
</tr>
</table>
<style>
table {
font-family: var(--font-sans);
border-collapse: collapse;
width: 100%;
}
code {
display: inline-block;
box-sizing: border-box;
padding: var(--spacing-s) var(--spacing-l);
margin: var(--spacing-xl) 0 var(--spacing-s) 0;
background-color: var(--grey-2);
color: var(--red-dark);
border-radius: var(--spacing-xs);
}
td, th {
border: 1px solid var(--grey-4);
text-align: left;
padding: var(--spacing-m);
width: 20%;
}
tr:nth-child(even) {
background-color: var(--grey-3);
}
hr {
margin: var(--layout-xl) 0px;
}
.border-light {
width: 40rem;
height: 10rem;
background-color: white;
border: var(--border-light);
}
.border-dark {
width: 40rem;
height: 10rem;
background-color: white;
border: var(--border-dark);
}
.border-radius-xs {
display: flex;
align-items: center;
justify-content: center;
color: white;
font-family: var(--font-sans);
width: 8rem;
height: 3rem;
background-color: black;
border-radius: var(--border-radius-xs);
margin-right: var(--spacing-l);
}
.border-radius-s {
display: flex;
align-items: center;
justify-content: center;
color: white;
font-family: var(--font-sans);
width: 8rem;
height: 3rem;
background-color: black;
border-radius: var(--border-radius-s);
margin-right: var(--spacing-l);
}
.border-radius-m {
display: flex;
align-items: center;
justify-content: center;
color: white;
font-family: var(--font-sans);
width: 8rem;
height: 3rem;
background-color: black;
border-radius: var(--border-radius-m);
margin-right: var(--spacing-l);
}
.border-radius-l {
display: flex;
align-items: center;
justify-content: center;
color: white;
font-family: var(--font-sans);
width: 8rem;
height: 3rem;
background-color: black;
border-radius: var(--border-radius-l);
margin-right: var(--spacing-l);
}
.border-radius-xl {
display: flex;
align-items: center;
justify-content: center;
color: white;
font-family: var(--font-sans);
width: 8rem;
height: 3rem;
background-color: black;
border-radius: var(--border-radius-xl);
margin-right: var(--spacing-l);
}
</style>

View File

@ -1,68 +0,0 @@
<svg
version="1.1"
id="Layer_1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px"
y="0px"
viewBox="0 0 115 40"
style="enable-background:new 0 0 115 40;"
xml:space="preserve">
<path
class="st0"
d="M111.16,40H3.91c-2.15,0-3.89-1.74-3.89-3.89V4.04c0-2.15,1.74-3.89,3.89-3.89h107.25
c2.15,0,3.89,1.74,3.89,3.89v32.07C115.05,38.26,113.31,40,111.16,40z" />
<path
class="st1"
d="M10.37,10.03v8.57c0.93-1.26,2.33-1.67,3.61-1.67c1.58,0,3.01,0.59,4.02,1.54c1.12,1.05,1.82,2.62,1.82,4.53
c0,1.78-0.62,3.42-1.82,4.61c-1.01,1.03-2.26,1.57-3.97,1.57c-2.05,0-3.09-0.95-3.66-1.78v1.39H6.63V10.03H10.37z
M10.97,20.98
c-0.44,0.46-0.82,1.13-0.82,2.11c0,0.95,0.41,1.64,0.85,2.05c0.59,0.57,1.41,0.85,2.11,0.85c0.64,0,1.36-0.26,1.93-0.8
c0.54-0.51,0.9-1.26,0.9-2.11c0-0.92-0.36-1.67-0.9-2.18c-0.59-0.57-1.23-0.77-1.98-0.77C12.26,20.14,11.56,20.37,10.97,20.98z" />
<path
class="st1"
d="M25.08,17.35v6.32c0,0.51,0.05,1.31,0.64,1.85c0.26,0.23,0.72,0.54,1.54,0.54c0.69,0,1.23-0.23,1.56-0.54
c0.54-0.51,0.62-1.28,0.62-1.85v-6.32h3.74v6.68c0,1.31-0.13,2.54-1.28,3.67c-1.31,1.28-3.23,1.49-4.59,1.49
c-1.41,0-3.31-0.21-4.62-1.49c-1.05-1.03-1.26-2.18-1.26-3.44v-6.91H25.08z" />
<path
class="st1"
d="M47.88,28.79h-3.74V27.4c-0.57,0.82-1.61,1.78-3.66,1.78c-1.71,0-2.96-0.54-3.97-1.57
c-1.19-1.18-1.82-2.83-1.82-4.61c0-1.9,0.7-3.47,1.82-4.53c1.01-0.95,2.44-1.54,4.02-1.54c1.27,0,2.67,0.41,3.61,1.67v-8.57h3.74
V28.79z
M39.53,20.91c-0.54,0.51-0.9,1.26-0.9,2.18c0,0.85,0.36,1.59,0.9,2.11c0.57,0.54,1.28,0.8,1.93,0.8
c0.69,0,1.52-0.28,2.11-0.85c0.44-0.41,0.85-1.1,0.85-2.05c0-0.98-0.39-1.64-0.82-2.11c-0.59-0.62-1.28-0.85-2.08-0.85
C40.77,20.14,40.12,20.34,39.53,20.91z" />
<path
class="st1"
d="M52.32,10.3c1.21,0,2.16,0.95,2.16,2.16c0,1.21-0.95,2.16-2.16,2.16c-1.21,0-2.16-0.95-2.16-2.16
C50.17,11.25,51.12,10.3,52.32,10.3z M54.19,17.35v11.44h-3.74V17.35H54.19z" />
<path
class="st1"
d="M60.49,10.03v8.57c0.93-1.26,2.33-1.67,3.61-1.67c1.58,0,3.01,0.59,4.02,1.54c1.12,1.05,1.82,2.62,1.82,4.53
c0,1.78-0.62,3.42-1.82,4.61c-1.01,1.03-2.26,1.57-3.97,1.57c-2.05,0-3.09-0.95-3.66-1.78v1.39h-3.74V10.03H60.49z
M61.06,20.98
c-0.44,0.46-0.82,1.13-0.82,2.11c0,0.95,0.41,1.64,0.85,2.05c0.59,0.57,1.41,0.85,2.11,0.85c0.64,0,1.36-0.26,1.93-0.8
c0.54-0.51,0.9-1.26,0.9-2.11c0-0.92-0.36-1.67-0.9-2.18c-0.59-0.57-1.23-0.77-1.98-0.77C62.34,20.14,61.65,20.37,61.06,20.98z" />
<path
class="st1"
d="M80.26,17.35H84v11.44h-3.74v-1.39c-1.01,1.54-2.46,1.77-3.42,1.77c-1.66,0-3.06-0.41-4.33-1.74
c-1.22-1.28-1.69-2.77-1.69-4.28c0-1.92,0.73-3.57,1.79-4.62c1.01-1,2.41-1.56,4.02-1.56c0.99,0,2.57,0.23,3.63,1.67V17.35z
M75.57,20.96c-0.39,0.39-0.85,1.05-0.85,2.08c0,1.03,0.44,1.7,0.77,2.05c0.51,0.54,1.31,0.9,2.18,0.9c0.74,0,1.44-0.31,1.93-0.8
c0.49-0.46,0.9-1.18,0.9-2.16c0-0.82-0.31-1.59-0.85-2.11c-0.57-0.54-1.39-0.8-2.05-0.8C76.8,20.14,76.06,20.47,75.57,20.96z" />
<path
class="st1"
d="M93.21,20.26c-0.57-0.33-1.31-0.64-2.03-0.64c-0.39,0-0.82,0.1-1.05,0.33c-0.13,0.13-0.23,0.33-0.23,0.51
c0,0.26,0.18,0.41,0.36,0.51c0.26,0.15,0.64,0.23,1.1,0.39l0.98,0.31c0.64,0.21,1.31,0.46,1.9,1c0.67,0.62,0.9,1.31,0.9,2.18
c0,1.52-0.67,2.49-1.18,3.01c-1.13,1.13-2.52,1.31-3.72,1.31c-1.54,0-3.21-0.33-4.7-1.64l1.57-2.49c0.36,0.31,0.87,0.67,1.26,0.85
c0.51,0.26,1.05,0.36,1.54,0.36c0.23,0,0.82,0,1.16-0.26c0.23-0.18,0.39-0.46,0.39-0.74c0-0.21-0.08-0.46-0.41-0.67
c-0.26-0.15-0.59-0.26-1.13-0.41l-0.92-0.28c-0.67-0.21-1.36-0.57-1.85-1.05c-0.54-0.57-0.82-1.21-0.82-2.08
c0-1.1,0.44-2.03,1.1-2.65c1.03-0.95,2.41-1.16,3.47-1.16c1.7,0,2.88,0.44,3.8,0.98L93.21,20.26z" />
<path
class="st1"
d="M108.43,23.73h-8.55c0,0.62,0.23,1.44,0.69,1.95c0.57,0.62,1.34,0.72,1.9,0.72c0.54,0,1.1-0.1,1.49-0.33
c0.05-0.03,0.49-0.31,0.8-0.95l3.49,0.36c-0.51,1.62-1.54,2.47-2.21,2.88c-1.1,0.67-2.34,0.85-3.62,0.85
c-1.72,0-3.24-0.31-4.57-1.64c-1-1-1.72-2.52-1.72-4.42c0-1.64,0.59-3.34,1.75-4.52c1.39-1.39,3.11-1.64,4.39-1.64
c1.28,0,3.13,0.23,4.55,1.72c1.36,1.44,1.62,3.24,1.62,4.65V23.73z
M105.03,21.48c-0.03-0.1-0.21-0.82-0.74-1.34
c-0.41-0.39-1-0.64-1.75-0.64c-0.95,0-1.52,0.39-1.87,0.74c-0.28,0.31-0.54,0.72-0.64,1.23H105.03z" />
</svg>

Before

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -1,201 +0,0 @@
<script>
import { View } from "svench";
</script>
<View name="Base">
<div class="white">white</div>
<div class="grey1">var(--grey-1)</div>
<div class="grey2">var(--grey-2)</div>
<div class="grey3">var(--grey-3)</div>
<div class="grey4">var(--grey-4)</div>
<div class="grey5">var(--grey-5)</div>
<div class="grey6">var(--grey-6)</div>
<div class="grey7">var(--grey-7)</div>
<div class="grey8">var(--grey-8)</div>
<div class="grey9">var(--grey-9)</div>
<div class="ink">var(--ink)</div>
</View>
<View name="Blue">
<div class="blue-light">var(--blue-light)</div>
<div class="blue">var(--blue)</div>
<div class="blue-dark">var(--blue-dark)</div>
</View>
<View name="Yellow">
<div class="yellow-light">var(--yellow-light)</div>
<div class="yellow">var(--yellow)</div>
<div class="yellow-dark">var(--yellow-dark)</div>
</View>
<View name="Red">
<div class="red-light">var(--red-light)</div>
<div class="red">var(--red)</div>
<div class="red-dark">var(--red-dark)</div>
</View>
<View name="Orange">
<div class="orange-light">var(--orange-light)</div>
<div class="orange">var(--orange)</div>
<div class="orange-dark">var(--orange-dark)</div>
</View>
<View name="Green">
<div class="green-light">var(--green-light)</div>
<div class="green">var(--green)</div>
<div class="green-dark">var(--green-dark)</div>
</View>
<View name="Purple">
<div class="purple-light">var(--purple-light)</div>
<div class="purple">var(--purple)</div>
<div class="purple-dark">var(--purple-dark)</div>
</View>
<style>
div {
height: 100px;
width: 800px;
display: flex;
align-items: center;
justify-content: center;
font-family: var(--font-sans);
}
.white {
background-color: white;
}
.grey1 {
background-color: var(--grey-1);
}
.grey1 {
background-color: var(--grey-1);
}
.grey2 {
background-color: var(--grey-2);
}
.grey3 {
background-color: var(--grey-3);
}
.grey4 {
background-color: var(--grey-4);
}
.grey5 {
background-color: var(--grey-5);
}
.grey6 {
background-color: var(--grey-6);
}
.grey7 {
background-color: var(--grey-7);
color: white;
}
.grey8 {
background-color: var(--grey-8);
color: white;
}
.grey9 {
background-color: var(--grey-9);
color: white;
}
.ink {
background-color: var(--ink);
color: white;
}
.blue {
background-color: var(--blue);
color: white;
}
.blue-light {
background-color: var(--blue-light);
}
.blue-dark {
background-color: var(--blue-dark);
color: white;
}
.yellow {
background-color: var(--yellow);
color: white;
}
.yellow-light {
background-color: var(--yellow-light);
}
.yellow-dark {
background-color: var(--yellow-dark);
color: white;
}
.red {
background-color: var(--red);
color: white;
}
.red-light {
background-color: var(--red-light);
}
.red-dark {
background-color: var(--red-dark);
color: white;
}
.orange {
background-color: var(--orange);
color: white;
}
.orange-light {
background-color: var(--orange-light);
}
.orange-dark {
background-color: var(--orange-dark);
color: white;
}
.green {
background-color: var(--green);
color: white;
}
.green-light {
background-color: var(--green-light);
}
.green-dark {
background-color: var(--green-dark);
color: white;
}
.purple {
background-color: var(--purple);
color: white;
}
.purple-light {
background-color: var(--purple-light);
}
.purple-dark {
background-color: var(--purple-dark);
color: white;
}
</style>

View File

@ -1,74 +0,0 @@
<script>
export let extraSmall = false,
small = false,
medium = false,
large = false,
extraLarge = false,
white = false,
grey = false,
black = false,
lh = false
</script>
<h1
class="bb-heading"
class:extraSmall
class:small
class:medium
class:large
class:extraLarge
class:white
class:grey
class:black
class:lh>
<slot />
</h1>
<style>
.bb-heading {
font-family: var(--font-sans);
font-weight: 600;
text-rendering: var(--text-render);
color: var(--ink);
font-size: var(--heading-font-size-m);
line-height: 0;
}
.extraSmall {
font-size: var(--heading-font-size-xs);
}
.small {
font-size: var(--heading-font-size-s);
}
.medium {
font-size: var(--heading-font-size-m);
}
.large {
font-size: var(--heading-font-size-l);
font-weight: 700;
}
.extraLarge {
font-size: var(--heading-font-size-xl);
font-weight: 700;
}
.white {
color: white;
}
.grey {
color: var(--grey-6);
}
.black {
color: var(--ink);
}
.lh {
line-height: 1.3;
}
</style>

View File

@ -1,16 +0,0 @@
<script>
import BudibaseLogo from "./Budibase-logo.svelte"
</script>
<div class="home-logo">
<BudibaseLogo />
</div>
<style>
.home-logo {
cursor: pointer;
height: 40px;
width: 100px;
margin-bottom: 20px;
}
</style>

View File

@ -1,8 +0,0 @@
<script>
import { View } from "svench";
import Logo from "./Logo.svelte";
</script>
<View name="default">
<Logo />
</View>

View File

@ -1,191 +0,0 @@
<script>
import { View } from "svench";
</script>
<h1>2 Scales, 1 Spatial System</h1>
<p>Budibase has 2 scales, spacing and layout, and follows a 4pt grid system.</p>
<p><strong>Spacing</strong> is used for smaller, more refined spacing needs, specifically within the context of a component.</p>
<p><strong>Layout</strong> is used for laying out your page and positing components within that page.</p>
<p>Spacing and layout will mostly exist alongside margin and padding.</p>
<p><code>margin: var(--spacing-s);</code></p>
<code>padding: var(--spacing-s) var(--spacing-l);</code>
<hr>
<h2>Spacing</h2>
<table>
<tr>
<th>Var</th>
<th>rem</th>
<th>px</th>
<th>Visual</th>
</tr>
<tr>
<td>var(--spacing-xs)</td>
<td>0.25</td>
<td>4</td>
<td><div class="xs"></div></td>
</tr>
<tr>
<td>var(--spacing-s)</td>
<td>0.5</td>
<td>8</td>
<td><div class="s"></div></td>
</tr>
<tr>
<td>var(--spacing-m)</td>
<td>.75</td>
<td>12</td>
<td><div class="m"></div></td>
</tr>
<tr>
<td>var(--spacing-l)</td>
<td>1</td>
<td>16</td>
<td><div class="l"></div></td>
</tr>
<tr>
<td>var(--spacing-xl)</td>
<td>1.25</td>
<td>20</td>
<td><div class="xl"></div></td>
</tr>
</table>
<hr>
<h2>Layout</h2>
<table>
<tr>
<th>Var</th>
<th>rem</th>
<th>px</th>
<th>Visual</th>
</tr>
<tr>
<td>var(--layout-xs)</td>
<td>1.25</td>
<td>20</td>
<td><div class="layout-xs"></div></td>
</tr>
<tr>
<td>var(--layout-s)</td>
<td>1.5</td>
<td>24</td>
<td><div class="layout-s"></div></td>
</tr>
<tr>
<td>var(--layout-m)</td>
<td>2</td>
<td>32</td>
<td><div class="layout-m"></div></td>
</tr>
<tr>
<td>var(--layout-l)</td>
<td>3</td>
<td>48</td>
<td><div class="layout-l"></div></td>
</tr>
<tr>
<td>var(--layout-xl)</td>
<td>4</td>
<td>64</td>
<td><div class="layout-xl"></div></td>
</tr>
</table>
<style>
table {
font-family: var(--font-sans);
border-collapse: collapse;
width: 100%;
}
code {
display: inline-block;
box-sizing: border-box;
padding: var(--spacing-s) var(--spacing-l);
margin: var(--spacing-s) 0;
background-color: var(--grey-2);
color: var(--red-dark);
border-radius: var(--spacing-xs);
}
td, th {
border: 1px solid var(--grey-4);
text-align: left;
padding: var(--spacing-m);
width: 25%;
}
tr:nth-child(even) {
background-color: var(--grey-3);
}
hr {
margin: var(--layout-xl) 0px;
}
.xs {
height: var(--spacing-xs);
width: var(--spacing-xs);
background-color: var(--purple);
}
.s {
height: var(--spacing-s);
width: var(--spacing-s);
background-color: var(--purple);
}
.m {
height: var(--spacing-m);
width: var(--spacing-m);
background-color: var(--purple);
}
.l {
height: var(--spacing-l);
width: var(--spacing-l);
background-color: var(--purple);
}
.xl {
height: var(--spacing-xl);
width: var(--spacing-xl);
background-color: var(--purple);
}
.layout-xs {
height: var(--layout-xs);
width: var(--layout-xs);
background-color: var(--blue);
}
.layout-s {
height: var(--layout-s);
width: var(--layout-s);
background-color: var(--blue);
}
.layout-m {
height: var(--layout-m);
width: var(--layout-m);
background-color: var(--blue);
}
.layout-l {
height: var(--layout-l);
width: var(--layout-l);
background-color: var(--blue);
}
.layout-xl {
height: var(--layout-xl);
width: var(--layout-xl);
background-color: var(--blue);
}
</style>

View File

@ -1,227 +0,0 @@
<script>
import { View } from "svench";
import Heading from "./Heading.svelte";
import Body from "./Body.svelte";
import Label from "./Label.svelte";
const username = "bbuserexample";
</script>
<style>
.typography-container {
display: grid;
grid-template-columns: 1fr 1fr;
}
.typography-column {
padding: var(--spacing-l);
}
.typography-column--grey {
background-color: var(--grey-5);
}
.label-wrapper {
padding: var(--spacing-m);
}
</style>
<h1 class="bb-heading bb-heading--large bb-heading--heavy">Typography</h1>
<p>
Budibase uses Inter as its typeface and three types can be used,
<strong>heading</strong>
,
<strong>body</strong>
and
<strong>label</strong>
</p>
<p>Each type has 5 sizes, xs, s, m l and xl.</p>
<p>Each type is available in the colors white, grey or black.</p>
<View name="Heading">
<div class="typography-container">
<div class="typography-column">
<Heading extraLarge black>Heading Extra Large Black</Heading>
<Heading large black>Heading Large Black</Heading>
<Heading medium black>Heading Medium Black</Heading>
<Heading small black>Heading Small Black</Heading>
<Heading extraSmall black>Heading Extra Small Black</Heading>
</div>
<div class="typography-column">
<Heading extraLarge grey>Heading Extra Large Grey</Heading>
<Heading large grey>Heading Large Grey</Heading>
<Heading medium grey>Heading Medium Grey</Heading>
<Heading small grey>Heading Small Grey</Heading>
<Heading extraSmall grey>Heading Extra Small Grey</Heading>
</div>
<div class="typography-column typography-column--grey">
<Heading extraLarge white>Heading Extra Large White</Heading>
<Heading large white>Heading Large White</Heading>
<Heading medium white>Heading Medium White</Heading>
<Heading small white>Heading Small White</Heading>
<Heading extraSmall white>Heading Extra Small White</Heading>
</div>
<div class="typography-column">
<Heading extraLarge black lh>Heading Extra Large Black Line Height</Heading>
<Heading large black lh>Heading Large Black Line Height</Heading>
<Heading medium black lh>Heading Medium Black Line Height</Heading>
<Heading small black lh>Heading Small Black Line Height</Heading>
<Heading extraSmall black lh>Heading Extra Small Black Line Height</Heading>
</div>
</div>
</View>
<View name="Body">
<div class="typography-container">
<div class="typography-column">
<Body extraLarge black>Body Extra Large Black</Body>
<Body large black>Body Large Black</Body>
<Body medium black>Body Medium Black</Body>
<Body small black>Body Small Black</Body>
<Body extraSmall black>Body Extra Small Black</Body>
</div>
<div class="typography-column">
<Body extraLarge lh black>Body Extra Large Black Line-height</Body>
<Body large lh black>Body Large Black Line-height</Body>
<Body medium lh black>Body Medium Black Line-height</Body>
<Body small lh black>Body Small Black Line-height</Body>
<Body extraSmall lh black>Body Extra Small Black Line-height</Body>
</div>
<div class="typography-column">
<Body extraLarge grey>Body Extra Large Grey</Body>
<Body large grey>Body Large Grey</Body>
<Body medium grey>Body Medium Grey</Body>
<Body small grey>Body Small Grey</Body>
<Body extraSmall grey>Body Extra Small Grey</Body>
</div>
<div class="typography-column typography-column--grey">
<Body extraLarge white>Body Extra Large White</Body>
<Body large white>Body Large White</Body>
<Body medium white>Body Medium White</Body>
<Body small white>Body Small White</Body>
<Body extraSmall white>Body Extra Small White</Body>
</div>
</div>
</View>
<View name="Label">
<div class="typography-container">
<div class="label-wrapper">
<Label extraLarge black forAttr={username}>Label Extra Large Black</Label>
<input type="text" />
</div>
<div class="label-wrapper">
<Label large black forAttr={username}>Label Large Black</Label>
<input type="text" />
</div>
<div class="label-wrapper">
<Label medium black forAttr={username}>Label Medium Black</Label>
<input type="text" />
</div>
<div class="label-wrapper">
<Label small black forAttr={username}>Label Small Black</Label>
<input type="text" />
</div>
<div class="label-wrapper">
<Label extraSmall black forAttr={username}>Label Extra Small Black</Label>
<input type="text" />
</div>
</div>
<div class="typography-container">
<div class="label-wrapper">
<Label extraLarge grey forAttr={username}>Label Extra Large Grey</Label>
<input type="text" />
</div>
<div class="label-wrapper">
<Label large grey forAttr={username}>Label Large Grey</Label>
<input type="text" />
</div>
<div class="label-wrapper">
<Label medium grey forAttr={username}>Label Medium Grey</Label>
<input type="text" />
</div>
<div class="label-wrapper">
<Label small grey forAttr={username}>Label Small Grey</Label>
<input type="text" />
</div>
<div class="label-wrapper">
<Label extraSmall grey forAttr={username}>Label Extra Small Grey</Label>
<input type="text" />
</div>
</div>
<div class="typography-container typography-column--grey">
<div class="label-wrapper">
<Label extraLarge white forAttr={username}>Label Extra Large White</Label>
<input type="text" />
</div>
<div class="label-wrapper">
<Label large white forAttr={username}>Label Large White</Label>
<input type="text" />
</div>
<div class="label-wrapper">
<Label medium white forAttr={username}>Label Medium White</Label>
<input type="text" />
</div>
<div class="label-wrapper">
<Label small white forAttr={username}>Label Small White</Label>
<input type="text" />
</div>
<div class="label-wrapper">
<Label extraSmall white forAttr={username}>Label Extra Small White</Label>
<input type="text" />
</div>
</div>
</View>

View File

@ -5,24 +5,28 @@
</script>
<label
class="spectrum-Checkbox spectrum-Checkbox--sizeM spectrum-Checkbox--emphasized">
class="spectrum-Checkbox spectrum-Checkbox--sizeM spectrum-Checkbox--emphasized"
>
<input
type="checkbox"
class="spectrum-Checkbox-input"
id="checkbox-1"
disabled
checked={!!value} />
checked={!!value}
/>
<span class="spectrum-Checkbox-box">
<svg
class="spectrum-Icon spectrum-UIIcon-Checkmark100 spectrum-Checkbox-checkmark"
focusable="false"
aria-hidden="true">
aria-hidden="true"
>
<use xlink:href="#spectrum-css-icon-Checkmark100" />
</svg>
<svg
class="spectrum-Icon spectrum-UIIcon-Dash100 spectrum-Checkbox-partialCheckmark"
focusable="false"
aria-hidden="true">
aria-hidden="true"
>
<use xlink:href="#spectrum-css-icon-Dash100" />
</svg>
</span>

View File

@ -23,10 +23,10 @@
}
$: type = schema?.type ?? "string"
$: customRenderer = customRenderers?.find(x => x.column === schema?.name)
$: renderer = customRenderer?.component ?? typeMap[type]
$: renderer = customRenderer?.component ?? typeMap[type] ?? StringRenderer
</script>
{#if renderer && (customRenderer || (value != null && value !== ''))}
{#if renderer && (customRenderer || (value != null && value !== ""))}
<svelte:component this={renderer} {row} {schema} {value} on:clickrelationship>
<slot />
</svelte:component>

View File

@ -4,7 +4,7 @@
export let value
</script>
<div>{dayjs(value).format('MMMM D YYYY, HH:mm')}</div>
<div>{dayjs(value).format("MMMM D YYYY, HH:mm")}</div>
<style>
div {

View File

@ -31,6 +31,7 @@
// Table state
let height = 0
let loaded = false
$: schema = fixSchema(schema)
$: if (!loading) loaded = true
$: rows = data ?? []
$: visibleRowCount = getVisibleRowCount(loaded, height, rows.length, rowCount)
@ -50,7 +51,7 @@
rows.length
)
// Reset state when data chanegs
// Reset state when data changes
$: data.length, reset()
const reset = () => {
nextScrollTop = 0
@ -59,6 +60,24 @@
timeout = null
}
const fixSchema = schema => {
let fixedSchema = {}
Object.entries(schema || {}).forEach(([fieldName, fieldSchema]) => {
if (typeof fieldSchema === "string") {
fixedSchema[fieldName] = {
type: fieldSchema,
name: fieldName,
}
} else {
fixedSchema[fieldName] = {
...fieldSchema,
name: fieldName,
}
}
})
return fixedSchema
}
const getVisibleRowCount = (loaded, height, allRows, rowCount) => {
if (!loaded) {
return rowCount || 0
@ -118,7 +137,6 @@
if (!field || !fieldSchema) {
return
}
schema[field].name = field
if (!fieldSchema?.autocolumn) {
columns.push(fieldSchema)
} else if (showAutoColumns) {
@ -192,16 +210,17 @@
on:scroll={onScroll}
class:quiet
style={`--row-height: ${rowHeight}px; --header-height: ${headerHeight}px;`}
class="container">
class="container"
>
<div style={contentStyle}>
<table class="spectrum-Table" class:spectrum-Table--quiet={quiet}>
{#if sortedRows?.length}
{#if fields.length}
<thead class="spectrum-Table-head">
<tr>
{#if showEditColumn}
<th class="spectrum-Table-headCell">
<div class="spectrum-Table-headCell-content">
{editColumnTitle || ''}
{editColumnTitle || ""}
</div>
</th>
{/if}
@ -209,15 +228,19 @@
<th
class="spectrum-Table-headCell"
class:is-sortable={schema[field].sortable !== false}
class:is-sorted-desc={sortColumn === field && sortOrder === 'Descending'}
class:is-sorted-asc={sortColumn === field && sortOrder === 'Ascending'}
on:click={() => sortBy(schema[field])}>
class:is-sorted-desc={sortColumn === field &&
sortOrder === "Descending"}
class:is-sorted-asc={sortColumn === field &&
sortOrder === "Ascending"}
on:click={() => sortBy(schema[field])}
>
<div class="spectrum-Table-headCell-content">
<div class="title">{getDisplayName(schema[field])}</div>
{#if schema[field]?.autocolumn}
<svg
class="spectrum-Icon spectrum-Table-autoIcon"
focusable="false">
focusable="false"
>
<use xlink:href="#spectrum-icon-18-MagicWand" />
</svg>
{/if}
@ -225,7 +248,8 @@
<svg
class="spectrum-Icon spectrum-UIIcon-ArrowDown100 spectrum-Table-sortedIcon"
focusable="false"
aria-hidden="true">
aria-hidden="true"
>
<use xlink:href="#spectrum-css-icon-Arrow100" />
</svg>
{/if}
@ -233,7 +257,8 @@
<svg
class="spectrum-Icon spectrum-Table-editIcon"
focusable="false"
on:click={e => editColumn(e, field)}>
on:click={e => editColumn(e, field)}
>
<use xlink:href="#spectrum-icon-18-Edit" />
</svg>
{/if}
@ -244,16 +269,18 @@
</thead>
{/if}
<tbody class="spectrum-Table-body">
{#if sortedRows?.length}
{#if sortedRows?.length && fields.length}
{#each sortedRows as row, idx}
<tr
on:click={() => toggleSelectRow(row)}
class="spectrum-Table-row"
class:hidden={idx < firstVisibleRow || idx > lastVisibleRow}>
class:hidden={idx < firstVisibleRow || idx > lastVisibleRow}
>
{#if idx >= firstVisibleRow && idx <= lastVisibleRow}
{#if showEditColumn}
<td
class="spectrum-Table-cell spectrum-Table-cell--divider">
class="spectrum-Table-cell spectrum-Table-cell--divider"
>
<div class="spectrum-Table-cell-content">
<SelectEditRenderer
data={row}
@ -261,21 +288,25 @@
onToggleSelection={() => toggleSelectRow(row)}
onEdit={e => editRow(e, row)}
{allowSelectRows}
{allowEditRows} />
{allowEditRows}
/>
</div>
</td>
{/if}
{#each fields as field}
<td
class="spectrum-Table-cell"
class:spectrum-Table-cell--divider={!!schema[field].divider}>
class:spectrum-Table-cell--divider={!!schema[field]
.divider}
>
<div class="spectrum-Table-cell-content">
<CellRenderer
{customRenderers}
{row}
schema={schema[field]}
value={row[field]}
on:clickrelationship>
on:clickrelationship
>
<slot />
</CellRenderer>
</div>
@ -285,14 +316,25 @@
</tr>
{/each}
{:else}
<div class="placeholder">
<tr class="placeholder-row">
{#if showEditColumn}
<td class="placeholder-offset" />
{/if}
{#each fields as field}
<td />
{/each}
<div class="placeholder" class:has-fields={fields.length > 0}>
<div class="placeholder-content">
<svg
class="spectrum-Icon spectrum-Icon--sizeXXL"
focusable="false">
focusable="false"
>
<use xlink:href="#spectrum-icon-18-Table" />
</svg>
<div>No rows found</div>
</div>
</div>
</tr>
{/if}
</tbody>
</table>
@ -315,7 +357,7 @@
overflow: auto;
}
.container.quiet {
border: none !important;
border: none;
}
table {
width: 100%;
@ -349,7 +391,7 @@
z-index: 2;
background-color: var(--spectrum-alias-background-color-secondary);
border-bottom: 1px solid
var(--spectrum-table-border-color, var(--spectrum-alias-border-color-mid)) !important;
var(--spectrum-table-border-color, var(--spectrum-alias-border-color-mid));
}
.spectrum-Table-headCell-content {
white-space: nowrap;
@ -364,7 +406,34 @@
text-overflow: ellipsis;
}
.placeholder-row {
position: relative;
height: 150px;
}
.placeholder-row td {
border-top: none !important;
border-bottom: none !important;
}
.placeholder-offset {
width: 1px;
}
.placeholder {
top: 0;
height: 100%;
left: 0;
width: 100%;
position: absolute;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}
.placeholder.has-fields {
top: var(--header-height);
height: calc(100% - var(--header-height));
}
.placeholder-content {
padding: 20px;
display: flex;
flex-direction: column;
@ -375,12 +444,13 @@
var(--spectrum-alias-text-color)
);
}
.placeholder div {
.placeholder-content div {
margin-top: 10px;
font-size: var(
--spectrum-table-cell-text-size,
var(--spectrum-alias-font-size-default)
);
text-align: center;
}
tbody {
@ -399,17 +469,17 @@
td {
padding-top: 0;
padding-bottom: 0;
border-bottom: none !important;
border-bottom: none;
border-top: 1px solid
var(--spectrum-table-border-color, var(--spectrum-alias-border-color-mid)) !important;
border-radius: 0 !important;
var(--spectrum-table-border-color, var(--spectrum-alias-border-color-mid));
border-radius: 0;
}
tr:first-child td {
border-top: none !important;
border-top: none;
}
tr:last-child td {
border-bottom: 1px solid
var(--spectrum-table-border-color, var(--spectrum-alias-border-color-mid)) !important;
var(--spectrum-table-border-color, var(--spectrum-alias-border-color-mid));
}
td.spectrum-Table-cell--divider {
width: 1px;

View File

@ -30,13 +30,15 @@
on:click={onClick}
class:is-selected={$selected.title === title}
class="spectrum-Tabs-item"
tabindex="0">
tabindex="0"
>
{#if icon}
<svg
class="spectrum-Icon spectrum-Icon--sizeM"
focusable="false"
aria-hidden="true"
aria-label="Folder">
aria-label="Folder"
>
<use xlink:href="#spectrum-icon-18-{icon}" />
</svg>
{/if}

View File

@ -44,23 +44,22 @@
})
function id() {
return (
"_" +
Math.random()
.toString(36)
.substr(2, 9)
)
return "_" + Math.random().toString(36).substr(2, 9)
}
</script>
<div
bind:this={container}
class="selected-border spectrum-Tabs spectrum-Tabs--{vertical ? 'vertical' : 'horizontal'}">
class="selected-border spectrum-Tabs spectrum-Tabs--{vertical
? 'vertical'
: 'horizontal'}"
>
<slot />
{#if $tab.info}
<div
class="spectrum-Tabs-selectionIndicator indicator-transition"
style="width: {width}; height: {height}; left: {left}; top: {top};" />
style="width: {width}; height: {height}; left: {left}; top: {top};"
/>
{/if}
</div>

View File

@ -1,19 +1,29 @@
<script>
import Avatar from '../Avatar/Avatar.svelte'
import ClearButton from '../ClearButton/ClearButton.svelte'
export let icon = "";
export let avatar = "";
export let invalid = false;
export let disabled = false;
export let closable = false;
import Avatar from "../Avatar/Avatar.svelte"
import ClearButton from "../ClearButton/ClearButton.svelte"
export let icon = ""
export let avatar = ""
export let invalid = false
export let disabled = false
export let closable = false
</script>
<div class:is-invalid={invalid} class:is-disabled={disabled} class="spectrum-Tags-item" role="listitem">
<div
class:is-invalid={invalid}
class:is-disabled={disabled}
class="spectrum-Tags-item"
role="listitem"
>
{#if avatar}
<Avatar url={avatar} />
{/if}
{#if icon}
<svg class="spectrum-Icon spectrum-Icon--sizeS" focusable="false" aria-hidden="true" aria-label="Tag">
<svg
class="spectrum-Icon spectrum-Icon--sizeS"
focusable="false"
aria-hidden="true"
aria-label="Tag"
>
<use xlink:href="#spectrum-icon-24-{icon}" />
</svg>
{/if}

View File

@ -1,20 +1,32 @@
<script>
export let selected = false;
export let open = false;
export let title;
export let icon;
export let selected = false
export let open = false
export let title
export let icon
</script>
<li
class:is-selected={selected} class:is-open={open} class="spectrum-TreeView-item">
class:is-selected={selected}
class:is-open={open}
class="spectrum-TreeView-item"
>
<a on:click class="spectrum-TreeView-itemLink" href="#">
{#if $$slots.default}
<svg class="spectrum-Icon spectrum-UIIcon-ChevronRight100 spectrum-TreeView-itemIndicator" focusable="false" aria-hidden="true">
<svg
class="spectrum-Icon spectrum-UIIcon-ChevronRight100 spectrum-TreeView-itemIndicator"
focusable="false"
aria-hidden="true"
>
<use xlink:href="#spectrum-css-icon-Chevron100" />
</svg>
{/if}
{#if icon}
<svg class="spectrum-TreeView-itemIcon spectrum-Icon spectrum-Icon--sizeM" focusable="false" aria-hidden="true" aria-label="Layers">
<svg
class="spectrum-TreeView-itemIcon spectrum-Icon spectrum-Icon--sizeM"
focusable="false"
aria-hidden="true"
aria-label="Layers"
>
<use xlink:href="#spectrum-icon-18-{icon}" />
</svg>
{/if}

View File

@ -3,9 +3,14 @@
export let quiet = false
export let standalone = true
export let width = '250px';
export let width = "250px"
</script>
<ul class:spectrum-TreeView--standalone={standalone} class:spectrum-TreeView--quiet={quiet} class="spectrum-TreeView" style="width: {width}">
<ul
class:spectrum-TreeView--standalone={standalone}
class:spectrum-TreeView--quiet={quiet}
class="spectrum-TreeView"
style="width: {width}"
>
<slot />
</ul>

View File

@ -1,29 +1,22 @@
<script>
import "@spectrum-css/typography/dist/index-vars.css"
// Sizes
export let xxxl = false;
export let xxl = false;
export let xl = false;
export let l = false;
export let m = false;
export let s = false;
export let xs = false;
export let xxs = false;
export let serif = false;
$: useDefault = ![xxxl, xxl, xl, l, m, s, xs, xxs].includes(true)
export let size = "M"
export let serif = false
export let noPadding = false
</script>
<p class="spectrum-Body"
<p
class:noPadding
class="spectrum-Body spectrum-Body--size{size}"
class:spectrum-Body--serif={serif}
class:spectrum-Body--sizeXXXL={xxxl}
class:spectrum-Body--sizeXXL={xxl}
class:spectrum-Body--sizeXL={xl}
class:spectrum-Body--sizeL={l}
class:spectrum-Body--sizeM={m || useDefault}
class:spectrum-Body--sizeS={s}
class:spectrum-Body--sizeXS={xs}
class:spectrum-Body--sizeXXS={xxs}>
>
<slot />
</p>
<style>
.noPadding {
padding: 0;
margin: 0;
}
</style>

View File

@ -2,19 +2,9 @@
import "@spectrum-css/typography/dist/index-vars.css"
// Sizes
export let xl = false;
export let l = false;
export let m = false;
export let s = false;
export let xs = false;
$: useDefault = ![xl, l, m, s, xs].includes(true)
export let size = "M"
</script>
<code class="spectrum-Code"
class:spectrum-Code--sizeXL={xl}
class:spectrum-Code--sizeL={l}
class:spectrum-Code--sizeM={m || useDefault}
class:spectrum-Code--sizeS={s}
class:spectrum-Code--sizeXS={xs}>
<code class="spectrum-Code spectrum-Code--size{size}">
<slot />
</code>

View File

@ -2,20 +2,14 @@
import "@spectrum-css/typography/dist/index-vars.css"
// Sizes
export let xl = false;
export let l = false;
export let m = false;
export let s = false;
export let size = "M"
export let serif = false;
$: useDefault = ![xl, l, m, s].includes(true)
export let serif = false
</script>
<p class="spectrum-Detail"
<p
class="spectrum-Detail spectrum-Detail--size{size}"
class:spectrum-Detail--serif={serif}
class:spectrum-Detail--sizeXL={xl}
class:spectrum-Detail--sizeL={l}
class:spectrum-Detail--sizeM={m || useDefault}
class:spectrum-Detail--sizeS={s}>
>
<slot />
</p>

View File

@ -2,27 +2,9 @@
import "@spectrum-css/typography/dist/index-vars.css"
// Sizes
export let xxxl = false
export let xxl = false
export let xl = false
export let l = false
export let m = false
export let s = false
export let xs = false
export let xxs = false
$: useDefault = ![xxxl, xxl, xl, l, m, s, xs, xxs].includes(true)
export let size = "M"
</script>
<h1
class="spectrum-Heading"
class:spectrum-Heading--sizeXXXL={xxxl}
class:spectrum-Heading--sizeXXL={xxl}
class:spectrum-Heading--sizeXL={xl}
class:spectrum-Heading--sizeL={l}
class:spectrum-Heading--sizeM={m || useDefault}
class:spectrum-Heading--sizeS={s}
class:spectrum-Heading--sizeXS={xs}
class:spectrum-Heading--sizeXXS={xxs}>
<h1 class="spectrum-Heading spectrum-Heading--size{size}">
<slot />
</h1>

View File

@ -26,8 +26,9 @@ export { default as DetailSummary } from "./DetailSummary/DetailSummary.svelte"
export { default as Popover } from "./Popover/Popover.svelte"
export { default as ProgressBar } from "./ProgressBar/ProgressBar.svelte"
export { default as ProgressCircle } from "./ProgressCircle/ProgressCircle.svelte"
export { default as Label } from "./Styleguide/Label.svelte"
export { default as Label } from "./Label/Label.svelte"
export { default as Layout } from "./Layout/Layout.svelte"
export { default as Page } from "./Layout/Page.svelte"
export { default as Link } from "./Link/Link.svelte"
export { default as Menu } from "./Menu/Menu.svelte"
export { default as MenuSection } from "./Menu/Section.svelte"

View File

@ -1,7 +1,5 @@
export const generateID = () => {
const rand = Math.random()
.toString(32)
.substring(2)
const rand = Math.random().toString(32).substring(2)
// Starts with a letter so that its a valid DOM ID
return `A${rand}`

View File

@ -2407,10 +2407,10 @@ svelte-portal@^1.0.0:
resolved "https://registry.yarnpkg.com/svelte-portal/-/svelte-portal-1.0.0.tgz#36a47c5578b1a4d9b4dc60fa32a904640ec4cdd3"
integrity sha512-nHf+DS/jZ6jjnZSleBMSaZua9JlG5rZv9lOGKgJuaZStfevtjIlUJrkLc3vbV8QdBvPPVmvcjTlazAzfKu0v3Q==
svelte@^3.37.0:
version "3.37.0"
resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.37.0.tgz#dc7cd24bcc275cdb3f8c684ada89e50489144ccd"
integrity sha512-TRF30F4W4+d+Jr2KzUUL1j8Mrpns/WM/WacxYlo5MMb2E5Qy2Pk1Guj6GylxsW9OnKQl1tnF8q3hG/hQ3h6VUA==
svelte@^3.38.2:
version "3.38.2"
resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.38.2.tgz#55e5c681f793ae349b5cc2fe58e5782af4275ef5"
integrity sha512-q5Dq0/QHh4BLJyEVWGe7Cej5NWs040LWjMbicBGZ+3qpFWJ1YObRmUDZKbbovddLC9WW7THTj3kYbTOFmU9fbg==
svgo@^1.0.0:
version "1.3.2"

View File

@ -12,9 +12,7 @@ Cypress.Commands.add("login", () => {
cy.visit(`localhost:${Cypress.env("PORT")}/builder`)
cy.contains("Create Test User").click()
cy.get("input")
.first()
.type("test@test.com")
cy.get("input").first().type("test@test.com")
cy.get('input[type="password"]').type("test")
@ -31,29 +29,17 @@ Cypress.Commands.add("createApp", name => {
.then($body => {
if ($body.find("input[name=apiKey]").length) {
// input was found, do something else here
cy.get("input[name=apiKey]")
.type(name)
.should("have.value", name)
cy.get("input[name=apiKey]").type(name).should("have.value", name)
cy.contains("Next").click()
}
})
.then(() => {
cy.get(".spectrum-Modal")
.within(() => {
cy.get("input")
.eq(0)
.type(name)
.should("have.value", name)
.blur()
cy.get("input").eq(0).type(name).should("have.value", name).blur()
cy.contains("Next").click()
cy.get("input")
.eq(1)
.type("test@test.com")
.blur()
cy.get("input")
.eq(2)
.type("test")
.blur()
cy.get("input").eq(1).type("test@test.com").blur()
cy.get("input").eq(2).type("test").blur()
cy.contains("Submit").click()
})
.then(() => {
@ -69,15 +55,11 @@ Cypress.Commands.add("deleteApp", name => {
cy.get(".apps").then($apps => {
cy.wait(1000)
if ($apps.find(`[data-cy="app-${name}"]`).length) {
cy.get(`[data-cy="app-${name}"]`)
.contains("Open")
.click()
cy.get(`[data-cy="app-${name}"]`).contains("Open").click()
cy.get("[data-cy=settings-icon]").click()
cy.get(".spectrum-Dialog").within(() => {
cy.contains("Danger Zone").click()
cy.get("input")
.type("DELETE")
.blur()
cy.get("input").type("DELETE").blur()
cy.contains("Delete Entire App").click()
})
}
@ -100,13 +82,8 @@ Cypress.Commands.add("createTable", tableName => {
// Enter table name
cy.get("[data-cy=new-table]").click()
cy.get(".spectrum-Modal").within(() => {
cy.get("input")
.first()
.type(tableName)
.blur()
cy.get(".spectrum-ButtonGroup")
.contains("Create")
.click()
cy.get("input").first().type(tableName).blur()
cy.get(".spectrum-ButtonGroup").contains("Create").click()
})
cy.contains(tableName).should("be.visible")
})
@ -118,10 +95,7 @@ Cypress.Commands.add("addColumn", (tableName, columnName, type) => {
// Configure column
cy.get(".spectrum-Modal").within(() => {
cy.get("input")
.first()
.type(columnName)
.blur()
cy.get("input").first().type(columnName).blur()
// Unset table display column
cy.contains("display column").click({ force: true })
@ -134,14 +108,9 @@ Cypress.Commands.add("addRow", values => {
cy.contains("Create row").click()
cy.get(".spectrum-Modal").within(() => {
for (let i = 0; i < values.length; i++) {
cy.get("input")
.eq(i)
.type(values[i])
.blur()
cy.get("input").eq(i).type(values[i]).blur()
}
cy.get(".spectrum-ButtonGroup")
.contains("Create")
.click()
cy.get(".spectrum-ButtonGroup").contains("Create").click()
})
})
@ -150,22 +119,12 @@ Cypress.Commands.add("createUser", (email, password, role) => {
cy.contains("Users").click()
cy.contains("Create user").click()
cy.get(".spectrum-Modal").within(() => {
cy.get("input")
.first()
.type(email)
.blur()
cy.get("input")
.eq(1)
.type(password)
.blur()
cy.get("select")
.first()
.select(role)
cy.get("input").first().type(email).blur()
cy.get("input").eq(1).type(password).blur()
cy.get("select").first().select(role)
// Save
cy.get(".spectrum-ButtonGroup")
.contains("Create User")
.click()
cy.get(".spectrum-ButtonGroup").contains("Create User").click()
})
})
@ -201,15 +160,9 @@ Cypress.Commands.add("navigateToFrontend", () => {
Cypress.Commands.add("createScreen", (screenName, route) => {
cy.get("[data-cy=new-screen]").click()
cy.get(".spectrum-Modal").within(() => {
cy.get("input")
.eq(0)
.type(screenName)
.blur()
cy.get("input").eq(0).type(screenName).blur()
if (route) {
cy.get("input")
.eq(1)
.type(route)
.blur()
cy.get("input").eq(1).type(route).blur()
}
cy.contains("Create Screen").click()
})

View File

@ -3,7 +3,7 @@
<head>
<meta charset='utf8'>
<meta name='viewport' content='width=device-width'>
<title>Budibase Builder</title>
<title>Budibase</title>
<link rel='icon' type='image/png' href='./src/favicon.png'>
</head>
<body id="app">

View File

@ -74,7 +74,7 @@
"@spectrum-css/vars": "^3.0.1",
"codemirror": "^5.59.0",
"downloadjs": "1.4.7",
"lodash": "4.17.13",
"lodash": "4.17.21",
"posthog-js": "1.4.5",
"remixicon": "2.5.0",
"shortid": "2.2.15",
@ -90,7 +90,7 @@
"@babel/preset-env": "^7.13.12",
"@babel/runtime": "^7.13.10",
"@rollup/plugin-replace": "^2.4.2",
"@roxi/routify": "2.15.1",
"@roxi/routify": "2.18.0",
"@sveltejs/vite-plugin-svelte": "^1.0.0-next.5",
"@testing-library/jest-dom": "^5.11.10",
"@testing-library/svelte": "^3.0.0",
@ -106,7 +106,7 @@
"rollup": "^2.44.0",
"rollup-plugin-copy": "^3.4.0",
"start-server-and-test": "^1.12.1",
"svelte": "^3.36.0",
"svelte": "^3.38.2",
"svelte-jester": "^1.3.2",
"vite": "^2.1.5"
},

View File

@ -0,0 +1,90 @@
export const gradient = (node, config = {}) => {
const defaultConfig = {
points: 12,
saturation: 0.85,
lightness: 0.7,
softness: 0.9,
seed: null,
}
// Applies a gradient background
const createGradient = config => {
config = {
...defaultConfig,
...config,
}
const { saturation, lightness, softness, points } = config
const seed = config.seed || Math.random().toString(32).substring(2)
// Hash function which returns a fixed hash between specified limits
// for a given seed and a given version
const rangeHash = (seed, min = 0, max = 100, version = 0) => {
const range = max - min
let hash = range + version
for (let i = 0; i < seed.length * 2 + version; i++) {
hash = (hash << 5) - hash + seed.charCodeAt(i % seed.length)
hash = ((hash & hash) % range) + version
}
return min + (hash % range)
}
// Generates a random HSL colour using the options specified
const randomHSL = (seed, version, alpha = 1) => {
const lowerSaturation = Math.min(100, saturation * 100)
const upperSaturation = Math.min(100, (saturation + 0.2) * 100)
const lowerLightness = Math.min(100, lightness * 100)
const upperLightness = Math.min(100, (lightness + 0.2) * 100)
const hue = rangeHash(seed, 0, 360, version)
const sat = `${rangeHash(
seed,
lowerSaturation,
upperSaturation,
version
)}%`
const light = `${rangeHash(
seed,
lowerLightness,
upperLightness,
version
)}%`
return `hsla(${hue},${sat},${light},${alpha})`
}
// Generates a radial gradient stop point
const randomGradientPoint = (seed, version) => {
const lowerTransparency = Math.min(100, softness * 100)
const upperTransparency = Math.min(100, (softness + 0.2) * 100)
const transparency = rangeHash(
seed,
lowerTransparency,
upperTransparency,
version
)
return (
`radial-gradient(at ` +
`${rangeHash(seed, 0, 100, version)}% ` +
`${rangeHash(seed, 0, 100, version + 1)}%,` +
`${randomHSL(seed, version, saturation)} 0,` +
`transparent ${transparency}%)`
)
}
let css = `opacity:0.9;background:${randomHSL(seed, 0, 0.7)};`
css += "background-image:"
for (let i = 0; i < points - 1; i++) {
css += `${randomGradientPoint(seed, i)},`
}
css += `${randomGradientPoint(seed, points)};`
node.style = css
}
// Apply the initial gradient
createGradient(config)
return {
// Apply a new gradient
update: config => {
createGradient(config)
},
}
}

Some files were not shown because too many files have changed in this diff Show More