Setting plugin (#328)

Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
Denis Bykhov
2021-11-22 13:06:14 +01:00
committed by GitHub
parent ef0c3e8c91
commit 5e807d466a
54 changed files with 2629 additions and 1284 deletions
@@ -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 { createQuery } from '@anticrm/presentation'
import setting from '@anticrm/setting'
import type { Integration, IntegrationType } from '@anticrm/setting'
import PluginCard from './PluginCard.svelte'
const typeQuery = createQuery()
const integrationQuery = createQuery()
let integrations: Integration[] = []
let integrationTypes: IntegrationType[] = []
typeQuery.query(setting.class.IntegrationType, {}, (res) => (integrationTypes = res))
integrationQuery.query(setting.class.Integration, {}, (res) => (integrations = res))
</script>
<div class="cards-container">
{#each integrationTypes as integrationType (integrationType._id)}
<PluginCard integration={integrations.find((p) => p.type === integrationType._id)} {integrationType} />
{/each}
</div>
<style lang="scss">
.cards-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(20rem, auto));
grid-auto-rows: minmax(12.5rem, auto);
grid-gap: 1.5rem;
padding: 3rem;
}
</style>
@@ -0,0 +1,108 @@
<!--
// 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 { createEventDispatcher } from 'svelte'
export let length: number = 6
export let value: string = ''
const dispatch = createEventDispatcher()
let digits: string[] = []
let areas: HTMLInputElement[] = []
let selected: number = 0
let filled: number
for (let i = 0; i < length; i++) digits[i] = ''
const onInput = (ev?: Event, n?: number): void => {
selected = -1
filled = 0
for (let i = 0; i < length; i++) {
if (digits[i] && digits[i].length > 1) {
if (i < length - 1) digits[i + 1] = digits[i].substring(1)
digits[i] = digits[i][0]
}
if (digits[i]) filled++
if (selected < 0 && selected < length && digits[i] === '') selected = i
}
digits = digits
if (filled === length) {
if ((ev as InputEvent).inputType === 'insertFromPaste' && n && n < length) selected = n
if ((ev as InputEvent).inputType === 'insertText' && selected < 0 && (n || n === 0)) selected = n + 1
if (selected === length) selected--
value = ''
for (let i = 0; i < length; i++) value += digits[i]
dispatch('filled', value)
}
if (selected !== -1 && selected < length) {
areas[selected].focus()
areas[selected].select()
}
}
const selectInput = (n: number): void => {
if (areas[n]) {
areas[n].focus()
areas[n].select()
}
}
const keyPressed = (ev: Event, n: number): void => {
if ((ev as KeyboardEvent).key === 'Backspace' && n > 0 && digits[n] === '') {
digits[n - 1] = ''
digits = digits
onInput(undefined, n - 1)
}
}
onMount(() => {
if (areas[0]) onInput()
})
</script>
<div class="flex-between">
{#each digits as digit, i}
<input
class="fs-title digit"
type="text"
bind:this={areas[i]}
bind:value={digit}
on:input={(ev) => {
onInput(ev, i)
}}
on:keydown={async (ev) => keyPressed(ev, i)}
on:click={async () => selectInput(i)}
/>
{/each}
</div>
<style lang="scss">
.digit {
width: 2.5rem;
height: 3rem;
text-align: center;
background-color: var(--theme-card-bg-dark);
border: 1px solid transparent;
border-style: none;
border-radius: 0.5rem;
&:focus {
border: 1px solid var(--primary-button-focused-border);
box-shadow: 0 0 0 3px var(--primary-button-outline);
}
}
</style>
@@ -0,0 +1,108 @@
<!--
// 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 { Button, Component, Label, Link } from '@anticrm/ui'
import { getMetadata } from '@anticrm/platform'
import { showPopup } from '@anticrm/ui'
import type { Integration, IntegrationType } from '@anticrm/setting'
import setting from '@anticrm/setting'
import { getClient } from '@anticrm/presentation'
import type { Ref, Space } from '@anticrm/core'
import login from '@anticrm/login'
export let integrationType: IntegrationType
export let integration: Integration | undefined
const accountId = getMetadata(login.metadata.LoginEmail)
const client = getClient()
async function close(res: any): Promise<void> {
if (res.value) {
await client.createDoc(setting.class.Integration, accountId as Ref<Space>, {
type: integrationType._id,
value: res.value
})
}
}
async function disconnect(): Promise<void> {
if (integration !== undefined) {
await client.removeDoc(setting.class.Integration, accountId as Ref<Space>, integration._id)
}
}
</script>
<div class="flex-col plugin-container">
<div class="flex-row-center header">
<div class="icon mr-4"><Component is={integrationType.icon} /></div>
<div class="flex-grow flex-col">
<div class="fs-title max-label overflow-label"><Label label={integrationType.label} /></div>
<div class="small-text content-dark-color max-label overflow-label">
<Label label={integrationType.description} />
</div>
</div>
</div>
<div class="content">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod temp</div>
<div class="footer">
{#if integration}
<Button label={'Disconnect'} on:click={disconnect} />
{:else}
<Button
label={'Add'}
primary
on:click={(e) => {
showPopup(integrationType.createComponent, {}, e.target, close)
}}
/>
<Link label={'Learn more'} />
{/if}
</div>
</div>
<style lang="scss">
.plugin-container {
background-color: var(--theme-button-bg-enabled);
border: 1px solid var(--theme-bg-accent-color);
border-radius: 0.75rem;
}
.header {
margin: 1.5rem 1.5rem 1rem;
}
.max-label {
max-width: calc(100% - 6.25rem);
}
.icon {
flex-shrink: 0;
width: 2.25rem;
height: 2.25rem;
}
.content {
margin: 0 1.5rem 0.25rem;
color: var(--theme-caption-color);
}
.footer {
flex-shrink: 0;
display: grid;
grid-auto-flow: column;
direction: rtl;
justify-content: start;
align-items: center;
column-gap: 1rem;
padding: 1.5rem 1.75rem 1.25rem;
height: 5.25rem;
mask-image: linear-gradient(90deg, rgba(0, 0, 0, 0) 1.25rem, rgba(0, 0, 0, 1) 2.5rem);
overflow: hidden;
border-radius: 0 0 1.25rem 1.25rem;
}
</style>
@@ -0,0 +1,35 @@
<!--
// 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.
-->
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 36 36"
style="enable-background:new 0 0 36 36;"
xml:space="preserve"
>
<path
fill={'#FFFFFF'}
d="M0,12.8c0-4.5,0-6.7,0.9-8.4c0.8-1.5,2-2.7,3.5-3.5C6.1,0,8.3,0,12.8,0h10.4c4.5,0,6.7,0,8.4,0.9 c1.5,0.8,2.7,2,3.5,3.5c0.9,1.7,0.9,4,0.9,8.4v10.4c0,4.5,0,6.7-0.9,8.4c-0.8,1.5-2,2.7-3.5,3.5c-1.7,0.9-4,0.9-8.4,0.9H12.8 c-4.5,0-6.7,0-8.4-0.9c-1.5-0.8-2.7-2-3.5-3.5C0,29.9,0,27.7,0,23.2V12.8z"
/>
<path fill={'#EA4335'} d="M25.8,8.4l-7.7,6l-7.9-6v0l0,0v8.4l7.8,6.1l7.8-5.9V8.4z" />
<path fill={'#FBBC05'} d="M27.8,6.9l-2,1.5V17l6.4-4.9V9.2C32.1,9.2,31.4,5,27.8,6.9z" />
<path fill={'#34A853'} d="M25.8,17v11.2h4.9c0,0,1.4-0.1,1.5-1.7V12.2L25.8,17z" />
<path fill={'#C5221F'} d="M10.2,28.3V16.8l0,0L10.2,28.3z" />
<path fill={'#C5221F'} d="M10.2,8.4l-2-1.5C4.6,5,3.9,9.2,3.9,9.2v2.9l6.4,4.6V8.4z" />
<path fill={'#C5221F'} d="M10.2,8.4v8.4l0,0L10.2,8.4L10.2,8.4z" />
<path fill={'#4285F4'} d="M3.9,12.2v14.4c0.1,1.6,1.5,1.7,1.5,1.7h4.9l0-11.5L3.9,12.2z" />
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

@@ -0,0 +1,44 @@
<!--
// 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.
-->
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 36 36"
style="enable-background:new 0 0 36 36;"
xml:space="preserve"
>
<linearGradient
id="SVGID_TELEGRAM_"
gradientUnits="userSpaceOnUse"
x1="18"
y1="-36"
x2="18"
y2="-72"
gradientTransform="matrix(1 0 0 -1 0 -36)"
>
<stop offset="0" style="stop-color:#37BBFE" />
<stop offset="1" style="stop-color:#007DBB" />
</linearGradient>
<path
fill={'url(#SVGID_TELEGRAM_)'}
d="M0,12.8c0-4.5,0-6.7,0.9-8.4c0.8-1.5,2-2.7,3.5-3.5C6.1,0,8.3,0,12.8,0h10.4c4.5,0,6.7,0,8.4,0.9 c1.5,0.8,2.7,2,3.5,3.5c0.9,1.7,0.9,4,0.9,8.4v10.4c0,4.5,0,6.7-0.9,8.4c-0.8,1.5-2,2.7-3.5,3.5c-1.7,0.9-4,0.9-8.4,0.9H12.8 c-4.5,0-6.7,0-8.4-0.9c-1.5-0.8-2.7-2-3.5-3.5C0,29.9,0,27.7,0,23.2V12.8z"
/>
<path
fill={'#FFFFFF'}
d="M27,10.6c0.2-1-0.8-1.9-1.7-1.4L7.5,17.2c-0.6,0.3-0.6,1.3,0.1,1.5l3.7,1.2c0.7,0.2,1.5,0.1,2.1-0.3l8.3-5.9 c0.2-0.2,0.5,0.2,0.3,0.4l-6,6.3c-0.6,0.6-0.5,1.7,0.2,2.1l6.7,4.3c0.7,0.5,1.7,0,1.9-0.9L27,10.6z"
/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

@@ -0,0 +1,83 @@
<!--
// 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 { IconClose, Label } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
const dispatch = createEventDispatcher()
</script>
<div class="card">
<div class="card-bg" />
<div class="flex-between header">
<div class="overflow-label fs-title"><Label label={'Connect Email account'} /></div>
<div
class="tool"
on:click={() => {
dispatch('close')
}}
>
<IconClose size={'small'} />
</div>
</div>
<div class="content">Not implemented yet</div>
</div>
<style lang="scss">
.card {
position: relative;
display: flex;
flex-direction: column;
width: 20rem;
min-width: 20rem;
max-width: 20rem;
border-radius: 1.25rem;
.header {
flex-shrink: 0;
margin: 1.75rem 1.75rem 1.25rem;
.tool {
cursor: pointer;
&:hover {
color: var(--theme-caption-color);
}
&:active {
color: var(--theme-content-accent-color);
}
}
}
.content {
flex-shrink: 0;
flex-grow: 1;
height: fit-content;
margin: 0 1.75rem 0.5rem;
}
.card-bg {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: var(--theme-card-bg);
border-radius: 1.25rem;
backdrop-filter: blur(15px);
box-shadow: var(--theme-card-shadow);
z-index: -1;
}
}
</style>
@@ -0,0 +1,183 @@
<!--
// 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 { getMetadata, serialize } from '@anticrm/platform'
import { Button, EditBox, IconClose, Label } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
import login from '@anticrm/login'
import PinPad from '../PinPad.svelte'
const dispatch = createEventDispatcher()
let requested = false
let secondFactor = false
let phone: string = ''
let code: string = ''
let password: string = ''
const url = getMetadata(login.metadata.TelegramUrl)
async function requestCode(): Promise<void> {
const res = await sendRequest('/auth', { phone: phone })
if (res.next === 'code') {
requested = true
}
}
async function sendPassword(): Promise<void> {
const res = await sendRequest('/auth/pass', { pass: password })
if (res.next === 'end') {
dispatch('close', { value: phone })
}
}
async function sendCode(): Promise<void> {
const res = await sendRequest('/auth/code', { code: code })
if (res.next === 'pass') {
secondFactor = true
} else if (res.next === 'end') {
dispatch('close', { value: phone })
}
}
async function sendRequest(path: string, data: any): Promise<any> {
const response = await fetch(url + path, {
method: 'POST',
headers: {
Authorization: 'Bearer ' + getMetadata(login.metadata.LoginToken),
'Content-Type': 'application/json'
},
body: serialize(data)
})
const res = await response.json()
if (res.err != null) {
throw new Error(res.err)
}
return res
}
function back() {
password = ''
code = ''
phone = ''
requested = false
secondFactor = false
}
</script>
<div class="card">
<div class="card-bg" />
<div class="flex-between header">
<div class="overflow-label fs-title"><Label label={'Connect Telegram account'} /></div>
<div
class="tool"
on:click={() => {
dispatch('close')
}}
>
<IconClose size={'small'} />
</div>
</div>
<div class="content">
{#if secondFactor}
<p><Label label={'Enter your second factor password'} /></p>
<EditBox label={'Password'} password placeholder={'password'} bind:value={password} />
<div class="footer">
<Button label={'Connect'} primary disabled={!password.length} on:click={sendPassword} />
<a class="link" href={'#'} on:click={back}><Label label={'Back'} /></a>
</div>
{:else if requested}
<p><Label label={'Enter the 5-digit code you received on your Telegram account.'} /></p>
<PinPad length={5} bind:value={code} />
<div class="footer">
<Button label={'Connect'} primary disabled={!code.match(/^\d{5}$/)} on:click={sendCode} />
<a class="link" href={'#'} on:click={back}><Label label={'Back'} /></a>
</div>
{:else}
<p><Label label={'Enter your Telegram phone number to connect your account.'} /></p>
<EditBox label={'Phone number'} placeholder={'+1 555 333 7777'} bind:value={phone} />
<div class="footer">
<Button label={'Next'} primary disabled={!phone.match(/^\+\d{9,15}$/)} on:click={requestCode} />
</div>
{/if}
</div>
</div>
<style lang="scss">
.card {
position: relative;
display: flex;
flex-direction: column;
width: 20rem;
min-width: 20rem;
max-width: 20rem;
border-radius: 1.25rem;
.header {
flex-shrink: 0;
margin: 1.75rem 1.75rem 1.25rem;
.tool {
cursor: pointer;
&:hover {
color: var(--theme-caption-color);
}
&:active {
color: var(--theme-content-accent-color);
}
}
}
.content {
flex-shrink: 0;
flex-grow: 1;
height: fit-content;
margin: 0 1.75rem 0.5rem;
p {
margin: 0 0 1rem;
}
.footer {
display: flex;
flex-direction: row-reverse;
justify-content: space-between;
align-items: center;
padding: 1rem 1.75rem 1.75rem;
.link {
color: var(--theme-content-dark-color);
&:hover {
color: var(--theme-caption-color);
}
&:active {
color: var(--theme-content-accent-color);
}
}
}
}
.card-bg {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: var(--theme-card-bg);
border-radius: 1.25rem;
backdrop-filter: blur(15px);
box-shadow: var(--theme-card-shadow);
z-index: -1;
}
}
</style>