Merge branch 'admin/user-management-ui' of github.com:Budibase/budibase into admin/user-management-ui
This commit is contained in:
commit
3e63c616d3
|
@ -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.
|
||||||
|
|
|
@ -16,6 +16,11 @@ 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
|
||||||
|
@ -33,7 +38,14 @@ static_resources:
|
||||||
route:
|
route:
|
||||||
cluster: server-dev
|
cluster: server-dev
|
||||||
|
|
||||||
- match: { prefix: "/" }
|
# the below three cases are needed to make sure
|
||||||
|
# all traffic prefixed for the builder is passed through
|
||||||
|
# correctly.
|
||||||
|
- match: { path: "/" }
|
||||||
|
route:
|
||||||
|
cluster: builder-dev
|
||||||
|
|
||||||
|
- match: { prefix: "/builder/" }
|
||||||
route:
|
route:
|
||||||
cluster: builder-dev
|
cluster: builder-dev
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
|
@ -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.37.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",
|
||||||
|
|
|
@ -6,15 +6,20 @@
|
||||||
"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",
|
"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": {
|
"devDependencies": {
|
||||||
"ioredis-mock": "^5.5.5"
|
"ioredis-mock": "^5.5.5"
|
||||||
|
|
|
@ -12,5 +12,8 @@ module.exports = {
|
||||||
SALT_ROUNDS: process.env.SALT_ROUNDS,
|
SALT_ROUNDS: process.env.SALT_ROUNDS,
|
||||||
REDIS_URL: process.env.REDIS_URL,
|
REDIS_URL: process.env.REDIS_URL,
|
||||||
REDIS_PASSWORD: process.env.REDIS_PASSWORD,
|
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,
|
isTest,
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,10 @@ module.exports = {
|
||||||
Client: require("./redis"),
|
Client: require("./redis"),
|
||||||
utils: require("./redis/utils"),
|
utils: require("./redis/utils"),
|
||||||
},
|
},
|
||||||
|
objectStore: {
|
||||||
|
...require("./objectStore"),
|
||||||
|
...require("./objectStore/utils"),
|
||||||
|
},
|
||||||
utils: {
|
utils: {
|
||||||
...require("./utils"),
|
...require("./utils"),
|
||||||
...require("./hashing"),
|
...require("./hashing"),
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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")
|
||||||
|
}
|
|
@ -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"
|
||||||
|
@ -51,6 +66,11 @@ balanced-match@^1.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
|
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
|
||||||
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
|
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"
|
||||||
|
@ -68,6 +88,15 @@ 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:
|
brace-expansion@^1.1.7:
|
||||||
version "1.1.11"
|
version "1.1.11"
|
||||||
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
|
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
|
||||||
|
@ -81,11 +110,33 @@ buffer-equal-constant-time@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:
|
cluster-key-slot@^1.1.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz#30474b2a981fb12172695833052bc0d01336d10d"
|
resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz#30474b2a981fb12172695833052bc0d01336d10d"
|
||||||
|
@ -147,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"
|
||||||
|
@ -200,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"
|
||||||
|
@ -265,6 +333,21 @@ 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:
|
ioredis-mock@^5.5.5:
|
||||||
version "5.5.5"
|
version "5.5.5"
|
||||||
resolved "https://registry.yarnpkg.com/ioredis-mock/-/ioredis-mock-5.5.5.tgz#dec9fedd238c6ab9f56c026fc366533144f8a256"
|
resolved "https://registry.yarnpkg.com/ioredis-mock/-/ioredis-mock-5.5.5.tgz#dec9fedd238c6ab9f56c026fc366533144f8a256"
|
||||||
|
@ -297,11 +380,21 @@ is-typedarray@~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"
|
||||||
|
@ -451,6 +544,11 @@ minimatch@^3.0.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
brace-expansion "^1.1.7"
|
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:
|
ms@2.1.2:
|
||||||
version "2.1.2"
|
version "2.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
|
||||||
|
@ -461,6 +559,11 @@ ms@^2.1.1:
|
||||||
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"
|
||||||
|
@ -476,6 +579,13 @@ 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:
|
os-tmpdir@~1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
|
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
|
||||||
|
@ -579,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"
|
||||||
|
@ -589,6 +712,20 @@ 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:
|
readline-sync@^1.4.9:
|
||||||
version "1.4.10"
|
version "1.4.10"
|
||||||
resolved "https://registry.yarnpkg.com/readline-sync/-/readline-sync-1.4.10.tgz#41df7fbb4b6312d673011594145705bf56d8873b"
|
resolved "https://registry.yarnpkg.com/readline-sync/-/readline-sync-1.4.10.tgz#41df7fbb4b6312d673011594145705bf56d8873b"
|
||||||
|
@ -637,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==
|
||||||
|
@ -647,6 +784,21 @@ 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"
|
||||||
|
@ -682,6 +834,34 @@ string-template@~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:
|
tmp@^0.0.33:
|
||||||
version "0.0.33"
|
version "0.0.33"
|
||||||
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
|
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
|
||||||
|
@ -721,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"
|
||||||
|
@ -744,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=
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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,6 +98,7 @@
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
{#if selectedImage}
|
{#if selectedImage}
|
||||||
|
{#if gallery}
|
||||||
<div class="gallery">
|
<div class="gallery">
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<div class="filename">{selectedImage.name}</div>
|
<div class="filename">{selectedImage.name}</div>
|
||||||
|
@ -132,9 +137,29 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="footer">File {selectedImageIdx + 1} of {fileCount}</div>
|
<div class="footer">File {selectedImageIdx + 1} of {fileCount}</div>
|
||||||
</div>
|
</div>
|
||||||
|
{:else if value?.length}
|
||||||
|
{#each value as file}
|
||||||
|
<div class="gallery">
|
||||||
|
<div class="title">
|
||||||
|
<div class="filename">{file.name}</div>
|
||||||
|
<div class="filesize">
|
||||||
|
{#if file.size <= BYTES_IN_MB}
|
||||||
|
{`${file.size / BYTES_IN_KB} KB`}
|
||||||
|
{:else}{`${file.size / BYTES_IN_MB} MB`}{/if}
|
||||||
|
</div>
|
||||||
|
{#if !disabled}
|
||||||
|
<div class="delete-button" on:click={removeFile}>
|
||||||
|
<Icon name="Close" />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
{/if}
|
{/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"
|
||||||
|
@ -245,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;
|
||||||
}
|
}
|
||||||
|
@ -276,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;
|
||||||
|
@ -331,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;
|
||||||
|
|
|
@ -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,6 +34,7 @@
|
||||||
<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}
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -37,6 +37,7 @@
|
||||||
}
|
}
|
||||||
focus = false
|
focus = false
|
||||||
updateValue(event.target.value)
|
updateValue(event.target.value)
|
||||||
|
dispatch("blur")
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateValueOnEnter = event => {
|
const updateValueOnEnter = event => {
|
||||||
|
|
|
@ -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,6 +28,7 @@
|
||||||
{fileSizeLimit}
|
{fileSizeLimit}
|
||||||
{processFiles}
|
{processFiles}
|
||||||
{handleFileTooLarge}
|
{handleFileTooLarge}
|
||||||
|
{gallery}
|
||||||
on:change={onChange}
|
on:change={onChange}
|
||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
|
|
|
@ -30,5 +30,6 @@
|
||||||
on:change={onChange}
|
on:change={onChange}
|
||||||
on:click
|
on:click
|
||||||
on:input
|
on:input
|
||||||
|
on:blur
|
||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
|
|
|
@ -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} {disabled} {error}>
|
<Field {label} {labelPosition} {disabled} {error}>
|
||||||
<Select
|
<Select
|
||||||
|
{quiet}
|
||||||
{error}
|
{error}
|
||||||
{disabled}
|
{disabled}
|
||||||
{readonly}
|
{readonly}
|
||||||
|
|
|
@ -1,62 +1,16 @@
|
||||||
<script>
|
<script>
|
||||||
export let forAttr = "",
|
import "@spectrum-css/fieldlabel/dist/index-vars.css"
|
||||||
extraSmall = false,
|
|
||||||
small = false,
|
export let size = "M"
|
||||||
medium = false,
|
|
||||||
large = false,
|
|
||||||
extraLarge = false,
|
|
||||||
white = false,
|
|
||||||
grey = false,
|
|
||||||
black = false
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<label
|
<label class={`spectrum-FieldLabel spectrum-FieldLabel--size${size}`}>
|
||||||
class="bb-label"
|
|
||||||
class:extraSmall
|
|
||||||
class:small
|
|
||||||
class:medium
|
|
||||||
class:large
|
|
||||||
class:extraLarge
|
|
||||||
class:white
|
|
||||||
class:grey
|
|
||||||
class:black
|
|
||||||
for={forAttr}
|
|
||||||
>
|
|
||||||
<slot />
|
<slot />
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.bb-label {
|
label {
|
||||||
font-family: var(--font-sans);
|
padding: 0;
|
||||||
font-weight: 500;
|
white-space: nowrap;
|
||||||
text-rendering: var(--text-render);
|
|
||||||
color: var(--ink);
|
|
||||||
font-size: var(--font-size-s);
|
|
||||||
margin-bottom: var(--spacing-s);
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
.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);
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,8 +8,10 @@
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
div {
|
div {
|
||||||
display: grid;
|
display: flex;
|
||||||
grid-template-columns: 1fr;
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: stretch;
|
||||||
max-width: 80ch;
|
max-width: 80ch;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: calc(var(--spacing-xl) * 2);
|
padding: calc(var(--spacing-xl) * 2);
|
||||||
|
@ -18,6 +20,7 @@
|
||||||
.wide {
|
.wide {
|
||||||
max-width: none;
|
max-width: none;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: var(--spacing-xl) calc(var(--spacing-xl) * 2);
|
padding: var(--spacing-xl) calc(var(--spacing-xl) * 2)
|
||||||
|
calc(var(--spacing-xl) * 2) calc(var(--spacing-xl) * 2);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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,11 +46,17 @@
|
||||||
|
|
||||||
<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}>
|
||||||
|
@ -57,7 +64,7 @@
|
||||||
<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>
|
||||||
|
@ -98,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;
|
||||||
|
@ -106,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>
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
export let title = undefined
|
export let title = undefined
|
||||||
export let size = "S"
|
export let size = "S"
|
||||||
export let cancelText = "Cancel"
|
export let cancelText = "Cancel"
|
||||||
|
export let warning = false
|
||||||
export let confirmText = "Confirm"
|
export let confirmText = "Confirm"
|
||||||
export let showCancelButton = true
|
export let showCancelButton = true
|
||||||
export let showConfirmButton = true
|
export let showConfirmButton = true
|
||||||
|
@ -60,6 +61,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
{#if showConfirmButton}
|
{#if showConfirmButton}
|
||||||
<Button
|
<Button
|
||||||
|
{warning}
|
||||||
group
|
group
|
||||||
cta
|
cta
|
||||||
{...$$restProps}
|
{...$$restProps}
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
}
|
}
|
||||||
$: 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 !== ""))}
|
||||||
|
|
|
@ -214,7 +214,7 @@
|
||||||
>
|
>
|
||||||
<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}
|
||||||
|
@ -269,9 +269,10 @@
|
||||||
</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={() => dispatch("click", row)}
|
||||||
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}
|
||||||
|
@ -316,7 +317,15 @@
|
||||||
</tr>
|
</tr>
|
||||||
{/each}
|
{/each}
|
||||||
{:else}
|
{:else}
|
||||||
<div class="placeholder">
|
<tr class="placeholder-row">
|
||||||
|
{#if showEditColumn}
|
||||||
|
<td class="placeholder-offset" />
|
||||||
|
{/if}
|
||||||
|
{#each fields as field}
|
||||||
|
<td />
|
||||||
|
{/each}
|
||||||
|
<div class="placeholder" class:has-fields={fields.length > 0}>
|
||||||
|
<div class="placeholder-content">
|
||||||
<svg
|
<svg
|
||||||
class="spectrum-Icon spectrum-Icon--sizeXXL"
|
class="spectrum-Icon spectrum-Icon--sizeXXL"
|
||||||
focusable="false"
|
focusable="false"
|
||||||
|
@ -325,6 +334,8 @@
|
||||||
</svg>
|
</svg>
|
||||||
<div>No rows found</div>
|
<div>No rows found</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</tr>
|
||||||
{/if}
|
{/if}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
@ -347,7 +358,7 @@
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
.container.quiet {
|
.container.quiet {
|
||||||
border: none !important;
|
border: none;
|
||||||
}
|
}
|
||||||
table {
|
table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -381,7 +392,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;
|
||||||
|
@ -396,7 +407,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;
|
||||||
|
@ -407,12 +445,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 {
|
||||||
|
@ -431,17 +470,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;
|
||||||
|
|
|
@ -3,11 +3,20 @@
|
||||||
|
|
||||||
export let size = "M"
|
export let size = "M"
|
||||||
export let serif = false
|
export let serif = false
|
||||||
|
export let noPadding = false
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<p
|
<p
|
||||||
class="spectrum-Body class:spectrum-Body--size{size}"
|
class:noPadding
|
||||||
|
class="spectrum-Body spectrum-Body--size{size}"
|
||||||
class:spectrum-Body--serif={serif}
|
class:spectrum-Body--serif={serif}
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.noPadding {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -2407,10 +2407,10 @@ svelte-portal@^1.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/svelte-portal/-/svelte-portal-1.0.0.tgz#36a47c5578b1a4d9b4dc60fa32a904640ec4cdd3"
|
resolved "https://registry.yarnpkg.com/svelte-portal/-/svelte-portal-1.0.0.tgz#36a47c5578b1a4d9b4dc60fa32a904640ec4cdd3"
|
||||||
integrity sha512-nHf+DS/jZ6jjnZSleBMSaZua9JlG5rZv9lOGKgJuaZStfevtjIlUJrkLc3vbV8QdBvPPVmvcjTlazAzfKu0v3Q==
|
integrity sha512-nHf+DS/jZ6jjnZSleBMSaZua9JlG5rZv9lOGKgJuaZStfevtjIlUJrkLc3vbV8QdBvPPVmvcjTlazAzfKu0v3Q==
|
||||||
|
|
||||||
svelte@^3.37.0:
|
svelte@^3.38.2:
|
||||||
version "3.37.0"
|
version "3.38.2"
|
||||||
resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.37.0.tgz#dc7cd24bcc275cdb3f8c684ada89e50489144ccd"
|
resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.38.2.tgz#55e5c681f793ae349b5cc2fe58e5782af4275ef5"
|
||||||
integrity sha512-TRF30F4W4+d+Jr2KzUUL1j8Mrpns/WM/WacxYlo5MMb2E5Qy2Pk1Guj6GylxsW9OnKQl1tnF8q3hG/hQ3h6VUA==
|
integrity sha512-q5Dq0/QHh4BLJyEVWGe7Cej5NWs040LWjMbicBGZ+3qpFWJ1YObRmUDZKbbovddLC9WW7THTj3kYbTOFmU9fbg==
|
||||||
|
|
||||||
svgo@^1.0.0:
|
svgo@^1.0.0:
|
||||||
version "1.3.2"
|
version "1.3.2"
|
||||||
|
|
|
@ -74,7 +74,7 @@
|
||||||
"@spectrum-css/vars": "^3.0.1",
|
"@spectrum-css/vars": "^3.0.1",
|
||||||
"codemirror": "^5.59.0",
|
"codemirror": "^5.59.0",
|
||||||
"downloadjs": "1.4.7",
|
"downloadjs": "1.4.7",
|
||||||
"lodash": "4.17.13",
|
"lodash": "4.17.21",
|
||||||
"posthog-js": "1.4.5",
|
"posthog-js": "1.4.5",
|
||||||
"remixicon": "2.5.0",
|
"remixicon": "2.5.0",
|
||||||
"shortid": "2.2.15",
|
"shortid": "2.2.15",
|
||||||
|
@ -90,7 +90,7 @@
|
||||||
"@babel/preset-env": "^7.13.12",
|
"@babel/preset-env": "^7.13.12",
|
||||||
"@babel/runtime": "^7.13.10",
|
"@babel/runtime": "^7.13.10",
|
||||||
"@rollup/plugin-replace": "^2.4.2",
|
"@rollup/plugin-replace": "^2.4.2",
|
||||||
"@roxi/routify": "2.15.1",
|
"@roxi/routify": "2.18.0",
|
||||||
"@sveltejs/vite-plugin-svelte": "^1.0.0-next.5",
|
"@sveltejs/vite-plugin-svelte": "^1.0.0-next.5",
|
||||||
"@testing-library/jest-dom": "^5.11.10",
|
"@testing-library/jest-dom": "^5.11.10",
|
||||||
"@testing-library/svelte": "^3.0.0",
|
"@testing-library/svelte": "^3.0.0",
|
||||||
|
@ -106,7 +106,7 @@
|
||||||
"rollup": "^2.44.0",
|
"rollup": "^2.44.0",
|
||||||
"rollup-plugin-copy": "^3.4.0",
|
"rollup-plugin-copy": "^3.4.0",
|
||||||
"start-server-and-test": "^1.12.1",
|
"start-server-and-test": "^1.12.1",
|
||||||
"svelte": "^3.37.0",
|
"svelte": "^3.38.2",
|
||||||
"svelte-jester": "^1.3.2",
|
"svelte-jester": "^1.3.2",
|
||||||
"vite": "^2.1.5"
|
"vite": "^2.1.5"
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,90 @@
|
||||||
|
export const gradient = (node, config = {}) => {
|
||||||
|
const defaultConfig = {
|
||||||
|
points: 12,
|
||||||
|
saturation: 0.85,
|
||||||
|
lightness: 0.7,
|
||||||
|
softness: 0.9,
|
||||||
|
seed: null,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Applies a gradient background
|
||||||
|
const createGradient = config => {
|
||||||
|
config = {
|
||||||
|
...defaultConfig,
|
||||||
|
...config,
|
||||||
|
}
|
||||||
|
const { saturation, lightness, softness, points } = config
|
||||||
|
const seed = config.seed || Math.random().toString(32).substring(2)
|
||||||
|
|
||||||
|
// Hash function which returns a fixed hash between specified limits
|
||||||
|
// for a given seed and a given version
|
||||||
|
const rangeHash = (seed, min = 0, max = 100, version = 0) => {
|
||||||
|
const range = max - min
|
||||||
|
let hash = range + version
|
||||||
|
for (let i = 0; i < seed.length * 2 + version; i++) {
|
||||||
|
hash = (hash << 5) - hash + seed.charCodeAt(i % seed.length)
|
||||||
|
hash = ((hash & hash) % range) + version
|
||||||
|
}
|
||||||
|
return min + (hash % range)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generates a random HSL colour using the options specified
|
||||||
|
const randomHSL = (seed, version, alpha = 1) => {
|
||||||
|
const lowerSaturation = Math.min(100, saturation * 100)
|
||||||
|
const upperSaturation = Math.min(100, (saturation + 0.2) * 100)
|
||||||
|
const lowerLightness = Math.min(100, lightness * 100)
|
||||||
|
const upperLightness = Math.min(100, (lightness + 0.2) * 100)
|
||||||
|
const hue = rangeHash(seed, 0, 360, version)
|
||||||
|
const sat = `${rangeHash(
|
||||||
|
seed,
|
||||||
|
lowerSaturation,
|
||||||
|
upperSaturation,
|
||||||
|
version
|
||||||
|
)}%`
|
||||||
|
const light = `${rangeHash(
|
||||||
|
seed,
|
||||||
|
lowerLightness,
|
||||||
|
upperLightness,
|
||||||
|
version
|
||||||
|
)}%`
|
||||||
|
return `hsla(${hue},${sat},${light},${alpha})`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generates a radial gradient stop point
|
||||||
|
const randomGradientPoint = (seed, version) => {
|
||||||
|
const lowerTransparency = Math.min(100, softness * 100)
|
||||||
|
const upperTransparency = Math.min(100, (softness + 0.2) * 100)
|
||||||
|
const transparency = rangeHash(
|
||||||
|
seed,
|
||||||
|
lowerTransparency,
|
||||||
|
upperTransparency,
|
||||||
|
version
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
`radial-gradient(at ` +
|
||||||
|
`${rangeHash(seed, 0, 100, version)}% ` +
|
||||||
|
`${rangeHash(seed, 0, 100, version + 1)}%,` +
|
||||||
|
`${randomHSL(seed, version, saturation)} 0,` +
|
||||||
|
`transparent ${transparency}%)`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
let css = `opacity:0.9;background:${randomHSL(seed, 0, 0.7)};`
|
||||||
|
css += "background-image:"
|
||||||
|
for (let i = 0; i < points - 1; i++) {
|
||||||
|
css += `${randomGradientPoint(seed, i)},`
|
||||||
|
}
|
||||||
|
css += `${randomGradientPoint(seed, points)};`
|
||||||
|
node.style = css
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply the initial gradient
|
||||||
|
createGradient(config)
|
||||||
|
|
||||||
|
return {
|
||||||
|
// Apply a new gradient
|
||||||
|
update: config => {
|
||||||
|
createGradient(config)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { Input, Select, DatePicker, Toggle, TextArea } from "@budibase/bbui"
|
import { Input, Select, DatePicker, Toggle, TextArea } from "@budibase/bbui"
|
||||||
import Dropzone from "components/common/Dropzone.svelte"
|
import Dropzone from "components/common/Dropzone.svelte"
|
||||||
import { capitalise } from "../../../helpers"
|
import { capitalise } from "helpers"
|
||||||
import LinkedRowSelector from "components/common/LinkedRowSelector.svelte"
|
import LinkedRowSelector from "components/common/LinkedRowSelector.svelte"
|
||||||
|
|
||||||
export let defaultValue
|
export let defaultValue
|
||||||
|
|
|
@ -59,7 +59,7 @@
|
||||||
|
|
||||||
const selectRelationship = ({ tableId, rowId, fieldName }) => {
|
const selectRelationship = ({ tableId, rowId, fieldName }) => {
|
||||||
$goto(
|
$goto(
|
||||||
`/builder/${$params.application}/data/table/${tableId}/relationship/${rowId}/${fieldName}`
|
`/builder/app/${$params.application}/data/table/${tableId}/relationship/${rowId}/${fieldName}`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
Body,
|
Body,
|
||||||
ModalContent,
|
ModalContent,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import { capitalise } from "../../../../helpers"
|
import { capitalise } from "helpers"
|
||||||
|
|
||||||
export let resourceId
|
export let resourceId
|
||||||
export let permissions
|
export let permissions
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { Label, Input, Layout } from "@budibase/bbui"
|
import { Label, Input, Layout } from "@budibase/bbui"
|
||||||
import KeyValueBuilder from "components/integration/KeyValueBuilder.svelte"
|
import KeyValueBuilder from "components/integration/KeyValueBuilder.svelte"
|
||||||
import { capitalise } from "../../../../helpers"
|
import { capitalise } from "helpers"
|
||||||
|
|
||||||
export let integration
|
export let integration
|
||||||
export let schema
|
export let schema
|
||||||
|
|
|
@ -1,22 +1,12 @@
|
||||||
<script>
|
<script>
|
||||||
import { isActive, url, goto } from "@roxi/routify"
|
|
||||||
import { onMount } from "svelte"
|
|
||||||
import {
|
import {
|
||||||
ActionMenu,
|
ActionMenu,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
Body,
|
|
||||||
MenuItem,
|
MenuItem,
|
||||||
Icon,
|
|
||||||
Heading,
|
Heading,
|
||||||
Avatar,
|
|
||||||
Search,
|
|
||||||
Layout,
|
|
||||||
ProgressCircle,
|
ProgressCircle,
|
||||||
SideNavigation as Navigation,
|
|
||||||
SideNavigationItem as Item,
|
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import api from "builderStore/api"
|
import { admin } from "stores/portal"
|
||||||
import { organisation, admin } from "stores/portal"
|
|
||||||
|
|
||||||
const MESSAGES = {
|
const MESSAGES = {
|
||||||
apps: "Create your first app",
|
apps: "Create your first app",
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
export let cancelText = "Cancel"
|
export let cancelText = "Cancel"
|
||||||
export let onOk = undefined
|
export let onOk = undefined
|
||||||
export let onCancel = undefined
|
export let onCancel = undefined
|
||||||
|
export let warning = true
|
||||||
|
|
||||||
let modal
|
let modal
|
||||||
|
|
||||||
|
@ -19,7 +20,13 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal bind:this={modal} on:hide={onCancel}>
|
<Modal bind:this={modal} on:hide={onCancel}>
|
||||||
<ModalContent onConfirm={onOk} {title} confirmText={okText} {cancelText} red>
|
<ModalContent
|
||||||
|
onConfirm={onOk}
|
||||||
|
{title}
|
||||||
|
confirmText={okText}
|
||||||
|
{cancelText}
|
||||||
|
{warning}
|
||||||
|
>
|
||||||
<Body size="S">
|
<Body size="S">
|
||||||
{body}
|
{body}
|
||||||
<slot />
|
<slot />
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
getBindableProperties,
|
getBindableProperties,
|
||||||
readableToRuntimeBinding,
|
readableToRuntimeBinding,
|
||||||
} from "builderStore/dataBinding"
|
} from "builderStore/dataBinding"
|
||||||
import { currentAsset, store } from "../../../builderStore"
|
import { currentAsset, store } from "builderStore"
|
||||||
import { handlebarsCompletions } from "constants/completions"
|
import { handlebarsCompletions } from "constants/completions"
|
||||||
import { addToText } from "./utils"
|
import { addToText } from "./utils"
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
import { isValid } from "@budibase/string-templates"
|
import { isValid } from "@budibase/string-templates"
|
||||||
import { handlebarsCompletions } from "constants/completions"
|
import { handlebarsCompletions } from "constants/completions"
|
||||||
import { readableToRuntimeBinding } from "../../../builderStore/dataBinding"
|
import { readableToRuntimeBinding } from "builderStore/dataBinding"
|
||||||
import { addToText } from "./utils"
|
import { addToText } from "./utils"
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
import { store, currentAsset, selectedComponent } from "builderStore"
|
import { store, currentAsset, selectedComponent } from "builderStore"
|
||||||
import iframeTemplate from "./iframeTemplate"
|
import iframeTemplate from "./iframeTemplate"
|
||||||
import { Screen } from "builderStore/store/screenTemplates/utils/Screen"
|
import { Screen } from "builderStore/store/screenTemplates/utils/Screen"
|
||||||
import { FrontendTypes } from "../../../constants"
|
import { FrontendTypes } from "constants"
|
||||||
|
|
||||||
let iframe
|
let iframe
|
||||||
let layout
|
let layout
|
||||||
|
@ -82,7 +82,8 @@
|
||||||
style="height: 100%; width: 100%"
|
style="height: 100%; width: 100%"
|
||||||
title="componentPreview"
|
title="componentPreview"
|
||||||
bind:this={iframe}
|
bind:this={iframe}
|
||||||
srcdoc={template} />
|
srcdoc={template}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
runtimeToReadableBinding,
|
runtimeToReadableBinding,
|
||||||
} from "builderStore/dataBinding"
|
} from "builderStore/dataBinding"
|
||||||
import BindingPanel from "components/common/bindings/BindingPanel.svelte"
|
import BindingPanel from "components/common/bindings/BindingPanel.svelte"
|
||||||
import { capitalise } from "../../../../helpers"
|
import { capitalise } from "helpers"
|
||||||
|
|
||||||
export let label = ""
|
export let label = ""
|
||||||
export let bindable = true
|
export let bindable = true
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
|
import { goto } from "@roxi/routify"
|
||||||
import {
|
import {
|
||||||
notifications,
|
notifications,
|
||||||
Button,
|
|
||||||
Link,
|
Link,
|
||||||
Input,
|
Input,
|
||||||
Modal,
|
Modal,
|
||||||
|
@ -18,27 +18,18 @@
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
})
|
})
|
||||||
notifications.success("Logged in successfully.")
|
notifications.success("Logged in successfully")
|
||||||
|
$goto("../portal")
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
notifications.error("Invalid credentials")
|
notifications.error("Invalid credentials")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createTestUser() {
|
|
||||||
try {
|
|
||||||
await auth.firstUser()
|
|
||||||
notifications.success("Test user created")
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err)
|
|
||||||
notifications.error("Could not create test user")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal fixed>
|
<Modal fixed>
|
||||||
<ModalContent
|
<ModalContent
|
||||||
size="L"
|
size="M"
|
||||||
title="Log In"
|
title="Log In"
|
||||||
onConfirm={login}
|
onConfirm={login}
|
||||||
confirmText="Log In"
|
confirmText="Log In"
|
||||||
|
@ -51,7 +42,6 @@
|
||||||
<Link target="_blank" href="/api/admin/auth/google">
|
<Link target="_blank" href="/api/admin/auth/google">
|
||||||
Sign In With Google
|
Sign In With Google
|
||||||
</Link>
|
</Link>
|
||||||
<Button secondary on:click={createTestUser}>Create Test User</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
const id = $params.application
|
const id = $params.application
|
||||||
await del(`/api/applications/${id}`)
|
await del(`/api/applications/${id}`)
|
||||||
loading = false
|
loading = false
|
||||||
$goto("/builder/")
|
$goto("/builder")
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,72 +1,84 @@
|
||||||
<script>
|
<script>
|
||||||
import { goto } from "@roxi/routify"
|
import {
|
||||||
import { ActionButton, Heading } from "@budibase/bbui"
|
Heading,
|
||||||
import { notifications } from "@budibase/bbui"
|
Icon,
|
||||||
import Spinner from "components/common/Spinner.svelte"
|
Body,
|
||||||
import download from "downloadjs"
|
Layout,
|
||||||
|
ActionMenu,
|
||||||
|
MenuItem,
|
||||||
|
Link,
|
||||||
|
} from "@budibase/bbui"
|
||||||
|
import { gradient } from "actions"
|
||||||
|
import { url } from "@roxi/routify"
|
||||||
|
|
||||||
export let name, _id
|
export let app
|
||||||
|
export let exportApp
|
||||||
let appExportLoading = false
|
export let deleteApp
|
||||||
|
|
||||||
async function exportApp() {
|
|
||||||
appExportLoading = true
|
|
||||||
try {
|
|
||||||
download(
|
|
||||||
`/api/backups/export?appId=${_id}&appname=${encodeURIComponent(name)}`
|
|
||||||
)
|
|
||||||
notifications.success("App Export Complete.")
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err)
|
|
||||||
notifications.error("App Export Failed.")
|
|
||||||
} finally {
|
|
||||||
appExportLoading = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="apps-card">
|
<div class="wrapper">
|
||||||
<Heading size="S">{name}</Heading>
|
<Layout noPadding gap="XS" alignContent="start">
|
||||||
<div class="card-footer" data-cy={`app-${name}`}>
|
<div class="preview" use:gradient={{ seed: app.name }} />
|
||||||
<ActionButton on:click={() => $goto(`/builder/${_id}`)}>
|
<div class="title">
|
||||||
Open
|
<Link href={$url(`../../app/${app._id}`)}>
|
||||||
{name}
|
<Heading size="XS">
|
||||||
→
|
{app.name}
|
||||||
</ActionButton>
|
</Heading>
|
||||||
{#if appExportLoading}
|
</Link>
|
||||||
<Spinner size="10" />
|
<ActionMenu align="right">
|
||||||
{:else}
|
<Icon slot="control" name="More" hoverable />
|
||||||
<ActionButton icon="Download" quiet />
|
<MenuItem on:click={() => exportApp(app)} icon="Download">
|
||||||
|
Export
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem on:click={() => deleteApp(app)} icon="Delete">
|
||||||
|
Delete
|
||||||
|
</MenuItem>
|
||||||
|
</ActionMenu>
|
||||||
|
</div>
|
||||||
|
<div class="status">
|
||||||
|
<Body noPadding size="S">
|
||||||
|
Edited {Math.floor(1 + Math.random() * 10)} months ago
|
||||||
|
</Body>
|
||||||
|
{#if Math.random() > 0.5}
|
||||||
|
<Icon name="LockClosed" />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
</Layout>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.apps-card {
|
.wrapper {
|
||||||
background-color: var(--background);
|
overflow: hidden;
|
||||||
padding: var(--spacing-xl) var(--spacing-xl) var(--spacing-xl)
|
}
|
||||||
var(--spacing-xl);
|
.preview {
|
||||||
max-width: 300px;
|
height: 135px;
|
||||||
max-height: 150px;
|
border-radius: var(--border-radius-s);
|
||||||
border-radius: var(--border-radius-m);
|
margin-bottom: var(--spacing-s);
|
||||||
border: var(--border-dark);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-footer {
|
.title,
|
||||||
|
.status {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-top: var(--spacing-m);
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
i {
|
.title :global(a) {
|
||||||
font-size: var(--font-size-l);
|
text-decoration: none;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
margin-right: var(--spacing-m);
|
||||||
|
}
|
||||||
|
.title :global(h1) {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
.title :global(h1:hover) {
|
||||||
|
color: var(--spectrum-global-color-blue-600);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: 0.2s all;
|
transition: color 130ms ease;
|
||||||
}
|
|
||||||
|
|
||||||
i:hover {
|
|
||||||
color: var(--blue);
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,50 +0,0 @@
|
||||||
<script>
|
|
||||||
import AppCard from "./AppCard.svelte"
|
|
||||||
import { Heading, Divider } from "@budibase/bbui"
|
|
||||||
import Spinner from "components/common/Spinner.svelte"
|
|
||||||
import { get } from "builderStore/api"
|
|
||||||
|
|
||||||
let promise = getApps()
|
|
||||||
|
|
||||||
async function getApps() {
|
|
||||||
const res = await get("/api/applications")
|
|
||||||
const json = await res.json()
|
|
||||||
|
|
||||||
if (res.ok) {
|
|
||||||
return json
|
|
||||||
} else {
|
|
||||||
throw new Error(json)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="root">
|
|
||||||
<Heading size="M">Your Apps</Heading>
|
|
||||||
<Divider size="M" />
|
|
||||||
{#await promise}
|
|
||||||
<div class="spinner-container">
|
|
||||||
<Spinner size="30" />
|
|
||||||
</div>
|
|
||||||
{:then apps}
|
|
||||||
<div class="apps">
|
|
||||||
{#each apps as app}
|
|
||||||
<AppCard {...app} />
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
{:catch err}
|
|
||||||
<h1 style="color:red">{err}</h1>
|
|
||||||
{/await}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.root {
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
.apps {
|
|
||||||
margin-top: var(--layout-m);
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
|
||||||
grid-gap: var(--layout-s);
|
|
||||||
justify-content: start;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
<script>
|
||||||
|
import { gradient } from "actions"
|
||||||
|
import {
|
||||||
|
Heading,
|
||||||
|
Button,
|
||||||
|
Icon,
|
||||||
|
ActionMenu,
|
||||||
|
MenuItem,
|
||||||
|
Link,
|
||||||
|
} from "@budibase/bbui"
|
||||||
|
import { url } from "@roxi/routify"
|
||||||
|
|
||||||
|
export let app
|
||||||
|
export let openApp
|
||||||
|
export let exportApp
|
||||||
|
export let deleteApp
|
||||||
|
export let last
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="title" class:last>
|
||||||
|
<div class="preview" use:gradient={{ seed: app.name }} />
|
||||||
|
<Link href={$url(`../../app/${app._id}`)}>
|
||||||
|
<Heading size="XS">
|
||||||
|
{app.name}
|
||||||
|
</Heading>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<div class:last>
|
||||||
|
Edited {Math.round(Math.random() * 10 + 1)} months ago
|
||||||
|
</div>
|
||||||
|
<div class:last>
|
||||||
|
{#if Math.random() < 0.33}
|
||||||
|
<div class="status status--open" />
|
||||||
|
Open
|
||||||
|
{:else if Math.random() < 0.33}
|
||||||
|
<div class="status status--locked-other" />
|
||||||
|
Locked by Will Wheaton
|
||||||
|
{:else}
|
||||||
|
<div class="status status--locked-you" />
|
||||||
|
Locked by you
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<div class:last>
|
||||||
|
<Button on:click={() => openApp(app)} size="S" secondary>Open</Button>
|
||||||
|
<ActionMenu align="right">
|
||||||
|
<Icon hoverable slot="control" name="More" />
|
||||||
|
<MenuItem on:click={() => exportApp(app)} icon="Download">Export</MenuItem>
|
||||||
|
<MenuItem on:click={() => deleteApp(app)} icon="Delete">Delete</MenuItem>
|
||||||
|
</ActionMenu>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.preview {
|
||||||
|
height: 40px;
|
||||||
|
width: 40px;
|
||||||
|
border-radius: var(--border-radius-s);
|
||||||
|
}
|
||||||
|
.title :global(a) {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.title :global(h1:hover) {
|
||||||
|
color: var(--spectrum-global-color-blue-600);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color 130ms ease;
|
||||||
|
}
|
||||||
|
.status {
|
||||||
|
height: 10px;
|
||||||
|
width: 10px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
.status--locked-you {
|
||||||
|
background-color: var(--spectrum-global-color-orange-600);
|
||||||
|
}
|
||||||
|
.status--locked-other {
|
||||||
|
background-color: var(--spectrum-global-color-red-600);
|
||||||
|
}
|
||||||
|
.status--open {
|
||||||
|
background-color: var(--spectrum-global-color-green-600);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,15 +0,0 @@
|
||||||
<script>
|
|
||||||
import { Button, Modal } from "@budibase/bbui"
|
|
||||||
import BuilderSettingsModal from "./BuilderSettingsModal.svelte"
|
|
||||||
|
|
||||||
let modal
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<Button primary quiet icon="Settings" text on:click={modal.show}>
|
|
||||||
Settings
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<Modal bind:this={modal} width="30%">
|
|
||||||
<BuilderSettingsModal />
|
|
||||||
</Modal>
|
|
|
@ -1,58 +1,50 @@
|
||||||
<script>
|
<script>
|
||||||
import { writable, get as svelteGet } from "svelte/store"
|
import { writable, get as svelteGet } from "svelte/store"
|
||||||
import { notifications, Heading, Button } from "@budibase/bbui"
|
import {
|
||||||
|
notifications,
|
||||||
|
Input,
|
||||||
|
ModalContent,
|
||||||
|
Dropzone,
|
||||||
|
Body,
|
||||||
|
Checkbox,
|
||||||
|
} from "@budibase/bbui"
|
||||||
import { store, automationStore, hostingStore } from "builderStore"
|
import { store, automationStore, hostingStore } from "builderStore"
|
||||||
import { string, object } from "yup"
|
import { string, mixed, object } from "yup"
|
||||||
import api, { get } from "builderStore/api"
|
import api, { get } from "builderStore/api"
|
||||||
import Spinner from "components/common/Spinner.svelte"
|
|
||||||
import { Info, User } from "./Steps"
|
|
||||||
import Indicator from "./Indicator.svelte"
|
|
||||||
import { goto } from "@roxi/routify"
|
|
||||||
import { fade } from "svelte/transition"
|
|
||||||
import { post } from "builderStore/api"
|
import { post } from "builderStore/api"
|
||||||
import analytics from "analytics"
|
import analytics from "analytics"
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
import Logo from "/assets/bb-logo.svg"
|
import { capitalise } from "helpers"
|
||||||
import { capitalise } from "../../helpers"
|
import { goto } from "@roxi/routify"
|
||||||
|
|
||||||
export let template
|
export let template
|
||||||
|
|
||||||
const currentStep = writable(0)
|
const values = writable({ name: null })
|
||||||
const values = writable({ roleId: "ADMIN" })
|
|
||||||
const errors = writable({})
|
const errors = writable({})
|
||||||
const touched = writable({})
|
const touched = writable({})
|
||||||
const steps = [Info, User]
|
const validator = {
|
||||||
let validators = [
|
name: string().required("Your application must have a name"),
|
||||||
{
|
file: template ? mixed().required("Please choose a file to import") : null,
|
||||||
applicationName: string().required("Your application must have a name"),
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
roleId: string()
|
|
||||||
.nullable()
|
|
||||||
.required("You need to select a role for this app"),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
let submitting = false
|
let submitting = false
|
||||||
let valid = false
|
let valid = false
|
||||||
$: checkValidity($values, validators[$currentStep])
|
$: checkValidity($values, validator)
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
const hostingInfo = await hostingStore.actions.fetch()
|
|
||||||
if (hostingInfo.type === "self") {
|
|
||||||
await hostingStore.actions.fetchDeployedApps()
|
await hostingStore.actions.fetchDeployedApps()
|
||||||
const existingAppNames = svelteGet(hostingStore).deployedAppNames
|
const existingAppNames = svelteGet(hostingStore).deployedAppNames
|
||||||
validators[0].applicationName = string()
|
validator.name = string()
|
||||||
.required("Your application must have a name.")
|
.required("Your application must have a name")
|
||||||
.test(
|
.test(
|
||||||
"non-existing-app-name",
|
"non-existing-app-name",
|
||||||
"App with same name already exists. Please try another app name.",
|
"Another app with the same name already exists",
|
||||||
value =>
|
value => {
|
||||||
!existingAppNames.some(
|
return !existingAppNames.some(
|
||||||
appName => appName.toLowerCase() === value.toLowerCase()
|
appName => appName.toLowerCase() === value.toLowerCase()
|
||||||
)
|
)
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const checkValidity = async (values, validator) => {
|
const checkValidity = async (values, validator) => {
|
||||||
|
@ -70,15 +62,24 @@
|
||||||
|
|
||||||
async function createNewApp() {
|
async function createNewApp() {
|
||||||
submitting = true
|
submitting = true
|
||||||
|
|
||||||
|
// Check a template exists if we are important
|
||||||
|
if (template && !$values.file) {
|
||||||
|
$errors.file = "Please choose a file to import"
|
||||||
|
valid = false
|
||||||
|
submitting = false
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Create form data to create app
|
// Create form data to create app
|
||||||
let data = new FormData()
|
let data = new FormData()
|
||||||
data.append("name", $values.applicationName)
|
data.append("name", $values.name)
|
||||||
data.append("useTemplate", template != null)
|
data.append("useTemplate", template != null)
|
||||||
if (template) {
|
if (template) {
|
||||||
data.append("templateName", template.name)
|
data.append("templateName", template.name)
|
||||||
data.append("templateKey", template.key)
|
data.append("templateKey", template.key)
|
||||||
data.append("templateFile", template.file)
|
data.append("templateFile", $values.file)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create App
|
// Create App
|
||||||
|
@ -89,7 +90,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
analytics.captureEvent("App Created", {
|
analytics.captureEvent("App Created", {
|
||||||
name: $values.applicationName,
|
name: $values.name,
|
||||||
appId: appJson._id,
|
appId: appJson._id,
|
||||||
template,
|
template,
|
||||||
})
|
})
|
||||||
|
@ -112,7 +113,7 @@
|
||||||
}
|
}
|
||||||
const userResp = await api.post(`/api/users/metadata/self`, user)
|
const userResp = await api.post(`/api/users/metadata/self`, user)
|
||||||
await userResp.json()
|
await userResp.json()
|
||||||
$goto(`./${appJson._id}`)
|
$goto(`/builder/app/${appJson._id}`)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
notifications.error(error)
|
notifications.error(error)
|
||||||
|
@ -121,129 +122,33 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="container">
|
<ModalContent
|
||||||
<div class="sidebar">
|
title={template ? "Import app" : "Create new app"}
|
||||||
<img src={Logo} alt="budibase icon" />
|
confirmText={template ? "Import app" : "Create app"}
|
||||||
<div class="steps">
|
onConfirm={createNewApp}
|
||||||
{#each steps as component, i}
|
disabled={!valid}
|
||||||
<Indicator
|
>
|
||||||
active={$currentStep === i}
|
{#if template}
|
||||||
done={i < $currentStep}
|
<Dropzone
|
||||||
step={i + 1}
|
error={$touched.file && $errors.file}
|
||||||
|
gallery={false}
|
||||||
|
label="File to import"
|
||||||
|
value={[$values.file]}
|
||||||
|
on:change={e => {
|
||||||
|
$values.file = e.detail?.[0]
|
||||||
|
$touched.file = true
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/if}
|
||||||
</div>
|
<Body size="S">
|
||||||
</div>
|
Give your new app a name, and choose which groups have access (paid plans
|
||||||
<div class="body">
|
only).
|
||||||
<div class="heading">
|
</Body>
|
||||||
<Heading size="L">Get started with Budibase</Heading>
|
<Input
|
||||||
</div>
|
bind:value={$values.name}
|
||||||
<div class="step">
|
error={$touched.name && $errors.name}
|
||||||
{#each steps as component, i (i)}
|
on:blur={() => ($touched.name = true)}
|
||||||
<div class:hidden={$currentStep !== i}>
|
label="Name"
|
||||||
<svelte:component
|
|
||||||
this={component}
|
|
||||||
{template}
|
|
||||||
{values}
|
|
||||||
{errors}
|
|
||||||
{touched}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
<Checkbox label="Group access" disabled value={true} text="All users" />
|
||||||
{/each}
|
</ModalContent>
|
||||||
</div>
|
|
||||||
<div class="footer">
|
|
||||||
{#if $currentStep > 0}
|
|
||||||
<Button medium secondary on:click={() => $currentStep--}>Back</Button>
|
|
||||||
{/if}
|
|
||||||
{#if $currentStep < steps.length - 1}
|
|
||||||
<Button medium cta on:click={() => $currentStep++} disabled={!valid}>
|
|
||||||
Next
|
|
||||||
</Button>
|
|
||||||
{/if}
|
|
||||||
{#if $currentStep === steps.length - 1}
|
|
||||||
<Button
|
|
||||||
medium
|
|
||||||
cta
|
|
||||||
on:click={createNewApp}
|
|
||||||
disabled={!valid || submitting}
|
|
||||||
>
|
|
||||||
{submitting ? "Loading..." : "Submit"}
|
|
||||||
</Button>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{#if submitting}
|
|
||||||
<div in:fade class="spinner-container">
|
|
||||||
<Spinner />
|
|
||||||
<span class="spinner-text">Creating your app...</span>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.container {
|
|
||||||
min-height: 600px;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 80px 1fr;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.sidebar {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: stretch;
|
|
||||||
padding: 40px 0;
|
|
||||||
background: var(--grey-1);
|
|
||||||
}
|
|
||||||
.steps {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
display: grid;
|
|
||||||
border-bottom-left-radius: 0.5rem;
|
|
||||||
border-top-left-radius: 0.5rem;
|
|
||||||
grid-gap: 30px;
|
|
||||||
align-content: center;
|
|
||||||
}
|
|
||||||
.heading {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
.body {
|
|
||||||
padding: 40px 60px 40px 60px;
|
|
||||||
display: grid;
|
|
||||||
align-items: center;
|
|
||||||
grid-template-rows: auto 1fr auto;
|
|
||||||
}
|
|
||||||
.footer {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: flex-end;
|
|
||||||
align-items: center;
|
|
||||||
gap: 15px;
|
|
||||||
}
|
|
||||||
.spinner-container {
|
|
||||||
background: var(--background);
|
|
||||||
position: absolute;
|
|
||||||
border-radius: 5px;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
display: grid;
|
|
||||||
justify-items: center;
|
|
||||||
align-content: center;
|
|
||||||
grid-gap: 50px;
|
|
||||||
}
|
|
||||||
.spinner-text {
|
|
||||||
font-size: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hidden {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
img {
|
|
||||||
height: 40px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -1,83 +0,0 @@
|
||||||
<script>
|
|
||||||
export let step, done, active
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="container" class:active class:done>
|
|
||||||
<div class="circle" class:active class:done>
|
|
||||||
{#if done}
|
|
||||||
<svg
|
|
||||||
width="12"
|
|
||||||
height="10"
|
|
||||||
viewBox="0 0 12 10"
|
|
||||||
fill="none"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fill-rule="evenodd"
|
|
||||||
clip-rule="evenodd"
|
|
||||||
d="M10.1212 0.319527C10.327 0.115582 10.6047 0.000803464 10.8944
|
|
||||||
4.20219e-06C11.1841 -0.00079506 11.4624 0.11245 11.6693
|
|
||||||
0.315256C11.8762 0.518062 11.9949 0.794134 11.9998 1.08379C12.0048
|
|
||||||
1.37344 11.8955 1.65339 11.6957 1.86313L5.82705 9.19893C5.72619
|
|
||||||
9.30757 5.60445 9.39475 5.46913 9.45527C5.3338 9.51578 5.18766 9.54839
|
|
||||||
5.03944 9.55113C4.89123 9.55388 4.74398 9.52671 4.60651
|
|
||||||
9.47124C4.46903 9.41578 4.34416 9.33316 4.23934 9.22833L0.350925
|
|
||||||
5.33845C0.242598 5.23751 0.155712 5.11578 0.0954499 4.98054C0.0351876
|
|
||||||
4.84529 0.00278364 4.69929 0.00017159 4.55124C-0.00244046 4.4032
|
|
||||||
0.024793 4.25615 0.0802466 4.11886C0.1357 3.98157 0.218238 3.85685
|
|
||||||
0.322937 3.75215C0.427636 3.64746 0.55235 3.56492 0.68964
|
|
||||||
3.50946C0.82693 3.45401 0.973983 3.42678 1.12203 3.42939C1.27007 3.432
|
|
||||||
1.41607 3.46441 1.55132 3.52467C1.68657 3.58493 1.80829 3.67182
|
|
||||||
1.90923 3.78014L4.98762 6.85706L10.0933 0.35187C10.1024 0.340482
|
|
||||||
10.1122 0.329679 10.1227 0.319527H10.1212Z"
|
|
||||||
fill="var(--background)"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
{:else}{step}{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.container::before {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
top: -30px;
|
|
||||||
width: 1px;
|
|
||||||
height: 30px;
|
|
||||||
background: var(--grey-5);
|
|
||||||
}
|
|
||||||
.container:first-child::before {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.container {
|
|
||||||
position: relative;
|
|
||||||
height: 45px;
|
|
||||||
display: grid;
|
|
||||||
place-items: center;
|
|
||||||
}
|
|
||||||
.container.active {
|
|
||||||
box-shadow: inset 3px 0 0 0 var(--blue);
|
|
||||||
}
|
|
||||||
.circle.active {
|
|
||||||
background: var(--blue);
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
.circle.done {
|
|
||||||
background: var(--grey-5);
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
.circle {
|
|
||||||
color: var(--grey-5);
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
display: grid;
|
|
||||||
place-items: center;
|
|
||||||
width: 30px;
|
|
||||||
height: 30px;
|
|
||||||
border-radius: 50%;
|
|
||||||
border: 1px solid var(--grey-5);
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,6 +0,0 @@
|
||||||
<script>
|
|
||||||
import { Button } from "@budibase/bbui"
|
|
||||||
import { auth } from "stores/backend"
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<Button primary quiet text icon="LogOut" on:click={auth.logout}>Log Out</Button>
|
|
|
@ -1,121 +0,0 @@
|
||||||
<script>
|
|
||||||
import { Label, Heading, Input, notifications } from "@budibase/bbui"
|
|
||||||
|
|
||||||
const BYTES_IN_MB = 1000000
|
|
||||||
const FILE_SIZE_LIMIT = BYTES_IN_MB * 5
|
|
||||||
|
|
||||||
export let template
|
|
||||||
export let values
|
|
||||||
export let errors
|
|
||||||
export let touched
|
|
||||||
|
|
||||||
let blurred = { appName: false }
|
|
||||||
let file
|
|
||||||
|
|
||||||
function handleFile(evt) {
|
|
||||||
const fileArray = Array.from(evt.target.files)
|
|
||||||
if (fileArray.some(file => file.size >= FILE_SIZE_LIMIT)) {
|
|
||||||
notifications.error(
|
|
||||||
`Files cannot exceed ${
|
|
||||||
FILE_SIZE_LIMIT / BYTES_IN_MB
|
|
||||||
}MB. Please try again with smaller files.`
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
file = evt.target.files[0]
|
|
||||||
template.file = file
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="container">
|
|
||||||
{#if template?.fromFile}
|
|
||||||
<Heading size="L">Import your Web App</Heading>
|
|
||||||
{:else}
|
|
||||||
<Heading size="L">Create your Web App</Heading>
|
|
||||||
{/if}
|
|
||||||
{#if template?.fromFile}
|
|
||||||
<div class="template">
|
|
||||||
<Label extraSmall grey>Import File</Label>
|
|
||||||
<div class="dropzone">
|
|
||||||
<input
|
|
||||||
id="file-upload"
|
|
||||||
accept=".txt"
|
|
||||||
type="file"
|
|
||||||
on:change={handleFile}
|
|
||||||
/>
|
|
||||||
<label for="file-upload" class:uploaded={file}>
|
|
||||||
{#if file}{file.name}{:else}Import{/if}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{:else if template}
|
|
||||||
<div class="template">
|
|
||||||
<Label extraSmall grey>Selected Template</Label>
|
|
||||||
<Heading size="S">{template.name}</Heading>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
<Input
|
|
||||||
on:change={() => ($touched.applicationName = true)}
|
|
||||||
bind:value={$values.applicationName}
|
|
||||||
label="Web App Name"
|
|
||||||
placeholder="Enter name of your web application"
|
|
||||||
error={$touched.applicationName && $errors.applicationName}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.container {
|
|
||||||
display: grid;
|
|
||||||
grid-gap: var(--spacing-xl);
|
|
||||||
margin-top: var(--spacing-xl);
|
|
||||||
}
|
|
||||||
|
|
||||||
.template :global(label) {
|
|
||||||
/* Fix layout due to LH 0 on heading */
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropzone {
|
|
||||||
text-align: center;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
flex-direction: column;
|
|
||||||
border-radius: 10px;
|
|
||||||
transition: all 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.uploaded {
|
|
||||||
color: var(--blue);
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="file"] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
|
||||||
font-family: var(--font-sans);
|
|
||||||
cursor: pointer;
|
|
||||||
font-weight: 500;
|
|
||||||
box-sizing: border-box;
|
|
||||||
overflow: hidden;
|
|
||||||
border-radius: var(--border-radius-s);
|
|
||||||
color: var(--ink);
|
|
||||||
padding: var(--spacing-m) var(--spacing-l);
|
|
||||||
transition: all 0.2s ease 0s;
|
|
||||||
display: inline-flex;
|
|
||||||
text-rendering: optimizeLegibility;
|
|
||||||
min-width: auto;
|
|
||||||
outline: none;
|
|
||||||
font-feature-settings: "case" 1, "rlig" 1, "calt" 0;
|
|
||||||
-webkit-box-align: center;
|
|
||||||
user-select: none;
|
|
||||||
flex-shrink: 0;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 100%;
|
|
||||||
background-color: var(--grey-2);
|
|
||||||
font-size: var(--font-size-xs);
|
|
||||||
line-height: normal;
|
|
||||||
border: var(--border-transparent);
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,29 +0,0 @@
|
||||||
<script>
|
|
||||||
import { Select, Heading } from "@budibase/bbui"
|
|
||||||
|
|
||||||
export let values
|
|
||||||
export let errors
|
|
||||||
export let touched
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="container">
|
|
||||||
<Heading size="L">What's your role for this app?</Heading>
|
|
||||||
<Select
|
|
||||||
bind:value={$values.roleId}
|
|
||||||
label="Role"
|
|
||||||
options={[
|
|
||||||
{ label: "Admin", value: "ADMIN" },
|
|
||||||
{ label: "Power User", value: "POWER_USER" },
|
|
||||||
]}
|
|
||||||
getOptionLabel={option => option.label}
|
|
||||||
getOptionValue={option => option.value}
|
|
||||||
error={$errors.roleId}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.container {
|
|
||||||
display: grid;
|
|
||||||
grid-gap: var(--spacing-xl);
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,2 +0,0 @@
|
||||||
export { default as Info } from "./Info.svelte"
|
|
||||||
export { default as User } from "./User.svelte"
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { writable } from 'svelte/store'
|
||||||
|
import api from "builderStore/api"
|
||||||
|
|
||||||
|
export function fetchData (url) {
|
||||||
|
const store = writable({status: 'LOADING', data: {}, error: {}})
|
||||||
|
|
||||||
|
async function get() {
|
||||||
|
store.update(u => ({...u, status: 'SUCCESS'}))
|
||||||
|
try {
|
||||||
|
const response = await api.get(url)
|
||||||
|
store.set({data: await response.json(), status: 'SUCCESS'})
|
||||||
|
} catch(e) {
|
||||||
|
store.set({data: {}, error: e, status: 'ERROR'})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get()
|
||||||
|
|
||||||
|
return {subscribe: store.subscribe, refresh: get}
|
||||||
|
}
|
|
@ -1,29 +0,0 @@
|
||||||
<script>
|
|
||||||
import { onMount } from "svelte"
|
|
||||||
import { goto } from "@roxi/routify"
|
|
||||||
import {
|
|
||||||
SideNavigation as Navigation,
|
|
||||||
SideNavigationItem as Item,
|
|
||||||
} from "@budibase/bbui"
|
|
||||||
import { admin } from "stores/portal"
|
|
||||||
import LoginForm from "components/login/LoginForm.svelte"
|
|
||||||
import BuilderSettingsButton from "components/start/BuilderSettingsButton.svelte"
|
|
||||||
import LogoutButton from "components/start/LogoutButton.svelte"
|
|
||||||
import Logo from "/assets/budibase-logo.svg"
|
|
||||||
import api from "builderStore/api"
|
|
||||||
|
|
||||||
let checklist
|
|
||||||
|
|
||||||
onMount(async () => {
|
|
||||||
await admin.init()
|
|
||||||
if (!$admin?.checklist?.adminUser) {
|
|
||||||
$goto("./admin")
|
|
||||||
} else {
|
|
||||||
$goto("./portal")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if $admin.checklist}
|
|
||||||
<slot />
|
|
||||||
{/if}
|
|
|
@ -1,69 +0,0 @@
|
||||||
<script>
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
Heading,
|
|
||||||
Label,
|
|
||||||
notifications,
|
|
||||||
Layout,
|
|
||||||
Input,
|
|
||||||
Body,
|
|
||||||
} from "@budibase/bbui"
|
|
||||||
import { goto } from "@roxi/routify"
|
|
||||||
import { onMount } from "svelte"
|
|
||||||
import api from "builderStore/api"
|
|
||||||
|
|
||||||
let adminUser = {}
|
|
||||||
|
|
||||||
async function save() {
|
|
||||||
try {
|
|
||||||
// Save the admin user
|
|
||||||
const response = await api.post(`/api/admin/users/init`, adminUser)
|
|
||||||
|
|
||||||
const json = await response.json()
|
|
||||||
if (response.status !== 200) throw new Error(json.message)
|
|
||||||
notifications.success(`Admin user created.`)
|
|
||||||
$goto("../portal")
|
|
||||||
} catch (err) {
|
|
||||||
notifications.error(`Failed to create admin user.`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<div class="container">
|
|
||||||
<header>
|
|
||||||
<Heading size="M">Create an admin user</Heading>
|
|
||||||
<Body size="S">The admin user has access to everything in budibase.</Body>
|
|
||||||
</header>
|
|
||||||
<div class="config-form">
|
|
||||||
<Layout gap="S">
|
|
||||||
<Input label="email" bind:value={adminUser.email} />
|
|
||||||
<Input
|
|
||||||
label="password"
|
|
||||||
type="password"
|
|
||||||
bind:value={adminUser.password}
|
|
||||||
/>
|
|
||||||
<Button cta on:click={save}>Create super admin user</Button>
|
|
||||||
</Layout>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
section {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
header {
|
|
||||||
text-align: center;
|
|
||||||
width: 80%;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.config-form {
|
|
||||||
margin-bottom: 42px;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,116 +1,33 @@
|
||||||
<script>
|
<script>
|
||||||
import {
|
import { onMount } from "svelte"
|
||||||
SideNavigation as Navigation,
|
import { goto } from "@roxi/routify"
|
||||||
SideNavigationItem as Item,
|
|
||||||
} from "@budibase/bbui"
|
|
||||||
import { auth } from "stores/backend"
|
import { auth } from "stores/backend"
|
||||||
import LoginForm from "components/login/LoginForm.svelte"
|
import { admin } from "stores/portal"
|
||||||
import BuilderSettingsButton from "components/start/BuilderSettingsButton.svelte"
|
|
||||||
import LogoutButton from "components/start/LogoutButton.svelte"
|
|
||||||
import Logo from "/assets/budibase-logo.svg"
|
|
||||||
|
|
||||||
let modal
|
let loaded = false
|
||||||
|
$: hasAdminUser = !!$admin?.checklist?.adminUser
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
await admin.init()
|
||||||
|
await auth.checkAuth()
|
||||||
|
loaded = true
|
||||||
|
})
|
||||||
|
|
||||||
|
// Force creation of an admin user if one doesn't exist
|
||||||
|
$: {
|
||||||
|
if (loaded && !hasAdminUser) {
|
||||||
|
$goto("./admin")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redirect to log in at any time if the user isn't authenticated
|
||||||
|
$: {
|
||||||
|
if (loaded && hasAdminUser && !$auth.user) {
|
||||||
|
$goto("./auth/login")
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if $auth}
|
{#if loaded}
|
||||||
{#if $auth.user}
|
|
||||||
<div class="root">
|
|
||||||
<div class="ui-nav">
|
|
||||||
<div class="home-logo">
|
|
||||||
<img src={Logo} alt="Budibase icon" />
|
|
||||||
</div>
|
|
||||||
<div class="nav-section">
|
|
||||||
<div class="nav-top">
|
|
||||||
<Navigation>
|
|
||||||
<Item href="/builder/" icon="Apps" selected>Apps</Item>
|
|
||||||
<Item external href="https://portal.budi.live/" icon="Servers">
|
|
||||||
Hosting
|
|
||||||
</Item>
|
|
||||||
<Item external href="https://docs.budibase.com/" icon="Book">
|
|
||||||
Documentation
|
|
||||||
</Item>
|
|
||||||
<Item
|
|
||||||
external
|
|
||||||
href="https://github.com/Budibase/budibase/discussions"
|
|
||||||
icon="PeopleGroup"
|
|
||||||
>
|
|
||||||
Community
|
|
||||||
</Item>
|
|
||||||
<Item
|
|
||||||
external
|
|
||||||
href="https://github.com/Budibase/budibase/issues/new/choose"
|
|
||||||
icon="Bug"
|
|
||||||
>
|
|
||||||
Raise an issue
|
|
||||||
</Item>
|
|
||||||
</Navigation>
|
|
||||||
</div>
|
|
||||||
<div class="nav-bottom">
|
|
||||||
<BuilderSettingsButton />
|
|
||||||
<LogoutButton />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="main">
|
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{:else}
|
|
||||||
<section class="login">
|
|
||||||
<LoginForm />
|
|
||||||
</section>
|
|
||||||
{/if}
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style>
|
|
||||||
.root {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 260px 1fr;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main {
|
|
||||||
grid-column: 2;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ui-nav {
|
|
||||||
grid-column: 1;
|
|
||||||
background-color: var(--background);
|
|
||||||
padding: 20px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
border-right: var(--border-light);
|
|
||||||
}
|
|
||||||
|
|
||||||
.home-logo {
|
|
||||||
cursor: pointer;
|
|
||||||
height: 40px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.home-logo img {
|
|
||||||
height: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-section {
|
|
||||||
margin: 20px 0 0 0;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: space-between;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-bottom :global(> *) {
|
|
||||||
margin-top: 5px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
<script>
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Heading,
|
||||||
|
notifications,
|
||||||
|
Layout,
|
||||||
|
Input,
|
||||||
|
Body,
|
||||||
|
} from "@budibase/bbui"
|
||||||
|
import { goto } from "@roxi/routify"
|
||||||
|
import api from "builderStore/api"
|
||||||
|
import { admin } from "stores/portal"
|
||||||
|
|
||||||
|
let adminUser = {}
|
||||||
|
|
||||||
|
async function save() {
|
||||||
|
try {
|
||||||
|
// Save the admin user
|
||||||
|
const response = await api.post(`/api/admin/users/init`, adminUser)
|
||||||
|
const json = await response.json()
|
||||||
|
if (response.status !== 200) {
|
||||||
|
throw new Error(json.message)
|
||||||
|
}
|
||||||
|
notifications.success(`Admin user created`)
|
||||||
|
await admin.init()
|
||||||
|
$goto("../portal")
|
||||||
|
} catch (err) {
|
||||||
|
notifications.error(`Failed to create admin user`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<div class="container">
|
||||||
|
<Layout gap="XS">
|
||||||
|
<img src="https://i.imgur.com/ZKyklgF.png" />
|
||||||
|
</Layout>
|
||||||
|
<div class="center">
|
||||||
|
<Layout gap="XS">
|
||||||
|
<Heading size="M">Create an admin user</Heading>
|
||||||
|
<Body size="M"
|
||||||
|
>The admin user has access to everything in Budibase.</Body
|
||||||
|
>
|
||||||
|
</Layout>
|
||||||
|
</div>
|
||||||
|
<Layout gap="XS">
|
||||||
|
<Input label="Email" bind:value={adminUser.email} />
|
||||||
|
<Input label="Password" type="password" bind:value={adminUser.password} />
|
||||||
|
</Layout>
|
||||||
|
<Layout gap="S">
|
||||||
|
<Button cta on:click={save}>Create super admin user</Button>
|
||||||
|
</Layout>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
section {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 260px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
.center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
width: 40px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -6,9 +6,9 @@
|
||||||
import ThemeEditorDropdown from "components/settings/ThemeEditorDropdown.svelte"
|
import ThemeEditorDropdown from "components/settings/ThemeEditorDropdown.svelte"
|
||||||
import FeedbackNavLink from "components/feedback/FeedbackNavLink.svelte"
|
import FeedbackNavLink from "components/feedback/FeedbackNavLink.svelte"
|
||||||
import { get } from "builderStore/api"
|
import { get } from "builderStore/api"
|
||||||
import { isActive, goto, layout, params } from "@roxi/routify"
|
import { isActive, goto, layout } from "@roxi/routify"
|
||||||
import Logo from "/assets/bb-logo.svg"
|
import Logo from "/assets/bb-logo.svg"
|
||||||
import { capitalise } from "../../../helpers"
|
import { capitalise } from "helpers"
|
||||||
|
|
||||||
// Get Package and set store
|
// Get Package and set store
|
||||||
export let application
|
export let application
|
||||||
|
@ -60,7 +60,7 @@
|
||||||
<img
|
<img
|
||||||
src={Logo}
|
src={Logo}
|
||||||
alt="budibase icon"
|
alt="budibase icon"
|
||||||
on:click={() => $goto(`/builder/`)}
|
on:click={() => $goto(`../../portal/`)}
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
import { notifications } from "@budibase/bbui"
|
import { notifications } from "@budibase/bbui"
|
||||||
import IntegrationConfigForm from "components/backend/DatasourceNavigator/TableIntegrationMenu/IntegrationConfigForm.svelte"
|
import IntegrationConfigForm from "components/backend/DatasourceNavigator/TableIntegrationMenu/IntegrationConfigForm.svelte"
|
||||||
import ICONS from "components/backend/DatasourceNavigator/icons"
|
import ICONS from "components/backend/DatasourceNavigator/icons"
|
||||||
import { capitalise } from "../../../../../../helpers"
|
import { capitalise } from "helpers"
|
||||||
|
|
||||||
let unsaved = false
|
let unsaved = false
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
<script>
|
||||||
|
import { goto } from "@roxi/routify"
|
||||||
|
$goto("../portal")
|
||||||
|
</script>
|
|
@ -0,0 +1,4 @@
|
||||||
|
<script>
|
||||||
|
import { goto } from "@roxi/routify"
|
||||||
|
$goto("./login")
|
||||||
|
</script>
|
|
@ -0,0 +1,5 @@
|
||||||
|
<script>
|
||||||
|
import LoginForm from "components/login/LoginForm.svelte"
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<LoginForm />
|
|
@ -1,123 +1,4 @@
|
||||||
<script>
|
<script>
|
||||||
import api from "builderStore/api"
|
import { goto } from "@roxi/routify"
|
||||||
import AppList from "components/start/AppList.svelte"
|
$goto("./portal")
|
||||||
import { get } from "builderStore/api"
|
|
||||||
import CreateAppModal from "components/start/CreateAppModal.svelte"
|
|
||||||
import { Button, Heading, Modal, ButtonGroup } from "@budibase/bbui"
|
|
||||||
import TemplateList from "components/start/TemplateList.svelte"
|
|
||||||
import analytics from "analytics"
|
|
||||||
import Banner from "/assets/orange-landscape.png"
|
|
||||||
|
|
||||||
let hasKey
|
|
||||||
let template
|
|
||||||
let modal
|
|
||||||
|
|
||||||
async function getApps() {
|
|
||||||
const res = await get("/api/applications")
|
|
||||||
const json = await res.json()
|
|
||||||
|
|
||||||
if (res.ok) {
|
|
||||||
return json
|
|
||||||
} else {
|
|
||||||
throw new Error(json)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchKeys() {
|
|
||||||
const response = await api.get(`/api/keys/`)
|
|
||||||
return await response.json()
|
|
||||||
}
|
|
||||||
|
|
||||||
async function checkIfKeysAndApps() {
|
|
||||||
const keys = await fetchKeys()
|
|
||||||
const apps = await getApps()
|
|
||||||
if (keys.userId) {
|
|
||||||
hasKey = true
|
|
||||||
analytics.identify(keys.userId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function selectTemplate(newTemplate) {
|
|
||||||
template = newTemplate
|
|
||||||
modal.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
function initiateAppImport() {
|
|
||||||
template = { fromFile: true }
|
|
||||||
modal.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeModal() {
|
|
||||||
template = null
|
|
||||||
modal.hide()
|
|
||||||
}
|
|
||||||
|
|
||||||
checkIfKeysAndApps()
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="container">
|
|
||||||
<div class="header">
|
|
||||||
<Heading size="M">Welcome to the Budibase Beta</Heading>
|
|
||||||
<ButtonGroup>
|
|
||||||
<Button secondary on:click={initiateAppImport}>Import Web App</Button>
|
|
||||||
<Button cta on:click={modal.show}>Create New Web App</Button>
|
|
||||||
</ButtonGroup>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="banner">
|
|
||||||
<img src={Banner} alt="rocket" />
|
|
||||||
<div class="banner-content">
|
|
||||||
Every accomplishment starts with a decision to try.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- <TemplateList onSelect={selectTemplate} /> -->
|
|
||||||
|
|
||||||
<AppList />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Modal bind:this={modal} padding={false} width="600px" on:hide={closeModal}>
|
|
||||||
<CreateAppModal {hasKey} {template} />
|
|
||||||
</Modal>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.container {
|
|
||||||
display: grid;
|
|
||||||
gap: var(--spacing-xl);
|
|
||||||
margin: 40px 80px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.banner {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
position: relative;
|
|
||||||
text-align: center;
|
|
||||||
color: white;
|
|
||||||
border-radius: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.banner img {
|
|
||||||
height: 250px;
|
|
||||||
width: 100%;
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.banner-content {
|
|
||||||
position: absolute;
|
|
||||||
font-size: 24px;
|
|
||||||
color: white;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-group {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -1,49 +1,53 @@
|
||||||
<script>
|
<script>
|
||||||
import { isActive, url, goto } from "@roxi/routify"
|
import { isActive, goto } from "@roxi/routify"
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
import {
|
import {
|
||||||
ActionMenu,
|
|
||||||
Checkbox,
|
|
||||||
MenuItem,
|
|
||||||
Icon,
|
Icon,
|
||||||
Heading,
|
|
||||||
Avatar,
|
Avatar,
|
||||||
Search,
|
Search,
|
||||||
Layout,
|
Layout,
|
||||||
ProgressCircle,
|
|
||||||
SideNavigation as Navigation,
|
SideNavigation as Navigation,
|
||||||
SideNavigationItem as Item,
|
SideNavigationItem as Item,
|
||||||
|
ActionMenu,
|
||||||
|
MenuItem,
|
||||||
|
Modal,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import api from "builderStore/api"
|
|
||||||
import ConfigChecklist from "components/common/ConfigChecklist.svelte"
|
import ConfigChecklist from "components/common/ConfigChecklist.svelte"
|
||||||
import { organisation, admin } from "stores/portal"
|
import { organisation, apps } from "stores/portal"
|
||||||
|
import { auth } from "stores/backend"
|
||||||
organisation.init()
|
import BuilderSettingsModal from "components/start/BuilderSettingsModal.svelte"
|
||||||
|
|
||||||
let orgName
|
let orgName
|
||||||
let orgLogo
|
let orgLogo
|
||||||
let user
|
let user
|
||||||
|
let oldSettingsModal
|
||||||
|
|
||||||
async function getInfo() {
|
async function getInfo() {
|
||||||
// fetch orgInfo
|
// fetch orgInfo
|
||||||
orgName = "ACME Inc."
|
orgName = "ACME Inc."
|
||||||
orgLogo = "https://via.placeholder.com/150"
|
orgLogo = "https://via.placeholder.com/150"
|
||||||
|
|
||||||
user = { name: "John Doe" }
|
user = { name: "John Doe" }
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(getInfo)
|
onMount(() => {
|
||||||
|
organisation.init()
|
||||||
|
getInfo()
|
||||||
|
})
|
||||||
|
|
||||||
let menu = [
|
let menu = [
|
||||||
{ title: "Apps", href: "/portal/apps" },
|
{ title: "Apps", href: "/builder/portal/apps" },
|
||||||
{ title: "Drafts", href: "/portal/drafts" },
|
{ title: "Drafts", href: "/builder/portal/drafts" },
|
||||||
{ title: "Users", href: "/portal/manage/users", heading: "Manage" },
|
{ title: "Users", href: "/builder/portal/manage/users", heading: "Manage" },
|
||||||
{ title: "Groups", href: "/portal/manage/groups" },
|
{ title: "Groups", href: "/builder/portal/manage/groups" },
|
||||||
{ title: "Auth", href: "/portal/manage/auth" },
|
{ title: "Auth", href: "/builder/portal/manage/auth" },
|
||||||
{ title: "Email", href: "/portal/manage/email" },
|
{ title: "Email", href: "/builder/portal/manage/email" },
|
||||||
{ title: "General", href: "/portal/settings/general", heading: "Settings" },
|
{
|
||||||
{ title: "Theming", href: "/portal/theming" },
|
title: "General",
|
||||||
{ title: "Account", href: "/portal/account" },
|
href: "/builder/portal/settings/general",
|
||||||
|
heading: "Settings",
|
||||||
|
},
|
||||||
|
{ title: "Theming", href: "/builder/portal/theming" },
|
||||||
|
{ title: "Account", href: "/builder/portal/account" },
|
||||||
]
|
]
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -51,7 +55,7 @@
|
||||||
<div class="nav">
|
<div class="nav">
|
||||||
<Layout paddingX="L" paddingY="L">
|
<Layout paddingX="L" paddingY="L">
|
||||||
<div class="branding">
|
<div class="branding">
|
||||||
<div class="name">
|
<div class="name" on:click={() => $goto("./apps")}>
|
||||||
<img
|
<img
|
||||||
src={$organisation?.logoUrl || "https://i.imgur.com/ZKyklgF.png"}
|
src={$organisation?.logoUrl || "https://i.imgur.com/ZKyklgF.png"}
|
||||||
alt="Logotype"
|
alt="Logotype"
|
||||||
|
@ -74,30 +78,42 @@
|
||||||
<div class="main">
|
<div class="main">
|
||||||
<div class="toolbar">
|
<div class="toolbar">
|
||||||
<Search placeholder="Global search" />
|
<Search placeholder="Global search" />
|
||||||
<div class="avatar">
|
<ActionMenu align="right">
|
||||||
|
<div slot="control" class="avatar">
|
||||||
<Avatar size="M" name="John Doe" />
|
<Avatar size="M" name="John Doe" />
|
||||||
<Icon size="XL" name="ChevronDown" />
|
<Icon size="XL" name="ChevronDown" />
|
||||||
</div>
|
</div>
|
||||||
|
<MenuItem icon="Settings" on:click={oldSettingsModal.show}>
|
||||||
|
Old settings
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem icon="LogOut" on:click={auth.logout}>Log out</MenuItem>
|
||||||
|
</ActionMenu>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="content">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<Modal bind:this={oldSettingsModal} width="30%">
|
||||||
|
<BuilderSettingsModal />
|
||||||
|
</Modal>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.container {
|
.container {
|
||||||
min-height: 100vh;
|
height: 100%;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 250px 1fr;
|
grid-template-columns: 250px 1fr;
|
||||||
|
align-items: stretch;
|
||||||
}
|
}
|
||||||
.nav {
|
.nav {
|
||||||
background: var(--background);
|
background: var(--background);
|
||||||
border-right: var(--border-light);
|
border-right: var(--border-light);
|
||||||
|
overflow: auto;
|
||||||
}
|
}
|
||||||
.main {
|
.main {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: auto 1fr;
|
grid-template-rows: auto 1fr;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.branding {
|
.branding {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
@ -112,6 +128,9 @@
|
||||||
grid-gap: var(--spacing-m);
|
grid-gap: var(--spacing-m);
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
.name:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
.avatar {
|
.avatar {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: auto auto;
|
grid-template-columns: auto auto;
|
||||||
|
@ -129,6 +148,7 @@
|
||||||
grid-template-columns: 250px auto;
|
grid-template-columns: 250px auto;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: var(--spacing-m) calc(var(--spacing-xl) * 2);
|
padding: var(--spacing-m) calc(var(--spacing-xl) * 2);
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
img {
|
img {
|
||||||
width: 28px;
|
width: 28px;
|
||||||
|
@ -139,4 +159,9 @@
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
.content {
|
||||||
|
overflow: auto;
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: 1fr;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
|
@ -0,0 +1,231 @@
|
||||||
|
<script>
|
||||||
|
import {
|
||||||
|
Heading,
|
||||||
|
Layout,
|
||||||
|
Button,
|
||||||
|
ActionButton,
|
||||||
|
ActionGroup,
|
||||||
|
ButtonGroup,
|
||||||
|
Select,
|
||||||
|
Modal,
|
||||||
|
ModalContent,
|
||||||
|
Page,
|
||||||
|
notifications,
|
||||||
|
Body,
|
||||||
|
} from "@budibase/bbui"
|
||||||
|
import CreateAppModal from "components/start/CreateAppModal.svelte"
|
||||||
|
import api, { del } from "builderStore/api"
|
||||||
|
import analytics from "analytics"
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
import { apps } from "stores/portal"
|
||||||
|
import download from "downloadjs"
|
||||||
|
import { goto } from "@roxi/routify"
|
||||||
|
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||||
|
import AppCard from "components/start/AppCard.svelte"
|
||||||
|
import AppRow from "components/start/AppRow.svelte"
|
||||||
|
|
||||||
|
let layout = "grid"
|
||||||
|
let template
|
||||||
|
let appToDelete
|
||||||
|
let creationModal
|
||||||
|
let deletionModal
|
||||||
|
let creatingApp = false
|
||||||
|
let loaded = false
|
||||||
|
|
||||||
|
const checkKeys = async () => {
|
||||||
|
const response = await api.get(`/api/keys/`)
|
||||||
|
const keys = await response.json()
|
||||||
|
if (keys.userId) {
|
||||||
|
analytics.identify(keys.userId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const initiateAppCreation = () => {
|
||||||
|
creationModal.show()
|
||||||
|
creatingApp = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const initiateAppImport = () => {
|
||||||
|
template = { fromFile: true }
|
||||||
|
creationModal.show()
|
||||||
|
creatingApp = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const stopAppCreation = () => {
|
||||||
|
template = null
|
||||||
|
creatingApp = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const openApp = app => {
|
||||||
|
$goto(`../../app/${app._id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const exportApp = app => {
|
||||||
|
try {
|
||||||
|
download(
|
||||||
|
`/api/backups/export?appId=${app._id}&appname=${encodeURIComponent(
|
||||||
|
app.name
|
||||||
|
)}`
|
||||||
|
)
|
||||||
|
notifications.success("App export complete")
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
notifications.error("App export failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteApp = app => {
|
||||||
|
appToDelete = app
|
||||||
|
deletionModal.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirmDeleteApp = async () => {
|
||||||
|
if (!appToDelete) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await del(`/api/applications/${appToDelete?._id}`)
|
||||||
|
await apps.load()
|
||||||
|
appToDelete = null
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
checkKeys()
|
||||||
|
await apps.load()
|
||||||
|
loaded = true
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Page wide>
|
||||||
|
{#if $apps.length}
|
||||||
|
<Layout noPadding>
|
||||||
|
<div class="title">
|
||||||
|
<Heading>Apps</Heading>
|
||||||
|
<ButtonGroup>
|
||||||
|
<Button secondary on:click={initiateAppImport}>Import app</Button>
|
||||||
|
<Button cta on:click={initiateAppCreation}>Create new app</Button>
|
||||||
|
</ButtonGroup>
|
||||||
|
</div>
|
||||||
|
<div class="filter">
|
||||||
|
<div class="select">
|
||||||
|
<Select quiet placeholder="Filter by groups" />
|
||||||
|
</div>
|
||||||
|
<ActionGroup>
|
||||||
|
<ActionButton
|
||||||
|
on:click={() => (layout = "grid")}
|
||||||
|
selected={layout === "grid"}
|
||||||
|
quiet
|
||||||
|
icon="ClassicGridView"
|
||||||
|
/>
|
||||||
|
<ActionButton
|
||||||
|
on:click={() => (layout = "table")}
|
||||||
|
selected={layout === "table"}
|
||||||
|
quiet
|
||||||
|
icon="ViewRow"
|
||||||
|
/>
|
||||||
|
</ActionGroup>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class:appGrid={layout === "grid"}
|
||||||
|
class:appTable={layout === "table"}
|
||||||
|
>
|
||||||
|
{#each $apps as app, idx (app._id)}
|
||||||
|
<svelte:component
|
||||||
|
this={layout === "grid" ? AppCard : AppRow}
|
||||||
|
{app}
|
||||||
|
{openApp}
|
||||||
|
{exportApp}
|
||||||
|
{deleteApp}
|
||||||
|
last={idx === $apps.length - 1}
|
||||||
|
/>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
|
{/if}
|
||||||
|
{#if !$apps.length && !creatingApp && loaded}
|
||||||
|
<div class="empty-wrapper">
|
||||||
|
<Modal inline>
|
||||||
|
<ModalContent
|
||||||
|
title="Create your first app"
|
||||||
|
confirmText="Create app"
|
||||||
|
showCancelButton={false}
|
||||||
|
showCloseIcon={false}
|
||||||
|
onConfirm={initiateAppCreation}
|
||||||
|
size="M"
|
||||||
|
>
|
||||||
|
<div slot="footer">
|
||||||
|
<Button on:click={initiateAppImport} secondary>Import app</Button>
|
||||||
|
</div>
|
||||||
|
<Body size="S">
|
||||||
|
The purpose of the Budibase builder is to help you build beautiful,
|
||||||
|
powerful applications quickly and easily.
|
||||||
|
</Body>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</Page>
|
||||||
|
<Modal
|
||||||
|
bind:this={creationModal}
|
||||||
|
padding={false}
|
||||||
|
width="600px"
|
||||||
|
on:hide={stopAppCreation}
|
||||||
|
>
|
||||||
|
<CreateAppModal {template} />
|
||||||
|
</Modal>
|
||||||
|
<ConfirmDialog
|
||||||
|
bind:this={deletionModal}
|
||||||
|
title="Confirm deletion"
|
||||||
|
okText="Delete app"
|
||||||
|
onOk={confirmDeleteApp}
|
||||||
|
>
|
||||||
|
Are you sure you want to delete the app <b>{appToDelete?.name}</b>?
|
||||||
|
</ConfirmDialog>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.title,
|
||||||
|
.filter {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select {
|
||||||
|
width: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.appGrid {
|
||||||
|
display: grid;
|
||||||
|
grid-gap: 50px;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||||
|
}
|
||||||
|
.appTable {
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: auto;
|
||||||
|
grid-template-columns: 1fr 1fr 1fr auto;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.appTable :global(> div) {
|
||||||
|
height: 70px;
|
||||||
|
display: grid;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-xl);
|
||||||
|
grid-template-columns: auto 1fr;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
padding: 0 var(--spacing-s);
|
||||||
|
}
|
||||||
|
.appTable :global(> div:not(.last)) {
|
||||||
|
border-bottom: var(--border-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-wrapper {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
</style>
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue