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 ### 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` to install project dependencies
`yarn bootstrap` will install all budibase modules and symlink them together using lerna. `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 1. Open a new console
2. `yarn dev` (from root) 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. 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 ## 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. 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", "created_at": "2021-04-14T16:20:04Z",
"repoId": 190729906, "repoId": 190729906,
"pullRequestNo": 1383 "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, "semi": false,
"singleQuote": false, "singleQuote": false,
"trailingComma": "es5", "trailingComma": "es5",
"arrowParens": "avoid",
"jsxBracketSameLine": false,
"plugins": ["prettier-plugin-svelte"], "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 container_name: budi-redis-dev
restart: always restart: always
image: redis image: redis
command: redis-server --requirepass ${REDIS_PASSWORD}
ports: ports:
- "${REDIS_PORT}:6379" - "${REDIS_PORT}:6379"
volumes: volumes:

View File

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

View File

@ -16,16 +16,16 @@ static_resources:
- name: local_services - name: local_services
domains: ["*"] domains: ["*"]
routes: 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/" } - match: { prefix: "/db/" }
route: route:
cluster: couchdb-service cluster: couchdb-service
prefix_rewrite: "/" prefix_rewrite: "/"
- match: { prefix: "/cache/" }
route:
cluster: redis-service
prefix_rewrite: "/"
- match: { prefix: "/api/admin/" } - match: { prefix: "/api/admin/" }
route: route:
cluster: worker-dev cluster: worker-dev
@ -38,6 +38,13 @@ static_resources:
route: route:
cluster: server-dev 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/" } - match: { prefix: "/builder/" }
route: route:
cluster: builder-dev cluster: builder-dev
@ -47,10 +54,6 @@ static_resources:
cluster: builder-dev cluster: builder-dev
prefix_rewrite: "/builder/" 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 # minio is on the default route because this works
# best, minio + AWS SDK doesn't handle path proxy # best, minio + AWS SDK doesn't handle path proxy
- match: { prefix: "/" } - match: { prefix: "/" }
@ -89,20 +92,6 @@ static_resources:
address: couchdb-service address: couchdb-service
port_value: 5984 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 - name: server-dev
connect_timeout: 0.25s connect_timeout: 0.25s
type: strict_dns type: strict_dns

View File

@ -21,7 +21,6 @@ static_resources:
cluster: app-service cluster: app-service
prefix_rewrite: "/" prefix_rewrite: "/"
# special case for presenting our static self hosting page
- match: { path: "/" } - match: { path: "/" }
route: route:
cluster: app-service cluster: app-service
@ -41,11 +40,6 @@ static_resources:
cluster: worker-service cluster: worker-service
prefix_rewrite: "/" prefix_rewrite: "/"
- match: { prefix: "/cache/" }
route:
cluster: redis-service
prefix_rewrite: "/"
- match: { prefix: "/db/" } - match: { prefix: "/db/" }
route: route:
cluster: couchdb-service cluster: couchdb-service
@ -117,18 +111,3 @@ static_resources:
address: couchdb-service address: couchdb-service
port_value: 5984 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 MINIO_SECRET_KEY=budibase
COUCH_DB_PASSWORD=budibase COUCH_DB_PASSWORD=budibase
COUCH_DB_USER=budibase COUCH_DB_USER=budibase
REDIS_PASSWORD=budibase
# This section contains variables that do not need to be altered under normal circumstances # This section contains variables that do not need to be altered under normal circumstances
APP_PORT=4002 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", "prettier-plugin-svelte": "^2.2.0",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"rollup-plugin-replace": "^2.2.0", "rollup-plugin-replace": "^2.2.0",
"svelte": "^3.30.0" "svelte": "^3.38.2"
}, },
"scripts": { "scripts": {
"setup": "./hosting/scripts/setup.js && yarn && yarn bootstrap && yarn build && yarn dev",
"bootstrap": "lerna link && lerna bootstrap", "bootstrap": "lerna link && lerna bootstrap",
"build": "lerna run build", "build": "lerna run build",
"initialise": "lerna run initialise", "initialise": "lerna run initialise",

View File

@ -1,18 +1,27 @@
{ {
"name": "@budibase/auth", "name": "@budibase/auth",
"version": "0.0.1", "version": "0.18.6",
"description": "Authentication middlewares for budibase builder and apps", "description": "Authentication middlewares for budibase builder and apps",
"main": "src/index.js", "main": "src/index.js",
"author": "Budibase", "author": "Budibase",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"dependencies": { "dependencies": {
"aws-sdk": "^2.901.0",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"ioredis": "^4.27.1",
"jsonwebtoken": "^8.5.1", "jsonwebtoken": "^8.5.1",
"koa-passport": "^4.1.4", "koa-passport": "^4.1.4",
"node-fetch": "^2.6.1",
"passport-google-auth": "^1.0.2", "passport-google-auth": "^1.0.2",
"passport-google-oauth": "^2.0.0", "passport-google-oauth": "^2.0.0",
"passport-jwt": "^4.0.0", "passport-jwt": "^4.0.0",
"passport-local": "^1.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. * @param {Object} scopes - the type, group and userID scopes of the configuration.
* @returns The most granular configuration document based on the scope. * @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( const response = await db.allDocs(
getConfigParams( getConfigParams(
{ type, user, group }, { 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 const config = row.doc
// Config is specific to a user and a group // Config is specific to a user and a group
if (config._id.includes(generateConfigID({ type, user, group }))) { if (config._id.includes(generateConfigID({ type, user, group }))) {
config.score = 4 return 4
} else if (config._id.includes(generateConfigID({ type, user }))) { } else if (config._id.includes(generateConfigID({ type, user }))) {
// Config is specific to a user only // Config is specific to a user only
config.score = 3 return 3
} else if (config._id.includes(generateConfigID({ type, group }))) { } else if (config._id.includes(generateConfigID({ type, group }))) {
// Config is specific to a group only // Config is specific to a group only
config.score = 2 return 2
} else if (config._id.includes(generateConfigID({ type }))) { } else if (config._id.includes(generateConfigID({ type }))) {
// Config is specific to a type only // Config is specific to a type only
config.score = 1 return 1
} }
return config return 0
}) }
// Find the config with the most granular scope based on context // 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.generateConfigID = generateConfigID
exports.getConfigParams = getConfigParams 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 = { module.exports = {
JWT_SECRET: process.env.JWT_SECRET, JWT_SECRET: process.env.JWT_SECRET,
COUCH_DB_URL: process.env.COUCH_DB_URL, COUCH_DB_URL: process.env.COUCH_DB_URL,
SALT_ROUNDS: process.env.SALT_ROUNDS, 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

@ -13,6 +13,6 @@ exports.compare = async (data, encrypted) => {
return bcrypt.compare(data, encrypted) return bcrypt.compare(data, encrypted)
} }
exports.newid = function() { exports.newid = function () {
return v4().replace(/-/g, "") return v4().replace(/-/g, "")
} }

View File

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

View File

@ -3,11 +3,35 @@ const database = require("../db")
const { getCookie, clearCookie } = require("../utils") const { getCookie, clearCookie } = require("../utils")
const { StaticDatabases } = require("../db/utils") const { StaticDatabases } = require("../db/utils")
module.exports = (noAuthPatterns = []) => { const PARAM_REGEX = /\/:(.*?)\//g
const regex = new RegExp(noAuthPatterns.join("|"))
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) => { return async (ctx, next) => {
// the path is not authenticated // 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() return next()
} }
try { try {
@ -30,10 +54,14 @@ module.exports = (noAuthPatterns = []) => {
if (ctx.isAuthenticated !== true) { if (ctx.isAuthenticated !== true) {
ctx.isAuthenticated = false ctx.isAuthenticated = false
} }
return next() return next()
} catch (err) { } catch (err) {
ctx.throw(err.status || 403, err) // allow configuring for public access
if (opts && opts.publicAllowed) {
ctx.isAuthenticated = false
} else {
ctx.throw(err.status || 403, err)
}
} }
} }
} }

View File

@ -51,7 +51,7 @@ async function authenticate(token, tokenSecret, profile, done) {
* from couchDB rather than environment variables, using this factory is necessary for dynamically configuring passport. * from couchDB rather than environment variables, using this factory is necessary for dynamically configuring passport.
* @returns Dynamically configured Passport Google Strategy * @returns Dynamically configured Passport Google Strategy
*/ */
exports.strategyFactory = async function(config) { exports.strategyFactory = async function (config) {
try { try {
const { clientID, clientSecret, callbackURL } = config const { clientID, clientSecret, callbackURL } = config

View File

@ -3,12 +3,12 @@ const env = require("../../environment")
exports.options = { exports.options = {
secretOrKey: env.JWT_SECRET, secretOrKey: env.JWT_SECRET,
jwtFromRequest: function(ctx) { jwtFromRequest: function (ctx) {
return ctx.cookies.get(Cookies.Auth) return ctx.cookies.get(Cookies.Auth)
}, },
} }
exports.authenticate = async function(jwt, done) { exports.authenticate = async function (jwt, done) {
try { try {
return done(null, jwt) return done(null, jwt)
} catch (err) { } catch (err) {

View File

@ -15,7 +15,7 @@ exports.options = {}
* @param {*} done - callback from passport to return user information and errors * @param {*} done - callback from passport to return user information and errors
* @returns The authenticated user, or errors if they occur * @returns The authenticated user, or errors if they occur
*/ */
exports.authenticate = async function(email, password, done) { exports.authenticate = async function (email, password, done) {
if (!email) return done(null, false, "Email Required.") if (!email) return done(null, false, "Email Required.")
if (!password) return done(null, false, "Password Required.") if (!password) return done(null, false, "Password Required.")

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" 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 => { exports.getGlobalUserByEmail = async email => {
const db = getDB(StaticDatabases.GLOBAL.name) const db = getDB(StaticDatabases.GLOBAL.name)
try { try {

View File

@ -36,6 +36,21 @@ asynckit@^0.4.0:
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= 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: aws-sign2@~0.7.0:
version "0.7.0" version "0.7.0"
resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" 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" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59"
integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== 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: base64url@3.x.x:
version "3.0.1" version "3.0.1"
resolved "https://registry.yarnpkg.com/base64url/-/base64url-3.0.1.tgz#6399d572e2bc3f90a9a8b22d5dbb0a32d33f788d" 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" resolved "https://registry.yarnpkg.com/bcryptjs/-/bcryptjs-2.4.3.tgz#9ab5627b93e60621ff7cdac5da9733027df1d0cb"
integrity sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms= 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: buffer-equal-constant-time@1.0.1:
version "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" resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= 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: caseless@~0.12.0:
version "0.12.0" version "0.12.0"
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= 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: combined-stream@^1.0.6, combined-stream@~1.0.6:
version "1.0.8" version "1.0.8"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" 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: dependencies:
delayed-stream "~1.0.0" 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: core-util-is@1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" 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: dependencies:
assert-plus "^1.0.0" 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: delayed-stream@~1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= 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: ecc-jsbn@~0.1.1:
version "0.1.2" version "0.1.2"
resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" 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: dependencies:
safe-buffer "^5.0.1" 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: extend@~3.0.2:
version "3.0.2" version "3.0.2"
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" 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" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== 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: forever-agent@~0.6.1:
version "0.6.1" version "0.6.1"
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" 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" combined-stream "^1.0.6"
mime-types "^2.1.12" 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: getpass@^0.1.1:
version "0.1.7" version "0.1.7"
resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" 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" jsprim "^1.2.2"
sshpk "^1.7.0" 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: is-typedarray@~1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= 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: isstream@~0.1.2:
version "0.1.2" version "0.1.2"
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= 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: jsbn@~0.1.0:
version "0.1.1" version "0.1.1"
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
@ -296,6 +465,16 @@ koa-passport@^4.1.4:
dependencies: dependencies:
passport "^0.4.0" 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: lodash.includes@^4.3.0:
version "4.3.0" version "4.3.0"
resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" 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" resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=
lodash@^4.14.0: lodash@^4.14.0, lodash@^4.17.21:
version "4.17.21" version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@ -358,11 +537,33 @@ mime@^1.4.1:
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== 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: ms@^2.1.1:
version "2.1.3" version "2.1.3"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== 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: node-forge@^0.7.1:
version "0.7.6" version "0.7.6"
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.6.tgz#fdf3b418aee1f94f0ef642cd63486c77ca9724ac" 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" resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.9.15.tgz#bd1fefaf686c96b75475aed5196412ff60cfb9c1"
integrity sha1-vR/vr2hslrdUda7VGWQS/2DPucE= 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: passport-google-auth@^1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/passport-google-auth/-/passport-google-auth-1.0.2.tgz#8b300b5aa442ef433de1d832ed3112877d0b2938" 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" resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24"
integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== 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: punycode@^2.1.0, punycode@^2.1.1:
version "2.1.1" version "2.1.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" 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" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== 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: request@^2.72.0, request@^2.74.0:
version "2.88.2" version "2.88.2"
resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" 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" tunnel-agent "^0.6.0"
uuid "^3.3.2" 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" version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== 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" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== 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: semver@^5.6.0:
version "5.7.1" version "5.7.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== 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: sshpk@^1.7.0:
version "1.16.1" version "1.16.1"
resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" 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" safer-buffer "^2.0.2"
tweetnacl "~0.14.0" 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: string-template@~1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/string-template/-/string-template-1.0.0.tgz#9e9f2233dc00f218718ec379a28a5673ecca8b96" resolved "https://registry.yarnpkg.com/string-template/-/string-template-1.0.0.tgz#9e9f2233dc00f218718ec379a28a5673ecca8b96"
integrity sha1-np8iM9wA8hhxjsN5oopWc+zKi5Y= 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: tough-cookie@~2.5.0:
version "2.5.0" version "2.5.0"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2"
@ -574,11 +901,29 @@ uri-js@^4.2.2:
dependencies: dependencies:
punycode "^2.1.0" 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: utils-merge@1.x.x:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= 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: uuid@^3.3.2:
version "3.4.0" version "3.4.0"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" 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" assert-plus "^1.0.0"
core-util-is "1.0.2" core-util-is "1.0.2"
extsprintf "^1.2.0" 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-postcss": "^4.0.0",
"rollup-plugin-svelte": "^7.1.0", "rollup-plugin-svelte": "^7.1.0",
"rollup-plugin-terser": "^7.0.2", "rollup-plugin-terser": "^7.0.2",
"svelte": "^3.37.0" "svelte": "^3.38.2"
}, },
"keywords": [ "keywords": [
"svelte" "svelte"

View File

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

View File

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

View File

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

View File

@ -1,12 +1,53 @@
<script> <script>
import "@spectrum-css/avatar/dist/index-vars.css" 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 url = ""
export let disabled = false export let disabled = false
export let name = "John Doe"
function getInitials(name) {
let parts = name.split(" ")
return parts.map(name => name[0]).join("")
}
</script> </script>
<img {#if url}
class:is-disabled={disabled} <img
class="spectrum-Avatar" class:is-disabled={disabled}
src={url} class="spectrum-Avatar"
alt="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:active
class="spectrum-Button spectrum-Button--size{size.toUpperCase()}" class="spectrum-Button spectrum-Button--size{size.toUpperCase()}"
{disabled} {disabled}
on:click|preventDefault> on:click|preventDefault
>
{#if icon} {#if icon}
<svg <svg
class="spectrum-Icon spectrum-Icon--size{size.toUpperCase()}" class="spectrum-Icon spectrum-Icon--size{size.toUpperCase()}"
focusable="false" focusable="false"
aria-hidden="true" aria-hidden="true"
aria-label={icon}> aria-label={icon}
>
<use xlink:href="#spectrum-icon-18-{icon}" /> <use xlink:href="#spectrum-icon-18-{icon}" />
</svg> </svg>
{/if} {/if}

View File

@ -1,15 +1,19 @@
<script> <script>
import "@spectrum-css/buttongroup/dist/index-vars.css" import "@spectrum-css/buttongroup/dist/index-vars.css"
export let vertical = false export let vertical = false
function group(element) { function group(element) {
const buttons = Array.from(element.getElementsByTagName('button')) const buttons = Array.from(element.getElementsByTagName("button"))
buttons.forEach(button => { buttons.forEach(button => {
button.classList.add('spectrum-ButtonGroup-item') button.classList.add("spectrum-ButtonGroup-item")
}) })
} }
</script> </script>
<div use:group class="spectrum-ButtonGroup" class:spectrum-ButtonGroup--vertical={vertical}> <div
<slot /> use:group
</div> class="spectrum-ButtonGroup"
class:spectrum-ButtonGroup--vertical={vertical}
>
<slot />
</div>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -15,6 +15,8 @@
export let fileSizeLimit = BYTES_IN_MB * 20 export let fileSizeLimit = BYTES_IN_MB * 20
export let processFiles = null export let processFiles = null
export let handleFileTooLarge = null export let handleFileTooLarge = null
export let gallery = true
export let error = null
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
const imageExtensions = [ const imageExtensions = [
@ -52,6 +54,8 @@
const newValue = [...value, ...processedFiles] const newValue = [...value, ...processedFiles]
dispatch("change", newValue) dispatch("change", newValue)
selectedImageIdx = newValue.length - 1 selectedImageIdx = newValue.length - 1
} else {
dispatch("change", fileList)
} }
} }
@ -94,45 +98,68 @@
<div class="container"> <div class="container">
{#if selectedImage} {#if selectedImage}
<div class="gallery"> {#if gallery}
<div class="title"> <div class="gallery">
<div class="filename">{selectedImage.name}</div> <div class="title">
<div class="filesize"> <div class="filename">{selectedImage.name}</div>
{#if selectedImage.size <= BYTES_IN_MB} <div class="filesize">
{`${selectedImage.size / BYTES_IN_KB} KB`} {#if selectedImage.size <= BYTES_IN_MB}
{:else}{`${selectedImage.size / BYTES_IN_MB} MB`}{/if} {`${selectedImage.size / BYTES_IN_KB} KB`}
{:else}{`${selectedImage.size / BYTES_IN_MB} MB`}{/if}
</div>
{#if !disabled}
<div class="delete-button" on:click={removeFile}>
<Icon name="Close" />
</div>
{/if}
</div> </div>
{#if !disabled} {#if isImage}
<div class="delete-button" on:click={removeFile}> <img alt="preview" src={selectedImage.url} />
<Icon name="Close" /> {:else}
<div class="placeholder">
<div class="extension">{selectedImage.extension}</div>
<div>Preview not supported</div>
</div> </div>
{/if} {/if}
</div> <div
{#if isImage} class="nav left"
<img alt="preview" src={selectedImage.url} /> class:visible={selectedImageIdx > 0}
{:else} on:click={navigateLeft}
<div class="placeholder"> >
<div class="extension">{selectedImage.extension}</div> <Icon name="ChevronLeft" />
<div>Preview not supported</div>
</div> </div>
{/if} <div
<div class="nav right"
class="nav left" class:visible={selectedImageIdx < fileCount - 1}
class:visible={selectedImageIdx > 0} on:click={navigateRight}
on:click={navigateLeft}> >
<Icon name="ChevronLeft" /> <Icon name="ChevronRight" />
</div>
<div class="footer">File {selectedImageIdx + 1} of {fileCount}</div>
</div> </div>
<div {:else if value?.length}
class="nav right" {#each value as file}
class:visible={selectedImageIdx < fileCount - 1} <div class="gallery">
on:click={navigateRight}> <div class="title">
<Icon name="ChevronRight" /> <div class="filename">{file.name}</div>
</div> <div class="filesize">
<div class="footer">File {selectedImageIdx + 1} of {fileCount}</div> {#if file.size <= BYTES_IN_MB}
</div> {`${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} {/if}
<div <div
class="spectrum-Dropzone" class="spectrum-Dropzone"
class:is-invalid={!!error}
class:disabled class:disabled
role="region" role="region"
tabindex="0" tabindex="0"
@ -140,19 +167,22 @@
on:dragleave={handleDragLeave} on:dragleave={handleDragLeave}
on:dragenter={handleDragOver} on:dragenter={handleDragOver}
on:drop={handleDrop} on:drop={handleDrop}
class:is-dragged={fileDragged}> class:is-dragged={fileDragged}
>
<div class="spectrum-IllustratedMessage spectrum-IllustratedMessage--cta"> <div class="spectrum-IllustratedMessage spectrum-IllustratedMessage--cta">
<input <input
id={fieldId} id={fieldId}
{disabled} {disabled}
type="file" type="file"
multiple multiple
on:change={handleFile} /> on:change={handleFile}
/>
<svg <svg
class="spectrum-IllustratedMessage-illustration" class="spectrum-IllustratedMessage-illustration"
width="125" width="125"
height="60" height="60"
viewBox="0 0 199 97.7"><defs> viewBox="0 0 199 97.7"
><defs>
<style> <style>
.cls-1, .cls-1,
.cls-2 { .cls-2 {
@ -170,25 +200,31 @@
</defs> </defs>
<path <path
class="cls-1" 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" /> <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="M105.5,73.5h19a2,2,0,0,0,2-2v-43" />
<path <path
class="cls-1" 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" /> <line class="cls-1" x1="105.5" y1="1.5" x2="126.5" y2="22.5" />
<path <path
class="cls-2" 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 <path
class="cls-2" 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 <path
class="cls-2" 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 <path
class="cls-1" 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 <rect
class="cls-1" class="cls-1"
x="1.5" x="1.5"
@ -196,16 +232,21 @@
width="58" width="58"
height="39" height="39"
rx="2" rx="2"
ry="2" /> ry="2"
/>
</svg> </svg>
<h2 <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 Drag and drop your file
</h2> </h2>
{#if !disabled} {#if !disabled}
<p <p
class="spectrum-Body spectrum-Body--sizeS spectrum-IllustratedMessage-description"> class="spectrum-Body spectrum-Body--sizeS spectrum-IllustratedMessage-description"
<label for={fieldId} class="spectrum-Link">Select a file to upload</label> >
<label for={fieldId} class="spectrum-Link"
>Select a file to upload</label
>
<br /> <br />
from your computer from your computer
</p> </p>
@ -229,6 +270,9 @@
.spectrum-Dropzone { .spectrum-Dropzone {
user-select: none; user-select: none;
} }
.spectrum-Dropzone.is-invalid {
border-color: var(--spectrum-global-color-red-400);
}
input[type="file"] { input[type="file"] {
display: none; display: none;
} }
@ -260,7 +304,7 @@
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: flex-start; justify-content: flex-start;
align-items: stretch; align-items: center;
} }
.filename { .filename {
flex: 1 1 auto; flex: 1 1 auto;
@ -315,6 +359,7 @@
.delete-button { .delete-button {
transition: all 0.3s; transition: all 0.3s;
margin-left: 10px; margin-left: 10px;
display: flex;
} }
.delete-button i { .delete-button i {
font-size: 2em; font-size: 2em;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,9 +5,11 @@
export let noPadding = false export let noPadding = false
export let gap = "M" export let gap = "M"
export let noGap = false export let noGap = false
export let alignContent = "normal"
</script> </script>
<div <div
style="align-content:{alignContent};"
class:horizontal class:horizontal
class="container paddingX-{!noPadding && paddingX} paddingY-{!noPadding && class="container paddingX-{!noPadding && paddingX} paddingY-{!noPadding &&
paddingY} gap-{!noGap && gap}" paddingY} gap-{!noGap && gap}"
@ -44,6 +46,9 @@
padding-top: var(--spacing-l); padding-top: var(--spacing-l);
padding-bottom: var(--spacing-l); padding-bottom: var(--spacing-l);
} }
.gap-XS {
grid-gap: var(--spacing-s);
}
.gap-S { .gap-S {
grid-gap: var(--spectrum-alias-grid-gutter-xsmall); 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--secondary={secondary}
class:spectrum-Link--overBackground={overBackground} class:spectrum-Link--overBackground={overBackground}
class:spectrum-Link--quiet={quiet} 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} {#if icon}
<svg <svg
class="spectrum-Icon spectrum-Icon--sizeM spectrum-Menu-itemIcon" class="spectrum-Icon spectrum-Icon--sizeS spectrum-Menu-itemIcon"
focusable="false" focusable="false"
aria-hidden="true" aria-hidden="true"
aria-label={icon} aria-label={icon}
@ -37,3 +37,9 @@
{/if} {/if}
<span class="spectrum-Menu-itemLabel"><slot /></span> <span class="spectrum-Menu-itemLabel"><slot /></span>
</li> </li>
<style>
.spectrum-Menu-itemIcon {
align-self: center;
}
</style>

View File

@ -1,7 +1,7 @@
<script> <script>
import "@spectrum-css/menu/dist/index-vars.css" import "@spectrum-css/menu/dist/index-vars.css"
</script> </script>
<ul class="spectrum-Menu" role="menu"> <ul class="spectrum-Menu" role="menu">
<slot /> <slot />
</ul> </ul>

View File

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

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}> <div on:click={increment}>
Click me Click me
{remaining} {remaining}
more time{remaining === 1 ? '' : 's'} more time{remaining === 1 ? "" : "s"}
to close this modal! to close this modal!
</div> </div>

View File

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

View File

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

View File

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

View File

@ -1,17 +1,25 @@
<script> <script>
import "@spectrum-css/toast/dist/index-vars.css" import "@spectrum-css/toast/dist/index-vars.css"
import Portal from "svelte-portal" import Portal from "svelte-portal"
import { flip } from "svelte/animate" import { flip } from "svelte/animate"
import { fly } from "svelte/transition" import { fly } from "svelte/transition"
import { notifications } from '../Stores/notifications' import { notifications } from "../Stores/notifications"
</script> </script>
<Portal target=".modal-container"> <Portal target=".modal-container">
<div class="notifications"> <div class="notifications">
{#each $notifications as {type, icon, message, id} (id)} {#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} {#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}" /> <use xlink:href="#spectrum-icon-18-{icon}" />
</svg> </svg>
{/if} {/if}
@ -21,24 +29,24 @@
</div> </div>
{/each} {/each}
</div> </div>
</Portal> </Portal>
<style>
.notifications { <style>
position: fixed; .notifications {
top: 10px; position: fixed;
left: 0; top: 10px;
right: 0; left: 0;
margin: 0 auto; right: 0;
padding: 0; margin: 0 auto;
z-index: 9999; padding: 0;
display: flex; z-index: 9999;
flex-direction: column; display: flex;
justify-content: flex-start; flex-direction: column;
align-items: center; justify-content: flex-start;
pointer-events: none; align-items: center;
} pointer-events: none;
.notification-offset { }
margin-bottom: 10px; .notification-offset {
} margin-bottom: 10px;
</style> }
</style>

View File

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

View File

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

View File

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

View File

@ -1,30 +1,44 @@
<script> <script>
import { getContext } from 'svelte' import { getContext } from "svelte"
const multilevel = getContext('sidenav-type'); const multilevel = getContext("sidenav-type")
export let href = ""; export let href = ""
export let external = false; export let external = false
export let heading = "" export let heading = ""
export let icon = ""; export let icon = ""
export let selected = false; export let selected = false
export let disabled = false; export let disabled = false
</script> </script>
<li class="spectrum-SideNav-item" class:is-selected={selected} class:is-disabled={disabled}> <li
{#if heading} class="spectrum-SideNav-item"
<h2 class="spectrum-SideNav-heading" id="nav-heading-{heading}">{heading}</h2> class:is-selected={selected}
class:is-disabled={disabled}
>
{#if heading}
<h2 class="spectrum-SideNav-heading" id="nav-heading-{heading}">
{heading}
</h2>
{/if}
<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"
>
<use xlink:href="#spectrum-icon-18-{icon}" />
</svg>
{/if} {/if}
<a target={external ? '_blank' : '_self'} {href} class="spectrum-SideNav-itemLink" aria-current="page"> <slot />
{#if icon} </a>
<svg class="spectrum-Icon spectrum-Icon--sizeM spectrum-SideNav-itemIcon" focusable="false" aria-hidden="true"> {#if multilevel && $$slots.subnav}
<use xlink:href="#spectrum-icon-18-{icon}" /> <ul class="spectrum-SideNav">
</svg> <slot name="subnav" />
{/if} </ul>
<slot /> {/if}
</a> </li>
{#if multilevel && $$slots.subnav}
<ul class="spectrum-SideNav">
<slot name="subnav" />
</ul>
{/if}
</li>

View File

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

View File

@ -50,12 +50,7 @@ export const createNotificationStore = () => {
} }
function id() { function id() {
return ( return "_" + Math.random().toString(36).substr(2, 9)
"_" +
Math.random()
.toString(36)
.substr(2, 9)
)
} }
export const notifications = createNotificationStore() 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> </script>
<label <label
class="spectrum-Checkbox spectrum-Checkbox--sizeM spectrum-Checkbox--emphasized"> class="spectrum-Checkbox spectrum-Checkbox--sizeM spectrum-Checkbox--emphasized"
>
<input <input
type="checkbox" type="checkbox"
class="spectrum-Checkbox-input" class="spectrum-Checkbox-input"
id="checkbox-1" id="checkbox-1"
disabled disabled
checked={!!value} /> checked={!!value}
/>
<span class="spectrum-Checkbox-box"> <span class="spectrum-Checkbox-box">
<svg <svg
class="spectrum-Icon spectrum-UIIcon-Checkmark100 spectrum-Checkbox-checkmark" class="spectrum-Icon spectrum-UIIcon-Checkmark100 spectrum-Checkbox-checkmark"
focusable="false" focusable="false"
aria-hidden="true"> aria-hidden="true"
>
<use xlink:href="#spectrum-css-icon-Checkmark100" /> <use xlink:href="#spectrum-css-icon-Checkmark100" />
</svg> </svg>
<svg <svg
class="spectrum-Icon spectrum-UIIcon-Dash100 spectrum-Checkbox-partialCheckmark" class="spectrum-Icon spectrum-UIIcon-Dash100 spectrum-Checkbox-partialCheckmark"
focusable="false" focusable="false"
aria-hidden="true"> aria-hidden="true"
>
<use xlink:href="#spectrum-css-icon-Dash100" /> <use xlink:href="#spectrum-css-icon-Dash100" />
</svg> </svg>
</span> </span>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,24 +1,34 @@
<script> <script>
import Avatar from '../Avatar/Avatar.svelte' import Avatar from "../Avatar/Avatar.svelte"
import ClearButton from '../ClearButton/ClearButton.svelte' import ClearButton from "../ClearButton/ClearButton.svelte"
export let icon = ""; export let icon = ""
export let avatar = ""; export let avatar = ""
export let invalid = false; export let invalid = false
export let disabled = false; export let disabled = false
export let closable = false; export let closable = false
</script> </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} {#if avatar}
<Avatar url={avatar} /> <Avatar url={avatar} />
{/if} {/if}
{#if icon} {#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}" /> <use xlink:href="#spectrum-icon-24-{icon}" />
</svg> </svg>
{/if} {/if}
<span class="spectrum-Tags-itemLabel"><slot /></span> <span class="spectrum-Tags-itemLabel"><slot /></span>
{#if closable} {#if closable}
<ClearButton on:click /> <ClearButton on:click />
{/if} {/if}
</div> </div>

View File

@ -1,7 +1,7 @@
<script> <script>
import "@spectrum-css/tags/dist/index-vars.css" import "@spectrum-css/tags/dist/index-vars.css"
</script> </script>
<div class="spectrum-Tags" role="list" aria-label="list"> <div class="spectrum-Tags" role="list" aria-label="list">
<slot /> <slot />
</div> </div>

View File

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

View File

@ -1,11 +1,16 @@
<script> <script>
import "@spectrum-css/treeview/dist/index-vars.css" import "@spectrum-css/treeview/dist/index-vars.css"
export let quiet = false export let quiet = false
export let standalone = true export let standalone = true
export let width = '250px'; export let width = "250px"
</script> </script>
<ul class:spectrum-TreeView--standalone={standalone} class:spectrum-TreeView--quiet={quiet} class="spectrum-TreeView" style="width: {width}"> <ul
<slot /> class:spectrum-TreeView--standalone={standalone}
</ul> class:spectrum-TreeView--quiet={quiet}
class="spectrum-TreeView"
style="width: {width}"
>
<slot />
</ul>

View File

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

View File

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

View File

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

View File

@ -2,27 +2,9 @@
import "@spectrum-css/typography/dist/index-vars.css" import "@spectrum-css/typography/dist/index-vars.css"
// Sizes // Sizes
export let xxxl = false export let size = "M"
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)
</script> </script>
<h1 <h1 class="spectrum-Heading spectrum-Heading--size{size}">
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}>
<slot /> <slot />
</h1> </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 Popover } from "./Popover/Popover.svelte"
export { default as ProgressBar } from "./ProgressBar/ProgressBar.svelte" export { default as ProgressBar } from "./ProgressBar/ProgressBar.svelte"
export { default as ProgressCircle } from "./ProgressCircle/ProgressCircle.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 Layout } from "./Layout/Layout.svelte"
export { default as Page } from "./Layout/Page.svelte"
export { default as Link } from "./Link/Link.svelte" export { default as Link } from "./Link/Link.svelte"
export { default as Menu } from "./Menu/Menu.svelte" export { default as Menu } from "./Menu/Menu.svelte"
export { default as MenuSection } from "./Menu/Section.svelte" export { default as MenuSection } from "./Menu/Section.svelte"

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