iniital ui contribution

Signed-off-by: Andrey Platov <andrey@hardcoreeng.com>
This commit is contained in:
Andrey Platov 2021-08-05 01:31:54 +02:00
parent 7a9a284855
commit 73d85935ba
No known key found for this signature in database
GPG Key ID: C8787EFEB4B64AF0
99 changed files with 6755 additions and 5 deletions

File diff suppressed because it is too large Load Diff

3
dev/prod/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
stats.json
dist/

43
dev/prod/package.json Normal file
View File

@ -0,0 +1,43 @@
{
"name": "prod",
"version": "1.0.0",
"license": "EPL-2.0",
"scripts": {
"build": "cross-env NODE_ENV=production webpack",
"analyze": "cross-env NODE_ENV=production webpack --json > stats.json",
"show": "webpack-bundle-analyzer stats.json dist",
"dev": "webpack serve --content-base public",
"dev-server": "cross-env CLIENT=server webpack serve --content-base public",
"start": "cross-env NODE_ENV=production webpack serve --content-base public",
"test": "echo 'no tests'",
"preformat-svelte": "prettier -w src/**/*.svelte",
"lint": "eslint --max-warnings=0 src",
"lint:fix": "yarn preformat-svelte && eslint --fix src",
"deploy": "cp -p public/* dist && aws s3 sync dist s3://anticrm-platform --delete --acl public-read"
},
"devDependencies": {
"cross-env":"^7.0.3",
"webpack":"^5.48.0",
"webpack-cli":"^4.7.2",
"mini-css-extract-plugin":"^2.2.0",
"dotenv-webpack":"^7.0.3",
"autoprefixer":"^10.3.1",
"postcss":"^8.3.6",
"svelte-preprocess":"^4.7.4",
"ts-loader":"^9.2.5",
"css-loader":"^6.2.0",
"postcss-loader":"^6.1.1",
"sass-loader":"^12.1.0",
"svelte-loader":"^3.1.2",
"postcss-load-config":"^3.1.0",
"file-loader":"^6.2.0",
"sass":"^1.37.5",
"webpack-dev-server":"^3.11.2",
"style-loader":"^3.2.1"
},
"dependencies": {
"@anticrm/ui": "~0.6.0",
"@anticrm/theme": "~0.6.0",
"svelte": "^3.42.1"
}
}

View File

@ -0,0 +1,5 @@
module.exports = {
plugins: [
require('autoprefixer')
]
}

BIN
dev/prod/public/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -0,0 +1,18 @@
<!doctype html>
<html>
<head>
<meta charset='utf8'>
<meta name='viewport' content='width=device-width'>
<title>Svelte app</title>
<link rel='icon' type='image/png' href='favicon.png'>
<link rel='stylesheet' href='bundle.css'>
</head>
<body style="margin: 0; overflow: hidden;">
<script src='/bundle.js'></script>
</body>
</html>

53
dev/prod/src/main.ts Normal file
View File

@ -0,0 +1,53 @@
//
// Copyright © 2020 Anticrm Platform Contributors.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
// import { setMetadata } from '@anticrm/platform'
import { createApp } from '@anticrm/ui'
// import login from '@anticrm/login'
// import pluginCore from '@anticrm/plugin-core'
// import meetingPlugin from '@anticrm/meeting'
// import { configurePlatform } from './platform'
// configurePlatform()
// const accountsUrl = process.env.APP_ACCOUNTS_URL
// const appHost = process.env.APP_WSHOST
// const appPort = process.env.APP_WSPORT
// const appToken = process.env.APP_TOKEN
// const meetingHost = process.env.MEETING_WSHOST
// const meetingPort = process.env.MEETING_WSPORT
// setMetadata(login.metadata.AccountsUrl, accountsUrl)
// setMetadata(pluginCore.metadata.ClientUrl, `${appHost}:${appPort}/${appToken}`)
// setMetadata(meetingPlugin.metadata.ClientUrl, `${meetingHost}:${meetingPort}`)
// platform.setMetadata(core.metadata.WSHost, host)
// platform.setMetadata(core.metadata.WSPort, port)
// const loginInfo = currentAccount()
// if (loginInfo) {
// platform.setMetadata(core.metadata.WhoAmI, loginInfo.email)
// platform.setMetadata(core.metadata.Token, loginInfo.token)
// }
// async function boot (): Promise<void> {
// uiService.createApp(document.body)
// }
// boot().catch(err => {
// new ErrorPage({ target: document.body, props: { error: err.message } })
// })
createApp(document.body)

57
dev/prod/src/platform.txt Normal file
View File

@ -0,0 +1,57 @@
//
// Copyright © 2020 Anticrm Platform Contributors.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import { addLocation } from '@anticrm/platform'
import { loginId } from '@anticrm/login'
import { clientId } from '@anticrm/client'
import { workbenchId } from '@anticrm/workbench'
import { chunterId } from '@anticrm/chunter'
import { recruitId } from '@anticrm/recruit'
import { tableId } from '@anticrm/table'
import { viewId } from '@anticrm/view'
import { taskId } from '@anticrm/task'
import { contactId } from '@anticrm/contact'
import { chunterServerId } from '@anticrm/chunter-server'
import '@anticrm/login-assets'
import '@anticrm/chunter-assets'
import '@anticrm/recruit-assets'
import '@anticrm/task-assets'
import '@anticrm/view-assets'
export function configurePlatform() {
// platform.setMetadata(ui.metadata.LoginApplication, 'login')
// platform.setMetadata(ui.metadata.DefaultApplication, 'workbench')
// if (process.env.CLIENT === 'dev')
addLocation(clientId, () => import(/* webpackChunkName: "client-dev" */ '@anticrm/plugin-client-dev'))
// else
// addLocation(core, () => import(/* webpackChunkName: "plugin-core" */ '@anticrm/plugin-core-impl'))
addLocation(loginId, () => import(/* webpackChunkName: "login" */ '@anticrm/plugin-login'))
addLocation(workbenchId, () => import(/* webpackChunkName: "workbench" */ '@anticrm/plugin-workbench'))
addLocation(chunterId, () => import(/* webpackChunkName: "chunter" */ '@anticrm/plugin-chunter'))
addLocation(recruitId, () => import(/* webpackChunkName: "recruit" */ '@anticrm/plugin-recruit'))
addLocation(tableId, () => import(/* webpackChunkName: "table" */ '@anticrm/table-resources'))
addLocation(viewId, () => import(/* webpackChunkName: "view" */ '@anticrm/view-resources'))
addLocation(taskId, () => import(/* webpackChunkName: "task" */ '@anticrm/task-resources'))
addLocation(contactId, () => import(/* webpackChunkName: "contact" */ '@anticrm/contact-resources'))
addLocation(chunterServerId, () => import(/* webpackChunkName: "chunter-server" */ '@anticrm/chunter-server'))
}

16
dev/prod/tsconfig.json Normal file
View File

@ -0,0 +1,16 @@
{
"compilerOptions": {
"outDir": "./dist/",
"noImplicitAny": true,
"module": "esnext",
"target": "esnext",
"allowJs": true,
"sourceMap": true,
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"lib": [
"esnext",
"dom"
]
}
}

163
dev/prod/webpack.config.js Normal file
View File

@ -0,0 +1,163 @@
//
// Copyright © 2020 Anticrm Platform Contributors.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const Dotenv = require('dotenv-webpack')
const path = require('path')
const autoprefixer = require('autoprefixer')
const mode = process.env.NODE_ENV || 'development'
const prod = mode === 'production'
module.exports = {
entry: {
bundle: [
'@anticrm/theme/styles/global.scss',
'./src/main.ts'
]
},
resolve: {
symlinks: true,
alias: {
svelte: path.resolve('./node_modules', 'svelte')
},
extensions: ['.mjs', '.js', '.svelte', '.ts'],
mainFields: ['svelte', 'browser', 'module', 'main']
},
output: {
path: __dirname + '/dist',
filename: '[name].js',
chunkFilename: '[name].[id].js',
publicPath: '/'
},
module: {
rules: [
{
test: /\.ts?$/,
use: 'ts-loader',
exclude: /node_modules/
},
{
test: /\.svelte$/,
use: {
loader: 'svelte-loader',
options: {
emitCss: true,
preprocess: require('svelte-preprocess')({ postcss: true })
}
// options: {
// dev: !prod,
// emitCss: true,
// hotReload: !prod,
// preprocess: require('svelte-preprocess')({
// babel: {
// presets: [
// [
// '@babel/preset-env',
// {
// loose: true,
// modules: false,
// targets: {
// esmodules: true
// }
// }
// ],
// '@babel/typescript'
// ],
// plugins: ['@babel/plugin-proposal-optional-chaining']
// }
// })
// }
}
},
{
test: /\.css$/,
use: [
prod ? MiniCssExtractPlugin.loader : 'style-loader',
'css-loader',
'postcss-loader'
]
},
{
test: /\.scss$/,
use: [
prod ? MiniCssExtractPlugin.loader : 'style-loader',
'css-loader',
'postcss-loader',
'sass-loader',
],
},
{
test: /\.(ttf|otf|eot|woff|woff2)$/,
use: {
loader: 'file-loader',
options: {
name: 'fonts/[name].[ext]',
esModule: false
}
}
},
{
test: /\.(jpg|png)$/,
use: {
loader: 'file-loader',
options: {
name: 'img/[name].[ext]',
esModule: false
}
}
},
{
test: /\.svg$/,
use: [
{
loader: 'file-loader',
options: {
esModule: false
}
},
{
loader: 'svgo-loader',
options: {
plugins: [
{ name: 'removeHiddenElems', active: false }
// { removeHiddenElems: { displayNone: false } },
// { cleanupIDs: false },
// { removeTitle: true }
]
}
}
]
}
]
},
mode,
plugins: [
new MiniCssExtractPlugin({
filename: '[name].css'
}),
new Dotenv()
],
devtool: prod ? false : 'source-map',
devServer: {
publicPath: '/',
historyApiFallback: {
disableDotRule: true
}
}
}

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,24 @@
{
"name": "@anticrm/theme",
"version": "0.6.0",
"main": "src/index.ts",
"svelte": "src/index.ts",
"author": "Anticrm Platform Contributors",
"license": "EPL-2.0",
"scripts": {
"build": "",
"test": "echo 'no tests'",
"lint": "ts-standard src",
"lint:fix": "ts-standard --fix src",
"format": "prettier --write 'src/**/*.{ts*,js*,yml}' && ts-standard --fix src",
"svelte-check": "svelte-check"
},
"devDependencies": {
"svelte-loader":"^3.1.2",
"sass":"^1.37.5",
"svelte-preprocess":"^4.7.4"
},
"dependencies": {
"svelte": "^3.37.0"
}
}

View File

@ -0,0 +1,5 @@
module.exports = {
plugins: [
require('autoprefixer')
]
}

View File

@ -0,0 +1,52 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { setContext, onMount } from 'svelte'
const getCurrentTheme = (): string => localStorage.getItem('theme') ?? 'theme-dark'
const getCurrnetFontSize = (): string => localStorage.getItem('fontsize') ?? 'normal-font'
const currentTheme = getCurrentTheme()
const currentFontSize = getCurrnetFontSize()
const setRootColors = (theme: string) => {
document.body.setAttribute('class', `${theme} ${getCurrnetFontSize()}`)
}
const setRootFontSize = (fontsize: string) => {
document.body.setAttribute('class', `${getCurrentTheme()} ${fontsize}`)
}
setContext('theme', {
currentTheme: currentTheme,
setTheme: (name: string) => {
setRootColors(name)
localStorage.setItem('theme', name)
}
})
setContext('fontsize', {
currentFontSize: currentFontSize,
setFontSize: (fontsize: string) => {
setRootFontSize(fontsize)
localStorage.setItem('fontsize', fontsize)
}
})
onMount(() => {
setRootColors(currentTheme)
setRootFontSize(currentFontSize)
})
</script>
<slot/>

View File

@ -0,0 +1,16 @@
//
// Copyright © 2020 Anticrm Platform Contributors.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
export { default as Theme } from './Theme.svelte'

5
packages/theme/src/svelte.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
declare module '*.svelte' {
import { SvelteComponent } from 'svelte'
const Component: SvelteComponent
export default Component
}

View File

@ -0,0 +1,206 @@
//
// Copyright © 2021 Anticrm Platform Contributors.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
/* Common Colors */
* {
--primary-button-enabled: #4474F6;
--primary-button-hovered: #2A5FF6;
--primary-button-pressed: #194CD7;
--primary-button-focused: #194CD7;
--primary-button-disabled: #5771B9;
--primary-button-focused-border: #7393EB;
--primary-button-outline: rgba(87, 132, 255, .3);
--primary-button-border: rgba(255, 255, 255, .09);
--system-error-color: #EE7A7A;
--system-error-60-color: rgba(238, 122, 122, .6); // #EE7A7A / 60%
--activity-status-active: #34DB80;
--activity-status-dnd: #D95757;
--activity-status-busy: #FCC500;
--activity-status-away: #9099A2;
--primary-color-purple-01: #4B38BD;
--primary-color-purple-02: #6552DB;
--primary-color-purple-03: #9D92C4;
--primary-color-orange-01: #CC4726;
--primary-color-orange-02: #F47758;
--primary-color-skyblue: #93CAF3;
--primary-color-pink: #FA8DA1;
--highlight-red: #F96E50;
}
/* Dark Theme */
.theme-dark {
--theme-bg-color: #18181e;
--theme-bg-selection: #282830;
--theme-menu-color: #111117;
--theme-menu-selection: #1D1D23;
--theme-menu-divider: rgba(255, 255, 255, .05);
--theme-scroll-bar: #2C2C34;
--theme-bg-modal: #2C2B35;
--theme-border-modal: rgba(0, 0, 0, 0.2);
--theme-chat-selection: radial-gradient(135.96% 3333.35% at -2.36% -27.63%, rgba(210, 183, 156, 0.11) 0%, rgba(204, 196, 184, 0.0785128) 20.8%, rgba(104, 104, 114, 0.11) 100%);
--theme-chat-divider: rgb(36, 36, 41);
--theme-bg-accent-color: rgba(255, 255, 255, .03);
--theme-bg-accent-hover: rgba(255, 255, 255, .06);
--theme-bg-accent-press: rgba(255, 255, 255, .07);
--theme-bg-focused-color: rgba(255, 255, 255, .1);
--theme-bg-focused-border: rgba(255, 255, 255, .4);
--theme-bg-accent-error: rgba(251, 158, 158, .06);
--theme-on-color: #94AEF3;
--theme-off-color: #77818E;
--theme-bg-check: #F2F2F2;
--theme-tooltip-color: #2C2C34;
--theme-button-bg-enabled: #1F1F25;
--theme-button-bg-hovered: #26262B;
--theme-button-bg-pressed: #2A2A30;
--theme-button-bg-disabled: #212127;
--theme-button-bg-focused: #2F2F34;
--theme-button-bg-error: #262026;
--theme-button-border-enabled: rgba(255, 255, 255, .06);
--theme-button-border-hovered: rgba(255, 255, 255, .08);
--theme-button-border-pressed: rgba(255, 255, 255, .12);
--theme-button-border-disabled: rgba(255, 255, 255, .06);
--theme-button-border-focused: rgba(255, 255, 255, .4);
--theme-button-border-error: rgba(205, 104, 104, .1);
--theme-caption-color: #fff;
--theme-content-accent-color: rgba(255, 255, 255, 0.8);
--theme-content-color: rgba(255, 255, 255, 0.6);
--theme-content-dark-color: rgba(255, 255, 255, 0.4);
--theme-content-trans-color: rgba(255, 255, 255, 0.3);
--theme-userlink-color: #b92d52;
--theme-userlink-hover: #b92d52;
--theme-doclink-color: #2d6ab9;
--theme-doclink-hover: #2d6ab9;
--theme-status-online: #6cd871;
--theme-shadow: 0px 4px 8px rgba(15, 15, 15, 0.5);
--theme-text-shadow: 0px 0px 8px rgba(255, 255, 255, 0.25);
}
/* Grey Theme */
.theme-grey {
--theme-bg-color: #393844;
--theme-bg-selection: #454351;
--theme-menu-color: #2C2B35;
--theme-menu-selection: #37363F;
--theme-menu-divider: rgba(255, 255, 255, .05);
--theme-scroll-bar: #494852;
--theme-bg-modal: #2C2B35;
--theme-border-modal: rgba(0, 0, 0, 0.2);
--theme-chat-selection: radial-gradient(135.96% 3333.35% at -2.36% -27.63%, rgba(210, 183, 156, 0.11) 0%, rgba(204, 196, 184, 0.0785128) 20.8%, rgba(104, 104, 114, 0.11) 100%);
--theme-chat-divider: rgb(66, 65, 76);
--theme-bg-accent-color: rgba(255, 255, 255, .03);
--theme-bg-accent-hover: rgba(255, 255, 255, .06);
--theme-bg-accent-press: rgba(255, 255, 255, .07);
--theme-bg-focused-color: rgba(255, 255, 255, .1);
--theme-bg-focused-border: rgba(255, 255, 255, .4);
--theme-bg-accent-error: rgba(251, 158, 158, .06);
--theme-on-color: #94AEF3;
--theme-off-color: #77818E;
--theme-bg-check: #F2F2F2;
--theme-tooltip-color: #4D4C58;
--theme-button-bg-enabled: #3F3E4A;
--theme-button-bg-hovered: #45444F;
--theme-button-bg-pressed: #494853;
--theme-button-bg-disabled: #41404B;
--theme-button-bg-focused: #4D4C57;
--theme-button-bg-error: #453E49;
--theme-button-border-enabled: rgba(255, 255, 255, .06);
--theme-button-border-hovered: rgba(255, 255, 255, .08);
--theme-button-border-pressed: rgba(255, 255, 255, .12);
--theme-button-border-disabled: rgba(255, 255, 255, .06);
--theme-button-border-focused: rgba(255, 255, 255, .4);
--theme-button-border-error: rgba(205, 104, 104, .1);
--theme-caption-color: #fff;
--theme-content-accent-color: rgba(255, 255, 255, 0.8);
--theme-content-color: rgba(255, 255, 255, 0.6);
--theme-content-dark-color: rgba(255, 255, 255, 0.4);
--theme-content-trans-color: rgba(255, 255, 255, 0.3);
--theme-userlink-color: #b92d52;
--theme-userlink-hover: #b92d52;
--theme-doclink-color: #2d6ab9;
--theme-doclink-hover: #2d6ab9;
--theme-status-online: #6cd871;
--theme-shadow: 0px 4px 8px rgba(15, 15, 15, 0.5);
}
/* Light Theme */
.theme-light {
--theme-bg-color: #FFFFFF;
--theme-bg-selection: #F1F1F4;
--theme-menu-color: #E7E7E7;
--theme-menu-selection: #DBDBDB;
--theme-menu-divider: rgba(0, 0, 0, .08);
--theme-scroll-bar: #CBCBCB;
--theme-bg-modal: #fff;
--theme-border-modal: rgba(0, 0, 0, 0.2);
--theme-chat-selection: radial-gradient(135.96% 3333.35% at -2.36% -27.63%, rgba(210, 183, 156, 0.11) 0%, rgba(204, 196, 184, 0.0785128) 20.8%, rgba(104, 104, 114, 0.11) 100%);
--theme-chat-divider: rgb(233, 233, 233);
--theme-bg-accent-color: rgba(0, 0, 0, .03);
--theme-bg-accent-hover: rgba(0, 0, 0, .05);
--theme-bg-accent-press: rgba(0, 0, 0, .07);
--theme-bg-focused-color: rgba(0, 0, 0, .03);
--theme-bg-focused-border: rgba(0, 0, 0, .4);
--theme-bg-accent-error: rgba(251, 158, 158, .06);
--theme-on-color: #4474F6;
--theme-off-color: #ECEEF5;
--theme-bg-check: #45444F;
--theme-tooltip-color: #F1F1F4;
--theme-button-bg-enabled: #F7F7F7;
--theme-button-bg-hovered: #F2F2F2;
--theme-button-bg-pressed: #EDEDED;
--theme-button-bg-disabled: #F7F7F7;
--theme-button-bg-focused: #F7F7F7;
--theme-button-bg-error: #FEF2F2;
--theme-button-border-enabled: rgba(0, 0, 0, .06);
--theme-button-border-hovered: rgba(0, 0, 0, .06);
--theme-button-border-pressed: rgba(255, 255, 255, .06);
--theme-button-border-disabled: rgba(255, 255, 255, .06);
--theme-button-border-focused: rgba(255, 255, 255, .4);
--theme-button-border-error: rgba(205, 104, 104, .1);
--theme-caption-color: #272121;
--theme-content-accent-color: rgba(39, 33, 33, 0.8);
--theme-content-color: rgba(39, 33, 33, 0.6);
--theme-content-dark-color: rgba(39, 33, 33, 0.4);
--theme-content-trans-color: rgba(31, 33, 43, 0.3);
--theme-userlink-color: #b92d52;
--theme-userlink-hover: #b92d52;
--theme-doclink-color: #2d6ab9;
--theme-doclink-hover: #2d6ab9;
--theme-status-online: #4ebc53;
--theme-shadow: 0px 4px 8px rgba(155, 155, 155, 0.5);
}
.small-font {
font-size: 12px;
}
.normal-font {
font-size: 14px;
}

View File

@ -0,0 +1,94 @@
//
// Copyright © 2021 Anticrm Platform Contributors.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
@import "./_colors.scss";
@font-face {
font-family: 'IBM Plex Sans';
font-style: normal;
font-weight: 400;
src: local('IBM Plex Sans'),
local('IBMPlexSans'),
url('../fonts/complete/woff2/IBMPlexSans-Regular.woff2') format('woff2'),
url('../fonts/complete/woff/IBMPlexSans-Regular.woff') format('woff');
}
@font-face {
font-family: 'IBM Plex Sans';
font-style: normal;
font-weight: 500;
src: local('IBM Plex Sans Medium'),
local('IBMPlexSans-Medium'),
url('../fonts/complete/woff2/IBMPlexSans-Medium.woff2') format('woff2'),
url('../fonts/complete/woff/IBMPlexSans-Medium.woff') format('woff');
}
@font-face {
font-family: 'IBM Plex Sans';
font-style: normal;
font-weight: 600;
src: local('IBM Plex Sans SemiBold'),
local('IBMPlexSans-SemiBold'),
url('../fonts/complete/woff2/IBMPlexSans-SemiBold.woff2') format('woff2'),
url('../fonts/complete/woff/IBMPlexSans-SemiBold.woff') format('woff');
}
@font-face {
font-family: 'IBM Plex Sans';
font-style: normal;
font-weight: 700;
src: local('IBM Plex Sans Bold'),
local('IBMPlexSans-Bold'),
url('../fonts/complete/woff2/IBMPlexSans-Bold.woff2') format('woff2'),
url('../fonts/complete/woff/IBMPlexSans-Bold.woff') format('woff');
}
* {
box-sizing: border-box;
scrollbar-color: var(--theme-menu-color) var(--theme-bg-color);
scrollbar-width: thin;
}
::-webkit-scrollbar {
width: 6px;
}
::-webkit-scrollbar:horizontal {
height: 6px;
}
::-webkit-scrollbar-track {
margin: 6px;
// background-color: var(--theme-scroll-bar);
}
::-webkit-scrollbar-thumb {
background-color: var(--theme-scroll-bar);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background-color: var(--theme-scroll-bar);
border-radius: 4px;
}
::-webkit-scrollbar-corner {
background-color: var(--theme-scroll-bar);
border-radius: 4px;
}
body {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;
font-family: 'IBM Plex Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto;
font-size: 14px;
font-weight: 400;
color: var(--theme-content-color);
background-color: var(--theme-menu-color);
}

View File

@ -0,0 +1,13 @@
@mixin bg-fullsize {
position: absolute;
top: 0px;
left: 0px;
min-width: 100%;
min-height: 100%;
}
@mixin bg-layer($color, $opacity) {
@include bg-fullsize;
background: $color;
opacity: $opacity;
}

View File

@ -0,0 +1,5 @@
const sveltePreprocess = require('svelte-preprocess')
module.exports = {
preprocess: sveltePreprocess()
};

View File

@ -0,0 +1,10 @@
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"declaration": true,
"outDir": "./lib",
"rootDir": "./src",
"strict": true
}
}

BIN
packages/ui/img/chen.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

BIN
packages/ui/img/elon.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

BIN
packages/ui/img/header-green.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

BIN
packages/ui/img/kathryn.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
packages/ui/img/tim.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

26
packages/ui/package.json Normal file
View File

@ -0,0 +1,26 @@
{
"name": "@anticrm/ui",
"version": "0.6.0",
"main": "src/index.ts",
"author": "Anticrm Platform Contributors",
"license": "EPL-2.0",
"scripts": {
"build": "",
"build:docs": "api-extractor run --local",
"test": "jest",
"lint": "eslint src",
"lint:fix": "eslint --fix src",
"format": "prettier --write 'src/**/*.{ts*,js*,yml}' && eslint --fix src",
"svelte-check": "svelte-check"
},
"devDependencies": {
"svelte-loader":"^3.1.2",
"sass":"^1.37.5",
"svelte-preprocess":"^4.7.4"
},
"dependencies": {
"@anticrm/platform": "~0.6.0",
"@anticrm/theme": "~0.6.0",
"svelte": "^3.37.0"
}
}

View File

@ -0,0 +1,5 @@
module.exports = {
plugins: [
require('autoprefixer')
]
}

View File

@ -0,0 +1,12 @@
import { locationToUrl } from '../location'
import { Location } from '../types'
describe('location', () => {
it('should translate location to url', () => {
const loc: Location = {
path: ['x', 'y']
}
const url = locationToUrl(loc)
expect(url).toBe('/x/y')
})
})

View File

@ -0,0 +1,84 @@
<!--
// Copyright © 2020 Anticrm Platform Contributors.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import type { IntlString, Asset } from '@anticrm/platform'
import type { AnySvelteComponent } from '../types'
import Icon from './Icon.svelte'
import Tooltip from './Tooltip.svelte'
export let label: IntlString
export let direction: string = 'top'
export let icon: Asset | AnySvelteComponent
export let size: 'small' | 'medium' | 'large'
export let action: () => Promise<void>
export let invisible: boolean = false
</script>
<Tooltip label={label} direction={direction}>
<button class="button {size}" on:click|stopPropagation={action}>
<div class="icon {size}" class:invisible={invisible}>
{#if typeof (icon) === 'string'}
<Icon {icon} {size}/>
{:else}
<svelte:component this={icon} size={size} />
{/if}
</div>
</button>
</Tooltip>
<style lang="scss">
.button {
display: flex;
justify-content: center;
align-items: center;
padding: 0;
border: 1px solid transparent;
border-radius: 4px;
outline: none;
background-color: transparent;
cursor: pointer;
.icon {
opacity: .3;
&.invisible {
opacity: 0;
}
}
&:hover .icon {
opacity: 1;
}
&:focus {
border: 1px solid var(--primary-button-focused-border);
box-shadow: 0 0 0 3px var(--primary-button-outline);
.icon {
opacity: 1;
}
}
}
.small {
width: 1.143em;
height: 1.143em;
}
.medium {
width: 1.429em;
height: 1.429em;
}
.large {
width: 1.714em;
height: 1.714em;
}
</style>

View File

@ -0,0 +1,96 @@
<!--
// Copyright © 2020 Anticrm Platform Contributors.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import type { IntlString } from '@anticrm/platform'
import Spinner from './Spinner.svelte'
import Label from './Label.svelte'
export let label: IntlString
export let primary: boolean = false
export let disabled: boolean = false
export let loading: boolean = false
export let width: string | undefined = undefined
</script>
<button class="button" class:primary disabled={disabled || loading} style={width ? 'width: ' + width : ''} on:click>
{#if loading}
<Spinner />
{:else}
<Label {label} />
{/if}
</button>
<style lang="scss">
.button {
display: flex;
align-items: center;
justify-content: center;
height: 48px;
padding: 0 25px;
color: var(--theme-caption-color);
background-color: var(--theme-button-bg-enabled);
border: 1px solid var(--theme-button-border-enabled);
border-radius: 12px;
outline: none;
user-select: none;
cursor: pointer;
font-family: inherit;
font-size: 14px;
font-weight: 600;
&:hover {
background-color: var(--theme-button-bg-hovered);
border-color: var(--theme-button-border-hovered);
}
&:focus {
background-color: var(--theme-button-bg-focused);
border-color: var(--theme-button-border-focused);
}
&:active {
background-color: var(--theme-button-bg-pressed);
border-color: var(--theme-button-border-pressed);
}
&:disabled {
background-color: var(--theme-button-bg-disabled);
border-color: var(--theme-button-border-disabled);
color: rgb(var(--theme-caption-color) / 40%);
cursor: not-allowed;
}
}
.primary {
background-color: var(--primary-button-enabled);
border-color: var(--primary-button-border);
&:hover {
background-color: var(--primary-button-hovered);
border-color: var(--primary-button-border);
}
&:focus {
background-color: var(--primary-button-focused);
border-color: var(--primary-button-focused-border);
box-shadow: 0 0 0 2px var(--primary-button-outline);
}
&:active {
background-color: var(--primary-button-pressed);
border-color: var(--primary-button-border);
box-shadow: none;
}
&:disabled {
background-color: var(--primary-button-disabled);
border-color: var(--primary-button-border);
color: rgb(var(--theme-caption-color) / 60%);
cursor: not-allowed;
}
}
</style>

View File

@ -0,0 +1,85 @@
<!--
// Copyright © 2020 Anticrm Platform Contributors.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
export let checked: boolean = false
</script>
<label class="checkbox">
<input class="chBox" type="checkbox" bind:checked={checked}>
<svg class="checkSVG" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<path class="back" d="M4,0h8c2.2,0,4,1.8,4,4v8c0,2.2-1.8,4-4,4H4c-2.2,0-4-1.8-4-4V4C0,1.8,1.8,0,4,0z"/>
<polygon class="check" points="7.3,11.5 4,8.3 5,7.4 7.3,9.7 11.8,5.1 12.7,6.1 "/>
<path class="border" d="M12,16H4c-2.2,0-4-1.8-4-4V4c0-2.2,1.8-4,4-4h8c2.2,0,4,1.8,4,4v8C16,14.2,14.2,16,12,16z M4,1 C2.3,1,1,2.3,1,4v8c0,1.7,1.3,3,3,3h8c1.7,0,3-1.3,3-3V4c0-1.7-1.3-3-3-3H4z"/>
</svg>
</label>
<style lang="scss">
.checkbox {
display: inline-block;
width: 16px;
height: 16px;
.chBox {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
border: 0;
padding: 0;
clip: rect(0 0 0 0);
overflow: hidden;
&:checked + .checkSVG {
& > .back {
fill: var(--theme-bg-check);
}
& > .check {
visibility: visible;
fill: var(--theme-button-bg-enabled);
}
& > .border {
visibility: hidden;
}
}
&:not(:disabled) + .checkSVG {
cursor: pointer;
}
&:disabled + .checkSVG {
filter: grayscale(70%);
}
&:focus-within + .checkSVG {
border: 1px solid var(--primary-button-focused-border);
box-shadow: 0 0 0 2px var(--primary-button-outline);
}
}
.checkSVG {
width: 16px;
height: 16px;
border-radius: 4px;
.back {
fill: var(--theme-button-bg-hovered);
}
.check {
visibility: hidden;
fill: var(--theme-button-bg-enabled);
}
.border {
fill: var(--theme-button-border-enabled);
}
}
}
</style>

View File

@ -0,0 +1,85 @@
<!--
// Copyright © 2020 Anticrm Platform Contributors.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import type { IntlString } from '@anticrm/platform'
import CheckBoxWithLabel from './CheckBoxWithLabel.svelte'
import Label from './Label.svelte'
import Add from './icons/Add.svelte'
export let label: IntlString
export let items: Array<Object> = [
{ id: 0, label: '15 minute phone call', done: true },
{ id: 1, label: 'Follow up email', done: false },
{ id: 2, label: 'First round interview', done: false },
{ id: 3, label: 'Follow up email', done: false },
{ id: 4, label: 'Second round interview', done: false },
{ id: 5, label: 'Third round interview', done: false },
]
export let editable: boolean = false
</script>
<div class="checkbox-list">
{#each items as item}
<div class="list-item"><CheckBoxWithLabel bind:label={item.label} bind:checked={item.done} {editable} /></div>
{/each}
<div class="add-item"
on:click={() => {
items.push({ id: Date.now(), label: 'New item', done: false })
items = items
}}
>
<div class="icon"><Add /></div>
<div class="label"><Label {label} /></div>
</div>
</div>
<style lang="scss">
.checkbox-list {
display: flex;
flex-direction: column;
align-items: stretch;
margin: 0 16px;
.list-item + .list-item {
margin-top: 20px;
}
.add-item {
display: flex;
align-items: center;
margin-top: 20px;
cursor: pointer;
.icon {
width: 16px;
height: 16px;
opacity: .6;
}
.label {
margin-left: 16px;
color: var(--theme-content-color);
}
&:hover {
.icon {
opacity: 1;
}
.label {
color: var(--theme-caption-color);
}
}
}
}
</style>

View File

@ -0,0 +1,140 @@
<!--
// Copyright © 2020 Anticrm Platform Contributors.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { onMount } from 'svelte'
import type { IntlString } from '@anticrm/platform'
import CheckBox from './CheckBox.svelte'
export let label: IntlString
export let checked: boolean = false
export let editable: boolean = false
let text: HTMLElement
let input: HTMLInputElement
let onEdit: boolean = false
let goOut: boolean = false
$: {
if (text && input) {
if (onEdit) {
text.style.visibility = 'hidden'
input.style.visibility = 'visible'
input.focus()
} else {
input.style.visibility = 'hidden'
text.style.visibility = 'visible'
}
}
}
const findNode = (el: Node, name: string): any => {
while (el.parentNode !== null) {
if (el.classList.contains(name)) return el
el = el.parentNode
}
return false
}
const waitClick = (event: any): void => {
if (onEdit) {
if (!findNode(event.target, 'edit-item')) onEdit = false
}
}
function computeSize(t: EventTarget | null) {
const target = t as HTMLInputElement
const value = target.value
text.innerHTML = label.replaceAll(' ', '&nbsp;')
target.style.width = text.clientWidth + 12 + 'px'
}
onMount(() => {
computeSize(input)
})
</script>
<svelte:window on:mousedown={waitClick} />
<div class="checkBox-container">
<CheckBox bind:checked={checked} />
<div class="label"
on:click={() => {
if (editable) {
onEdit = true
}
}}
>
<input bind:this={input} type="text" bind:value={label}
class="edit-item"
on:input={(ev) => ev.target && computeSize(ev.target)}
/>
<div class="text" class:checked bind:this={text}>{label}</div>
</div>
</div>
<style lang="scss">
.checkBox-container {
display: flex;
align-items: center;
.label {
position: relative;
margin-left: 16px;
color: var(--theme-caption-color);
&.onEdit {
margin: 2px 0px 1px 17px;
text-decoration: none;
}
.edit-item {
max-width: 100%;
height: 21px;
margin: -3px;
padding: 2px;
font-family: inherit;
font-size: 14px;
line-height: 150%;
color: var(--theme-caption-color);
background-color: transparent;
border: 1px solid transparent;
border-radius: 2px;
outline: none;
&:focus {
border-color: var(--primary-button-enabled);
}
&::-webkit-contacts-auto-fill-button,
&::-webkit-credentials-auto-fill-button {
visibility: hidden;
display: none !important;
pointer-events: none;
height: 0;
width: 0;
margin: 0;
}
}
.text {
position: absolute;
top: 0;
left: 0;
&.checked {
text-decoration: line-through;
color: var(--theme-content-dark-color);
}
}
}
}
</style>

View File

@ -0,0 +1,67 @@
<!--
// Copyright © 2020 Anticrm Platform Contributors.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { getResource } from '@anticrm/platform'
import type { AnyComponent } from '../types'
// import Icon from './Icon.svelte'
import Spinner from './Spinner.svelte'
import ErrorBoundary from './internal/ErrorBoundary'
export let is: AnyComponent
export let props = {}
$: component = getResource(is)
</script>
{#await component}
<div class="spinner-container"><div class="inner"><Spinner /></div></div>
{:then Ctor}
<ErrorBoundary>
<Ctor {...props} on:change on:close on:open on:click/>
</ErrorBoundary>
{:catch err}
ERROR: {console.log(err, JSON.stringify(component))}
{props}
{err}
<!-- <Icon icon={ui.icon.Error} size="32" /> -->
{/await}
<style lang="scss">
.spinner-container {
display: flex;
height: 100%;
}
@keyframes makeVisible {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.spinner-container .inner {
margin: auto;
opacity: 0;
animation-name: makeVisible;
animation-duration: 0.25s;
animation-delay: 0.1s;
}
</style>

View File

@ -0,0 +1,264 @@
<!--
// Copyright © 2020 Anticrm Platform Contributors.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import type { IntlString } from '@anticrm/platform'
import Label from './Label.svelte'
import PopupMenu from './PopupMenu.svelte'
import Calendar from './icons/Calendar.svelte'
import Close from './icons/Close.svelte'
import Back from './icons/Back.svelte'
import Forward from './icons/Forward.svelte'
export let title: IntlString
export let selected: Date = new Date(Date.now())
export let show: boolean = false
let view: Date = selected
const months: Array<string> = [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December'
]
let monthYear: string
let days: Array<number>
const daysInMonth = (date: Date): number => {
return 33 - new Date(date.getFullYear(), date.getMonth(), 33).getDate()
}
$: {
monthYear = months[view.getMonth()] + ' ' + view.getFullYear()
days = []
for (let i = 1; i <= daysInMonth(view); i++) {
days.push(new Date(view.getFullYear(), view.getMonth(), i).getDay())
}
}
</script>
<div class="dataPicker">
<PopupMenu bind:show={show}>
<button
slot="trigger"
class="btn"
class:selected={show}
on:click|preventDefault={() => {
show = !show
}}
>
<div class="icon">
{#if show}<Close size={'small'} />{:else}<Calendar size={'medium'} />{/if}
</div>
</button>
<div class="header">
<div class="title"><Label label={title} /></div>
<div class="nav">
<button
class="btn arrow"
on:click|preventDefault={() => {
view.setMonth(view.getMonth() - 1)
view = view
}}><div class="icon"><Back size={'small'} /></div></button>
<div class="monthYear">
{monthYear}
</div>
<button
class="btn arrow"
on:click|preventDefault={() => {
view.setMonth(view.getMonth() + 1)
view = view
}}><div class="icon"><Forward size={'small'} /></div></button>
</div>
</div>
<div class="calendar">
<div class="caption">Mo</div>
<div class="caption">Tu</div>
<div class="caption">We</div>
<div class="caption">Th</div>
<div class="caption">Fr</div>
<div class="caption">Sa</div>
<div class="caption">Su</div>
{#each days as day, i}
<div
class="day"
class:selected={i + 1 === selected.getDate() &&
view.getMonth() === selected.getMonth() &&
view.getFullYear() === selected.getFullYear()}
style="grid-column: {day + 1}/{day + 2};"
on:click={() => {
selected = new Date(view.getFullYear(), view.getMonth(), i + 1)
show = false
}}
>
{i + 1}
</div>
{/each}
</div>
</PopupMenu>
<div class="selectDate">
<div class="title"><Label label={title} /></div>
<div class="date">
{selected.getMonth() + 1} / {selected.getDate()} / {selected.getFullYear()}
</div>
</div>
</div>
<style lang="scss">
.dataPicker {
display: flex;
flex-wrap: nowrap;
.btn {
display: flex;
justify-content: center;
align-items: center;
margin: 0;
padding: 0;
width: 36px;
height: 36px;
background-color: var(--theme-button-bg-focused);
border: 1px solid transparent;
border-radius: 8px;
outline: none;
cursor: pointer;
.icon {
display: flex;
justify-content: center;
align-items: center;
width: 20px;
height: 20px;
opacity: 0.3;
}
&.arrow {
width: 32px;
height: 32px;
border: 1px solid var(--theme-bg-accent-color);
border-radius: 4px;
.icon {
width: 16px;
height: 16px;
}
}
&.selected {
background-color: var(--theme-button-bg-focused);
border: 1px solid var(--theme-bg-accent-color);
.icon {
opacity: 0.6;
}
}
&:hover {
background-color: var(--theme-button-bg-pressed);
border: 1px solid var(--theme-bg-accent-color);
.icon {
opacity: 1;
}
}
&:focus {
border: 1px solid var(--primary-button-focused-border);
box-shadow: 0 0 0 3px var(--primary-button-outline);
.icon {
opacity: 1;
}
}
}
.header {
display: flex;
flex-direction: column;
color: var(--theme-caption-color);
.title {
margin-bottom: 12px;
font-size: 14px;
font-weight: 500;
text-align: left;
}
.nav {
display: flex;
justify-content: space-between;
align-items: center;
min-width: 264px;
.monthYear {
margin: 0 16px;
line-height: 150%;
white-space: nowrap;
}
}
}
.calendar {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 2px;
margin-top: 8px;
.caption {
display: flex;
justify-content: center;
align-items: center;
width: 36px;
height: 36px;
font-size: 12px;
color: var(--theme-content-dark-color);
}
.day {
display: flex;
justify-content: center;
align-items: center;
width: 36px;
height: 36px;
font-family: inherit;
color: var(--theme-content-dark-color);
border-radius: 8px;
cursor: pointer;
&.selected {
background-color: var(--theme-button-bg-focused);
border: 1px solid var(--theme-bg-accent-color);
color: var(--theme-caption-color);
}
}
}
.selectDate {
margin-left: 12px;
.title {
font-size: 12px;
font-weight: 500;
color: var(--theme-content-accent-color);
}
.date {
font-size: 14px;
color: var(--theme-caption-color);
}
}
}
</style>

View File

@ -0,0 +1,120 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import type { IntlString } from '@anticrm/platform'
import { createEventDispatcher } from 'svelte'
import Close from './internal/icons/Close.svelte'
import ScrollBox from './ScrollBox.svelte'
import Button from './Button.svelte'
import Label from './Label.svelte'
export let label: IntlString
export let okLabel: IntlString
export let okAction: () => void
const dispatch = createEventDispatcher()
</script>
<div class="container">
<form class="dialog">
<div class="header">
<div class="title"><Label {label}/></div>
<div class="tool" on:click={() => { dispatch('close') }}><Close size={'small'}/></div>
</div>
<div class="content">
<ScrollBox vertical gap={0}><slot/></ScrollBox>
</div>
<div class="footer">
<Button label={okLabel} primary on:click={() => { okAction(); dispatch('close') }}/>
<Button label={'Cancel'} on:click={() => { okAction(); dispatch('close') }}/>
</div>
</form>
</div>
<style lang="scss">
.container {
position: relative;
display: flex;
justify-content: space-between;
flex-direction: row-reverse;
width: 100vw;
max-height: 100vh;
height: 100vh;
.dialog {
display: flex;
flex-direction: column;
min-width: 40%;
max-width: 80%;
width: auto;
max-height: 100vh;
height: 100vh;
background-color: var(--theme-bg-color);
border-radius: 30px 0 0 30px;
box-shadow: 0px 50px 120px rgba(0, 0, 0, 0.4);
.header {
display: flex;
justify-content: space-between;
align-items: center;
flex-shrink: 0;
padding: 0 32px 0 40px;
height: 72px;
.title {
flex-grow: 1;
font-weight: 500;
font-size: 18px;
color: var(--theme-caption-color);
user-select: none;
}
.tool {
width: 16px;
height: 16px;
margin-left: 12px;
opacity: .4;
cursor: pointer;
&:hover {
opacity: 1;
}
}
}
.content {
flex-shrink: 0;
width: 640px;
margin: 0 40px;
height: calc(100vh - 168px);
}
.footer {
display: flex;
overflow: hidden;
flex-direction: row-reverse;
align-items: center;
flex-shrink: 0;
gap: 12px;
padding: 0 40px;
height: 96px;
mask-image: linear-gradient(90deg, rgba(0, 0, 0, 0) 20px, rgba(0, 0, 0, 1) 40px);
}
}
}
</style>

View File

@ -0,0 +1,79 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import Label from './Label.svelte'
</script>
<div class="header">
<div class="user-container">
<div class="avatar"></div>
<div class="info">
<div class="name">Candidate Name</div>
<div class="title">Candidate title</div>
</div>
</div>
</div>
<style lang="scss">
.header {
display: flex;
justify-content: center;
align-items: center;
width: 640px;
min-height: 240px;
background-image: url(../../img/header-green.png);
background-repeat: no-repeat;
background-clip: border-box;
background-size: cover;
border-radius: 20px;
.user-container {
display: flex;
flex-direction: column;
align-items: center;
.avatar {
width: 80px;
height: 80px;
border-radius: 50%;
background-color: #C4C4C4;
}
.info {
display: flex;
flex-direction: column;
align-items: center;
margin-top: 10px;
.name {
font-size: 16px;
font-weight: 500;
line-height: 150%;
color: var(--theme-caption-color);
}
.title {
font-size: 12px;
font-weight: 500;
color: var(--theme-caption-color);
opacity: .6;
}
}
}
}
</style>

View File

@ -0,0 +1,114 @@
<!--
// Copyright © 2020 Anticrm Platform Contributors.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { onMount } from 'svelte'
import type { IntlString } from '@anticrm/platform'
import Label from './Label.svelte'
export let label: IntlString | undefined
export let width: string | undefined
export let value: string | undefined
export let placeholder: string = 'placeholder'
export let password: boolean = false
export let focus: boolean = false
let text: HTMLElement
let input: HTMLInputElement
function computeSize(t: EventTarget | null) {
const target = t as HTMLInputElement
const value = target.value
text.innerHTML = (value === '' ? placeholder : value).replaceAll(' ', '&nbsp;')
target.style.width = text.clientWidth + 6 + 'px'
}
onMount(() => {
if (focus) {
input.focus()
focus = false
}
computeSize(input)
})
</script>
<div class="editbox" style="{width ? 'width: ' + width : ''}"
on:click={() => { input.focus() }}
>
<div class="text" bind:this={text}></div>
{#if label}<div class="label"><Label label={label}/></div>{/if}
{#if password}
<input bind:this={input} type="password" bind:value {placeholder} on:input={(ev) => ev.target && computeSize(ev.target)} />
{:else}
<input bind:this={input} type="text" bind:value {placeholder} on:input={(ev) => ev.target && computeSize(ev.target)} />
{/if}
</div>
<style lang="scss">
.text {
position: absolute;
visibility: hidden;
}
.editbox {
display: flex;
flex-direction: column;
min-width: 50px;
height: auto;
.label {
margin-bottom: 4px;
font-size: 12px;
font-weight: 500;
color: var(--theme-caption-color);
opacity: .8;
pointer-events: none;
user-select: none;
}
input {
max-width: 100%;
height: 21px;
margin: -3px;
padding: 2px;
font-family: inherit;
font-size: 14px;
line-height: 150%;
color: var(--theme-caption-color);
background-color: transparent;
border: 1px solid transparent;
border-radius: 2px;
outline: none;
&:focus {
border-color: var(--primary-button-enabled);
}
&::placeholder {
color: var(--theme-content-dark-color);
}
&::-webkit-contacts-auto-fill-button,
&::-webkit-credentials-auto-fill-button {
visibility: hidden;
display: none !important;
pointer-events: none;
height: 0;
width: 0;
margin: 0;
}
}
}
</style>

View File

@ -0,0 +1,76 @@
<!--
// Copyright © 2020 Anticrm Platform Contributors.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import type { IntlString, Asset } from '@anticrm/platform'
import type { AnySvelteComponent } from '../types'
import Icon from './Icon.svelte'
export let icon: Asset | AnySvelteComponent
export let width: string | undefined = undefined
export let value: string | undefined = undefined
export let placeholder: string = 'placeholder'
</script>
<div class="editbox" style={width ? 'width: ' + width : ''}>
<input type="text" bind:value {placeholder} />
<div class="icon">
{#if typeof (icon) === 'string'}
<Icon {icon} size={'small'} />
{:else}
<svelte:component this={icon} size={'small'} />
{/if}
</div>
</div>
<style lang="scss">
.editbox {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0;
min-width: 268px;
height: 40px;
background-color: var(--theme-bg-focused-color);
border: 1px solid var(--theme-bg-accent-color);
border-radius: 8px;
&:focus-within {
border-color: var(--theme-bg-focused-border);
}
input {
width: 100%;
height: 40px;
margin: 0;
padding: 10px 12px;
font-family: inherit;
color: var(--theme-caption-color);
background-color: transparent;
outline: none;
border: none;
border-radius: 8px;
&::placeholder {
color: var(--theme-content-trans-color);
}
}
.icon {
margin: 12px;
width: 16px;
height: 16px;
opacity: .3;
}
}
</style>

View File

@ -0,0 +1,34 @@
<!--
// Copyright © 2020 Anticrm Platform Contributors.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
export let column: number = 2
export let rowGap: number = 40
export let columnGap: number = 24
const style = `grid-template-columns: repeat(${column}, 1fr); row-gap: ${rowGap}px; column-gap: ${columnGap}px;`
</script>
<div class="grid" {style}>
<slot />
</div>
<style lang="scss">
.grid {
display: grid;
grid-template-columns: 1fr 1fr;
row-gap: 40px;
column-gap: 24px;
}
</style>

View File

@ -0,0 +1,45 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import type { Asset } from '@anticrm/platform'
import { getMetadata } from '@anticrm/platform'
export let icon: Asset
export let size: 'small' | 'medium' | 'large'
export let fill = 'var(--theme-caption-color)'
let url: string
$: url = getMetadata(icon) ?? 'https://anticrm.org/logo.svg'
</script>
<svg class={size} {fill}>
<use href={url} />
</svg>
<style lang="scss">
.small {
width: 1.143em;
height: 1.143em;
}
.medium {
width: 1.429em;
height: 1.429em;
}
.large {
width: 1.714em;
height: 1.714em;
}
</style>

View File

@ -0,0 +1,37 @@
<!--
// Copyright © 2020 Anticrm Platform Contributors.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
export let size: 'small' | 'medium' | 'large'
</script>
<div class={size}>
<slot />
</div>
<style lang="scss">
.small {
width: 1.143em;
height: 1.143em;
}
.medium {
width: 1.429em;
height: 1.429em;
}
.large {
width: 1.714em;
height: 1.714em;
}
</style>

View File

@ -0,0 +1,32 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import type { IntlString } from '@anticrm/platform'
import { translate } from '@anticrm/platform'
export let label: IntlString
export let params: Record<string, any> = {}
$: translation = translate(label, params)
</script>
{#await translation}
{label}
{:then text}
{text}
{/await}

View File

@ -0,0 +1,110 @@
<!--
// Copyright © 2020 Anticrm Platform Contributors.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import type { IntlString } from '@anticrm/platform'
import type { AnySvelteComponent } from '../types'
import Label from './Label.svelte'
import Check from './icons/Check.svelte'
export let title: IntlString | undefined = undefined
export let component: AnySvelteComponent | undefined = undefined
export let props: Object = {}
export let selectable: boolean = false
export let selected: boolean = false
export let action: () => Promise<void> = async () => {}
if (title) {
component = Label
props = { label: title }
}
</script>
<button class="popup-item" on:click={() => {
if (selectable) selected = !selected
action()
}}>
<div class="title">
<svelte:component this={component} {...props}/>
</div>
{#if selectable}
<div class="check" class:selected={selected}><Check/></div>
{/if}
</button>
<style lang="scss">
.popup-item {
position: relative;
display: flex;
justify-content: space-between;
align-items: center;
margin: 0;
padding: 8px 12px;
height: 40px;
background-color: transparent;
border: 1px solid transparent;
border-radius: 8px;
outline: none;
cursor: pointer;
.title {
flex-grow: 1;
font-size: 14px;
line-height: 18px;
text-align: left;
color: var(--theme-content-accent-color);
}
.check {
margin-left: 12px;
width: 20px;
height: 20px;
border-radius: 50%;
opacity: 0;
&.selected {
opacity: .8;
}
}
&:hover {
background-color: var(--theme-button-bg-pressed);
border: 1px solid var(--theme-bg-accent-color);
.title {
color: var(--theme-caption-color);
}
.check {
opacity: .2;
&.selected {
opacity: 1;
}
}
}
&:focus {
border: 1px solid var(--primary-button-focused-border);
box-shadow: 0 0 0 3px var(--primary-button-outline);
z-index: 1;
.title {
color: var(--theme-caption-color);
}
.check {
opacity: .2;
&.selected {
opacity: .8;
}
}
}
}
</style>

View File

@ -0,0 +1,125 @@
<!--
// Copyright © 2020 Anticrm Platform Contributors.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { afterUpdate, onDestroy } from 'svelte/internal'
export let margin: number = 12
export let show: boolean
let trigger: HTMLElement
let popup: HTMLElement
let scrolling: boolean
let elScroll: Node
afterUpdate(() => {
if (show) showPopup()
else hidePopup()
})
const showPopup = (): void => {
fitPopup()
popup.style.visibility = 'visible'
elScroll = findNode(trigger, 'scrollBox')
if (elScroll) elScroll.addEventListener('scroll', startScroll)
}
const hidePopup = (): void => {
if (popup) {
popup.style.visibility = 'hidden'
popup.style.maxHeight = ''
}
if (elScroll) elScroll.removeEventListener('scroll', startScroll)
}
const fitPopup = (): void => {
const rectT = trigger.getBoundingClientRect()
const rectP = popup.getBoundingClientRect()
scrolling = false
if (rectT.top > document.body.clientHeight - rectT.bottom) {
// Up
if (rectT.top - 20 - margin < rectP.height) {
scrolling = true
popup.style.maxHeight = `${rectT.top - margin - 20}px`
popup.style.top = '20px'
} else popup.style.top = `${rectT.top - rectP.height - margin}px`
} else {
// Down
if (rectT.bottom + rectP.height + 20 + margin > document.body.clientHeight) {
scrolling = true
popup.style.maxHeight = `${document.body.clientHeight - rectT.bottom - margin - 20}px`
}
popup.style.top = `${rectT.bottom + margin}px`
}
if (rectT.left + rectP.width + 20 > document.body.clientWidth) {
popup.style.left = `${document.body.clientWidth - rectP.width - 20}px`
} else popup.style.left = `${rectT.left}px`
}
const findNode = (el: Node, name: string): any => {
while (el.parentNode !== null) {
if (el.classList.contains(name)) return el
el = el.parentNode
}
return false
}
const waitClick = (event: any): void => {
event.stopPropagation()
if (show) {
if (!findNode(event.target, 'popup-menu')) show = false
}
}
const startScroll = (): void => { show = false }
onDestroy(() => {
if (elScroll) elScroll.removeEventListener('scroll', startScroll)
})
</script>
<svelte:window on:mouseup={waitClick} on:resize={startScroll} />
<div class="popup-menu">
<div bind:this={trigger}>
<slot name="trigger" />
</div>
<div class="popup" bind:this={popup}>
{#if show}
<div class="content" class:scrolling><slot /></div>
{/if}
</div>
</div>
<style lang="scss">
.popup {
position: fixed;
visibility: hidden;
display: flex;
flex-direction: column;
padding: 16px;
color: var(--theme-caption-color);
background-color: var(--theme-button-bg-hovered);
border: 1px solid var(--theme-button-border-enabled);
border-radius: 12px;
box-shadow: 0px 20px 60px rgba(0, 0, 0, 0.6);
user-select: none;
z-index: 10;
.content {
display: flex;
flex-direction: column;
&.scrolling {
overflow-y: auto;
}
}
}
</style>

View File

@ -0,0 +1,47 @@
<!--
// Copyright © 2020 Anticrm Platform Contributors.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
export let value: number
export let min: number = 0
export let max: number = 100
export let color: string = '#50BCF9'
const proc: number = (max - min) / 100
if (value > max) value = max
if (value < min) value = min
</script>
<div class="progress-container">
<div class="bar" style="background-color: {color}; width: calc(100% * {Math.round((value - min) / proc)} / 100);"/>
</div>
<style lang="scss">
.progress-container {
position: relative;
width: 100%;
height: 4px;
background-color: var(--theme-button-bg-hovered);
border-radius: 2px;
.bar {
position: absolute;
top: 0;
left: 0;
height: 100%;
border-radius: 2px;
}
}
</style>

View File

@ -0,0 +1,27 @@
<!--
// Copyright © 2020 Anticrm Platform Contributors.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
</script>
<div class="row">
<slot />
</div>
<style lang="scss">
.row {
grid-column-start: 1;
grid-column-end: -1;
}
</style>

View File

@ -0,0 +1,77 @@
<!--
// Copyright © 2020 Anticrm Platform Contributors.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
export let gap: number = 12
export let vertical: boolean = false
export let stretch: boolean = false
export let bothScroll: boolean = false
</script>
<div class="scroll" class:vertical={vertical} class:bothScroll={bothScroll}>
<div class="box" class:stretch={stretch} style="gap: {gap}px">
<slot/>
</div>
</div>
<style lang="scss">
.scroll {
position: relative;
width: auto;
height: 100%;
overflow-x: auto;
overflow-y: hidden;
margin-right: 0;
margin-bottom: -5px;
.box {
position: absolute;
display: grid;
grid-auto-flow: column;
padding: 0 0 5px 0;
gap: 24px;
top: 0;
left: 0;
width: auto;
height: 100%;
&.stretch {
width: 100%;
}
}
&.vertical {
margin: 0 -10px 0 -10px;
overflow-x: hidden;
overflow-y: auto;
.box {
grid-auto-flow: row;
padding: 0 10px 0 10px;
width: 100%;
height: auto;
&.stretch {
height: 100%;
}
}
}
&.bothScroll {
margin: 0 -5px -5px 0;
overflow: auto;
.box {
padding: 0 5px 5px 0;
}
}
}
</style>

View File

@ -0,0 +1,75 @@
<!--
// Copyright © 2020 Anticrm Platform Contributors.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { IntlString } from '@anticrm/platform'
import type { AnySvelteComponent } from '../types'
import Label from './Label.svelte'
import ArrowUp from './icons/Up.svelte'
import ArrowDown from './icons/Down.svelte'
export let icon: AnySvelteComponent
export let label: IntlString
export let closed: boolean = false
</script>
<div class="section-container"
on:click|preventDefault={() => {
closed = !closed
}}
>
<svelte:component this={icon} size={'medium'} />
<div class="title"><Label {label} /></div>
<div class="arrow">{#if closed}<ArrowUp size={'small'} />{:else}<ArrowDown size={'small'} />{/if}</div>
</div>
<div class="section-content" class:hidden={closed}><slot/></div>
<style lang="scss">
.section-container {
display: flex;
flex-direction: row;
align-items: center;
flex-wrap: nowrap;
width: 100%;
height: 80px;
min-height: 80px;
cursor: pointer;
user-select: none;
.title {
flex-grow: 1;
margin-left: 12px;
font-weight: 500;
color: var(--theme-caption-color);
}
.arrow {
margin: 8px;
}
}
.section-content {
margin: 16px 0 54px;
height: auto;
visibility: visible;
&.hidden {
margin: 0;
height: 0;
visibility: hidden;
}
}
:global(.section-container + .section-container),
:global(.section-content + .section-container) {
border-top: 1px solid var(--theme-menu-divider);
}
</style>

View File

@ -0,0 +1,76 @@
<!--
// Copyright © 2020 Anticrm Platform Contributors.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
export let gap: number = 12
export let vertical: boolean = false
export let stretch: boolean = false
export let bothScroll: boolean = false
</script>
<div class="scrollBox" class:vertical class:bothScroll>
<div class="box" class:stretch style="gap: {gap}px">
<slot />
</div>
</div>
<style lang="scss">
.scrollBox {
position: relative;
width: auto;
height: 100%;
overflow-x: auto;
overflow-y: hidden;
margin-right: 0;
margin-bottom: -5px;
.box {
position: absolute;
display: grid;
grid-auto-flow: column;
padding: 0 0 5px 0;
gap: 24px;
top: 0;
left: 0;
width: auto;
height: 100%;
&.stretch {
width: 100%;
}
}
&.vertical {
margin: 0 -10px 0 -10px;
overflow-x: hidden;
overflow-y: auto;
.box {
padding: 0 10px 0 10px;
width: 100%;
height: auto;
grid-auto-flow: row;
&.stretch {
height: 100%;
}
}
}
&.bothScroll {
margin: 0 -5px -5px 0;
overflow: auto;
.box {
padding: 0 5px 5px 0;
}
}
}
</style>

View File

@ -0,0 +1,117 @@
<!--
// Copyright © 2020 Anticrm Platform Contributors.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import type { AnySvelteComponent, IPopupItem } from '../types'
import Label from './Label.svelte'
import PopupMenu from './PopupMenu.svelte'
import PopupItem from './PopupItem.svelte'
import ActionIcon from './ActionIcon.svelte'
import Close from './icons/Close.svelte'
export let component: AnySvelteComponent | undefined = undefined
export let items: Array<IPopupItem>
export let item: IPopupItem
export let vAlign: 'top' | 'middle' | 'bottom' = 'bottom'
export let hAlign: 'left' | 'center' | 'right' = 'left'
export let margin: number = 16
export let gap: number = 8
let byTitle: boolean = (component) ? false : true
let pressed: boolean = false
</script>
<PopupMenu {vAlign} {hAlign} {margin} bind:show={pressed}>
<button class="btn" slot="trigger" style="margin: {gap/2}px;"
on:click={(event) => {
pressed = !pressed
event.stopPropagation()
}}
>
<div class="title">
{#if byTitle }
<Label label={item.title}/>
{:else}
<svelte:component this={component} {...item.props}/>
{/if}
</div>
<div class="icon"><ActionIcon label={'Remove'} direction={'top'} icon={Close} size={'small'} action={async () => { item.selected = false }}/></div>
</button>
{#if byTitle }
<PopupItem bind:title={item.title} selectable bind:selected={item.selected}/>
{:else}
<PopupItem bind:component={component} bind:props={item.props} selectable bind:selected={item.selected}/>
{/if}
{#each items.filter(i => !i.selected) as noItem}
{#if byTitle }
<PopupItem title={noItem.title} selectable bind:selected={noItem.selected} action={async () => {
pressed = false
item.selected = false
}}/>
{:else}
<PopupItem component={component} props={noItem.props} selectable bind:selected={noItem.selected} action={async () => {
pressed = false
item.selected = false
}}/>
{/if}
{/each}
</PopupMenu>
<style lang="scss">
.btn {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: nowrap;
margin: 0;
padding: 8px 12px;
width: auto;
height: 40px;
background-color: var(--theme-button-bg-pressed);
border: 1px solid var(--theme-bg-accent-color);
border-radius: 12px;
outline: none;
cursor: pointer;
.title {
flex-grow: 1;
text-align: left;
color: var(--theme-caption-color);
}
.icon {
width: 16px;
height: 16px;
margin-left: 12px;
opacity: .8;
}
&:hover {
background-color: var(--theme-button-bg-pressed);
border: 1px solid var(--theme-bg-accent-color);
.icon {
opacity: 1;
}
}
&:focus {
border: 1px solid var(--primary-button-focused-border);
box-shadow: 0 0 0 3px var(--primary-button-outline);
.icon {
opacity: 1;
}
}
}
</style>

View File

@ -0,0 +1,28 @@
<div class="spinner">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M12 0v1c6.1 0 11 4.9 11 11s-4.9 11-11 11v1c6.6 0 12-5.4 12-12S18.6 0 12 0z" fill="#fff"/>
<linearGradient id="a" gradientUnits="userSpaceOnUse" x1="0" y1="22" x2="0" y2="2">
<stop offset="0" stop-color="#fff"/>
<stop offset="1" stop-color="#fff" stop-opacity="0"/>
</linearGradient>
<path d="M12 23C5.9 23 1 18.1 1 12S5.9 1 12 1V0C5.4 0 0 5.4 0 12s5.4 12 12 12v-1z" fill="url(#a)"/>
</svg>
</div>
<style lang="scss">
.spinner {
width: 24px;
height: 24px;
-webkit-animation: spinCircle 1s infinite linear;
animation: spinCircle 1s infinite linear;
}
@-webkit-keyframes spinCircle {
from { -webkit-transform: rotate(0deg); }
to { -webkit-transform: rotate(-359deg); }
}
@keyframes spinCircle {
from { transform: rotate(0deg); }
to { transform: rotate(-359deg); }
}
</style>

View File

@ -0,0 +1,48 @@
<!--
// Copyright © 2020 Anticrm Platform Contributors.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import type { Status } from '@anticrm/platform'
import { Severity } from '@anticrm/platform'
import StatusControl from './internal/Status.svelte'
export let status: Status
</script>
{#if status.severity !== Severity.OK}
<div class="message-container" class:error={status.severity === Severity.ERROR}>
<StatusControl {status} />
</div>
{/if}
<style lang="scss">
.message-container {
display: flex;
flex-direction: row;
align-items: center;
padding: 12px 16px;
background-color: var(--theme-bg-accent-color);
border: 1px solid var(--theme-bg-accent-hover);
border-radius: 8px;
}
.error {
color: var(--system-error-color);
fill: var(--system-error-color);
background-color: var(--theme-button-bg-error);
border-color: var(--system-error-60-color);
}
</style>

View File

@ -0,0 +1,73 @@
<!--
// Copyright © 2020 Anticrm Platform Contributors.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { IntlString } from '@anticrm/platform'
import Label from './Label.svelte'
interface Tab {
title: IntlString
}
export let tabs: Array<Tab> = [{ title: 'General' }, { title: 'Attachments' }]
export let selected: IntlString = 'General'
</script>
<div class="tabs-container">
{#each tabs as tab}
<div class="tab" class:selected={tab.title === selected}
on:click={() => { selected = tab.title }}>
<Label label={tab.title}/>
</div>
{/each}
<div class="grow"/>
</div>
<style lang="scss">
.tabs-container {
display: flex;
flex-direction: row;
align-items: stretch;
flex-wrap: nowrap;
margin-bottom: 16px;
width: 100%;
height: 72px;
min-height: 72px;
border-bottom: 1px solid var(--theme-menu-divider);
.tab {
display: flex;
align-items: center;
height: 72px;
color: var(--theme-content-trans-color);
cursor: pointer;
user-select: none;
&.selected {
border-top: 2px solid transparent;
border-bottom: 2px solid var(--theme-caption-color);
color: var(--theme-caption-color);
cursor: default;
}
}
.tab + .tab {
margin-left: 40px;
}
.grow {
min-width: 40px;
flex-grow: 1;
}
}
</style>

View File

@ -0,0 +1,73 @@
<!--
// Copyright © 2020 Anticrm Platform Contributors.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import type { IntlString } from '@anticrm/platform'
import Label from './Label.svelte'
export let label: IntlString | undefined
export let width: string | undefined
export let height: string | undefined
export let value: string | undefined
export let placeholder: string | undefined
</script>
<div class="textarea" style="{width ? `width: ${width}px;` : ''} {height ? `height: ${height}px;` : ''}">
{#if label}<div class="label"><Label label={label} /></div>{/if}
<textarea bind:value {placeholder} />
</div>
<style lang="scss">
.textarea {
display: flex;
flex-direction: column;
min-width: 50px;
min-height: 36px;
.label {
margin-bottom: 4px;
font-size: 12px;
font-weight: 500;
color: var(--theme-caption-color);
opacity: .8;
pointer-events: none;
user-select: none;
}
textarea {
width: auto;
min-height: 70px;
margin: -3px;
padding: 2px;
font-family: inherit;
font-size: 14px;
line-height: 150%;
color: var(--theme-caption-color);
background-color: transparent;
border: 1px solid transparent;
border-radius: 2px;
outline: none;
overflow-y: scroll;
resize: none;
&:focus {
border-color: var(--primary-button-enabled);
}
&::placeholder {
color: var(--theme-content-dark-color);
}
}
}
</style>

View File

@ -0,0 +1,89 @@
<!--
// Copyright © 2020 Anticrm Platform Contributors.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
export let on: boolean = false
</script>
<label class="toggle">
<input class="chBox" type="checkbox" bind:checked={on}>
<span class="toggle-switch"></span>
</label>
<style lang="scss">
.toggle {
display: inline-block;
height: 28px;
line-height: 28px;
vertical-align: middle;
font-size: 14px;
user-select: none;
.chBox {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
border: 0;
padding: 0;
clip: rect(0 0 0 0);
overflow: hidden;
&:checked + .toggle-switch {
background-color: var(--theme-on-color);
&:before {
transform: translateX(22px);
}
}
&:not(:disabled) + .toggle-switch {
cursor: pointer;
}
&:disabled + .toggle-switch {
filter: grayscale(70%);
&:before {
background: #eee;
}
}
&:focus-within + .toggle-switch {
border: 1px solid var(--primary-button-focused-border);
box-shadow: 0 0 0 2px var(--primary-button-outline);
}
}
.toggle-switch {
position: relative;
display: inline-block;
box-sizing: border-box;
width: 54px;
height: 30px;
border-radius: 50px;
vertical-align: top;
background-color: var(--theme-off-color);
border: 1px solid transparent;
transition: .2s;
&:before {
content: '';
position: absolute;
top: 2px;
left: 3px;
display: inline-block;
width: 24px;
height: 24px;
border-radius: 50%;
background: #fff;
box-shadow: 1px 2px 7px rgba(119, 129, 142, 0.1);
transition: .15s;
}
}
}
</style>

View File

@ -0,0 +1,56 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import type { IntlString } from '@anticrm/platform'
import Toggle from './Toggle.svelte'
import Label from './Label.svelte'
export let label: IntlString
export let description: IntlString | undefined = undefined
export let on: boolean = false
</script>
<div class="toggleWithLabel">
<div class="caption">
<Label {label} />
{#if description}
<span><Label label={description} /></span>
{/if}
</div>
<Toggle bind:on={on}/>
</div>
<style lang="scss">
.toggleWithLabel {
display: flex;
justify-content: space-between;
align-items: center;
.caption {
margin-right: 16px;
font-size: 14px;
font-weight: 400;
color: var(--theme-caption-color);
user-select: none;
span {
display: block;
font-size: 12px;
opacity: .3;
}
}
}
</style>

View File

@ -0,0 +1,125 @@
<!--
// Copyright © 2020 Anticrm Platform Contributors.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import type { IntlString } from '@anticrm/platform'
import Label from './Label.svelte'
export let label: IntlString
export let direction: string = 'top'
</script>
<div class="container">
<div class="trigger"><slot/></div>
<div class="tooltip {direction}">
<Label label={label}/>
</div>
</div>
<style lang="scss">
.container {
position: relative;
display: flex;
justify-content: center;
align-items: center;
.trigger:hover + .tooltip {
opacity: 1;
&.top {
transform: translateY(-10px);
}
&.bottom {
transform: translateY(10px);
}
&.right {
transform: translateX(10px);
}
&.left {
transform: translateX(-10px);
}
}
.tooltip {
box-sizing: border-box;
position: absolute;
padding: 8px;
color: var(--theme-caption-color);
background-color: var(--theme-tooltip-color);
border: 1px solid var(--theme-bg-accent-color);
border-radius: 8px;
box-shadow: 0px 8px 20px rgba(0, 0, 0, 0.25);
opacity: 0;
transition: transform .3s ease, opacity .2s ease-in-out;
pointer-events: none;
user-select: none;
text-align: center;
transition-delay: .2s;
z-index: 10;
&::after {
content: "";
position: absolute;
width: 14px;
height: 14px;
background-color: var(--theme-tooltip-color);
border: 1px solid var(--theme-bg-accent-color);
border-radius: 0 0 3px;
mask-image: linear-gradient(-45deg, rgba(0, 0, 0, 1) 9px, rgba(0, 0, 0, 0) 9.1px);
}
&.top::after, &.bottom::after {
left: 50%;
margin-left: -8px;
}
&.top {
bottom: 100%;
box-shadow: 0px -8px 20px rgba(0, 0, 0, 0.25);
&::after {
bottom: -5px;
transform: rotate(45deg);
}
}
&.bottom {
top: 100%;
box-shadow: 0px -8px 20px rgba(0, 0, 0, 0.25);
&::after {
top: -5px;
transform: rotate(-135deg);
}
}
&.right::after, &.left::after {
top: 50%;
margin-top: -8px;
}
&.right {
left: 100%;
box-shadow: -8px 0px 20px rgba(0, 0, 0, 0.25);
&::after {
left: -5px;
transform: rotate(135deg);
}
}
&.left {
right: 100%;
box-shadow: 8px 0px 20px rgba(0, 0, 0, 0.25);
&::after {
right: -5px;
transform: rotate(-45deg);
}
}
}
}
</style>

View File

@ -0,0 +1,11 @@
<script lang="ts">
import IconSize from '../IconSize.svelte'
export let size: 'small' | 'medium' | 'large'
const fill: string = 'var(--theme-caption-color)'
</script>
<IconSize {size}>
<svg {fill} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<path d="M14,7.5H8.5V2c0-0.3-0.2-0.5-0.5-0.5S7.5,1.7,7.5,2v5.5H2C1.7,7.5,1.5,7.7,1.5,8S1.7,8.5,2,8.5h5.5V14 c0,0.3,0.2,0.5,0.5,0.5s0.5-0.2,0.5-0.5V8.5H14c0.3,0,0.5-0.2,0.5-0.5S14.3,7.5,14,7.5z"/>
</svg>
</IconSize>

View File

@ -0,0 +1,11 @@
<script lang="ts">
import IconSize from '../IconSize.svelte'
export let size: 'small' | 'medium' | 'large'
const fill: string = 'var(--theme-caption-color)'
</script>
<IconSize {size}>
<svg {fill} viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<polygon points="10.6,14.4 4.3,8 10.6,1.6 11.4,2.4 5.7,8 11.4,13.6 "/>
</svg>
</IconSize>

View File

@ -0,0 +1,13 @@
<script lang="ts">
import IconSize from '../IconSize.svelte'
export let size: 'small' | 'medium' | 'large'
const fill: string = 'var(--theme-caption-color)'
</script>
<IconSize {size}>
<svg {fill} viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path
d="M19.5,5h-2.1V4.5c0-0.3-0.2-0.5-0.5-0.5s-0.5,0.2-0.5,0.5V5H8.1V4.5C8.1,4.2,7.9,4,7.6,4S7.1,4.2,7.1,4.5V5H5 C4.2,5,3.5,5.7,3.5,6.5V19c0,0.8,0.7,1.5,1.5,1.5h14.5c0.8,0,1.5-0.7,1.5-1.5V6.5C21,5.7,20.3,5,19.5,5z M5,6h2.1v0.5 c0,0.3,0.2,0.5,0.5,0.5s0.5-0.2,0.5-0.5V6h8.3v0.5c0,0.3,0.2,0.5,0.5,0.5s0.5-0.2,0.5-0.5V6h2.1C19.7,6,20,6.3,20,6.5v3.1H4.5V6.5 C4.5,6.3,4.7,6,5,6z M19.5,19.5H5c-0.3,0-0.5-0.2-0.5-0.5v-8.3H20V19C20,19.2,19.7,19.5,19.5,19.5z"
/>
</svg>
</IconSize>

View File

@ -0,0 +1,27 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import IconSize from '../IconSize.svelte'
export let size: 'small' | 'medium' | 'large'
const fill: string = '#4474F6'
</script>
<IconSize {size}>
<svg {fill} xmlns="http://www.w3.org/2000/svg">
<path d="M10,0C4.5,0,0,4.5,0,10s4.5,10,10,10s10-4.5,10-10S15.5,0,10,0z M13.7,8l-4,5c-0.1,0.1-0.3,0.2-0.5,0.2 S8.8,13.1,8.6,13l-2.3-2.5C6,10.2,6,9.7,6.3,9.4c0.3-0.3,0.8-0.3,1.1,0l1.7,1.9L12.5,7c0.3-0.3,0.7-0.4,1.1-0.1 C13.9,7.2,14,7.6,13.7,8z"/>
</svg>
</IconSize>

View File

@ -0,0 +1,11 @@
<script lang="ts">
import IconSize from '../IconSize.svelte'
export let size: 'small' | 'medium' | 'large'
const fill: string = 'var(--theme-caption-color)'
</script>
<IconSize {size}>
<svg {fill} xmlns="http://www.w3.org/2000/svg">
<path d="M8.7,8l5.6-5.6c0.2-0.2,0.2-0.5,0-0.7s-0.5-0.2-0.7,0L8,7.3L2.4,1.6c-0.2-0.2-0.5-0.2-0.7,0s-0.2,0.5,0,0.7L7.3,8l-5.6,5.6 c-0.2,0.2-0.2,0.5,0,0.7c0.1,0.1,0.2,0.1,0.4,0.1s0.3,0,0.4-0.1L8,8.7l5.6,5.6c0.1,0.1,0.2,0.1,0.4,0.1s0.3,0,0.4-0.1 c0.2-0.2,0.2-0.5,0-0.7L8.7,8z"/>
</svg>
</IconSize>

View File

@ -0,0 +1,15 @@
<script lang="ts">
import IconSize from '../IconSize.svelte'
export let size: 'small' | 'medium' | 'large'
const fill: string = 'var(--theme-caption-color)'
</script>
<IconSize {size}>
<svg {fill} viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<g>
<path d="M10,2.8C6,2.8,2.8,6,2.8,10S6,17.2,10,17.2h4.2c0.4,0,0.6,0,0.8,0c1.1-0.2,1.9-1,2.1-2c0-0.2,0-0.4,0-0.8V10 C17.2,6,14,2.8,10,2.8z M16.1,14.9c-0.1,0.6-0.6,1.1-1.2,1.2c-0.1,0-0.3,0-0.7,0H10c-3.4,0-6.2-2.8-6.2-6.2c0-3.4,2.8-6.2,6.2-6.2 s6.2,2.8,6.2,6.2v4.2C16.2,14.6,16.2,14.8,16.1,14.9z"/>
<path d="M12.5,8.7h-5C7.2,8.7,7,8.9,7,9.2s0.2,0.5,0.5,0.5h5c0.3,0,0.5-0.2,0.5-0.5S12.8,8.7,12.5,8.7z"/>
<path d="M12.5,12H10c-0.3,0-0.5,0.2-0.5,0.5S9.7,13,10,13h2.5c0.3,0,0.5-0.2,0.5-0.5S12.8,12,12.5,12z"/>
</g>
</svg>
</IconSize>

View File

@ -0,0 +1,11 @@
<script lang="ts">
import IconSize from '../IconSize.svelte'
export let size: 'small' | 'medium' | 'large'
const fill: string = 'var(--theme-caption-color)'
</script>
<IconSize {size}>
<svg {fill} viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<path d="M7.8,11.8L11.7,8H4L7.8,11.8z"/>
</svg>
</IconSize>

View File

@ -0,0 +1,11 @@
<script lang="ts">
import IconSize from '../IconSize.svelte'
export let size: 'small' | 'medium' | 'large'
const fill: string = 'var(--theme-caption-color)'
</script>
<IconSize {size}>
<svg {fill} viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<polygon points="6.4,14.4 5.6,13.6 11.3,8 5.6,2.4 6.4,1.6 12.7,8 "/>
</svg>
</IconSize>

View File

@ -0,0 +1,11 @@
<script lang="ts">
import IconSize from '../IconSize.svelte'
export let size: 'small' | 'medium' | 'large'
const fill: string = 'var(--theme-caption-color)'
</script>
<IconSize {size}>
<svg {fill} viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<path d="M14.4,13.6L11.7,11c0.8-1,1.3-2.2,1.3-3.5c0-3-2.5-5.5-5.5-5.5C4.5,2,2,4.5,2,7.5c0,3,2.5,5.5,5.5,5.5 c1.3,0,2.6-0.5,3.5-1.3l2.6,2.6c0.2,0.2,0.5,0.2,0.7,0C14.5,14.2,14.5,13.8,14.4,13.6z M3,7.5C3,5,5,3,7.5,3S12,5,12,7.5 S10,12,7.5,12S3,10,3,7.5z"/>
</svg>
</IconSize>

View File

@ -0,0 +1,14 @@
<script lang="ts">
import IconSize from '../IconSize.svelte'
export let size: 'small' | 'medium' | 'large'
const fill: string = 'var(--theme-caption-color)'
</script>
<IconSize {size}>
<svg {fill} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<g>
<path d="M12,0H4C1.8,0,0,1.8,0,4v8c0,2.2,1.8,4,4,4h8c2.2,0,4-1.8,4-4V4C16,1.8,14.2,0,12,0z M15,12c0,1.7-1.3,3-3,3H4 c-1.7,0-3-1.3-3-3V4c0-1.7,1.3-3,3-3h8c1.7,0,3,1.3,3,3V12z"/>
<path d="M11.6,4.7L6.8,9.8L4.4,7.3c-0.2-0.2-0.5-0.2-0.7,0C3.5,7.5,3.5,7.8,3.6,8l3.2,3.2l5.5-5.9c0.2-0.2,0.2-0.5,0-0.7 C12.1,4.4,11.8,4.5,11.6,4.7z"/>
</g>
</svg>
</IconSize>

View File

@ -0,0 +1,11 @@
<script lang="ts">
import IconSize from '../IconSize.svelte'
export let size: 'small' | 'medium' | 'large'
const fill: string = 'var(--theme-caption-color)'
</script>
<IconSize {size}>
<svg {fill} viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<path d="M7.8,7.2L4,11h7.7L7.8,7.2z"/>
</svg>
</IconSize>

View File

@ -0,0 +1,33 @@
<script lang="ts">
import { onDestroy } from 'svelte'
let hours = ''
let minutes = ''
let delimiter = false
function updateTime () {
const date = new Date()
const h = date.getHours()
hours = h < 10 ? `0${h}` : h.toString()
const m = date.getMinutes()
minutes = m < 10 ? `0${m}` : m.toString()
delimiter = !delimiter
}
const interval = setInterval(updateTime, 500)
updateTime()
onDestroy(() => clearInterval(interval))
</script>
<div>
<span>{hours}</span>
<span class:h={!delimiter}>:</span>
<span>{minutes}</span>
</div>
<style>
.h {
visibility: hidden;
}
</style>

View File

@ -0,0 +1,32 @@
//
// Copyright © 2020 Anticrm Platform Contributors.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import ErrorComponent from './ErrorComponent.svelte'
export default class errorBoundary extends ErrorComponent {
constructor (config) {
let error = null
config.props.$$slots.default = config.props.$$slots.default.map((x) => (...args) => {
try {
return x(...args)
} catch (e) {
error = e
}
})
super(config)
if (error) {
this.$set({ error: error })
}
}
}

View File

@ -0,0 +1,38 @@
<!--
// Copyright © 2020 Anticrm Platform Contributors.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script>
export let error = null
</script>
<style>
.error {
border: 1px solid red;
}
.trace {
font-family: monospace;
}
</style>
<slot>
{#if error}
<div class="error">
<b>{error.message}</b>
<pre class="trace">
{error.stack}
</pre>
</div>
{/if}
</slot>

View File

@ -0,0 +1,27 @@
<script lang="ts">
import { getContext } from 'svelte'
import FontSize from './icons/FontSize.svelte'
const { currentFontSize, setFontSize } = getContext('fontsize')
const fontsizes = ['small-font', 'normal-font']
let current = fontsizes.indexOf(currentFontSize)
function changeFontSize () {
current++
setFontSize(fontsizes[current % fontsizes.length])
}
</script>
<div class="container" on:click={changeFontSize}>
<FontSize size={'small'}/>
</div>
<style lang="scss">
.container {
display: flex;
align-items: center;
height: 56px;
}
</style>

View File

@ -0,0 +1,129 @@
<script lang="ts">
import { onDestroy } from 'svelte'
import { OK } from '@anticrm/platform'
import { PlatformEvent, addEventListener } from '@anticrm/platform'
import type { AnyComponent } from '../../types'
// import { applicationShortcutKey } from '../../utils'
import { location } from '../../location'
import { Theme } from '@anticrm/theme'
import Component from '../Component.svelte'
import StatusComponent from './Status.svelte'
import Clock from './Clock.svelte'
// import Mute from './icons/Mute.svelte'
import WiFi from './icons/WiFi.svelte'
import ThemeSelector from './ThemeSelector.svelte'
import FontSizeSelector from './FontSizeSelector.svelte'
let application: AnyComponent | undefined
onDestroy(location.subscribe((loc) => {
if (loc.path[0]) {
application = loc.path[0] as AnyComponent
}
}))
let status = OK
addEventListener(PlatformEvent, async (_event, _status) => {
status = _status
})
</script>
<Theme>
<div id="ui-root">
<div class="status-bar">
<div class="container">
<div class="status-messages">
<StatusComponent {status} />
</div>
<div class="widgets">
<div class="clock">
<Clock />
</div>
<div class="widget">
<ThemeSelector />
</div>
<div class="widget">
<FontSizeSelector />
</div>
<div class="widget">
<WiFi size={'small'}/>
</div>
</div>
</div>
</div>
<div class="app">
{#if application}
<Component is={application} props={{}} />
{:else}
<div class="caption-1 error">
Application not found: {application}
</div>
{/if}
</div>
</div>
</Theme>
<style lang="scss">
$status-bar-height: 32px;
#ui-root {
position: relative;
display: flex;
flex-direction: column;
height: 100vh;
.status-bar {
min-height: $status-bar-height;
min-width: 1200px;
.container {
display: flex;
align-items: center;
height: 100%;
.status-messages {
flex-grow: 1;
text-align: center;
}
.widgets {
display: flex;
align-items: center;
flex-direction: row-reverse;
.clock {
margin: 0 40px 0 24px;
font-weight: 500;
font-size: 12px;
color: var(--theme-caption-color);
opacity: 0.3;
user-select: none;
}
.widget {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
opacity: 0.3;
}
}
}
}
.error {
margin-top: 45vh;
text-align: center;
}
.app {
height: calc(100vh - #{$status-bar-height});
min-width: 1200px;
min-height: 600px;
}
}
</style>

View File

@ -0,0 +1,36 @@
<script lang="ts">
import type { Status } from '@anticrm/platform'
import { Severity } from '@anticrm/platform'
import Info from './icons/Info.svelte'
import Label from '../Label.svelte'
export let status: Status
</script>
<div class="status">
{#if status.severity !== Severity.OK}
<Info size={'small'}/>
{/if}
{#if status.severity !== Severity.OK}
<span><Label label={status.code} params={status.params}/></span>
{/if}
</div>
<style lang="scss">
.status {
display: flex;
justify-content: center;
align-items: center;
font-size: 11px;
opacity: .3;
user-select: none;
span {
margin-left: 8px;
}
&:hover {
opacity: 1;
}
}
</style>

View File

@ -0,0 +1,27 @@
<script lang="ts">
import { getContext } from 'svelte'
import Mute from './icons/Mute.svelte'
const { currentTheme, setTheme } = getContext('theme')
const themes = ['theme-light', 'theme-grey', 'theme-dark']
let current = themes.indexOf(currentTheme)
function changeTheme () {
current++
setTheme(themes[current % themes.length])
}
</script>
<div class="container" on:click={changeTheme}>
<Mute size={'small'}/>
</div>
<style lang="scss">
.container {
display: flex;
align-items: center;
height: 56px;
}
</style>

View File

@ -0,0 +1,27 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import IconSize from '../../IconSize.svelte'
export let size: 'small' | 'medium' | 'large'
const fill: string = 'var(--theme-caption-color)'
</script>
<IconSize {size}>
<svg viewBox="0 0 24 24" {fill} xmlns="http://www.w3.org/2000/svg">
<path d="M12.8,12l7.6-7.6c0.2-0.2,0.2-0.6,0-0.8s-0.6-0.2-0.8,0L12,11.2L4.4,3.6c-0.2-0.2-0.6-0.2-0.8,0s-0.2,0.6,0,0.8 l7.6,7.6l-7.6,7.6c-0.2,0.2-0.2,0.6,0,0.8c0.1,0.1,0.3,0.2,0.4,0.2s0.3-0.1,0.4-0.2l7.6-7.6l7.6,7.6c0.1,0.1,0.3,0.2,0.4,0.2 s0.3-0.1,0.4-0.2c0.2-0.2,0.2-0.6,0-0.8L12.8,12z"/>
</svg>
</IconSize>

View File

@ -0,0 +1,14 @@
<script lang="ts">
import IconSize from '../../IconSize.svelte'
export let size: 'small' | 'medium' | 'large'
const fill: string = 'var(--theme-caption-color)'
</script>
<IconSize {size}>
<svg {fill} viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<g>
<path d="M3.7,3.4l-3.1,9.1h1.3l0.9-2.6h3.5l0.9,2.6h1.3L5.3,3.4H3.7z M3,8.8l1.4-4.2h0.1L6,8.8H3z"/>
<path d="M14.7,11.4V7.9c0-0.7-0.2-1.3-0.7-1.7c-0.5-0.4-1.1-0.6-2-0.6c-0.6,0-1.1,0.1-1.5,0.4c-0.4,0.3-0.7,0.6-0.9,0.9l0.7,0.7 C10.5,7.3,10.7,7,11,6.8c0.3-0.2,0.6-0.3,1-0.3c0.5,0,0.9,0.1,1.1,0.4c0.2,0.2,0.4,0.6,0.4,1v0.6h-1.3c-1,0-1.7,0.2-2.2,0.5 c-0.5,0.3-0.7,0.8-0.7,1.5c0,0.6,0.2,1.1,0.6,1.5c0.4,0.4,0.9,0.5,1.6,0.5c0.5,0,0.9-0.1,1.2-0.3s0.6-0.5,0.7-0.9h0.1 c0,0.3,0.2,0.6,0.3,0.8c0.2,0.2,0.5,0.3,0.8,0.3h0.7v-1H14.7z M13.4,10.5c0,0.3-0.2,0.6-0.5,0.8c-0.3,0.2-0.7,0.3-1.2,0.3 c-0.4,0-0.7-0.1-0.9-0.3s-0.3-0.4-0.3-0.7v-0.3c0-0.3,0.1-0.6,0.4-0.7c0.3-0.2,0.7-0.2,1.2-0.2h1.3V10.5z"/>
</g>
</svg>
</IconSize>

View File

@ -0,0 +1,29 @@
<!--
// Copyright © 2020 Anticrm Platform Contributors.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import IconSize from '../../IconSize.svelte'
export let size: 'small' | 'medium' | 'large'
const fill: string = 'var(--theme-caption-color)'
</script>
<IconSize {size}>
<svg {fill} viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<g opacity="0.6">
<path d="M8,16c-4.4,0-8-3.6-8-8s3.6-8,8-8s8,3.6,8,8S12.4,16,8,16z M8,1C4.1,1,1,4.1,1,8c0,3.9,3.1,7,7,7c3.9,0,7-3.1,7-7 C15,4.1,11.9,1,8,1z"/>
<path d="M8,12.1c0.4,0,0.8-0.3,0.8-0.8S8.4,10.5,8,10.5s-0.8,0.3-0.8,0.8S7.6,12.1,8,12.1z M7.6,9.6h0.8l0.2-6.2H7.5 L7.6,9.6z"/>
</g>
</svg>
</IconSize>

View File

@ -0,0 +1,11 @@
<script lang="ts">
import IconSize from '../../IconSize.svelte'
export let size: 'small' | 'medium' | 'large'
const fill: string = 'var(--theme-caption-color)'
</script>
<IconSize {size}>
<svg {fill} viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M11.9,21.6c-0.1,0-0.1,0-0.2,0c-5-0.2-9.1-4.3-9.3-9.3c-0.1-5.2,3.7-9.5,8.8-9.9c0.2,0,0.5,0.1,0.6,0.3 c0.1,0.2,0.1,0.5,0,0.7c-1.9,2.6-1.6,6.1,0.6,8.3c2.2,2.3,5.8,2.6,8.3,0.7c0.2-0.1,0.4-0.2,0.7,0c0.2,0.1,0.3,0.3,0.3,0.6 C21.2,17.8,16.9,21.6,11.9,21.6z M10,3.8c-3.8,0.9-6.5,4.3-6.4,8.5c0.2,4.4,3.8,7.9,8.1,8.1c4,0.1,7.6-2.6,8.5-6.4 c-2.9,1.4-6.4,0.9-8.7-1.5C9.1,10.2,8.6,6.7,10,3.8z"/>
</svg>
</IconSize>

View File

@ -0,0 +1,13 @@
<script lang="ts">
import IconSize from '../../IconSize.svelte'
export let size: 'small' | 'medium' | 'large'
const fill: string = 'var(--theme-caption-color)'
</script>
<IconSize {size}>
<svg {fill} viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M12,11c-2.4,0-4.5,1-6,2.7c-0.2,0.3-0.2,0.6,0.1,0.8s0.6,0.2,0.8-0.1C8.1,13,10,12.2,12,12.2 c2,0,3.9,0.8,5.1,2.3c0.1,0.1,0.3,0.2,0.5,0.2c0.1,0,0.3,0,0.4-0.1c0.3-0.2,0.3-0.6,0.1-0.8C16.6,11.9,14.4,11,12,11z"/>
<path d="M12,17.2c-1,0-2,0.4-2.6,1c-0.2,0.2-0.2,0.6,0,0.8c0.2,0.2,0.6,0.2,0.8,0c0.8-0.9,2.7-0.9,3.6,0 c0.1,0.1,0.3,0.2,0.4,0.2c0.2,0,0.3-0.1,0.4-0.2c0.2-0.2,0.2-0.6,0-0.8C14,17.6,13,17.2,12,17.2z"/>
<path d="M22.5,9.9C19.8,6.6,16,4.7,12,4.7c-4,0-7.8,1.9-10.5,5.2c-0.2,0.3-0.2,0.6,0.1,0.8c0.3,0.2,0.6,0.2,0.8-0.1 c2.4-3,5.9-4.7,9.5-4.7c3.6,0,7.1,1.7,9.5,4.8c0.1,0.1,0.3,0.2,0.5,0.2c0.1,0,0.3,0,0.4-0.1C22.6,10.6,22.7,10.2,22.5,9.9z"/>
</svg>
</IconSize>

105
packages/ui/src/index.ts Normal file
View File

@ -0,0 +1,105 @@
//
// Copyright © 2020 Anticrm Platform Contributors.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import { SvelteComponent } from 'svelte'
import Root from './components/internal/Root.svelte'
export type { AnyComponent, AnySvelteComponent, Action } from './types'
// export { applicationShortcutKey } from './utils'
export { getCurrentLocation, navigate, location } from './location'
export { default as EditBox } from './components/EditBox.svelte'
export { default as Label } from './components/Label.svelte'
export { default as Button } from './components/Button.svelte'
export { default as StatusControl } from './components/StatusControl.svelte'
export { default as Component } from './components/Component.svelte'
export { default as Icon } from './components/Icon.svelte'
export { default as ActionIcon } from './components/ActionIcon.svelte'
export { default as Toggle } from './components/Toggle.svelte'
export { default as Dialog } from './components/Dialog.svelte'
export { default as ToggleWithLabel } from './components/ToggleWithLabel.svelte'
export { default as Tooltip } from './components/Tooltip.svelte'
export { default as CheckBox } from './components/CheckBox.svelte'
export { default as Progress } from './components/Progress.svelte'
export { default as Tabs } from './components/Tabs.svelte'
export { default as ScrollBox } from './components/ScrollBox.svelte'
export { default as PopupMenu } from './components/PopupMenu.svelte'
export { default as PopupItem } from './components/PopupItem.svelte'
export { default as SelectBox } from './components/SelectBox.svelte'
export { default as SelectItem } from './components/SelectItem.svelte'
export { default as TextArea } from './components/TextArea.svelte'
export { default as Section } from './components/Section.svelte'
export { default as DatePicker } from './components/DatePicker.svelte'
export { default as EditStylish } from './components/EditStylish.svelte'
export { default as Grid } from './components/Grid.svelte'
export { default as Row } from './components/Row.svelte'
export { default as DialogHeader } from './components/DialogHeader.svelte'
export { default as CheckBoxWithLabel } from './components/CheckBoxWithLabel.svelte'
export { default as CheckBoxList } from './components/CheckBoxList.svelte'
export { default as IconSize } from './components/IconSize.svelte'
export { default as IconAdd } from './components/icons/Add.svelte'
export { default as IconSearch } from './components/icons/Search.svelte'
export { default as IconToDo } from './components/icons/ToDo.svelte'
export { default as IconComments } from './components/icons/Comments.svelte'
export function createApp (target: HTMLElement): SvelteComponent {
return new Root({ target })
}
// let documentProvider: DocumentProvider | undefined
// async function open (doc: Document): Promise<void> {
// if (documentProvider != null) {
// return await documentProvider.open(doc)
// }
// return await Promise.reject(new Error('Document provider is not registred'))
// }
// function selection (): Document | undefined {
// if (documentProvider != null) {
// return documentProvider.selection()
// }
// return undefined
// }
// function registerDocumentProvider (provider: DocumentProvider): void {
// documentProvider = provider
// }
import type { AnySvelteComponent, AnyComponent } from './types'
import { writable } from 'svelte/store'
interface CompAndProps {
is: AnySvelteComponent | AnyComponent | undefined
props: any
element: HTMLElement | undefined
}
export const store = writable<CompAndProps>({
is: undefined,
props: {},
element: undefined
})
export function showModal (component: AnySvelteComponent | AnyComponent, props: any, element?: HTMLElement): void {
store.set({ is: component, props, element: element })
}
export function closeModal (): void {
store.set({ is: undefined, props: {}, element: undefined })
}

116
packages/ui/src/location.ts Normal file
View File

@ -0,0 +1,116 @@
//
// Copyright © 2020, 2021 Anticrm Platform Contributors.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import { Location as PlatformLocation } from './types'
import { writable, derived } from 'svelte/store'
export function locationToUrl (location: PlatformLocation): string {
let result = '/'
if (location.path != null) {
result += location.path.map((p) => encodeURIComponent(p)).join('/')
}
if (location.query != null) {
const queryValue = Object.entries(location.query)
.map((e) => {
if (e[1] != null) {
// Had value
return e[0] + '=' + e[1]
} else {
return e[0]
}
})
.join('&')
if (queryValue.length > 0) {
result += '?' + queryValue
}
}
if (location.fragment != null && location.fragment.length > 0) {
result += '#' + location.fragment
}
return result
}
function parseLocation (location: Location): PlatformLocation {
return {
path: parsePath(location.pathname),
query: parseQuery(location.search),
fragment: parseHash(location.hash)
}
}
function parseQuery (query: string): Record<string, string | null> {
query = query.trim()
if (query.length === 0 || !query.startsWith('?')) {
return {}
}
query = query.substring(1)
const vars = query.split('&')
const result: Record<string, string | null> = {}
for (let i = 0; i < vars.length; i++) {
const pair = vars[i].split('=')
const key = pair[0]
if (key.length > 0) {
if (pair.length > 1) {
const value = pair[1]
result[key] = value
} else {
result[key] = null
}
}
}
return result
}
function parsePath (path: string): string[] {
const split = path.split('/').map((ps) => decodeURIComponent(ps))
if (split.length >= 1) {
if (split[0] === '') {
split.splice(0, 1)
}
}
if (split.length >= 1) {
if (split[split.length - 1] === '') {
split.splice(split.length - 1, 1)
}
}
return split
}
function parseHash (hash: string): string {
if (hash.startsWith('#')) {
return hash.substring(1)
}
return hash
}
// ------------------------
export function getCurrentLocation (): PlatformLocation {
return parseLocation(window.location)
}
const locationWritable = writable(getCurrentLocation())
window.addEventListener('popstate', () => {
locationWritable.set(getCurrentLocation())
})
export const location = derived(locationWritable, (loc) => loc)
export function navigate (location: PlatformLocation): void {
const url = locationToUrl(location)
history.pushState(null, '', url)
locationWritable.set(location)
}

4
packages/ui/src/svg.d.ts vendored Normal file
View File

@ -0,0 +1,4 @@
declare module '*.svg' {
const content: string
export default content
}

98
packages/ui/src/types.ts Normal file
View File

@ -0,0 +1,98 @@
//
// Copyright © 2020 Anticrm Platform Contributors.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import { /* Metadata, Plugin, plugin, */ Resource /*, Service */ } from '@anticrm/platform'
import { /* getContext, */ SvelteComponent } from 'svelte'
import type { Asset, IntlString } from '@anticrm/platform'
/**
* Describe a browser URI location parsed to path, query and fragment.
*/
export interface Location {
path: string[] // A useful path value
query?: Record<string, string | null> // a value of query parameters, no duplication are supported
fragment?: string // a value of fragment
}
export type AnySvelteComponent = typeof SvelteComponent
export type Component<C extends AnySvelteComponent> = Resource<C>
export type AnyComponent = Resource<AnySvelteComponent>
export const CONTEXT_PLATFORM = 'platform'
export const CONTEXT_PLATFORM_UI = 'platform-ui'
export interface Document {} // eslint-disable-line @typescript-eslint/no-empty-interface
/**
* Allow to control currently selected document.
*/
export interface DocumentProvider {
/**
* Opening a document
* */
open: (doc: Document) => Promise<void>
/**
* Return currently selected document, if one.
*/
selection: () => Document | undefined
}
export interface Action {
label: IntlString
icon: Asset | AnySvelteComponent
action: () => Promise<void>
}
export interface IPopupItem {
_id?: number
title?: IntlString | undefined
component?: AnySvelteComponent | undefined
props?: Object
selected?: boolean
action?: Function
}
// export default plugin(
// 'ui' as Plugin<UIService>,
// {},
// {
// metadata: {
// LoginApplication: '' as Metadata<string>,
// DefaultApplication: '' as Metadata<string>
// },
// icon: {
// Default: '' as Asset,
// Error: '' as Asset,
// Network: '' as Asset,
// Search: '' as Asset,
// Add: '' as Asset,
// ArrowDown: '' as Asset,
// Message: '' as Asset,
// Phone: '' as Asset,
// Mail: '' as Asset,
// More: '' as Asset
// },
// component: {
// Icon: '' as AnyComponent,
// Spinner: '' as AnyComponent,
// BadComponent: '' as AnyComponent
// },
// method: {
// AnAction: '' as Resource<(args: any) => void>
// }
// }
// )

102
packages/ui/src/utils.ts Normal file
View File

@ -0,0 +1,102 @@
//
// Copyright © 2020 Anticrm Platform Contributors.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
// import type { Metadata } from '@anticrm/platform'
import type { AnyComponent /*, Location */ } from './types'
// import { Readable, derived, writable } from 'svelte/store'
// import { onDestroy, getContext, setContext } from 'svelte'
// function windowLocation (): Location {
// return parseLocation(window.location)
// }
// const locationWritable = writable(windowLocation())
// window.addEventListener('popstate', () => {
// locationWritable.set(windowLocation())
// })
// const location: Readable<Location> = derived(locationWritable, (loc) => loc)
// function subscribeLocation (listener: (location: Location) => void, destroyFactory: (op: () => void) => void): void {
// const unsubscribe = location.subscribe((location) => {
// listener(location)
// })
// destroyFactory(unsubscribe)
// }
// function navigate (newUrl: string): void {
// const curUrl = locationToUrl(windowLocation())
// if (curUrl === newUrl) {
// return
// }
// history.pushState(null, '', newUrl)
// locationWritable.set(windowLocation())
// }
// function navigateJoin (
// path: string[] | undefined,
// query: Record<string, string> | undefined,
// fragment: string | undefined
// ): void {
// const newLocation = windowLocation()
// if (path != null) {
// newLocation.path = path
// }
// if (query != null) {
// // For query we do replace
// const currentQuery = newLocation.query || {}
// for (const kv of Object.entries(query)) {
// currentQuery[kv[0]] = kv[1]
// }
// }
// if (fragment) {
// newLocation.fragment = fragment
// }
// navigate(locationToUrl(newLocation))
// }
// const CONTEXT_ROUTE_VALUE = 'routes.context'
// export function newRouter<T> (
// pattern: string,
// matcher: (match: T) => void,
// defaults: T | undefined = undefined
// ): ApplicationRouter<T> {
// const r: Router<any> = getContext(CONTEXT_ROUTE_VALUE)
// const navigateOp = (loc: Location): void => {
// navigate(locationToUrl(loc))
// }
// const result = r ? r.newRouter<T>(pattern, defaults) : new Router<T>(pattern, r, defaults, navigateOp)
// result.subscribe(matcher)
// if (!r) {
// // No parent, we need to subscribe for location changes.
// subscribeLocation((loc) => {
// result.update(loc)
// }, onDestroy)
// }
// if (r) {
// // We need to remove child router from parent, if component is destroyed
// onDestroy(() => r.clearChildRouter())
// }
// setContext(CONTEXT_ROUTE_VALUE, result)
// return result
// }
// R O U T E R M E T A D A T A K E Y S
// export function applicationShortcutKey (shortcut: string): Metadata<AnyComponent> {
// return ('shortcut:ui.' + shortcut) as Metadata<AnyComponent>
// }

View File

@ -0,0 +1,5 @@
const sveltePreprocess = require('svelte-preprocess')
module.exports = {
preprocess: sveltePreprocess()
};

15
packages/ui/tsconfig.json Normal file
View File

@ -0,0 +1,15 @@
{
"compilerOptions": {
"moduleResolution": "node",
"target": "esnext",
"module": "esnext",
"declaration": true,
"outDir": "./lib",
"strict": true,
"esModuleInterop": true,
"lib": [
"esnext",
"dom"
]
}
}

View File

@ -475,6 +475,22 @@
"packageName": "@anticrm/dev-server", "packageName": "@anticrm/dev-server",
"projectFolder": "dev/server", "projectFolder": "dev/server",
"shouldPublish": true "shouldPublish": true
},
{
"packageName": "@anticrm/theme",
"projectFolder": "packages/theme",
"shouldPublish": true
},
{
"packageName": "@anticrm/ui",
"projectFolder": "packages/ui",
"shouldPublish": true
},
{
"packageName": "prod",
"projectFolder": "dev/prod",
"shouldPublish": true
} }
] ]
} }