Migrate to new static styles property for lit-element

This commit is contained in:
Martin Kleinschrodt 2019-03-26 17:23:49 +01:00
parent 70e7b98fb8
commit a0bfd7294a
54 changed files with 3835 additions and 4394 deletions

36
package-lock.json generated
View File

@ -724,7 +724,7 @@
},
"ansi-escapes": {
"version": "3.1.0",
"resolved": "http://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz",
"integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==",
"dev": true
},
@ -1806,7 +1806,7 @@
},
"es6-promisify": {
"version": "5.0.0",
"resolved": "http://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz",
"resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz",
"integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=",
"dev": true,
"requires": {
@ -2267,7 +2267,7 @@
},
"camelcase-keys": {
"version": "2.1.0",
"resolved": "http://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
"resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
"integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=",
"dev": true,
"requires": {
@ -2315,7 +2315,7 @@
},
"meow": {
"version": "3.7.0",
"resolved": "http://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
"resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
"integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=",
"dev": true,
"requires": {
@ -2490,7 +2490,7 @@
"dependencies": {
"pify": {
"version": "2.3.0",
"resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
"dev": true
}
@ -2547,7 +2547,7 @@
},
"globby": {
"version": "8.0.1",
"resolved": "http://registry.npmjs.org/globby/-/globby-8.0.1.tgz",
"resolved": "https://registry.npmjs.org/globby/-/globby-8.0.1.tgz",
"integrity": "sha512-oMrYrJERnKBLXNLVTqhm3vPEdJ/b2ZE28xN4YARiix1NOIOBPEpOUnm844K1iu/BkphCaf2WNFwMszv8Soi1pw==",
"dev": true,
"requires": {
@ -3836,7 +3836,7 @@
"dependencies": {
"semver": {
"version": "5.3.0",
"resolved": "http://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
"integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=",
"dev": true
}
@ -4067,7 +4067,7 @@
},
"os-homedir": {
"version": "1.0.2",
"resolved": "http://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
"resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
"integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=",
"dev": true
},
@ -4099,7 +4099,7 @@
},
"get-stream": {
"version": "3.0.0",
"resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
"integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=",
"dev": true
}
@ -4107,7 +4107,7 @@
},
"os-tmpdir": {
"version": "1.0.2",
"resolved": "http://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
"integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
"dev": true
},
@ -4135,7 +4135,7 @@
},
"p-is-promise": {
"version": "1.1.0",
"resolved": "http://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz",
"resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz",
"integrity": "sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4=",
"dev": true
},
@ -4304,7 +4304,7 @@
},
"path-is-absolute": {
"version": "1.0.1",
"resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"dev": true
},
@ -4844,7 +4844,7 @@
},
"safe-regex": {
"version": "1.1.0",
"resolved": "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
"resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
"integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=",
"dev": true,
"requires": {
@ -5168,7 +5168,7 @@
},
"sprintf-js": {
"version": "1.0.3",
"resolved": "http://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
"dev": true
},
@ -5248,7 +5248,7 @@
},
"string_decoder": {
"version": "1.1.1",
"resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dev": true,
"requires": {
@ -5263,7 +5263,7 @@
},
"strip-ansi": {
"version": "3.0.1",
"resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"dev": true,
"requires": {
@ -5278,7 +5278,7 @@
},
"strip-eof": {
"version": "1.0.0",
"resolved": "http://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
"resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
"integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=",
"dev": true
},
@ -5319,7 +5319,7 @@
},
"tar": {
"version": "2.2.1",
"resolved": "http://registry.npmjs.org/tar/-/tar-2.2.1.tgz",
"resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz",
"integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=",
"dev": true,
"requires": {

View File

@ -11,7 +11,9 @@
<link rel="apple-touch-icon" href="/assets/icons/apple-touch-180.png">
<style> html, body {
background: #59c6ff;
background: linear-gradient(rgb(89, 198, 255), rgb(7, 124, 185));
width: 100%;
height: 100%;
margin: 0;
overscroll-behavior-y: contain;
} </style>

File diff suppressed because it is too large Load Diff

View File

@ -9,8 +9,8 @@
"@polymer/paper-spinner": "^3.0.0-pre.18",
"@webcomponents/webcomponentsjs": "^2.0.0",
"autosize": "^4.0.2",
"lit-element": "^2.0.1",
"lit-html": "^0.14.0",
"lit-element": "^2.1.0",
"lit-html": "^1.0.0",
"localforage": "^1.7.3"
},
"devDependencies": {

View File

@ -1,6 +1,5 @@
import { localize } from "@padloc/core/lib/locale.js";
import { shared } from "../styles";
import { element, html, property } from "./base.js";
import { element, html, property, css } from "./base.js";
import { Dialog } from "./dialog.js";
const defaultButtonLabel = localize("OK");
@ -36,34 +35,35 @@ export class AlertDialog extends Dialog<AlertOptions, number> {
@property({ reflect: true })
horizontal: boolean = false;
static styles = [
...Dialog.styles,
css`
:host([hide-icon]) .info-icon {
display: none;
}
:host([hide-icon]) .info-text,
:host([hide-icon]) .info-title {
text-align: center;
}
:host([horizontal]) .buttons {
flex-direction: row;
}
:host([horizontal]) button {
flex: 1;
}
.info-text:not(.small) {
font-size: var(--font-size-default);
}
`
];
renderContent() {
const { message, dialogTitle, options, icon } = this;
return html`
${shared}
<style>
:host([hide-icon]) .info-icon {
display: none;
}
:host([hide-icon]) .info-text,
:host([hide-icon]) .info-title {
text-align: center;
}
:host([horizontal]) .buttons {
flex-direction: row;
}
:host([horizontal]) button {
flex: 1;
}
.info-text:not(.small) {
font-size: var(--font-size-default);
}
</style>
<div class="info" ?hidden=${!dialogTitle && !message}>
<pl-icon class="info-icon" icon="${icon}"></pl-icon>
<div class="info-body">

View File

@ -4,7 +4,7 @@ import { app, router } from "../init.js";
import { AutoLock } from "../mixins/auto-lock.js";
import { ErrorHandling } from "../mixins/error-handling.js";
import { AutoSync } from "../mixins/auto-sync.js";
import { BaseElement, html, property, query, listen } from "./base.js";
import { BaseElement, html, css, property, query, listen } from "./base.js";
import "./icon.js";
import { Input } from "./input.js";
import { View } from "./view.js";
@ -51,109 +51,110 @@ class App extends AutoSync(ErrorHandling(AutoLock(BaseElement))) {
this._applyPath(router.path);
}
render() {
return html`
${config.cssVars} ${shared}
static styles = [
config.cssVars,
shared,
css`
:host {
background: linear-gradient(
var(--color-gradient-highlight-to) 0%,
var(--color-gradient-highlight-from) 100%
);
overflow: hidden;
color: var(--color-foreground);
position: absolute;
width: 100%;
height: 100%;
animation: fadeIn 0.5s backwards 0.2s;
perspective: 1000px;
}
<style>
:host {
background: linear-gradient(
var(--color-gradient-highlight-to) 0%,
var(--color-gradient-highlight-from) 100%
);
overflow: hidden;
color: var(--color-foreground);
position: absolute;
width: 100%;
height: 100%;
animation: fadeIn 0.5s backwards 0.2s;
perspective: 1000px;
}
.wrapper {
display: flex;
transform: translate3d(0, 0, 0);
transform-origin: 0 center;
transition: transform 0.4s cubic-bezier(0.6, 0, 0.2, 1);
will-change: transform, opacity;
${mixins.fullbleed()}
${mixins.gradientDark()}
}
.wrapper {
display: flex;
transform: translate3d(0, 0, 0);
transform-origin: 0 center;
transition: transform 0.4s cubic-bezier(0.6, 0, 0.2, 1);
will-change: transform, opacity;
pl-menu {
width: 200px;
}
.views {
flex: 1;
position: relative;
perspective: 1000px;
margin: var(--gutter-size);
margin-left: 0;
}
.views > * {
will-change: opacity;
${mixins.fullbleed()}
}
.views > :not(.showing) {
opacity: 0;
z-index: -1;
pointer-events: none;
}
.wrapper:not(.active),
:host(.dialog-open) .wrapper {
transform: translate3d(0, 0, -150px) rotateX(5deg);
}
@media (max-width: 700px) {
.views {
transition: transform 0.3s cubic-bezier(0.6, 0, 0.2, 1);
${mixins.fullbleed()}
${mixins.gradientDark()}
}
pl-menu {
width: 200px;
}
.views {
flex: 1;
position: relative;
perspective: 1000px;
margin: var(--gutter-size);
margin-left: 0;
margin: 0;
}
.views > * {
will-change: opacity;
${mixins.fullbleed()}
border-radius: 0;
}
.views > :not(.showing) {
:host([menu-open]) .views {
transform: translate(200px, 0);
}
pl-menu {
transition: transform 0.3s cubic-bezier(0.6, 0, 0.2, 1), opacity 0.3s cubic-bezier(0.6, 0, 0.2, 1);
}
:host(:not([menu-open])) pl-menu {
opacity: 0;
z-index: -1;
pointer-events: none;
transform: translate(-100px, 0);
}
}
.wrapper:not(.active),
:host(.dialog-open) .wrapper {
transform: translate3d(0, 0, -150px) rotateX(5deg);
@media (min-width: 1200px) {
.wrapper {
border-radius: 8px;
overflow: hidden;
box-shadow: rgba(0, 0, 0, 0.5) 0 1px 3px;
margin: auto;
overflow: hidden;
top: 20px;
left: 20px;
right: 20px;
bottom: 20px;
max-width: 1200px;
max-height: 900px;
}
}
`
];
@media (max-width: ${config.narrowWidth}px) {
.views {
transition: transform 0.3s cubic-bezier(0.6, 0, 0.2, 1);
${mixins.fullbleed()}
}
.views {
margin: 0;
}
.views > * {
border-radius: 0;
}
:host([menu-open]) .views {
transform: translate(200px, 0);
}
pl-menu {
transition: transform 0.3s cubic-bezier(0.6, 0, 0.2, 1),
opacity 0.3s cubic-bezier(0.6, 0, 0.2, 1);
}
:host(:not([menu-open])) pl-menu {
opacity: 0;
transform: translate(-100px, 0);
}
}
@media (min-width: ${config.wideWidth}px) {
.wrapper {
border-radius: 8px;
overflow: hidden;
box-shadow: rgba(0, 0, 0, 0.5) 0 1px 3px;
margin: auto;
overflow: hidden;
top: 20px;
left: 20px;
right: 20px;
bottom: 20px;
max-width: 1200px;
max-height: 900px;
}
}
</style>
render() {
return html`
<pl-start id="startView"></pl-start>
<div class="wrapper">

View File

@ -1,8 +1,8 @@
import "reflect-metadata";
import { EventTarget, Event } from "@padloc/core/lib/event-target.js";
import { LitElement, html } from "@polymer/lit-element";
import { UpdatingElement, PropertyDeclaration } from "@polymer/lit-element/lib/updating-element.js";
export { html };
import { LitElement, html, css, svg } from "lit-element";
import { UpdatingElement, PropertyDeclaration } from "lit-element/lib/updating-element.js";
export { html, css, svg };
export interface BasePrototype extends BaseElement {}
@ -146,12 +146,6 @@ export class BaseElement extends LitElement {
}
}
}
// TODO after upgrading to lit-element > 0.12
render() {
throw "Not Implemented";
return html``;
}
}
/**
@ -170,6 +164,7 @@ export function property(options?: PropertyDeclaration) {
return (proto: Object, name: string) => {
options = options || {};
if (!options.type) {
// @ts-ignore
options.type = getType(proto, name);
}
(proto.constructor as typeof UpdatingElement).createProperty(name, options);

View File

@ -2,7 +2,7 @@ import { VaultItem, Field } from "@padloc/core/lib/item.js";
import { setClipboard } from "@padloc/core/lib/platform.js";
import { localize as $l } from "@padloc/core/lib/locale.js";
import { shared, mixins } from "../styles";
import { BaseElement, html, property } from "./base.js";
import { BaseElement, html, css, property } from "./base.js";
export class Clipboard extends BaseElement {
@ -17,13 +17,9 @@ export class Clipboard extends BaseElement {
return !!this.item && !!this.field;
}
render() {
const { item, field, _tMinusClear } = this;
return html`
${ shared }
<style>
static styles = [
shared,
css`
:host {
display: flex;
text-align: center;
@ -61,13 +57,18 @@ export class Clipboard extends BaseElement {
flex-direction: column;
justify-content: center;
align-items: center;
border-radius: 0 var(--border-radius) var(--border-radius) 0;
}
.countdown {
font-size: var(--font-size-small);
}
</style>
`
];
render() {
const { item, field, _tMinusClear } = this;
return html`
<div class="content">
<div class="title">${ $l("Copied To Clipboard:") }</div>
<div class="name">${ item!.name } / ${ field!.name }</div>

View File

@ -2,7 +2,7 @@ import { Vault } from "@padloc/core/lib/vault.js";
import { FieldType, VaultItem } from "@padloc/core/lib/item.js";
import { localize as $l } from "@padloc/core/lib/locale.js";
import { app } from "../init.js";
import { element, html, query } from "./base.js";
import { element, html, css, query } from "./base.js";
import { Input } from "./input.js";
import { Select } from "./select.js";
import { Dialog } from "./dialog.js";
@ -88,25 +88,28 @@ export class CreateItemDialog extends Dialog<undefined, VaultItem> {
@query("#templateSelect")
private _templateSelect: Select<Template>;
static styles = [
...Dialog.styles,
css`
:host {
--gutter-size: 12px;
}
pl-input,
pl-select {
text-align: center;
margin: var(--gutter-size);
}
h1 {
display: block;
text-align: center;
}
`
];
renderContent() {
return html`
<style>
:host {
--gutter-size: 12px;
}
pl-input,
pl-select {
text-align: center;
margin: var(--gutter-size);
}
h1 {
display: block;
text-align: center;
}
</style>
<h1>${$l("Create Vault Item")}</h1>
<pl-input id="nameInput" .label=${$l("Item Name")} @enter=${() => this._enter()}> </pl-input>

View File

@ -1,6 +1,6 @@
import { shared, mixins } from "../styles";
import { animateElement } from "../animation";
import { BaseElement, html, property, observe, listen } from "./base.js";
import { BaseElement, html, css, property, observe, listen } from "./base.js";
import { Input } from "./input.js";
export class Dialog<I, R> extends BaseElement {
@ -27,84 +27,84 @@ export class Dialog<I, R> extends BaseElement {
});
}
static styles = [
shared,
css`
:host {
display: block;
${mixins.fullbleed()}
position: fixed;
z-index: 10;
${mixins.scroll()}
}
:host(:not([open])) {
pointer-events: none;
}
.outer {
min-height: 100%;
display: flex;
position: relative;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 10px;
box-sizing: border-box;
}
.scrim {
display: block;
background: #000000;
opacity: 0;
transition: opacity 400ms cubic-bezier(0.6, 0, 0.2, 1);
transform: translate3d(0, 0, 0);
${mixins.fullbleed()}
position: fixed;
}
:host([open]) .scrim {
opacity: 0.8;
}
.inner {
position: relative;
width: 100%;
box-sizing: border-box;
max-width: var(--pl-dialog-max-width, 400px);
z-index: 1;
border-radius: var(--border-radius);
box-shadow: rgba(0, 0, 0, 0.25) 0 0 5px;
overflow: hidden;
background: var(--color-tertiary);
}
.outer {
transform: translate3d(0, 0, 0);
/* transition: transform 400ms cubic-bezier(1, -0.3, 0, 1.3), opacity 400ms cubic-bezier(0.6, 0, 0.2, 1); */
transition: transform 400ms cubic-bezier(0.6, 0, 0.2, 1), opacity 400ms cubic-bezier(0.6, 0, 0.2, 1);
}
.actions {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
grid-gap: var(--gutter-size);
margin: var(--gutter-size);
}
.actions.vertical {
grid-template-columns: 1fr;
}
:host(:not([open])) .outer {
opacity: 0;
transform: translate3d(0, 0, 0) scale(0.8);
}
`
];
render() {
return html`
${shared}
<style>
:host {
display: block;
${mixins.fullbleed()}
position: fixed;
z-index: 10;
${mixins.scroll()}
}
:host(:not([open])) {
pointer-events: none;
}
.outer {
min-height: 100%;
display: flex;
position: relative;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 10px;
box-sizing: border-box;
}
.scrim {
display: block;
background: #000000;
opacity: 0;
transition: opacity 400ms cubic-bezier(0.6, 0, 0.2, 1);
transform: translate3d(0, 0, 0);
${mixins.fullbleed()}
position: fixed;
}
:host([open]) .scrim {
opacity: 0.8;
}
.inner {
position: relative;
width: 100%;
box-sizing: border-box;
max-width: var(--pl-dialog-max-width, 400px);
z-index: 1;
border-radius: var(--border-radius);
box-shadow: rgba(0, 0, 0, 0.25) 0 0 5px;
overflow: hidden;
background: var(--color-tertiary);
}
.outer {
transform: translate3d(0, 0, 0);
/* transition: transform 400ms cubic-bezier(1, -0.3, 0, 1.3), opacity 400ms cubic-bezier(0.6, 0, 0.2, 1); */
transition: transform 400ms cubic-bezier(0.6, 0, 0.2, 1), opacity 400ms cubic-bezier(0.6, 0, 0.2, 1);
}
.actions {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
grid-gap: var(--gutter-size);
margin: var(--gutter-size);
}
.actions.vertical {
grid-template-columns: 1fr;
}
:host(:not([open])) .outer {
opacity: 0;
transform: translate3d(0, 0, 0) scale(0.8);
}
</style>
<div class="scrim"></div>
<div class="outer" @click=${() => this.dismiss()}>

View File

@ -3,7 +3,7 @@ import { localize as $l } from "@padloc/core/lib/locale.js";
import { CSV, ImportFormat } from "../import.js";
import { supportedFormats, asCSV } from "../export.js";
import { app } from "../init.js";
import { element, html, query } from "./base.js";
import { element, html, css, query } from "./base.js";
import { Select } from "./select.js";
import { Dialog } from "./dialog.js";
@ -14,35 +14,38 @@ export class ExportDialog extends Dialog<void, void> {
@query("#vaultSelect")
private _vaultSelect: Select<Vault>;
static styles = [
...Dialog.styles,
css`
.inner {
display: flex;
flex-direction: column;
}
pl-input,
pl-select,
button {
text-align: center;
margin: 0 10px 10px 10px;
background: var(--shade-2-color);
border-radius: 8px;
}
h1 {
display: block;
text-align: center;
}
.csv-note {
font-size: var(--font-size-micro);
text-align: center;
padding: 0px 20px 20px 20px;
}
`
];
renderContent() {
return html`
<style>
.inner {
display: flex;
flex-direction: column;
}
pl-input,
pl-select,
button {
text-align: center;
margin: 0 10px 10px 10px;
background: var(--shade-2-color);
border-radius: 8px;
}
h1 {
display: block;
text-align: center;
}
.csv-note {
font-size: var(--font-size-micro);
text-align: center;
padding: 0px 20px 20px 20px;
}
</style>
<h1>${$l("Export Data")}</h1>
<pl-select

View File

@ -1,7 +1,7 @@
import { FieldType, FieldDef, FIELD_DEFS } from "@padloc/core/lib/item.js";
import { localize as $l } from "@padloc/core/lib/locale.js";
import { shared } from "../styles";
import { BaseElement, element, html, property, query } from "./base.js";
import { BaseElement, element, html, css, property, query } from "./base.js";
import "./icon.js";
import { Input } from "./input.js";
import { Select } from "./select.js";
@ -45,6 +45,80 @@ export class FieldElement extends BaseElement {
}
}
static styles = [
shared,
css`
:host {
display: flex;
align-items: center;
border-radius: 8px;
min-height: 80px;
}
.field-buttons {
display: flex;
flex-direction: column;
justify-content: center;
}
.field-buttons.left {
margin: 0 -4px 0 4px;
}
:host(:not(:hover)) .field-buttons.right {
visibility: hidden;
}
.field-header {
display: flex;
margin-bottom: 4px;
}
.fields-container {
margin: 8px;
}
.field-name {
flex: 1;
min-width: 0;
font-size: var(--font-size-tiny);
font-weight: bold;
color: var(--color-highlight);
padding: 0 10px;
}
.field-type {
width: 95px;
font-weight: bold;
margin-left: 4px;
padding: 0;
padding-left: 10px;
font-size: var(--font-size-micro);
color: var(--color-gradient-warning-to);
}
.field-value {
font-family: var(--font-family-mono);
font-size: 110%;
flex: 1;
padding: 0 10px;
opacity: 1;
--rule-width: 1px;
}
pl-input,
pl-select {
height: auto;
line-height: 30px;
box-sizing: border-box;
}
pl-input[readonly] {
background: transparent;
}
`
];
render() {
const fieldDef = FIELD_DEFS[this.type] || FIELD_DEFS.text;
let inputType: string;
@ -67,107 +141,29 @@ export class FieldElement extends BaseElement {
}
const mask = fieldDef.mask && !this.editing;
return html`
${shared}
<style>
:host {
display: flex;
align-items: center;
border-radius: 8px;
min-height: 80px;
}
.field-buttons {
display: flex;
flex-direction: column;
justify-content: center;
}
.field-buttons.left {
margin: 0 -4px 0 4px;
}
:host(:not(:hover)) .field-buttons.right {
visibility: hidden;
}
.field-header {
display: flex;
margin-bottom: 4px;
}
.fields-container {
margin: 8px;
}
.field-name {
flex: 1;
min-width: 0;
font-size: var(--font-size-tiny);
font-weight: bold;
color: var(--color-highlight);
padding: 0 10px;
}
.field-type {
width: 95px;
font-weight: bold;
margin-left: 4px;
padding: 0;
padding-left: 10px;
font-size: var(--font-size-micro);
color: var(--color-gradient-warning-to);
}
.field-value {
font-family: var(--font-family-mono);
font-size: 110%;
flex: 1;
padding: 0 10px;
opacity: 1;
--rule-width: 1px;
}
pl-input,
pl-select {
height: auto;
line-height: 30px;
box-sizing: border-box;
}
pl-input[readonly] {
background: transparent;
}
</style>
<div class="field-buttons left" ?hidden=${!this.editing}>
<pl-icon
icon="remove"
class="tap"
@click=${() => this.dispatch("remove")}>
</pl-icon>
<pl-icon icon="remove" class="tap" @click=${() => this.dispatch("remove")}> </pl-icon>
<pl-icon
?hidden=${this.type !== "password"}
icon="generate"
class="tap"
@click=${() => this.dispatch("generate")}>
@click=${() => this.dispatch("generate")}
>
</pl-icon>
</div>
<div class="fields-container flex">
<div class="field-header">
<pl-input class="field-name"
<pl-input
class="field-name"
id="nameInput"
placeholder="${$l("Enter Field Name")}"
.value=${this.name}
@input=${() => (this.name = this._nameInput.value)}
@click=${() => !this._nameInput.value && this.dispatch("edit")}
?readonly=${!this.editing}>
?readonly=${!this.editing}
>
</pl-input>
<pl-select
@ -177,9 +173,9 @@ export class FieldElement extends BaseElement {
?hidden=${!this.editing}
.options=${Object.values(FIELD_DEFS)}
.selected=${fieldDef}
@change=${() => (this.type = this._typeSelect.selected!.type)}>
@change=${() => (this.type = this._typeSelect.selected!.type)}
>
</pl-select>
</div>
<pl-input
@ -193,26 +189,21 @@ export class FieldElement extends BaseElement {
.value=${this.value}
@input=${() => (this.value = this._valueInput.value)}
@click=${() => !this._valueInput.value && this.dispatch("edit")}
autosize>
autosize
>
</pl-input>
</div>
<div class="field-buttons right" ?hidden=${this.editing}>
<pl-icon
.icon=${(this._valueInput ? this._valueInput.masked : mask) ? "show" : "hide"}
class="tap"
?hidden=${!fieldDef.mask}
@click=${() => this._toggleMask()}>
</pl-icon>
<pl-icon
icon="copy"
class="tap"
@click=${() => this.dispatch("copy")}>
@click=${() => this._toggleMask()}
>
</pl-icon>
<pl-icon icon="copy" class="tap" @click=${() => this.dispatch("copy")}> </pl-icon>
</div>
`;
}

View File

@ -2,7 +2,7 @@ import { randomArt } from "@padloc/core/lib/randomart.js";
import { getProvider } from "@padloc/core/lib/crypto.js";
import { svg } from "lit-html";
import { until } from "lit-html/directives/until.js";
import { BaseElement, html, element, property } from "./base";
import { BaseElement, html, css, element, property } from "./base";
@element("pl-fingerprint")
export class Fingerprint extends BaseElement {
@ -30,27 +30,29 @@ export class Fingerprint extends BaseElement {
return !!this.key;
}
static styles = [
css`
:host {
display: block;
width: 100px;
height: 100px;
position: relative;
overflow: hidden;
background: var(--color-background);
color: var(--color-foreground);
}
svg {
width: 100%;
height: 100%;
fill: currentColor;
pointer-events: none;
}
`
];
render() {
return html`
<style>
:host {
display: block;
width: 100px;
height: 100px;
position: relative;
overflow: hidden;
background: var(--color-background);
color: var(--color-foreground);
}
svg {
width: 100%;
height: 100%;
fill: currentColor;
pointer-events: none;
}
</style>
${until(this._grid())}
`;
}

View File

@ -1,8 +1,7 @@
import { randomString, chars } from "@padloc/core/lib/util.js";
import { generatePassphrase } from "@padloc/core/lib/diceware.js";
import { localize as $l } from "@padloc/core/lib/locale.js";
import { shared } from "../styles";
import { html, property, query, listen } from "./base.js";
import { html, css, property, query, listen } from "./base.js";
import { Dialog } from "./dialog.js";
import { Slider } from "./slider.js";
import { ToggleButton } from "./toggle-button.js";
@ -58,78 +57,82 @@ export class Generator extends Dialog<void, string> {
@query("#length")
private _length: Slider;
static styles = [
...Dialog.styles,
css`
.inner {
background: var(--color-quaternary);
}
.header {
background: var(--color-tertiary);
text-align: center;
font-weight: bold;
}
.header-title {
font-size: 120%;
padding: 20px 20px 10px 20px;
}
.charsets {
display: flex;
}
.charsets > * {
flex: 1;
}
pl-toggle-button {
display: block;
border-bottom: solid 1px rgba(0, 0, 0, 0.1);
}
pl-slider {
display: flex;
height: var(--row-height);
border-bottom: solid 1px rgba(0, 0, 0, 0.1);
}
pl-select {
border-bottom: solid 1px rgba(0, 0, 0, 0.1);
}
.result {
font-family: var(--font-family-mono);
text-align: center;
font-size: 120%;
overflow-wrap: break-word;
font-weight: bold;
padding: 20px;
}
.arrow {
display: block;
margin: -10px auto;
font-size: 120%;
}
`
];
renderContent() {
const { value } = this;
return html`
${shared}
<style>
.inner {
background: var(--color-quaternary);
}
.header {
background: var(--color-tertiary);
text-align: center;
border-bottom: solid 3px var(--color-shade-1);
font-weight: bold;
}
.header-title {
font-size: 120%;
padding: 20px 20px 10px 20px;
}
.charsets {
display: flex;
}
.charsets > * {
flex: 1;
}
.tabs {
margin-bottom: -2px;
}
pl-toggle-button {
display: block;
border-bottom: solid 1px rgba(0, 0, 0, 0.1);
}
pl-slider {
display: flex;
height: var(--row-height);
border-bottom: solid 1px rgba(0, 0, 0, 0.1);
}
pl-select {
border-bottom: solid 1px rgba(0, 0, 0, 0.1);
}
.result {
font-family: var(--font-family-mono);
text-align: center;
font-size: 120%;
overflow-wrap: break-word;
font-weight: bold;
padding: 20px;
}
.arrow {
display: block;
margin: -10px auto;
font-size: 120%;
}
</style>
<div class="header">
<div class="header-title">${$l("Generate Password")}</div>
<div class="tabs">
<div class="flex tap" ?active=${this.mode === "words"} @click=${() => this._selectMode("words")}>
<div
class="flex tab tap"
?active=${this.mode === "words"}
@click=${() => this._selectMode("words")}
>
${$l("passphrase")}
</div>
<div class="flex tap" ?active=${this.mode === "chars"} @click=${() => this._selectMode("chars")}>
<div
class="flex tab tap"
?active=${this.mode === "chars"}
@click=${() => this._selectMode("chars")}
>
${$l("random string")}
</div>
</div>

View File

@ -2,7 +2,7 @@ import { Org, OrgMember, Group } from "@padloc/core/lib/org.js";
import { localize as $l } from "@padloc/core/lib/locale.js";
import { app } from "../init.js";
import { prompt } from "../dialog.js";
import { element, html, property, query } from "./base.js";
import { element, html, css, property, query } from "./base.js";
import { Dialog } from "./dialog.js";
import { LoadingButton } from "./loading-button.js";
import { Input } from "./input.js";
@ -151,6 +151,25 @@ export class GroupDialog extends Dialog<InputType, void> {
return !!this.org;
}
static styles = [
...Dialog.styles,
css`
.inner {
background: var(--color-quaternary);
}
pl-toggle-button {
display: block;
padding: 0 15px 0 0;
}
.delete-button {
color: var(--color-negative);
font-size: var(--font-size-default);
}
`
];
renderContent() {
const org = this.org!;
const memFilter = this._membersFilter.toLowerCase();
@ -164,22 +183,6 @@ export class GroupDialog extends Dialog<InputType, void> {
const canDelete = this.group && canEdit;
return html`
<style>
.inner {
background: var(--color-quaternary);
}
pl-toggle-button {
display: block;
padding: 0 15px 0 0;
}
.delete-button {
color: var(--color-negative);
font-size: var(--font-size-default);
}
</style>
<header>
<pl-icon icon="group"></pl-icon>
<pl-input

View File

@ -1,6 +1,6 @@
import { Group } from "@padloc/core/lib/org.js";
import { shared } from "../styles";
import { BaseElement, element, html, property } from "./base.js";
import { BaseElement, element, html, css, property } from "./base.js";
import "./icon.js";
@element("pl-group-item")
@ -8,41 +8,42 @@ export class GroupItem extends BaseElement {
@property()
group: Group;
static styles = [
shared,
css`
:host {
display: flex;
align-items: center;
padding: 4px 0;
}
.icon {
font-size: 120%;
margin: 8px;
background: #eee;
border: solid 1px #ddd;
width: 45px;
height: 45px;
}
.tags {
margin: 4px 0;
}
.group-name {
font-weight: bold;
margin-bottom: 4px;
}
.group-info {
flex: 1;
width: 0;
}
`
];
render() {
return html`
${shared}
<style>
:host {
display: flex;
align-items: center;
padding: 4px 0;
}
.icon {
font-size: 120%;
margin: 8px;
background: #eee;
border: solid 1px #ddd;
width: 45px;
height: 45px;
}
.tags {
margin: 4px 0;
}
.group-name {
font-weight: bold;
margin-bottom: 4px;
}
.group-info {
flex: 1;
width: 0;
}
</style>
<pl-icon class="icon" icon="group"></pl-icon>
<div class="group-info">

View File

@ -1,334 +1,335 @@
import { BaseElement, html, property } from "./base.js";
import { BaseElement, element, html, css, property } from "./base.js";
@element("pl-icon")
export class PlIcon extends BaseElement {
@property({ reflect: true })
icon: string = "";
static styles = [
css`
:host {
display: inline-block;
text-align: center;
font-family: "FontAwesome";
color: inherit;
font-size: inherit;
position: relative;
height: 40px;
width: 40px;
font-weight: normal !important;
border-radius: 100%;
overflow: hidden;
}
div {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
margin: auto;
height: 0;
line-height: 0;
}
:host([icon="add"]) > div::before {
content: "\\f067";
}
:host([icon="menu"]) > div::before {
content: "\\f0c9";
}
:host([icon="close"]) > div::before {
content: "\\f00d";
}
:host([icon="more"]) > div::before {
content: "\\f141";
}
:host([icon="delete"]) > div::before {
content: "\\f2ed";
}
:host([icon="copy"]) > div::before {
/* content: "\\f24d"; */
content: "\\f0c5";
}
:host([icon="edit"]) > div::before {
content: "\\f303";
}
:host([icon="forward"]) > div::before {
content: "\\f054";
}
:host([icon="backward"]) > div::before {
content: "\\f053";
}
:host([icon="check"]) > div::before {
content: "\\f00c";
}
:host([icon="cancel"]) > div::before {
content: "\\f00d";
}
:host([icon="generate"]) > div::before {
content: "\\f0d0";
}
:host([icon="tag"]) > div::before {
content: "\\f02b";
}
:host([icon="tags"]) > div::before {
content: "\\f02c";
}
:host([icon="dropdown"]) > div::before {
content: "\\f0d7";
}
:host([icon="dropup"]) > div::before {
content: "\\f0d8";
}
:host([icon="settings"]) > div::before {
content: "\\f013";
}
:host([icon="cloud"]) > div::before {
content: "\\f0c2";
}
:host([icon="lock"]) > div::before {
content: "\\f023";
}
:host([icon="refresh"]) > div::before {
content: "\\f2f1";
}
:host([icon="unlock"]) > div::before {
content: "\\f13e";
}
:host([icon="export"]) > div::before {
content: "\\f093";
}
:host([icon="import"]) > div::before {
content: "\\f019";
}
:host([icon="search"]) > div::before {
content: "\\f002";
}
:host([icon="info"]) > div::before {
content: "\\f129";
}
:host([icon="info-round"]) > div::before {
content: "\\f05a";
}
:host([icon="download"]) > div::before {
content: "\\f019";
}
:host([icon="upload"]) > div::before {
content: "\\f093";
}
:host([icon="show"]) > div::before {
content: "\\f06e";
}
:host([icon="hide"]) > div::before {
content: "\\f070";
}
:host([icon="checked"]) > div::before {
content: "\\f14a";
}
:host([icon="checkall"]) > div::before {
content: "\\f560";
}
:host([icon="success"]) > div::before {
content: "\\f058";
}
:host([icon="unchecked"]) > div::before {
content: "\\f146";
}
:host([icon="share"]) > div::before {
content: "\\f045";
}
:host([icon="logout"]) > div::before {
content: "\\f2f5";
}
:host([icon="mail"]) > div::before {
content: "\\f0e0";
}
:host([icon="user"]) > div::before {
content: "\\f007";
}
:host([icon="record"]) > div::before {
content: "\\f15b";
}
:host([icon="mobile"]) > div::before {
content: "\\f10b";
font-size: 140%;
}
:host([icon="database"]) > div::before {
content: "\\f1c0";
}
:host([icon="time"]) > div::before {
content: "\\f017";
}
:host([icon="error"]) > div::before {
content: "\\f071";
}
:host([icon="question"]) > div::before {
content: "\\f059";
}
:host([icon="desktop"]) > div::before {
content: "\\f109";
font-size: 140%;
}
:host([icon="group"]) > div::before {
content: "\\f0c0";
}
:host([icon="vaults"]) > div::before {
content: "\\f1b3";
}
:host([icon="vault"]) > div::before {
content: "\\f1b2";
}
:host([icon="share"]) > div::before {
content: "\\f064";
}
:host([icon="invite"]) > div::before {
content: "\\f234";
}
:host([icon="trusted"]) > div::before {
content: "\\f4fc";
}
:host([icon="removeuser"]) > div::before {
content: "\\f506";
}
:host([icon="org"]) > div::before {
content: "\\f1ad";
}
// :host([icon="logo"]) > div::before {
// content: "\\f447";
// }
:host([icon="list"]) > div::before {
content: "\\f0ca";
}
:host([icon="remove"]) > div::before {
content: "\\f056";
}
:host([icon="password"]) > div::before {
content: "\\f069";
}
:host([icon="admins"]) > div::before {
content: "\\f509";
}
:host([icon="archive"]) > div::before {
content: "\\f187";
}
:host([icon="attachment"]) > div::before {
content: "\\f0c6";
}
:host([icon="file"]) > div::before {
content: "\\f15b";
}
:host([icon="file-video"]) > div::before {
content: "\\f1c8";
}
:host([icon="file-pdf"]) > div::before {
content: "\\f1c1";
}
:host([icon="file-image"]) > div::before {
content: "\\f1c5";
}
:host([icon="file-csv"]) > div::before {
content: "\\f6dd";
}
:host([icon="file-code"]) > div::before {
content: "\\f1c9";
}
:host([icon="file-archive"]) > div::before {
content: "\\f1c6";
}
:host([icon="file-audio"]) > div::before {
content: "\\f1c7";
}
:host([icon="file-text"]) > div::before {
content: "\\f15c";
}
:host([icon="arrow-down"]) > div::before {
content: "\\f309";
}
:host([icon="favorite"]) > div::before {
content: "\\f005";
}
:host([icon="logo"]) > div::before {
font-family: "Padlock";
content: "\\0041";
font-size: 110%;
}
`
];
render() {
return html`
<style>
:host {
display: inline-block;
text-align: center;
font-family: "FontAwesome";
color: inherit;
font-size: inherit;
position: relative;
height: 40px;
width: 40px;
font-weight: normal !important;
border-radius: 100%;
overflow: hidden;
}
div {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
margin: auto;
height: 0;
line-height: 0;
}
:host([icon="add"]) > div::before {
content: "\\f067";
}
:host([icon="menu"]) > div::before {
content: "\\f0c9";
}
:host([icon="close"]) > div::before {
content: "\\f00d";
}
:host([icon="more"]) > div::before {
content: "\\f141";
}
:host([icon="delete"]) > div::before {
content: "\\f2ed";
}
:host([icon="copy"]) > div::before {
/* content: "\\f24d"; */
content: "\\f0c5";
}
:host([icon="edit"]) > div::before {
content: "\\f303";
}
:host([icon="forward"]) > div::before {
content: "\\f054";
}
:host([icon="backward"]) > div::before {
content: "\\f053";
}
:host([icon="check"]) > div::before {
content: "\\f00c";
}
:host([icon="cancel"]) > div::before {
content: "\\f00d";
}
:host([icon="generate"]) > div::before {
content: "\\f0d0";
}
:host([icon="tag"]) > div::before {
content: "\\f02b";
}
:host([icon="tags"]) > div::before {
content: "\\f02c";
}
:host([icon="dropdown"]) > div::before {
content: "\\f0d7";
}
:host([icon="dropup"]) > div::before {
content: "\\f0d8";
}
:host([icon="settings"]) > div::before {
content: "\\f013";
}
:host([icon="cloud"]) > div::before {
content: "\\f0c2";
}
:host([icon="lock"]) > div::before {
content: "\\f023";
}
:host([icon="refresh"]) > div::before {
content: "\\f2f1";
}
:host([icon="unlock"]) > div::before {
content: "\\f13e";
}
:host([icon="export"]) > div::before {
content: "\\f093";
}
:host([icon="import"]) > div::before {
content: "\\f019";
}
:host([icon="search"]) > div::before {
content: "\\f002";
}
:host([icon="info"]) > div::before {
content: "\\f129";
}
:host([icon="info-round"]) > div::before {
content: "\\f05a";
}
:host([icon="download"]) > div::before {
content: "\\f019";
}
:host([icon="upload"]) > div::before {
content: "\\f093";
}
:host([icon="show"]) > div::before {
content: "\\f06e";
}
:host([icon="hide"]) > div::before {
content: "\\f070";
}
:host([icon="checked"]) > div::before {
content: "\\f14a";
}
:host([icon="checkall"]) > div::before {
content: "\\f560";
}
:host([icon="success"]) > div::before {
content: "\\f058";
}
:host([icon="unchecked"]) > div::before {
content: "\\f146";
}
:host([icon="share"]) > div::before {
content: "\\f045";
}
:host([icon="logout"]) > div::before {
content: "\\f2f5";
}
:host([icon="mail"]) > div::before {
content: "\\f0e0";
}
:host([icon="user"]) > div::before {
content: "\\f007";
}
:host([icon="record"]) > div::before {
content: "\\f15b";
}
:host([icon="mobile"]) > div::before {
content: "\\f10b";
font-size: 140%;
}
:host([icon="database"]) > div::before {
content: "\\f1c0";
}
:host([icon="time"]) > div::before {
content: "\\f017";
}
:host([icon="error"]) > div::before {
content: "\\f071";
}
:host([icon="question"]) > div::before {
content: "\\f059";
}
:host([icon="desktop"]) > div::before {
content: "\\f109";
font-size: 140%;
}
:host([icon="group"]) > div::before {
content: "\\f0c0";
}
:host([icon="vaults"]) > div::before {
content: "\\f1b3";
}
:host([icon="vault"]) > div::before {
content: "\\f1b2";
}
:host([icon="share"]) > div::before {
content: "\\f064";
}
:host([icon="invite"]) > div::before {
content: "\\f234";
}
:host([icon="trusted"]) > div::before {
content: "\\f4fc";
}
:host([icon="removeuser"]) > div::before {
content: "\\f506";
}
:host([icon="org"]) > div::before {
content: "\\f1ad";
}
// :host([icon="logo"]) > div::before {
// content: "\\f447";
// }
:host([icon="list"]) > div::before {
content: "\\f0ca";
}
:host([icon="remove"]) > div::before {
content: "\\f056";
}
:host([icon="password"]) > div::before {
content: "\\f069";
}
:host([icon="admins"]) > div::before {
content: "\\f509";
}
:host([icon="archive"]) > div::before {
content: "\\f187";
}
:host([icon="attachment"]) > div::before {
content: "\\f0c6";
}
:host([icon="file"]) > div::before {
content: "\\f15b";
}
:host([icon="file-video"]) > div::before {
content: "\\f1c8";
}
:host([icon="file-pdf"]) > div::before {
content: "\\f1c1";
}
:host([icon="file-image"]) > div::before {
content: "\\f1c5";
}
:host([icon="file-csv"]) > div::before {
content: "\\f6dd";
}
:host([icon="file-code"]) > div::before {
content: "\\f1c9";
}
:host([icon="file-archive"]) > div::before {
content: "\\f1c6";
}
:host([icon="file-audio"]) > div::before {
content: "\\f1c7";
}
:host([icon="file-text"]) > div::before {
content: "\\f15c";
}
:host([icon="arrow-down"]) > div::before {
content: "\\f309";
}
:host([icon="favorite"]) > div::before {
content: "\\f005";
}
:host([icon="logo"]) > div::before {
font-family: "Padlock";
content: "\\0041";
font-size: 110%;
}
</style>
<div></div>
`;
}
}
window.customElements.define("pl-icon", PlIcon);

View File

@ -4,7 +4,7 @@ import { localize as $l } from "@padloc/core/lib/locale.js";
import * as imp from "../import.js";
import { prompt, alert } from "../dialog.js";
import { app } from "../init.js";
import { element, html, query, property } from "./base.js";
import { element, html, css, query, property } from "./base.js";
import { Select } from "./select.js";
import { Dialog } from "./dialog.js";
@ -18,35 +18,38 @@ export class ImportDialog extends Dialog<string, void> {
@query("#vaultSelect")
private _vaultSelect: Select<Vault>;
static styles = [
...Dialog.styles,
css`
.inner {
display: flex;
flex-direction: column;
}
pl-input,
pl-select,
button {
text-align: center;
margin: 0 10px 10px 10px;
background: var(--shade-2-color);
border-radius: 8px;
}
h1 {
display: block;
text-align: center;
}
.csv-note {
font-size: var(--font-size-micro);
text-align: center;
padding: 0px 20px 20px 20px;
}
`
];
renderContent() {
return html`
<style>
.inner {
display: flex;
flex-direction: column;
}
pl-input,
pl-select,
button {
text-align: center;
margin: 0 10px 10px 10px;
background: var(--shade-2-color);
border-radius: 8px;
}
h1 {
display: block;
text-align: center;
}
.csv-note {
font-size: var(--font-size-micro);
text-align: center;
padding: 0px 20px 20px 20px;
}
</style>
<h1>${$l("Import Data")}</h1>
<pl-select id="formatSelect" .options=${imp.supportedFormats} .label=${$l("Format")} disabled></pl-select>

View File

@ -2,7 +2,7 @@
import autosize from "autosize/src/autosize.js";
import { cache } from "lit-html/directives/cache.js";
import { shared, mixins } from "../styles";
import { BaseElement, element, html, property, query, listen } from "./base.js";
import { BaseElement, element, html, css, property, query, listen } from "./base.js";
let activeInput: Input | null = null;
@ -82,6 +82,99 @@ export class Input extends BaseElement {
}
}
static styles = [
shared,
css`
:host {
display: block;
position: relative;
font-size: inherit;
font-weight: inherit;
font-family: inherit;
background: var(--shade-2-color);
border-radius: var(--border-radius);
}
:host(:not([multiline])) {
padding: 0 10px;
height: var(--row-height);
}
input {
box-sizing: border-box;
text-overflow: ellipsis;
box-shadow: none;
}
input,
textarea {
text-align: inherit;
width: 100%;
height: 100%;
min-height: inherit;
line-height: inherit;
caret-color: currentColor;
}
textarea {
overflow-wrap: break-word;
}
::-webkit-search-cancel-button {
display: none;
}
::-webkit-placeholder {
text-shadow: inherit;
color: inherit;
opacity: 0.5;
}
--fullbleed: {
position: absolute;
}
.mask {
pointer-events: none;
font-size: 150%;
padding: inherit;
line-height: inherit;
letter-spacing: -4.5px;
margin-left: -4px;
${mixins.fullbleed()}
}
label {
position: absolute;
top: 0;
left: 0;
right: 0;
padding: 13px;
opacity: 0.5;
transition: transform 0.2s, color 0.2s, opacity 0.5s;
cursor: text;
}
label[float] {
transform: scale(0.8) translate(0, -32px);
color: var(--color-highlight);
font-weight: bold;
opacity: 1;
}
input[disabled],
textarea[disabled] {
opacity: 1;
-webkit-text-fill-color: currentColor;
}
input[invsbl],
textarea[invsbl] {
opacity: 0;
}
`
];
render() {
const {
value,
@ -152,98 +245,6 @@ export class Input extends BaseElement {
);
return html`
${shared}
<style>
:host {
display: block;
position: relative;
font-size: inherit;
font-weight: inherit;
font-family: inherit;
background: var(--shade-2-color);
border-radius: var(--border-radius);
}
:host(:not([multiline])) {
padding: 0 10px;
height: var(--row-height);
}
input {
box-sizing: border-box;
text-overflow: ellipsis;
box-shadow: none;
}
input,
textarea {
text-align: inherit;
width: 100%;
height: 100%;
min-height: inherit;
line-height: inherit;
caret-color: currentColor;
}
textarea {
overflow-wrap: break-word;
}
::-webkit-search-cancel-button {
display: none;
}
::-webkit-placeholder {
text-shadow: inherit;
color: inherit;
opacity: 0.5;
}
--fullbleed: {
position: absolute;
}
.mask {
pointer-events: none;
font-size: 150%;
padding: inherit;
line-height: inherit;
letter-spacing: -4.5px;
margin-left: -4px;
${mixins.fullbleed()}
}
label {
position: absolute;
top: 0;
left: 0;
right: 0;
padding: 13px;
opacity: 0.5;
transition: transform 0.2s, color 0.2s, opacity 0.5s;
cursor: text;
}
label[float] {
transform: scale(0.8) translate(0, -32px);
color: var(--color-highlight);
font-weight: bold;
opacity: 1;
}
input[disabled],
textarea[disabled] {
opacity: 1;
-webkit-text-fill-color: currentColor;
}
input[invsbl],
textarea[invsbl] {
opacity: 0;
}
</style>
${input}
<label for="input" ?float=${focused || !!value || !!placeholder} ?hidden=${!label}>${label}</label>

View File

@ -3,9 +3,8 @@ import { Invite } from "@padloc/core/lib/invite.js";
import { localize as $l } from "@padloc/core/lib/locale.js";
import { formatDateFromNow } from "../util.js";
import { app } from "../init";
import { shared } from "../styles";
import { alert, dialog } from "../dialog.js";
import { element, html, property, query } from "./base.js";
import { element, html, css, property, query } from "./base.js";
import { Dialog } from "./dialog.js";
import { LoadingButton } from "./loading-button.js";
import { Input } from "./input.js";
@ -50,6 +49,78 @@ export class InviteDialog extends Dialog<Invite, void> {
return !!this.invite;
}
static styles = [
...Dialog.styles,
css`
:host {
text-align: center;
}
h1 {
display: block;
text-align: center;
}
.invite {
overflow: hidden;
}
.invite-text {
font-size: var(--font-size-small);
margin: 20px;
}
.invite-text.small {
font-size: var(--font-size-tiny);
}
.invite-text.error {
color: var(--color-error);
text-shadow: none;
font-weight: bold;
}
.invite-email {
font-size: 120%;
margin: 15px;
font-weight: bold;
}
.invite-code {
font-size: 200%;
font-family: var(--font-family-mono);
text-transform: uppercase;
margin: 20px;
letter-spacing: 5px;
font-weight: bold;
user-select: text;
cursor: text;
}
.tags {
justify-content: center;
overflow: visible;
margin: 20px 0;
}
.tag.org {
font-size: var(--font-size-small);
padding: 4px 16px;
}
.code-input {
border-radius: 8px;
margin: 15px;
}
.close-button {
position: absolute;
top: 0;
right: 0;
}
`
];
renderContent() {
const { email, expires, expired, org, accepted, purpose } = this.invite!;
const forMe = email === app.account!.email;
@ -72,77 +143,6 @@ export class InviteDialog extends Dialog<Invite, void> {
};
return html`
${shared}
<style>
:host {
text-align: center;
}
h1 {
display: block;
text-align: center;
}
.invite {
overflow: hidden;
}
.invite-text {
font-size: var(--font-size-small);
margin: 20px;
}
.invite-text.small {
font-size: var(--font-size-tiny);
}
.invite-text.error {
color: var(--color-error);
text-shadow: none;
font-weight: bold;
}
.invite-email {
font-size: 120%;
margin: 15px;
font-weight: bold;
}
.invite-code {
font-size: 200%;
font-family: var(--font-family-mono);
text-transform: uppercase;
margin: 20px;
letter-spacing: 5px;
font-weight: bold;
user-select: text;
cursor: text;
}
.tags {
justify-content: center;
overflow: visible;
margin: 20px 0;
}
.tag.org {
font-size: var(--font-size-small);
padding: 4px 16px;
}
.code-input {
border-radius: 8px;
margin: 15px;
}
.close-button {
position: absolute;
top: 0;
right: 0;
}
</style>
<div class="invite">
<pl-icon icon="cancel" class="tap close-button" @click=${() => this.done()}></pl-icon>

View File

@ -4,7 +4,7 @@ import { localize as $l } from "@padloc/core/lib/locale.js";
import { formatDateFromNow } from "../util.js";
import { shared } from "../styles";
import { app } from "../init";
import { BaseElement, element, html, property } from "./base.js";
import { BaseElement, element, html, css, property } from "./base.js";
import "./icon.js";
@element("pl-invite-item")
@ -16,6 +16,69 @@ export class InviteItem extends BaseElement {
return !!this.invite;
}
static styles = [
shared,
css`
:host {
display: flex;
align-items: center;
padding: 4px 0;
}
.icon {
font-size: 120%;
margin: 10px;
background: #eee;
border: solid 1px #ddd;
width: 45px;
height: 45px;
}
.tags {
margin: 4px 0;
}
.invite-info {
flex: 1;
width: 0;
}
.invite:hover {
background: #fafafa;
}
.invite .tags {
padding: 0;
margin: 0;
}
.invite-email {
font-weight: bold;
margin-bottom: 4px;
}
.invite-code {
text-align: center;
margin-right: 15px;
}
.invite-code-label {
font-weight: bold;
font-size: var(--font-size-micro);
}
.invite-code-value {
font-size: 140%;
font-family: var(--font-family-mono);
font-weight: bold;
text-transform: uppercase;
cursor: text;
user-select: text;
letter-spacing: 2px;
}
`
];
render() {
const inv = this.invite!;
const account = app.account!;
@ -43,68 +106,6 @@ export class InviteItem extends BaseElement {
}
return html`
${shared}
<style>
:host {
display: flex;
align-items: center;
padding: 4px 0;
}
.icon {
font-size: 120%;
margin: 10px;
background: #eee;
border: solid 1px #ddd;
width: 45px;
height: 45px;
}
.tags {
margin: 4px 0;
}
.invite-info {
flex: 1;
width: 0;
}
.invite:hover {
background: #fafafa;
}
.invite .tags {
padding: 0;
margin: 0;
}
.invite-email {
font-weight: bold;
margin-bottom: 4px;
}
.invite-code {
text-align: center;
margin-right: 15px;
}
.invite-code-label {
font-weight: bold;
font-size: var(--font-size-micro);
}
.invite-code-value {
font-size: 140%;
font-family: var(--font-family-mono);
font-weight: bold;
text-transform: uppercase;
cursor: text;
user-select: text;
letter-spacing: 2px;
}
</style>
<pl-icon class="icon" icon="mail"></pl-icon>
<div class="invite-info">

View File

@ -9,7 +9,7 @@ import { mixins } from "../styles";
import { confirm, dialog } from "../dialog.js";
import { app, router } from "../init.js";
import { setClipboard } from "../clipboard.js";
import { element, html, property, query, queryAll, listen } from "./base.js";
import { element, html, css, property, query, queryAll, listen } from "./base.js";
import { Dialog } from "./dialog.js";
import "./icon.js";
import { Input } from "./input.js";
@ -75,6 +75,80 @@ export class ItemDialog extends Dialog<string, void> {
this.requestUpdate();
}
static styles = [
...Dialog.styles,
css`
:host {
${mixins.scroll()}
}
.inner {
max-width: 400px;
background: var(--color-quaternary);
}
header {
display: block;
}
.header-inner {
display: flex;
}
pl-input.name {
padding: 0 10px;
}
pl-tags-input {
margin: 5px 5px -5px 5px;
}
:host(:not([editing])) pl-field:hover {
background: #eee;
}
.add-button {
display: flex;
align-items: center;
justify-content: center;
padding: 6px;
}
.add-button pl-icon {
width: 30px;
position: relative;
top: 1px;
}
.updated {
text-align: center;
font-size: var(--font-size-tiny);
color: #888;
background: rgba(255, 255, 255, 0.5);
position: absolute;
left: 10px;
bottom: 10px;
}
.updated::before {
font-family: FontAwesome;
font-size: 80%;
content: "\\f303\ ";
}
h4 {
font-size: var(--font-size-tiny);
color: var(--color-primary);
font-weight: bold;
margin: 10px;
}
.fabs {
position: static;
}
`
];
renderContent() {
if (app.locked || !this._item || !this._vault) {
return html``;
@ -88,78 +162,6 @@ export class ItemDialog extends Dialog<string, void> {
const attachments = this._item!.attachments || [];
return html`
<style>
:host {
${mixins.scroll()}
}
.inner {
max-width: 400px;
background: var(--color-quaternary);
}
header {
display: block;
}
.header-inner {
display: flex;
}
pl-input.name {
padding: 0 10px;
}
pl-tags-input {
margin: 5px 5px -5px 5px;
}
:host(:not([editing])) pl-field:hover {
background: #eee;
}
.add-button {
display: flex;
align-items: center;
justify-content: center;
padding: 6px;
}
.add-button pl-icon {
width: 30px;
position: relative;
top: 1px;
}
.updated {
text-align: center;
font-size: var(--font-size-tiny);
color: #888;
background: rgba(255, 255, 255, 0.5);
position: absolute;
left: 10px;
bottom: 10px;
}
.updated::before {
font-family: FontAwesome;
font-size: 80%;
content: "\\f303\ ";
}
h4 {
font-size: var(--font-size-tiny);
color: var(--color-primary);
font-weight: bold;
margin: 10px;
}
.fabs {
position: static;
}
</style>
<header>
<div class="header-inner">
<pl-input

View File

@ -3,7 +3,7 @@ import { Vault } from "@padloc/core/lib/vault.js";
import { Tag } from "@padloc/core/lib/item.js";
import { app } from "../init.js";
import { shared, mixins } from "../styles";
import { BaseElement, element, property, html, listen } from "./base.js";
import { BaseElement, element, css, property, html, listen } from "./base.js";
@element("pl-items-filter")
export class ItemsFilter extends BaseElement {
@ -15,130 +15,132 @@ export class ItemsFilter extends BaseElement {
this.requestUpdate();
}
static styles = [
shared,
css`
:host {
display: block;
text-align: center;
overflow: visible;
}
button {
display: flex;
margin: 0 auto;
align-items: center;
font-weight: bold;
padding: 6px 12px;
border-radius: 20px;
line-height: normal;
box-sizing: border-box;
overflow: hidden;
max-width: 100%;
background: var(--color-shade-1);
border-bottom: solid 2px var(--color-shade-2);
margin-bottom: -2px;
font-size: var(--font-size-small);
}
button.vault {
background: var(--color-primary);
color: var(--color-tertiary);
text-shadow: rgba(0, 0, 0, 0.2) 0 2px 0;
border-bottom: solid 2px var(--color-shade-4);
}
button.filter-tag {
background: var(--color-secondary);
color: var(--color-tertiary);
text-shadow: rgba(0, 0, 0, 0.2) 0 2px 0;
border-bottom: solid 2px #222;
}
button.favorites {
background: var(--color-negative);
color: var(--color-tertiary);
text-shadow: rgba(0, 0, 0, 0.2) 0 2px 0;
border-bottom: solid 2px var(--color-shade-4);
}
button pl-icon {
font-size: 85%;
width: 20px;
height: 20px;
margin-right: -6px;
}
.list pl-icon {
margin-left: -6px;
margin-right: 0;
font-size: 80%;
width: 25px;
}
button > div,
.option > div {
${mixins.ellipsis()}
flex: 1;
}
.scrim {
z-index: 10;
border-top: solid 3px #f2f2f2;
background: rgba(255, 255, 255, 0.5);
${mixins.fullbleed()}
top: 60px;
will-change: opacity;
transition: opacity 200ms cubic-bezier(0.6, 0, 0.2, 1);
}
:host(:not([selecting])) .scrim {
opacity: 0;
pointer-events: none;
}
:host(:not([selecting])) .list {
transform: translate3d(0, -100%, 0);
}
.list {
padding: 12px 6px 6px 6px;
background: var(--color-tertiary);
box-sizing: border-box;
max-height: 100%;
width: 100%;
font-size: var(--font-size-tiny);
border-bottom: solid 3px var(--color-shade-1);
will-change: transform;
transition: transform 200ms cubic-bezier(0.6, 0, 0.2, 1);
${mixins.scroll()}
}
.list button {
margin-bottom: 6px;
border-bottom: none;
padding: 5px 10px;
text-shadow: none;
}
h4 {
margin: 16px 10px 10px 10px;
text-align: center;
opacity: 0.8;
}
.no-tags {
font-size: var(--font-size-micro);
padding: 5px 10px 15px 10px;
opacity: 0.5;
}
`
];
render() {
const { vault, tag } = app.filter;
const cl = vault ? "vault" : tag ? "filter-tag" : "all";
const label = vault ? vault.toString() : tag || $l("All Items");
return html`
${shared}
<style>
:host {
display: block;
text-align: center;
overflow: visible;
}
button {
display: flex;
margin: 0 auto;
align-items: center;
font-weight: bold;
padding: 6px 12px;
border-radius: 20px;
line-height: normal;
box-sizing: border-box;
overflow: hidden;
max-width: 100%;
background: var(--color-shade-1);
border-bottom: solid 2px var(--color-shade-2);
margin-bottom: -2px;
font-size: var(--font-size-small);
}
button.vault {
background: var(--color-primary);
color: var(--color-tertiary);
text-shadow: rgba(0, 0, 0, 0.2) 0 2px 0;
border-bottom: solid 2px var(--color-shade-4);
}
button.filter-tag {
background: var(--color-secondary);
color: var(--color-tertiary);
text-shadow: rgba(0, 0, 0, 0.2) 0 2px 0;
border-bottom: solid 2px #222;
}
button.favorites {
background: var(--color-negative);
color: var(--color-tertiary);
text-shadow: rgba(0, 0, 0, 0.2) 0 2px 0;
border-bottom: solid 2px var(--color-shade-4);
}
button pl-icon {
font-size: 85%;
width: 20px;
height: 20px;
margin-right: -6px;
}
.list pl-icon {
margin-left: -6px;
margin-right: 0;
font-size: 80%;
width: 25px;
}
button > div, .option > div {
${mixins.ellipsis()}
flex: 1;
}
.scrim {
z-index: 10;
border-top: solid 3px #f2f2f2;
background: rgba(255, 255, 255, 0.5);
${mixins.fullbleed()}
top: 60px;
will-change: opacity;
transition: opacity 200ms cubic-bezier(0.6, 0, 0.2, 1);
}
:host(:not([selecting])) .scrim {
opacity: 0;
pointer-events: none;
}
:host(:not([selecting])) .list {
transform: translate3d(0, -100%, 0);
}
.list {
padding: 12px 6px 6px 6px;
background: var(--color-tertiary);
box-sizing: border-box;
max-height: 100%;
width: 100%;
font-size: var(--font-size-tiny);
border-bottom: solid 3px var(--color-shade-1);
will-change: transform;
transition: transform 200ms cubic-bezier(0.6, 0, 0.2, 1);
${mixins.scroll()}
}
.list button {
margin-bottom: 6px;
border-bottom: none;
padding: 5px 10px;
text-shadow: none;
}
h4 {
margin: 16px 10px 10px 10px;
text-align: center;
opacity: 0.8;
}
.no-tags {
font-size: var(--font-size-micro);
padding: 5px 10px 15px 10px;
opacity: 0.5;
}
</style>
<button class="tap ${cl}" @click=${() => (this._selecting = !this._selecting)}>
<div>${label}</div>

View File

@ -8,7 +8,7 @@ import { setClipboard } from "../clipboard.js";
import { app, router } from "../init.js";
import { dialog, confirm } from "../dialog.js";
import { shared, mixins } from "../styles";
import { element, html, property, query, listen } from "./base.js";
import { element, html, css, property, query, listen } from "./base.js";
import { View } from "./view.js";
import { CreateItemDialog } from "./create-item-dialog.js";
import { Input } from "./input.js";
@ -138,203 +138,204 @@ export class ItemsList extends View {
this._resizeHandler();
}
static styles = [
shared,
css`
:host {
display: flex;
flex-direction: column;
box-sizing: border-box;
position: relative;
background: var(--color-quaternary);
border-radius: var(--border-radius);
}
header {
overflow: visible;
z-index: 10;
}
pl-items-filter {
flex: 1;
min-width: 0;
}
main {
padding-bottom: 70px;
}
.section-header {
grid-column: 1/-1;
font-weight: bold;
display: flex;
align-items: flex-end;
height: 35px;
box-sizing: border-box;
padding: 0 10px 5px 10px;
background: var(--color-quaternary);
display: flex;
z-index: 1;
position: -webkit-sticky;
position: sticky;
top: -3px;
margin-bottom: -8px;
font-size: var(--font-size-small);
}
.items {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
grid-gap: var(--gutter-size);
padding: 8px;
cursor: pointer;
}
.item {
box-sizing: border-box;
display: flex;
align-items: center;
margin: 0;
}
.item-body {
flex: 1;
min-width: 0;
}
.item .tags {
padding: 0 8px;
}
.item-header {
height: var(--row-height);
line-height: var(--row-height);
position: relative;
display: flex;
align-items: center;
}
.item-name {
padding-left: 15px;
${mixins.ellipsis()}
font-weight: bold;
flex: 1;
min-width: 0;
}
.item-fields {
position: relative;
display: flex;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
.item-fields::after {
content: "";
display: block;
width: 8px;
flex: none;
}
.item-field {
cursor: pointer;
font-size: var(--font-size-tiny);
line-height: 32px;
height: 32px;
text-align: center;
position: relative;
flex: 1;
font-weight: bold;
margin: 0 0 8px 8px;
border-radius: 8px;
${mixins.shade2()}
}
.item-field > * {
transition: transform 0.2s cubic-bezier(1, -0.3, 0, 1.3), opacity 0.2s;
}
.copied-message {
${mixins.fullbleed()}
border-radius: inherit;
}
.item-field:not(.copied) .copied-message,
.item-field.copied .item-field-label {
opacity: 0;
transform: scale(0);
}
.copied-message {
font-weight: bold;
background: var(--color-primary);
color: var(--color-background);
}
.copied-message::before {
font-family: "FontAwesome";
content: "\\f00c\\ ";
}
.item-field-label {
padding: 0 15px;
pointer-events: none;
${mixins.ellipsis()}
}
.item:focus:not([selected]) {
border-color: var(--color-highlight);
color: #4ca8d9;
}
.item[selected] {
background: #e6e6e6;
border-color: #ddd;
}
.item-check {
position: relative;
width: 30px;
height: 30px;
box-sizing: border-box;
border: solid 3px #eee;
background: #eee;
border-radius: 30px;
margin: 10px;
margin-right: 5px;
}
.item-check::after {
content: "";
display: block;
${mixins.fullbleed()}
background: var(--color-primary);
border-radius: inherit;
transition: transform 0.2s, opacity 0.2s;
transition-timing-function: cubic-bezier(1, -0.3, 0, 1.3);
}
.item-check:not([checked])::after {
opacity: 0;
transform: scale(0);
}
.selected-count {
text-align: center;
display: block;
margin-left: 12px;
background: #eee;
border-radius: var(--border-radius);
padding: 12px 4px;
line-height: 1.2em;
font-size: var(--font-size-tiny);
font-weight: bold;
box-shadow: rgba(0, 0, 0, 0.3) 0px 0px 4px;
}
`
];
render() {
return html`
${shared}
<style>
:host {
display: flex;
flex-direction: column;
box-sizing: border-box;
position: relative;
background: var(--color-quaternary);
border-radius: var(--border-radius);
}
header {
overflow: visible;
z-index: 10;
}
pl-items-filter {
flex: 1;
min-width: 0;
}
main {
padding-bottom: 70px;
}
.section-header {
grid-column: 1/-1;
font-weight: bold;
display: flex;
align-items: flex-end;
height: 35px;
box-sizing: border-box;
padding: 0 10px 5px 10px;
background: var(--color-quaternary);
display: flex;
z-index: 1;
position: -webkit-sticky;
position: sticky;
top: -3px;
margin-bottom: -8px;
font-size: var(--font-size-small);
}
.items {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
grid-gap: var(--gutter-size);
padding: 8px;
cursor: pointer;
}
.item {
box-sizing: border-box;
display: flex;
align-items: center;
margin: 0;
}
.item-body {
flex: 1;
min-width: 0;
}
.item .tags {
padding: 0 8px;
}
.item-header {
height: var(--row-height);
line-height: var(--row-height);
position: relative;
display: flex;
align-items: center;
}
.item-name {
padding-left: 15px;
${mixins.ellipsis()}
font-weight: bold;
flex: 1;
min-width: 0;
}
.item-fields {
position: relative;
display: flex;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
.item-fields::after {
content: "";
display: block;
width: 8px;
flex: none;
}
.item-field {
cursor: pointer;
font-size: var(--font-size-tiny);
line-height: 32px;
height: 32px;
text-align: center;
position: relative;
flex: 1;
font-weight: bold;
margin: 0 0 8px 8px;
border-radius: 8px;
${mixins.shade2()}
}
.item-field > * {
transition: transform 0.2s cubic-bezier(1, -0.3, 0, 1.3), opacity 0.2s;
}
.copied-message {
${mixins.fullbleed()}
border-radius: inherit;
}
.item-field:not(.copied) .copied-message, .item-field.copied .item-field-label {
opacity: 0;
transform: scale(0);
}
.copied-message {
font-weight: bold;
background: var(--color-primary);
color: var(--color-background);
}
.copied-message::before {
font-family: "FontAwesome";
content: "\\f00c\\ ";
}
.item-field-label {
padding: 0 15px;
pointer-events: none;
${mixins.ellipsis()}
}
.item:focus:not([selected]) {
border-color: var(--color-highlight);
color: #4ca8d9;
}
.item[selected] {
background: #e6e6e6;
border-color: #ddd;
}
.item-check {
position: relative;
width: 30px;
height: 30px;
box-sizing: border-box;
border: solid 3px #eee;
background: #eee;
border-radius: 30px;
margin: 10px;
margin-right: 5px;
}
.item-check::after {
content: "";
display: block;
${mixins.fullbleed()}
background: var(--color-primary);
border-radius: inherit;
transition: transform 0.2s, opacity 0.2s;
transition-timing-function: cubic-bezier(1, -0.3, 0, 1.3);
}
.item-check:not([checked])::after {
opacity: 0;
transform: scale(0);
}
.selected-count {
text-align: center;
display: block;
margin-left: 12px;
background: #eee;
border-radius: var(--border-radius);
padding: 12px 4px;
line-height: 1.2em;
font-size: var(--font-size-tiny);
font-weight: bold;
box-shadow: rgba(0, 0, 0, 0.3) 0px 0px 4px;
}
</style>
<header ?hidden=${this._filterShowing}>
<pl-icon icon="menu" class="tap menu-button" @click=${() => this.dispatch("toggle-menu")}></pl-icon>
@ -537,11 +538,17 @@ export class ItemsList extends View {
@click=${() => this.selectItem(item)}
index="${index}"
>
<div
class="item-check"
?hidden=${!this.multiSelect}
?checked=${this._multiSelect.has(item.item.id)}
></div>
${cache(
this.multiSelect
? html`
<div
class="item-check"
?hidden=${!this.multiSelect}
?checked=${this._multiSelect.has(item.item.id)}
></div>
`
: ""
)}
<div class="item-body">
<div class="item-header">
@ -567,17 +574,25 @@ export class ItemsList extends View {
<div class="item-fields">
${item.item.fields.map(
(f: Field, i: number) => html`
<div class="item-field" @click=${(e: MouseEvent) => this._copyField(item.item, i, e)}>
<div
class="item-field tap"
@click=${(e: MouseEvent) => this._copyField(item.item, i, e)}
>
<div class="item-field-label">${f.name}</div>
<div class="copied-message">${$l("copied")}</div>
</div>
`
)}
<div class="item-field" disabled ?hidden=${!!item.item.fields.length}>
${$l("No Fields")}
</div>
${cache(
!item.item.fields.length
? html`
<div class="item-field" disabled ?hidden=${!!item.item.fields.length}>
${$l("No Fields")}
</div>
`
: ""
)}
</div>
</div>
</div>

View File

@ -1,6 +1,6 @@
import "@polymer/paper-spinner/paper-spinner-lite.js";
import { shared, mixins } from "../styles";
import { BaseElement, element, html, property, listen } from "./base.js";
import { BaseElement, element, html, css, property, listen } from "./base.js";
import "./icon.js";
type ButtonState = "idle" | "loading" | "success" | "fail";
@ -14,64 +14,66 @@ export class LoadingButton extends BaseElement {
private _stopTimeout: number;
static styles = [
shared,
css`
:host {
display: flex;
height: var(--row-height);
}
:host([state="loading"]) button {
cursor: progress;
}
button {
background: transparent;
position: relative;
flex: 1;
height: auto;
}
button > * {
transition: transform 0.2s cubic-bezier(1, -0.3, 0, 1.3), opacity 0.2s;
${mixins.absoluteCenter()}
}
button > .label {
display: flex;
align-items: center;
justify-content: center;
${mixins.fullbleed()}
}
:host(.vertical) .label {
flex-direction: column;
}
button.loading .label,
button.success .label,
button.fail .label,
button:not(.loading) .spinner,
button:not(.success) .icon-success,
button:not(.fail) .icon-fail {
opacity: 0.5;
transform: scale(0);
}
button pl-icon {
font-size: 120%;
}
paper-spinner-lite {
line-height: normal;
--paper-spinner-color: currentColor;
--paper-spinner-stroke-width: 2px;
}
`
];
render() {
const { state, noTab } = this;
return html`
${shared}
<style>
:host {
display: flex;
height: var(--row-height);
}
:host([state="loading"]) button {
cursor: progress;
}
button {
background: transparent;
position: relative;
flex: 1;
height: auto;
}
button > * {
transition: transform 0.2s cubic-bezier(1, -0.3, 0, 1.3), opacity 0.2s;
${mixins.absoluteCenter()}
}
button > .label {
display: flex;
align-items: center;
justify-content: center;
${mixins.fullbleed()}
}
:host(.vertical) .label {
flex-direction: column;
}
button.loading .label, button.success .label, button.fail .label,
button:not(.loading) .spinner,
button:not(.success) .icon-success,
button:not(.fail) .icon-fail {
opacity: 0.5;
transform: scale(0);
}
button pl-icon {
font-size: 120%;
}
paper-spinner-lite {
line-height: normal;
--paper-spinner-color: currentColor;
--paper-spinner-stroke-width: 2px;
}
</style>
<button type="button" class="${state}" tabindex="${noTab ? "-1" : ""}">
<div class="label"><slot></slot></div>

View File

@ -1,8 +1,8 @@
import { localize as $l } from "@padloc/core/lib/locale.js";
import { ErrorCode } from "@padloc/core/lib/error.js";
import { app, router } from "../init.js";
import { element, html, property, query } from "./base.js";
import { StartForm, sharedStyles } from "./start-form.js";
import { element, html, css, property, query } from "./base.js";
import { StartForm } from "./start-form.js";
import { Input } from "./input.js";
import { PasswordInput } from "./password-input.js";
import { LoadingButton } from "./loading-button.js";
@ -35,29 +35,30 @@ export class Login extends StartForm {
super.reset();
}
static styles = [
...StartForm.styles,
css`
.hint {
font-size: var(--font-size-tiny);
box-sizing: border-box;
transition: max-height 0.3s;
max-height: 100px;
margin: 40px 0 -20px 0;
}
button.signup {
background: none;
border: none;
height: auto;
line-height: normal;
font-weight: bold;
height: var(--row-height);
}
`
];
render() {
return html`
${sharedStyles}
<style>
.hint {
font-size: var(--font-size-tiny);
box-sizing: border-box;
transition: max-height 0.3s;
max-height: 100px;
margin: 40px 0 -20px 0;
}
button.signup {
background: none;
border: none;
height: auto;
line-height: normal;
font-weight: bold;
height: var(--row-height);
}
</style>
<div flex></div>
<form>

View File

@ -1,11 +1,75 @@
import { svg } from "lit-html";
import { BaseElement, element, html, property } from "./base.js";
import { BaseElement, element, html, css, svg, property } from "./base.js";
@element("pl-logo")
export class Logo extends BaseElement {
@property({ reflect: true })
reveal: boolean = false;
static styles = [
css`
:host {
position: relative;
display: block;
width: 200px;
height: 52px;
}
:host([reveal]) .padloc > * {
stroke-dashoffset: 0;
}
svg {
width: 100%;
height: 100%;
fill: none;
stroke: currentColor;
stroke-linecap: round;
stroke-width: 7;
}
.padloc > * {
transition: stroke-dashoffset 1.4s cubic-bezier(0.57, 0.25, 0, 0.99);
stroke-dashoffset: 0;
}
.p {
stroke-dashoffset: 234px;
stroke-dasharray: 234px;
transition-delay: 0s;
}
.a {
stroke-dashoffset: 190px;
stroke-dasharray: 190px;
transition-delay: 0.1s;
}
.d {
stroke-dashoffset: 232px;
stroke-dasharray: 232px;
transition-delay: 0.2s;
}
.l {
stroke-dashoffset: 155px;
stroke-dasharray: 155px;
transition-delay: 0.3s;
}
.o {
stroke-dashoffset: 168px;
stroke-dasharray: 168px;
transition-delay: 0.4s;
}
.c {
stroke-dashoffset: 237px;
stroke-dasharray: 237px;
transition-delay: 0.5s;
}
`
];
_logo() {
return svg`
<svg viewBox="0 -12 380 120" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
@ -53,69 +117,6 @@ export class Logo extends BaseElement {
}
render() {
return html`
<style>
:host {
position: relative;
display: block;
width: 200px;
height: 52px;
}
:host([reveal]) .padloc > * {
stroke-dashoffset: 0;
}
svg {
width: 100%;
height: 100%;
fill: none;
stroke: currentColor;
stroke-linecap: round;
stroke-width: 7;
}
.padloc > * {
transition: stroke-dashoffset 1.4s cubic-bezier(0.57, 0.25, 0, 0.99);
stroke-dashoffset: 0;
}
.p {
stroke-dashoffset: 234px;
stroke-dasharray: 234px;
transition-delay: 0s;
}
.a {
stroke-dashoffset: 190px;
stroke-dasharray: 190px;
transition-delay: 0.1s;
}
.d {
stroke-dashoffset: 232px;
stroke-dasharray: 232px;
transition-delay: 0.2s;
}
.l {
stroke-dashoffset: 155px;
stroke-dasharray: 155px;
transition-delay: 0.3s;
}
.o {
stroke-dashoffset: 168px;
stroke-dasharray: 168px;
transition-delay: 0.4s;
}
.c {
stroke-dashoffset: 237px;
stroke-dasharray: 237px;
transition-delay: 0.5s;
}
</style>
${this._logo()}
`;
}

View File

@ -4,7 +4,7 @@ import { localize as $l } from "@padloc/core/lib/locale.js";
import { mixins } from "../styles";
import { app } from "../init.js";
import { confirm, choose } from "../dialog.js";
import { element, html, property, query } from "./base.js";
import { element, html, css, property, query } from "./base.js";
import { Dialog } from "./dialog.js";
import { LoadingButton } from "./loading-button.js";
import "./icon.js";
@ -288,6 +288,53 @@ export class MemberDialog extends Dialog<InputType, void> {
return !!this.org && !!this.member;
}
static styles = [
...Dialog.styles,
css`
.inner {
background: var(--color-quaternary);
}
pl-toggle-button {
display: block;
padding: 0 15px 0 0;
}
.more-button {
font-size: var(--font-size-small);
align-self: flex-start;
width: 30px;
height: 30px;
margin-top: 5px;
}
.subheader {
margin: 8px;
font-weight: bold;
display: flex;
align-items: flex-end;
padding: 0 8px;
font-size: var(--font-size-small);
}
.subheader .permission {
width: 50px;
font-size: var(--font-size-tiny);
text-align: center;
${mixins.ellipsis()}
}
.item {
display: flex;
align-items: center;
}
.item pl-toggle {
margin-right: 14px;
}
`
];
renderContent() {
const org = this.org!;
const member = this.member!;
@ -296,50 +343,6 @@ export class MemberDialog extends Dialog<InputType, void> {
const memberIsOwner = org.isOwner(member);
return html`
<style>
.inner {
background: var(--color-quaternary);
}
pl-toggle-button {
display: block;
padding: 0 15px 0 0;
}
.more-button {
font-size: var(--font-size-small);
align-self: flex-start;
width: 30px;
height: 30px;
margin-top: 5px;
}
.subheader {
margin: 8px;
font-weight: bold;
display: flex;
align-items: flex-end;
padding: 0 8px;
font-size: var(--font-size-small);
}
.subheader .permission {
width: 50px;
font-size: var(--font-size-tiny);
text-align: center;
${mixins.ellipsis()}
}
.item {
display: flex;
align-items: center;
}
.item pl-toggle {
margin-right: 14px;
}
</style>
<header>
<pl-member-item .member=${member} class="flex"></pl-member-item>
<pl-icon

View File

@ -1,7 +1,7 @@
import { OrgMember, OrgRole } from "@padloc/core/lib/org.js";
import { localize as $l } from "@padloc/core/lib/locale.js";
import { shared } from "../styles";
import { BaseElement, element, html, property } from "./base.js";
import { BaseElement, element, html, css, property } from "./base.js";
import "./fingerprint.js";
@element("pl-member-item")
@ -12,55 +12,56 @@ export class MemberItem extends BaseElement {
@property()
hideRole: boolean = false;
static styles = [
shared,
css`
:host {
display: flex;
align-items: center;
padding: 8px;
}
pl-fingerprint {
color: var(--color-secondary);
--color-background: var(--color-tertiary);
width: 45px;
height: 45px;
border-radius: 100%;
border: solid 1px var(--border-color);
margin-right: 8px;
}
.member-info {
flex: 1;
width: 0;
}
.name-wrapper {
display: flex;
}
.name-wrapper > .tags {
margin: 0 0 0 4px;
}
.member-name {
font-weight: bold;
flex: 1;
width: 0;
}
.member-email {
font-size: 90%;
}
`
];
render() {
const isAdmin = this.member.role === OrgRole.Admin;
const isOwner = this.member.role === OrgRole.Owner;
const isSuspended = this.member.role === OrgRole.Suspended;
return html`
${shared}
<style>
:host {
display: flex;
align-items: center;
padding: 8px;
}
pl-fingerprint {
color: var(--color-secondary);
--color-background: var(--color-tertiary);
width: 45px;
height: 45px;
border-radius: 100%;
border: solid 1px var(--border-color);
margin-right: 8px;
}
.member-info {
flex: 1;
width: 0;
}
.name-wrapper {
display: flex;
}
.name-wrapper > .tags {
margin: 0 0 0 4px;
}
.member-name {
font-weight: bold;
flex: 1;
width: 0;
}
.member-email {
font-size: 90%;
}
</style>
<pl-fingerprint .key=${this.member.publicKey}></pl-fingerprint>
<div class="member-info">

View File

@ -3,7 +3,7 @@ import { FilterParams } from "@padloc/core/lib/app.js";
import { localize as $l } from "@padloc/core/lib/locale.js";
import { app, router } from "../init.js";
import { shared, mixins } from "../styles";
import { BaseElement, element, property, html, listen } from "./base.js";
import { BaseElement, element, property, html, css, listen } from "./base.js";
import "./logo.js";
@element("pl-menu")
@ -30,104 +30,107 @@ export class Menu extends BaseElement {
router.go(path);
}
static styles = [
shared,
css`
:host {
display: flex;
flex-direction: column;
color: var(--color-tertiary);
font-size: var(--font-size-small);
}
.scroller {
flex: 1;
height: 0;
${mixins.scroll()}
padding: 10px 0;
}
li {
background: transparent;
border: none;
display: flex;
align-items: center;
height: 40px;
margin: 2px 10px;
padding-right: 10px;
border-radius: 8px;
overflow: hidden;
height: 40px;
}
li:not([selected]):hover {
background: rgba(0, 0, 0, 0.1);
}
li[selected] {
background: rgba(255, 255, 255, 0.2);
}
li div {
flex: 1;
${mixins.ellipsis()}
}
h3 {
font-size: 100%;
margin-top: 30px;
padding: 0 20px;
opacity: 0.8;
font-weight: normal;
}
.vault,
.menu-tag {
height: 35px;
font-size: var(--font-size-tiny);
}
.vault pl-icon,
.menu-tag pl-icon {
width: 30px;
height: 30px;
font-size: 90%;
}
pl-logo {
height: 30px;
margin: 15px 0 20px 0;
opacity: 0.25;
}
.no-tags {
font-size: var(--font-size-micro);
padding: 0 20px;
opacity: 0.5;
width: 100px;
}
.footer {
padding: 5px;
display: flex;
align-items: center;
}
.footer pl-icon {
width: 30px;
height: 30px;
font-size: var(--font-size-tiny);
}
.syncing {
width: 20px;
height: 20px;
margin: 5px;
--paper-spinner-color: currentColor;
--paper-spinner-stroke-width: 2px;
}
`
];
render() {
return html`
${shared}
<style>
:host {
display: flex;
flex-direction: column;
color: var(--color-tertiary);
font-size: var(--font-size-small);
}
.scroller {
flex: 1;
height: 0;
${mixins.scroll()}
padding: 10px 0;
}
li {
background: transparent;
border: none;
display: flex;
align-items: center;
height: 40px;
margin: 2px 10px;
padding-right: 10px;
border-radius: 8px;
overflow: hidden;
height: 40px;
}
li:not([selected]):hover {
background: rgba(0, 0, 0, 0.1);
}
li[selected] {
background: rgba(255, 255, 255, 0.2);
}
li div {
flex: 1;
${mixins.ellipsis()}
}
h3 {
font-size: 100%;
margin-top: 30px;
padding: 0 20px;
opacity: 0.8;
font-weight: normal;
}
.vault, .menu-tag {
height: 35px;
font-size: var(--font-size-tiny);
}
.vault pl-icon, .menu-tag pl-icon {
width: 30px;
height: 30px;
font-size: 90%;
}
pl-logo {
height: 30px;
margin: 15px 0 20px 0;
opacity: 0.25;
}
.no-tags {
font-size: var(--font-size-micro);
padding: 0 20px;
opacity: 0.5;
width: 100px;
}
.footer {
padding: 5px;
display: flex;
align-items: center;
}
.footer pl-icon {
width: 30px;
height: 30px;
font-size: var(--font-size-tiny);
}
.syncing {
width: 20px;
height: 20px;
margin: 5px;
--paper-spinner-color: currentColor;
--paper-spinner-stroke-width: 2px;
}
</style>
<div class="scroller">
<pl-logo reveal></pl-logo>

View File

@ -2,7 +2,7 @@ import { Vault } from "@padloc/core/lib/vault.js";
import { VaultItem } from "@padloc/core/lib/item.js";
import { localize as $l } from "@padloc/core/lib/locale.js";
import { app } from "../init.js";
import { element, property, html, query } from "./base.js";
import { element, property, html, css, query } from "./base.js";
import { Select } from "./select.js";
import { Dialog } from "./dialog.js";
@ -16,34 +16,37 @@ export class MoveItemsDialog extends Dialog<{ vault: Vault; item: VaultItem }[],
@query("#vaultSelect")
private _vaultSelect: Select<Vault>;
static styles = [
...Dialog.styles,
css`
pl-input,
pl-select {
text-align: center;
margin: 12px;
}
.actions {
margin: 12px;
grid-gap: 12px;
}
h1 {
display: block;
text-align: center;
}
.message {
margin: 8px;
text-align: center;
}
`
];
renderContent() {
const itemsDescription =
this.items.length === 1 ? `'${this.items[0].item.name}'` : $l("{0} Items", this.items.length.toString());
return html`
<style>
pl-input,
pl-select {
text-align: center;
margin: 12px;
}
.actions {
margin: 12px;
grid-gap: 12px;
}
h1 {
display: block;
text-align: center;
}
.message {
margin: 8px;
text-align: center;
}
</style>
<h1>${$l("Move {0} To", itemsDescription)}</h1>
<div class="message" ?hidden=${this.vaults.length}>

View File

@ -1,6 +1,6 @@
import { getSingleton } from "../singleton.js";
import { shared, mixins } from "../styles";
import { BaseElement, element, html, property } from "./base.js";
import { BaseElement, element, html, css, property } from "./base.js";
export type NotificationType = "info" | "warning";
@ -21,50 +21,50 @@ export class Notification extends BaseElement {
private _hideTimeout: number;
static styles = [
shared,
css`
:host {
display: flex;
align-items: center;
text-align: center;
transition: transform 0.5s cubic-bezier(1, -0.3, 0, 1.3);
position: fixed;
left: 15px;
right: 15px;
bottom: 15px;
z-index: 10;
max-width: 400px;
margin: 0 auto;
border-radius: var(--border-radius);
color: var(--color-background);
text-shadow: rgba(0, 0, 0, 0.2) 0 2px 0;
${mixins.gradientDark(true)}
}
:host(:not(.showing)) {
transform: translateY(130%);
}
:host([type="warning"]) {
${mixins.gradientWarning(true)}
}
.message {
flex: 1;
min-width: 0;
padding: 15px 0 15px 15px;
font-weight: bold;
}
pl-icon.close-button {
margin: auto 5px;
}
`
];
render() {
return html`
${shared}
<style>
:host {
display: flex;
align-items: center;
text-align: center;
transition: transform 0.5s cubic-bezier(1, -0.3, 0, 1.3);
position: fixed;
left: 15px;
right: 15px;
bottom: 15px;
z-index: 10;
max-width: 400px;
margin: 0 auto;
border-radius: var(--border-radius);
color: var(--color-background);
text-shadow: rgba(0, 0, 0, 0.2) 0 2px 0;
${mixins.gradientDark(true)}
}
:host(:not(.showing)) {
transform: translateY(130%);
}
:host([type="warning"]) {
${mixins.gradientWarning(true)}
}
.message {
flex: 1;
min-width: 0;
padding: 15px 0 15px 15px;
font-weight: bold;
}
pl-icon.close-button {
margin: auto 5px;
}
</style>
<div class="message">${this.message}</div>
<pl-icon icon="close" class="close-button tap" @click=${() => this.dismiss()}></pl-icon>

View File

@ -4,7 +4,7 @@ import { OrgMember, OrgRole, Group } from "@padloc/core/lib/org.js";
import { shared, mixins } from "../styles";
import { dialog, prompt, choose, confirm } from "../dialog.js";
import { app, router } from "../init.js";
import { element, html, property, query, observe } from "./base.js";
import { element, html, css, property, query, observe } from "./base.js";
import { View } from "./view.js";
import { Input } from "./input.js";
import { VaultDialog } from "./vault-dialog.js";
@ -146,6 +146,69 @@ export class OrgView extends View {
return !!this._org;
}
static styles = [
shared,
css`
:host {
display: flex;
flex-direction: column;
background: var(--color-quaternary);
border-radius: var(--border-radius);
}
.wrapper {
position: relative;
width: 100%;
height: 100%;
max-width: 600px;
margin: 0 auto;
}
.subview {
position: relative;
${mixins.fullbleed()}
${mixins.scroll()}
}
header {
display: block;
border: none;
}
.header-inner {
display: flex;
align-items: center;
margin-bottom: 10px;
}
.header-inner .title {
text-align: center;
}
header > .tabs {
margin: -10px;
}
.tabs .spacer {
padding: 0;
}
.new-button {
display: flex;
font-weight: bold;
align-items: center;
justify-content: center;
padding: 8px;
}
.new-button > pl-icon {
font-size: 80%;
width: 30px;
height: 30px;
}
`
];
render() {
const org = this._org!;
const isOwner = org.isOwner(app.account!);
@ -160,49 +223,6 @@ export class OrgView extends View {
: org.members;
return html`
${shared}
<style>
:host {
display: flex;
flex-direction: column;
background: var(--color-quaternary);
border-radius: var(--border-radius);
}
.wrapper {
position: relative;
width: 100%;
height: 100%;
max-width: 600px;
margin: 0 auto;
}
.subview {
position: relative;
${mixins.fullbleed()}
${mixins.scroll()}
}
header {
display: block;
}
.header-inner {
display: flex;
align-items: center;
margin-bottom: 10px;
}
.header-inner .title {
text-align: center;
}
.tabs .spacer {
padding: 0;
}
</style>
<header>
<div class="header-inner narrow">
<pl-icon class="tap menu-button" icon="menu" @click=${() => this.dispatch("toggle-menu")}></pl-icon>
@ -250,6 +270,14 @@ export class OrgView extends View {
<pl-icon icon="cancel" class="tap" @click=${this._clearMembersFilter}></pl-icon>
</div>
<ul>
<li
class="new-button tap"
@click=${this._createInvite}
?hidden=${!isOwner || members.length < 50}
>
<pl-icon icon="invite"></pl-icon>
<div>${$l("Invite New Member")}</div>
</li>
${members.map(
member => html`
<li class="tap member" @click=${() => this._showMember(member)}>
@ -257,8 +285,8 @@ export class OrgView extends View {
</li>
`
)}
<li class="centering padded tap" @click=${this._createInvite} ?hidden=${!isOwner}>
<pl-icon icon="invite"></pl-icon>
<li class="new-button tap" @click=${this._createInvite} ?hidden=${!isOwner}>
<pl-icon icon="add"></pl-icon>
<div>${$l("Invite New Member")}</div>
</li>
</ul>
@ -273,7 +301,7 @@ export class OrgView extends View {
</li>
`
)}
<li class="centering padded tap" @click=${this._createGroup} ?hidden=${!isOwner}>
<li class="new-button tap" @click=${this._createGroup} ?hidden=${!isOwner}>
<pl-icon icon="add"></pl-icon>
<div>${$l("New Group")}</div>
</li>
@ -289,7 +317,7 @@ export class OrgView extends View {
</li>
`
)}
<li class="centering padded tap" @click=${this._createVault}>
<li class="new-button tap" @click=${this._createVault}>
<pl-icon icon="add"></pl-icon>
<div>${$l("New Vault")}</div>
</li>
@ -305,18 +333,12 @@ export class OrgView extends View {
</li>
`
)}
<li class="centering padded tap" @click=${this._createInvite}>
<pl-icon icon="invite"></pl-icon>
<li class="new-button tap" @click=${this._createInvite}>
<pl-icon icon="add"></pl-icon>
<div>${$l("Invite New Member")}</div>
</li>
</ul>
</div>
<div class="fabs" ?hidden=${!isOwner}>
<div class="flex"></div>
<pl-icon icon="invite" class="tap fab" @click=${() => this._createInvite()}></pl-icon>
</div>
</div>
</main>
`;

View File

@ -1,4 +1,4 @@
import { element, html } from "./base.js";
import { element, html, css } from "./base.js";
import { Input } from "./input.js";
import "./icon.js";
@ -9,47 +9,36 @@ export class PasswordInput extends Input {
this.type = "password";
}
static styles = [
...Input.styles,
css`
input {
font-size: 120%;
font-family: var(--font-family-mono);
}
.mask-icon {
position: absolute;
z-index: 1;
right: 5px;
top: 0;
bottom: 0;
margin: auto;
opacity: 0.8;
}
`
];
render() {
return html`
${super.render()}
<style>
/*
:host {
padding: 5px 10px !important;
}
label {
padding: 18px;
}
label[float] {
transform: scale(0.8) translate(0, -38px);
}
*/
input {
font-size: 120%;
font-family: var(--font-family-mono);
}
.mask-icon {
position: absolute;
z-index: 1;
right: 5px;
top: 0;
bottom: 0;
margin: auto;
opacity: 0.8;
}
</style>
<pl-icon
icon="${this.type === "password" ? "show" : "hide"}"
class="mask-icon tap"
@click=${this._toggleMasked}>
@click=${this._toggleMasked}
>
</pl-icon>
`;
}

View File

@ -1,5 +1,5 @@
import { localize } from "@padloc/core/lib/locale.js";
import { element, html, property, query } from "./base.js";
import { element, html, css, property, query } from "./base.js";
import { Input } from "./input.js";
import { LoadingButton } from "./loading-button.js";
import { Dialog } from "./dialog.js";
@ -50,35 +50,38 @@ export class PromptDialog extends Dialog<PromptOptions, string | null> {
@query("pl-input")
private _input: Input;
static styles = [
...Dialog.styles,
css`
h1 {
display: block;
text-align: center;
}
.message {
margin: 20px;
text-align: center;
}
pl-input {
text-align: center;
margin: 8px;
}
.validation-message {
position: relative;
margin-top: 15px;
font-weight: bold;
font-size: var(--font-size-small);
color: var(--color-error);
text-shadow: none;
text-align: center;
}
`
];
renderContent() {
return html`
<style>
h1 {
display: block;
text-align: center;
}
.message {
margin: 20px;
text-align: center;
}
pl-input {
text-align: center;
margin: 8px;
}
.validation-message {
position: relative;
margin-top: 15px;
font-weight: bold;
font-size: var(--font-size-small);
color: var(--color-error);
text-shadow: none;
text-align: center;
}
</style>
<h1 ?hidden=${!this.title}>${this.title}</h1>
<div class="message" ?hidden=${!this.message}>${this.message}</div>

View File

@ -1,7 +1,7 @@
import { localize as $l } from "@padloc/core/lib/locale.js";
import { app, router } from "../init.js";
import { element, html, property, query } from "./base.js";
import { StartForm, sharedStyles } from "./start-form.js";
import { element, html, css, property, query } from "./base.js";
import { StartForm } from "./start-form.js";
import { Input } from "./input.js";
import { LoadingButton } from "./loading-button.js";
import { alert, choose, prompt } from "../dialog.js";
@ -33,52 +33,53 @@ export class Recover extends StartForm {
super.reset();
}
static styles = [
...StartForm.styles,
css`
h1 {
display: block;
text-align: center;
margin: 30px;
}
.title {
width: 300px;
margin: 30px auto;
font-size: var(--font-size-small);
font-weight: bold;
letter-spacing: 0.5px;
padding: 0 10px;
}
#submitButton {
margin-bottom: 30px;
}
.login {
text-decoration: underline;
cursor: pointer;
}
.recovery-notes {
text-align: left;
padding: 20px;
margin: 10px;
}
.recovery-notes ul {
list-style: disc;
}
.recovery-notes li {
margin: 10px 20px 0 20px;
background: transparent;
border: none;
}
`
];
render() {
return html`
${sharedStyles}
<style include="shared">
h1 {
display: block;
text-align: center;
margin: 30px;
}
.title {
width: 300px;
margin: 30px auto;
font-size: var(--font-size-small);
font-weight: bold;
letter-spacing: 0.5px;
padding: 0 10px;
}
#submitButton {
margin-bottom: 30px;
}
.login {
text-decoration: underline;
cursor: pointer;
}
.recovery-notes {
text-align: left;
padding: 20px;
margin: 10px;
}
.recovery-notes ul {
list-style: disc;
}
.recovery-notes li {
margin: 10px 20px 0 20px;
background: transparent;
border: none;
}
</style>
<div flex></div>
<form>

View File

@ -1,5 +1,5 @@
import { shared } from "../styles";
import { BaseElement, element, html, property, query, observe } from "./base.js";
import { BaseElement, element, html, css, property, query, observe } from "./base.js";
@element("pl-select")
export class Select<T> extends BaseElement {
@ -13,66 +13,67 @@ export class Select<T> extends BaseElement {
@query("select")
private _select: HTMLSelectElement;
static styles = [
shared,
css`
:host {
display: block;
position: relative;
padding: 0;
height: var(--row-height);
padding: 0 15px;
background: var(--shade-2-color);
border-radius: var(--border-radius);
}
select {
width: 100%;
height: 100%;
box-sizing: border-box;
cursor: pointer;
}
option {
background-color: var(--color-tertiary);
color: var(--color-secondary);
}
label {
position: absolute;
top: 0;
left: 0;
right: 0;
padding: 13px;
opacity: 0.5;
transition: transform 0.2s, color 0.2s, opacity 0.5s;
cursor: text;
pointer-events: none;
}
label[float] {
transform: scale(0.8) translate(0, -32px);
color: var(--color-highlight);
font-weight: bold;
opacity: 1;
}
pl-icon {
position: absolute;
width: 20px;
height: 20px;
top: 0;
right: 5px;
bottom: 0;
margin: auto;
pointer-events: none;
}
`
];
render() {
const { options, selected, label } = this;
return html`
${shared}
<style>
:host {
display: block;
position: relative;
padding: 0;
height: var(--row-height);
padding: 0 15px;
background: var(--shade-2-color);
border-radius: var(--border-radius);
}
select {
width: 100%;
height: 100%;
box-sizing: border-box;
cursor: pointer;
}
option {
background-color: var(--color-tertiary);
color: var(--color-secondary);
}
label {
position: absolute;
top: 0;
left: 0;
right: 0;
padding: 13px;
opacity: 0.5;
transition: transform 0.2s, color 0.2s, opacity 0.5s;
cursor: text;
pointer-events: none;
}
label[float] {
transform: scale(0.8) translate(0, -32px);
color: var(--color-highlight);
font-weight: bold;
opacity: 1;
}
pl-icon {
position: absolute;
width: 20px;
height: 20px;
top: 0;
right: 5px;
bottom: 0;
margin: auto;
pointer-events: none;
}
</style>
<select id="selectEl" .selectedIndex=${options.indexOf(selected)} @change=${() => this._changed()}>
${options.map(
o => html`

View File

@ -2,7 +2,7 @@ import { localize as $l } from "@padloc/core/lib/locale.js";
import { shared, mixins } from "../styles";
import { alert, confirm, prompt, dialog } from "../dialog";
import { app } from "../init.js";
import { element, html, query, listen } from "./base.js";
import { element, html, css, query, listen } from "./base.js";
import { View } from "./view.js";
import "./icon.js";
import { Slider } from "./slider.js";
@ -32,79 +32,80 @@ export class Settings extends View {
return !!app.account;
}
static styles = [
shared,
css`
:host {
${mixins.fullbleed()}
display: flex;
flex-direction: column;
background: var(--color-quaternary);
border-radius: var(--border-radius);
}
h1 {
display: block;
text-align: center;
}
.wrapper {
max-width: 500px;
margin: 0 auto;
padding: 0 8px 8px 8px;
}
button {
display: block;
}
.item {
width: 100%;
box-sizing: border-box;
margin: 8px 0;
}
.account {
font-size: 110%;
display: flex;
align-items: center;
}
pl-fingerprint {
width: 60px;
height: 60px;
border-radius: 100%;
border: solid 1px var(--border-color);
margin: 15px;
}
.account-info {
flex: 1;
min-width: 0;
padding-right: 18px;
}
.account-email {
${mixins.ellipsis()}
}
.account-email {
font-weight: bold;
${mixins.ellipsis()}
}
.account pl-icon {
width: 50px;
height: 50px;
margin: 5px;
}
`
];
render() {
const { settings } = app;
const account = app.account!;
return html`
${shared}
<style>
:host {
${mixins.fullbleed()}
display: flex;
flex-direction: column;
background: var(--color-quaternary);
border-radius: var(--border-radius);
}
h1 {
display: block;
text-align: center;
}
.wrapper {
max-width: 500px;
margin: 0 auto;
padding: 0 8px 8px 8px;
}
button {
display: block;
}
.item {
width: 100%;
box-sizing: border-box;
margin: 8px 0;
}
.account {
font-size: 110%;
display: flex;
align-items: center;
}
pl-fingerprint {
width: 60px;
height: 60px;
border-radius: 100%;
border: solid 1px var(--border-color);
margin: 15px;
}
.account-info {
flex: 1;
min-width: 0;
padding-right: 18px;
}
.account-email {
${mixins.ellipsis()}
}
.account-email {
font-weight: bold;
${mixins.ellipsis()}
}
.account pl-icon {
width: 50px;
height: 50px;
margin: 5px;
}
</style>
<header class="narrow">
<pl-icon class="tap menu-button" icon="menu" @click=${() => this.dispatch("toggle-menu")}></pl-icon>

View File

@ -4,8 +4,8 @@ import { generatePassphrase } from "@padloc/core/lib/diceware.js";
import { isTouch } from "@padloc/core/lib/platform.js";
import { passwordStrength } from "../util.js";
import { app, router } from "../init.js";
import { element, html, property, query } from "./base.js";
import { StartForm, sharedStyles } from "./start-form.js";
import { element, html, css, property, query } from "./base.js";
import { StartForm } from "./start-form.js";
import { Input } from "./input.js";
import { PasswordInput } from "./password-input.js";
import { LoadingButton } from "./loading-button.js";
@ -92,88 +92,89 @@ export class Signup extends StartForm {
this._step = step;
}
static styles = [
...StartForm.styles,
css`
h1 {
display: block;
text-align: center;
margin: 20px 10px;
}
.master-password-form {
max-width: 500px;
}
.title {
width: 300px;
margin: 30px auto;
font-size: var(--font-size-small);
font-weight: bold;
letter-spacing: 0.5px;
padding: 0 10px;
}
#submitButton {
margin-bottom: 30px;
}
.login {
text-decoration: underline;
cursor: pointer;
}
pl-input:not([focused]) + .hint {
color: rgba(0, 0, 0, 0.2);
text-shadow: none;
}
.master-password {
position: relative;
background: var(--shade-2-color);
font-family: var(--font-family-mono);
font-size: 120%;
padding: 20px 40px;
overflow-wrap: break-word;
}
.master-password-cover {
${mixins.fullbleed()}
height: 1em;
margin: auto;
font-weight: bold;
text-shadow: none;
color: rgba(0, 0, 0, 0.3);
}
.master-password-edit {
font-size: 80%;
width: 35px;
height: 35px;
vertical-align: middle;
position: absolute;
right: 5px;
top: 0;
bottom: 0;
margin: auto;
z-index: 1;
text-shadow: none;
color: rgba(0, 0, 0, 0.3);
}
.master-password:hover {
background: var(--shade-3-color);
}
.master-password:not(:hover) .master-password-value,
.master-password:not(:hover) .master-password-edit,
.master-password:hover .master-password-cover {
opacity: 0;
}
`
];
render() {
return html`
${sharedStyles}
<style>
h1 {
display: block;
text-align: center;
margin: 20px 10px;
}
.master-password-form {
max-width: 500px;
}
.title {
width: 300px;
margin: 30px auto;
font-size: var(--font-size-small);
font-weight: bold;
letter-spacing: 0.5px;
padding: 0 10px;
}
#submitButton {
margin-bottom: 30px;
}
.login {
text-decoration: underline;
cursor: pointer;
}
pl-input:not([focused]) + .hint {
color: rgba(0, 0, 0, 0.2)
text-shadow: none;
}
.master-password {
position: relative;
background: var(--shade-2-color);
font-family: var(--font-family-mono);
font-size: 120%;
padding: 20px 40px;
overflow-wrap: break-word;
}
.master-password-cover {
${mixins.fullbleed()}
height: 1em;
margin: auto;
font-weight: bold;
text-shadow: none;
color: rgba(0, 0, 0, 0.3);
}
.master-password-edit {
font-size: 80%;
width: 35px;
height: 35px;
vertical-align: middle;
position: absolute;
right: 5px;
top: 0;
bottom: 0;
margin: auto;
z-index: 1;
text-shadow: none;
color: rgba(0, 0, 0, 0.3);
}
.master-password:hover {
background: var(--shade-3-color);
}
.master-password:not(:hover) .master-password-value,
.master-password:not(:hover) .master-password-edit,
.master-password:hover .master-password-cover {
opacity: 0;
}
</style>
<div class="wrapper" hidden>
<div flex></div>

View File

@ -1,4 +1,4 @@
import { BaseElement, element, html, property, query } from "./base.js";
import { BaseElement, element, html, css, property, query } from "./base.js";
import { shared } from "../styles";
@element("pl-slider")
@ -13,12 +13,9 @@ export class Slider extends BaseElement {
@query("input") private _input: HTMLInputElement;
render() {
return html`
${shared}
<style include="shared">
static styles = [
shared,
css`
:host {
display: flex;
align-items: center;
@ -31,7 +28,7 @@ export class Slider extends BaseElement {
--knob-size: var(--slider-knob-size, 25px);
}
input[type=range] {
input[type="range"] {
-webkit-appearance: none;
width: 100%;
margin: 0;
@ -41,11 +38,11 @@ export class Slider extends BaseElement {
min-height: var(--knob-size);
}
input[type=range]:focus {
input[type="range"]:focus {
outline: none;
}
input[type=range]::-webkit-slider-runnable-track {
input[type="range"]::-webkit-slider-runnable-track {
width: 100%;
height: var(--track-size);
cursor: pointer;
@ -53,7 +50,7 @@ export class Slider extends BaseElement {
border-radius: 100%;
}
input[type=range]::-webkit-slider-thumb {
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
height: var(--knob-size);
width: var(--knob-size);
@ -67,7 +64,7 @@ export class Slider extends BaseElement {
background-clip: content-box;
}
input[type=range]:active::-webkit-slider-thumb {
input[type="range"]:active::-webkit-slider-thumb {
transform: scale(1.1);
}
@ -82,21 +79,24 @@ export class Slider extends BaseElement {
.value-display {
margin-left: 10px;
}
`
];
</style>
render() {
return html`
<label>${this.label}</label>
<label>${this.label}</label>
<input
type="range"
.value=${this.value}
.min=${this.min}
.max=${this.max}
.step=${this.step}
@input=${() => this._inputChange()}
/>
<input
type="range"
.value=${this.value}
.min=${this.min}
.max=${this.max}
.step=${this.step}
@input=${() => this._inputChange()}>
<span class="value-display" ?hidden=${this.hideValue}>${this.value}${this.unit}</span>
`;
<span class="value-display" ?hidden=${this.hideValue}>${this.value}${this.unit}</span>
`;
}
private _inputChange() {

View File

@ -1,68 +1,75 @@
import { shared, mixins } from "../styles";
import { BaseElement, html, query } from "./base.js";
import { mixins, shared } from "../styles";
import { BaseElement, css, query } from "./base.js";
import { animateElement, animateCascade } from "../animation.js";
import { Logo } from "./logo.js";
import "./icon.js";
export const sharedStyles = html`
${shared}
<style>
@keyframes reveal {
from { transform: translate(0, 30px); opacity: 0; }
to { opacity: 1; }
const styles = css`
@keyframes reveal {
from {
transform: translate(0, 30px);
opacity: 0;
}
@keyframes fade {
to { transform: translate(0, -200px); opacity: 0; }
to {
opacity: 1;
}
}
:host, .wrapper {
display: flex;
flex-direction: column;
align-items: center;
${mixins.fullbleed()}
${mixins.scroll()}
@keyframes fade {
to {
transform: translate(0, -200px);
opacity: 0;
}
}
form {
width: 100%;
max-width: 400px;
}
:host,
.wrapper {
display: flex;
flex-direction: column;
align-items: center;
${mixins.fullbleed()}
${mixins.scroll()}
}
form > * {
border-radius: 8px;
margin: 10px;
}
form {
width: 100%;
max-width: 400px;
}
pl-logo {
margin: 20px auto;
}
form > * {
border-radius: 8px;
margin: 10px;
}
pl-loading-button {
overflow: hidden;
font-weight: bold;
}
pl-logo {
margin: 20px auto;
}
.hint {
font-size: var(--font-size-small);
box-sizing: border-box;
padding: 0 10px;
margin-bottom: 30px;
transition: color 0.2s;
}
pl-loading-button {
overflow: hidden;
font-weight: bold;
}
.hint.warning {
color: #ffc107;
font-weight: bold;
margin: 0;
padding: 0;
text-shadow: none;
}
</style>
.hint {
font-size: var(--font-size-small);
box-sizing: border-box;
padding: 0 10px;
margin-bottom: 30px;
transition: color 0.2s;
}
.hint.warning {
color: #ffc107;
font-weight: bold;
margin: 0;
padding: 0;
text-shadow: none;
}
`;
export abstract class StartForm extends BaseElement {
static styles = [shared, styles];
@query("pl-logo")
_logo: Logo;

View File

@ -1,6 +1,6 @@
import { app } from "../init.js";
import { shared, mixins } from "../styles";
import { BaseElement, element, html, property, listen, query } from "./base.js";
import { BaseElement, element, html, css, property, listen, query } from "./base.js";
import { Unlock } from "./unlock.js";
import { Login } from "./login.js";
import { Signup } from "./signup.js";
@ -59,55 +59,55 @@ export class Start extends BaseElement {
}
}
static styles = [
shared,
css`
:host {
--color-background: var(--color-primary);
--color-foreground: var(--color-tertiary);
--color-highlight: var(--color-secondary);
color: var(--color-foreground);
display: flex;
flex-direction: column;
z-index: 5;
text-align: center;
text-shadow: rgba(0, 0, 0, 0.15) 0 2px 0;
background: linear-gradient(180deg, #59c6ff 0%, #077cb9 100%);
transform: translate3d(0, 0, 0);
transition: transform 0.4s cubic-bezier(1, 0, 0.2, 1);
${mixins.fullbleed()}
${mixins.scroll()}
}
main {
${mixins.fullbleed()}
background: transparent;
min-height: 510px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.form:not(.showing) {
opacity: 0;
transition: opacity 1s;
pointer-events: none;
}
:host([open]) {
pointer-events: none;
}
:host([open]) {
transition-delay: 0.4s;
transform: translate3d(0, -100%, 0);
}
`
];
render() {
return html`
${shared}
<style>
:host {
--color-background: var(--color-primary);
--color-foreground: var(--color-tertiary);
--color-highlight: var(--color-secondary);
color: var(--color-foreground);
display: flex;
flex-direction: column;
z-index: 5;
text-align: center;
text-shadow: rgba(0, 0, 0, 0.15) 0 2px 0;
background: linear-gradient(180deg, #59c6ff 0%, #077cb9 100%);
transform: translate3d(0, 0, 0);
transition: transform 0.4s cubic-bezier(1, 0, 0.2, 1);
${mixins.fullbleed()}
${mixins.scroll()}
}
main {
${mixins.fullbleed()}
background: transparent;
min-height: 510px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.form:not(.showing) {
opacity: 0;
transition: opacity 1s;
pointer-events: none;
}
:host([open]) {
pointer-events: none;
}
:host([open]) {
transition-delay: 0.4s;
transform: translate3d(0, -100%, 0);
}
</style>
<pl-unlock class="form"></pl-unlock>
<pl-login class="form"></pl-login>

View File

@ -3,7 +3,7 @@ import { Vault } from "@padloc/core/lib/vault.js";
import { Tag } from "@padloc/core/lib/item.js";
import { app } from "../init.js";
import { shared } from "../styles";
import { BaseElement, element, html, property, query } from "./base.js";
import { BaseElement, element, html, css, property, query } from "./base.js";
import { Input } from "./input";
import "./icon.js";
@ -23,6 +23,82 @@ export class TagsInput extends BaseElement {
private _focusTimeout: number = 0;
static styles = [
shared,
css`
:host {
display: block;
position: relative;
z-index: 1;
overflow: visible;
font-size: var(--font-size-small);
overflow: visible;
}
.wrapper {
flex-wrap: wrap;
overflow: visible;
margin-top: -6px;
}
.wrapper > * {
margin-top: 6px;
}
.tags.small .tag {
padding: 5px 7px;
}
.results {
padding: 0;
border-radius: 8px;
margin-top: 0;
flex-direction: column;
align-items: flex-start;
}
.results .tag {
padding: 6px 8px;
margin-top: 6px;
}
.input-wrapper {
font-size: var(--font-size-micro);
padding: 0 4px;
height: 26px;
line-height: 26px;
background: #eee;
box-sizing: border-box;
border-radius: 8px;
align-self: stretch;
position: relative;
min-width: 80px;
overflow: hidden;
}
.input-wrapper pl-input {
font-size: inherit;
position: absolute;
height: 100%;
width: 100%;
box-sizing: border-box;
padding-left: 20px;
top: 0;
font-weight: bold;
pointer-events: none;
}
.add-tag {
height: 26px;
overflow: visible;
}
.add-tag .input-wrapper pl-icon {
height: 25px;
}
`
];
render() {
const { tags, editing, vault, _showResults } = this;
const { value } = this._input || { value: "" };
@ -34,81 +110,6 @@ export class TagsInput extends BaseElement {
}
return html`
${shared}
<style>
:host {
display: block;
position: relative;
z-index: 1;
overflow: visible;
font-size: var(--font-size-small);
overflow: visible;
}
.wrapper {
flex-wrap: wrap;
overflow: visible;
margin-top: -6px;
}
.wrapper > * {
margin-top: 6px;
}
.tags.small .tag {
padding: 5px 7px;
}
.results {
padding: 0;
border-radius: 8px;
margin-top: 0;
flex-direction: column;
align-items: flex-start;
}
.results .tag {
padding: 6px 8px;
margin-top: 6px;
}
.input-wrapper {
font-size: var(--font-size-micro);
padding: 0 4px;
height: 26px;
line-height: 26px;
background: #eee;
box-sizing: border-box;
border-radius: 8px;
align-self: stretch;
position: relative;
min-width: 80px;
overflow: hidden;
}
.input-wrapper pl-input {
font-size: inherit;
position: absolute;
height: 100%;
width: 100%;
box-sizing: border-box;
padding-left: 20px;
top: 0;
font-weight: bold;
pointer-events: none;
}
.add-tag {
height: 26px;
overflow: visible;
}
.add-tag .input-wrapper pl-icon {
height: 25px;
}
</style>
<div class="tags small wrapper">
<div class="tag highlight tap" @click=${() => this._vaultClicked()}>
<pl-icon icon="vault"></pl-icon>

View File

@ -1,5 +1,5 @@
import { shared, mixins } from "../styles";
import { BaseElement, element, html, property, query } from "./base.js";
import { BaseElement, element, html, css, property, query } from "./base.js";
import { Toggle } from "./toggle.js";
@element("pl-toggle-button")
@ -14,13 +14,9 @@ export class ToggleButton extends BaseElement {
@query("pl-toggle")
_toggle: Toggle;
render() {
const { active, label } = this;
return html`
${shared}
<style>
static styles = [
shared,
css`
:host {
display: inline-block;
font-size: inherit;
@ -59,8 +55,12 @@ export class ToggleButton extends BaseElement {
display: inline-block;
pointer-events: none;
}
</style>
`
];
render() {
const { active, label } = this;
return html`
<button @click=${() => this.toggle()}>
<pl-toggle .active=${active} @change=${() => (this.active = this._toggle.active)}"></pl-toggle>

View File

@ -1,4 +1,4 @@
import { BaseElement, element, html, property, listen } from "./base.js";
import { BaseElement, element, html, css, property, listen } from "./base.js";
import { shared } from "../styles";
@element("pl-toggle")
@ -7,49 +7,51 @@ export class Toggle extends BaseElement {
active: boolean = false;
@property() notap: boolean = false;
static styles = [
shared,
css`
:host {
--width: var(--toggle-width, 36px);
--height: var(--toggle-height, 24px);
--gutter-width: var(--toggle-gutter-width, 2px);
--color-off: var(--toggle-color-off, var(--color-foreground));
--color-on: var(--toggle-color-on, var(--color-highlight));
--color-knob: var(--toggle-color-knob, var(--color-background));
display: inline-block;
width: var(--width);
height: var(--height);
background: var(--color-off);
border-radius: var(--height);
transition: background 0.5s ease;
}
.knob {
--size: calc(var(--height) - 2 * var(--gutter-width));
display: block;
height: var(--size);
width: var(--size);
margin: var(--gutter-width);
background: var(--color-knob);
border-radius: var(--size);
transition: transform 0.5s cubic-bezier(1, -0.5, 0, 1.5) -0.2s, background 0.5s, opacity 0.5s;
}
:host([active]) {
background: var(--color-on);
}
:host([active]) .knob {
--dx: calc(var(--width) - var(--height));
transform: translate(var(--dx), 0);
}
`
];
render() {
return html`
${shared}
<style>
:host {
--width: var(--toggle-width, 36px);
--height: var(--toggle-height, 24px);
--gutter-width: var(--toggle-gutter-width, 2px);
--color-off: var(--toggle-color-off, var(--color-foreground));
--color-on: var(--toggle-color-on, var(--color-highlight));
--color-knob: var(--toggle-color-knob, var(--color-background));
display: inline-block;
width: var(--width);
height: var(--height);
background: var(--color-off);
border-radius: var(--height);
transition: background 0.5s ease;
}
.knob {
--size: calc(var(--height) - 2 * var(--gutter-width));
display: block;
height: var(--size);
width: var(--size);
margin: var(--gutter-width);
background: var(--color-knob);
border-radius: var(--size);
transition: transform 0.5s cubic-bezier(1, -0.5, 0, 1.5) -0.2s, background 0.5s, opacity 0.5s;
}
:host([active]) {
background: var(--color-on);
}
:host([active]) .knob {
--dx: calc(var(--width) - var(--height));
transform: translate(var(--dx), 0);
}
</style>
<div class="knob"></div>
`;
}

View File

@ -1,8 +1,8 @@
import { localize as $l } from "@padloc/core/lib/locale.js";
import { ErrorCode } from "@padloc/core/lib/error.js";
import { app, router } from "../init.js";
import { element, property, html, query } from "./base.js";
import { StartForm, sharedStyles } from "./start-form";
import { element, property, html, css, query } from "./base.js";
import { StartForm } from "./start-form";
import { PasswordInput } from "./password-input.js";
import { LoadingButton } from "./loading-button.js";
import { confirm } from "../dialog.js";
@ -29,23 +29,24 @@ export class Unlock extends StartForm {
setTimeout(() => this._passwordInput.focus(), 100);
}
static styles = [
...StartForm.styles,
css`
.current-account {
font-size: var(--font-size-tiny);
margin: 30px;
}
.logout {
text-decoration: underline;
cursor: pointer;
}
`
];
render() {
const email = app.account && app.account.email;
return html`
${sharedStyles}
<style>
.current-account {
font-size: var(--font-size-tiny);
margin: 30px;
}
.logout {
text-decoration: underline;
cursor: pointer;
}
</style>
<div flex></div>
<form>

View File

@ -4,7 +4,7 @@ import { localize as $l } from "@padloc/core/lib/locale.js";
import { mixins } from "../styles";
import { app } from "../init.js";
import { prompt } from "../dialog.js";
import { element, html, property, query } from "./base.js";
import { element, html, css, property, query } from "./base.js";
import { Dialog } from "./dialog.js";
import { LoadingButton } from "./loading-button.js";
import { Input } from "./input.js";
@ -232,6 +232,45 @@ export class VaultDialog extends Dialog<InputType, void> {
return !!this.org;
}
static styles = [
...Dialog.styles,
css`
.inner {
background: var(--color-quaternary);
}
.delete-button {
color: var(--color-negative);
font-size: var(--font-size-default);
}
.subheader {
margin: 8px;
font-weight: bold;
display: flex;
align-items: flex-end;
padding: 0 8px;
font-size: var(--font-size-small);
}
.subheader .permission {
width: 50px;
text-align: center;
font-size: var(--font-size-tiny);
${mixins.ellipsis()}
}
.item {
display: flex;
align-items: center;
}
.item pl-toggle {
margin-right: 14px;
}
`
];
renderContent() {
const org = this.org!;
const isAdmin = org.isAdmin(app.account!);
@ -248,42 +287,6 @@ export class VaultDialog extends Dialog<InputType, void> {
const groups = filter ? org.groups.filter(({ name }) => name.toLowerCase().includes(filter)) : org.groups;
return html`
<style>
.inner {
background: var(--color-quaternary);
}
.delete-button {
color: var(--color-negative);
font-size: var(--font-size-default);
}
.subheader {
margin: 8px;
font-weight: bold;
display: flex;
align-items: flex-end;
padding: 0 8px;
font-size: var(--font-size-small);
}
.subheader .permission {
width: 50px;
text-align: center;
font-size: var(--font-size-tiny);
${mixins.ellipsis()}
}
.item {
display: flex;
align-items: center;
}
.item pl-toggle {
margin-right: 14px;
}
</style>
<header>
<pl-icon icon="vault"></pl-icon>
<pl-input

View File

@ -1,6 +1,6 @@
import { VaultID } from "@padloc/core/lib/vault.js";
import { shared } from "../styles";
import { BaseElement, element, html, property } from "./base.js";
import { BaseElement, element, html, css, property } from "./base.js";
import "./icon.js";
@element("pl-vault-item")
@ -8,41 +8,42 @@ export class VaultItem extends BaseElement {
@property()
vault: { id: VaultID; name: string };
static styles = [
shared,
css`
:host {
display: flex;
align-items: center;
padding: 4px 0;
}
.icon {
font-size: 120%;
margin: 8px;
background: #eee;
border: solid 1px #ddd;
width: 45px;
height: 45px;
}
.tags {
margin: 4px 0;
}
.vault-name {
font-weight: bold;
margin-bottom: 4px;
}
.vault-info {
flex: 1;
width: 0;
}
`
];
render() {
return html`
${shared}
<style>
:host {
display: flex;
align-items: center;
padding: 4px 0;
}
.icon {
font-size: 120%;
margin: 8px;
background: #eee;
border: solid 1px #ddd;
width: 45px;
height: 45px;
}
.tags {
margin: 4px 0;
}
.vault-name {
font-weight: bold;
margin-bottom: 4px;
}
.vault-info {
flex: 1;
width: 0;
}
</style>
<pl-icon class="icon" icon="vault"></pl-icon>
<div class="vault-info">

View File

@ -1,452 +0,0 @@
import { Vault } from "@padloc/core/lib/vault.js";
import { VaultItem, Field } from "@padloc/core/lib/item.js";
import { localize as $l } from "@padloc/core/lib/locale.js";
import { repeat } from "lit-html/directives/repeat.js";
import { cache } from "lit-html/directives/cache.js";
import { setClipboard } from "../clipboard.js";
import { app, router } from "../init.js";
import { shared, mixins } from "../styles";
import { BaseElement, element, html, property, listen } from "./base.js";
// import { CreateItemDialog } from "./create-item-dialog.js";
// import { MoveItemsDialog } from "./move-items-dialog.js";
import "./icon.js";
import "./browse-filter.js";
@element("pl-vault")
export class Vaults extends BaseElement {
@property()
vault: Vault;
@property()
selected: string = "";
@property()
multiSelect: boolean = false;
//
// @dialog("pl-create-item-dialog")
// private _createItemDialog: CreateItemDialog;
//
// @dialog("pl-move-items-dialog")
// private _moveItemsDialog: MoveItemsDialog;
@property()
private _items: VaultItem[] = [];
private _multiSelect = new Map<string, VaultItem>();
private _sections = new Map<number, string>();
@listen("items-added", app)
@listen("items-deleted", app)
@listen("item-changed", app)
@listen("items-moved", app)
@listen("settings-changed", app)
@listen("vault-changed", app)
@listen("filter-changed", app)
@listen("unlock", app)
@listen("lock", app)
@listen("synchronize", app)
_updateItems() {
let items = [...this.vault.items];
this._sections.clear();
const recentCount = 0;
const recent = items
.sort((a, b) => {
return (b.lastUsed || b.updated).getTime() - (a.lastUsed || a.updated).getTime();
})
.slice(0, recentCount);
items = items.slice(recentCount);
items = recent.concat(
items.sort((a, b) => {
const x = a.name.toLowerCase();
const y = b.name.toLowerCase();
return x > y ? 1 : x < y ? -1 : 0;
})
);
for (let i = 0, item, prevSection; i < items.length; i++) {
item = items[i];
const section =
i < recentCount
? $l("Recently Used")
: (item && item.name[0] && item.name[0].toUpperCase()) || $l("No Name");
const firstInSection = section !== prevSection;
if (firstInSection) {
this._sections.set(i, section);
}
prevSection = section;
}
this._items = items;
}
selectItem(item: VaultItem) {
if (this.multiSelect) {
if (this._multiSelect.has(item.id)) {
this._multiSelect.delete(item.id);
} else {
this._multiSelect.set(item.id, item);
}
this.requestUpdate();
} else {
router.go(`item/${item.id}`);
}
}
selectAll() {
this.multiSelect = true;
for (const item of this.vault.items) {
this._multiSelect.set(item.id, item);
}
this.requestUpdate();
}
clearSelection() {
this._multiSelect.clear();
this.requestUpdate();
}
cancelMultiSelect() {
this._multiSelect.clear();
this.multiSelect = false;
this.requestUpdate();
}
//
// private async _newItem() {
// await this._createItemDialog.show();
// }
//
// private async _deleteItems() {
// const confirmed = await confirm(
// $l("Are you sure you want to delete these items? This action can not be undone!"),
// $l("Delete {0} Items", this._multiSelect.size.toString()),
// $l("Cancel"),
// { type: "warning" }
// );
// if (confirmed) {
// await app.deleteItems([...this._multiSelect.values()]);
// this.cancelMultiSelect();
// }
// }
//
// private async _moveItems() {
// const movedItems = await this._moveItemsDialog.show([...this._multiSelect.values()]);
// if (movedItems) {
// this.cancelMultiSelect();
// }
// }
private _copyField(item: VaultItem, index: number, e: Event) {
e.stopPropagation();
setClipboard(item, item.fields[index]);
const fieldEl = e.target as HTMLElement;
fieldEl.classList.add("copied");
setTimeout(() => fieldEl.classList.remove("copied"), 1000);
}
private _renderItem(index: number) {
const item = this._items[index];
const section = this._sections.get(index);
const tags = [];
// if (item.warning) {
// tags.push({ icon: "error", class: "tag warning", name: "" });
// }
const t = item.tags[0];
if (t) {
tags.push({
name: item.tags.length > 1 ? `${t} (+${item.tags.length - 1})` : t,
icon: "",
class: ""
});
}
const attCount = (item.attachments && item.attachments.length) || 0;
if (attCount) {
tags.push({
name: "",
icon: "attachment",
class: ""
});
}
return html`
${cache(
section
? html`
<div class="section-header">
<div>${section}</div>
<div class="spacer"></div>
<div>${section}</div>
</div>
`
: html``
)}
<div
class="item tap"
?selected=${item.id === this.selected}
@click=${() => this.selectItem(item)}
index="${index}"
>
<div class="item-check" ?hidden=${!this.multiSelect} ?checked=${this._multiSelect.has(item.id)}></div>
<div class="item-body">
<div class="item-header">
<div class="item-name" ?disabled=${!item.name}>
${item.name || $l("No Name")}
</div>
<div class="tags small">
${tags.map(tag =>
tag.icon
? html`
<div class="tag ${tag.class}">
<pl-icon icon="${tag.icon}"></pl-icon>
</div>
`
: html`
<div class="ellipsis tag ${tag.class}">${tag.name}</div>
`
)}
</div>
</div>
<div class="item-fields">
${item.fields.map(
(f: Field, i: number) => html`
<div class="item-field tap" @click=${(e: MouseEvent) => this._copyField(item, i, e)}>
<div class="item-field-label">${f.name}</div>
<div class="copied-message">${$l("copied")}</div>
</div>
`
)}
<div class="item-field" disabled ?hidden=${!!item.fields.length}>
${$l("No Fields")}
</div>
</div>
</div>
</div>
`;
}
shouldUpdate() {
return !!this.vault;
}
render() {
return html`
${shared}
<style>
:host {
display: flex;
flex-direction: column;
box-sizing: border-box;
position: relative;
}
h1 {
color: var(--color-tertiary);
text-shadow: rgba(0, 0, 0, 0.2) 0 2px 0;
font-weight: 400;
}
main {
padding-bottom: 70px;
background: var(--color-quaternary);
border-radius: var(--border-radius);
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
grid-gap: 6px;
}
.section-header {
background: inherit;
position: sticky;
top: 0;
z-index: 1;
display: flex;
height: 30px;
line-height: 30px;
padding: 0 15px;
font-size: var(--font-size-tiny);
font-weight: bold;
box-sizing: border-box;
border-radius: var(--border-radius);
margin-bottom: -6px;
margin-top: 8px;
font-weight: 800;
}
.item {
box-sizing: border-box;
display: flex;
align-items: center;
margin: 0;
/* background: var(--color-tertiary); */
/* margin: 10px; */
/* box-shadow: rgba(0, 0, 0, 0.3) 0px 1px 3px; */
/* border-radius: var(--border-radius); */
}
.item-body {
flex: 1;
min-width: 0;
}
.item .tags {
padding: 0 8px;
}
.item-header {
height: var(--row-height);
line-height: var(--row-height);
position: relative;
display: flex;
align-items: center;
}
.item-name {
padding-left: 15px;
${mixins.ellipsis()}
font-weight: bold;
flex: 1;
min-width: 0;
}
.item-fields {
position: relative;
display: flex;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
.item-fields::after {
content: "";
display: block;
width: 6px;
flex: none;
}
.item-field {
cursor: pointer;
font-size: var(--font-size-tiny);
line-height: 32px;
height: 32px;
text-align: center;
position: relative;
flex: 1;
font-weight: bold;
margin: 0 0 8px 8px;
border-radius: 8px;
${mixins.shade2()}
}
.item-field > * {
transition: transform 0.2s cubic-bezier(1, -0.3, 0, 1.3), opacity 0.2s;
}
.copied-message {
${mixins.fullbleed()}
border-radius: inherit;
}
.item-field:not(.copied) .copied-message, .item-field.copied .item-field-label {
opacity: 0;
transform: scale(0);
}
.copied-message {
font-weight: bold;
background: var(--color-primary);
color: var(--color-background);
}
.copied-message::before {
font-family: "FontAwesome";
content: "\\f00c\\ ";
}
.item-field-label {
padding: 0 15px;
pointer-events: none;
${mixins.ellipsis()}
}
.item:focus:not([selected]) {
border-color: var(--color-highlight);
color: #4ca8d9;
}
.item[selected] {
background: #e6e6e6;
border-color: #ddd;
}
.item-check {
position: relative;
width: 30px;
height: 30px;
box-sizing: border-box;
border: solid 3px #eee;
background: #eee;
border-radius: 30px;
margin: 10px;
margin-right: 5px;
}
.item-check::after {
content: "";
display: block;
${mixins.fullbleed()}
background: var(--color-primary);
border-radius: inherit;
transition: transform 0.2s, opacity 0.2s;
transition-timing-function: cubic-bezier(1, -0.3, 0, 1.3);
}
.item-check:not([checked])::after {
opacity: 0;
transform: scale(0);
}
.selected-count {
text-align: center;
display: block;
margin-left: 15px;
background: #ddd;
border-radius: var(--border-radius);
padding: 5px;
line-height: 1.2em;
font-size: var(--font-size-tiny);
font-weight: bold;
}
</style>
<h1>${this.vault.name}</h1>
<main id="main">
${repeat(this._items, item => item.id, (_: any, index: number) => this._renderItem(index))}
</main>
<div class="empty-placeholder" ?hidden=${!!this.vault.items.size || app.filter.text}>
<pl-icon icon="list"></pl-icon>
<div>${$l("This vault doesn't have any items yet!")}</div>
</div>
`;
}
}

View File

@ -1,66 +1,67 @@
import { html } from "@polymer/lit-element";
import { css } from "lit-element";
export const narrowWidth = 700;
export const wideWidth = 1200;
export const cssVars = html`
<style>
:host {
--font-family: "Nunito";
--font-family-fallback: sans-serif;
--font-family-mono: "Inconsolata";
export const cssVars = css`
:host {
--font-family: "Nunito";
--font-family-fallback: sans-serif;
--font-family-mono: "Inconsolata";
--font-size-micro: 12px;
--font-size-tiny: 14px;
--font-size-small: 16px;
--font-size-default: 18px;
--font-weight-thin: 100;
--font-weight-light: 300;
--font-weight-regular: 400;
--font-weight-bold: 700;
--font-size-micro: 12px;
--font-size-tiny: 14px;
--font-size-small: 16px;
--font-size-default: 18px;
--font-weight-thin: 100;
--font-weight-light: 300;
--font-weight-regular: 400;
--font-weight-bold: 700;
--color-primary: #3bb7f9;
--color-secondary: #444;
--color-tertiary: #ffffff;
--color-quaternary: #fafafa;
/* --color-negative: #D7322D; */
--color-negative: #ff6666;
--color-primary: #3bb7f9;
--color-secondary: #444;
--color-tertiary: #ffffff;
--color-quaternary: #fafafa;
/* --color-negative: #D7322D; */
--color-negative: #ff6666;
--color-background: var(--color-tertiary);
--color-foreground: var(--color-secondary);
--color-highlight: var(--color-primary);
--color-error: var(--color-negative);
--color-background: var(--color-tertiary);
--color-foreground: var(--color-secondary);
--color-highlight: var(--color-primary);
--color-error: var(--color-negative);
--color-shade-1: rgba(0, 0, 0, 0.05);
--color-shade-2: rgba(0, 0, 0, 0.1);
--color-shade-3: rgba(0, 0, 0, 0.15);
--color-shade-4: rgba(0, 0, 0, 0.2);
--color-shade-1: rgba(0, 0, 0, 0.05);
--color-shade-2: rgba(0, 0, 0, 0.1);
--color-shade-3: rgba(0, 0, 0, 0.15);
--color-shade-4: rgba(0, 0, 0, 0.2);
--color-gradient-highlight-from: rgb(7, 124, 185);
--color-gradient-highlight-to: rgb(89, 198, 255);
--color-gradient-warning-from: #f25b00;
--color-gradient-warning-to: #f49300;
--color-gradient-dark-from: #222;
--color-gradient-dark-to: #555;
--color-gradient-highlight-from: rgb(7, 124, 185);
--color-gradient-highlight-to: rgb(89, 198, 255);
--color-gradient-warning-from: #f25b00;
--color-gradient-warning-to: #f49300;
--color-gradient-dark-from: #222;
--color-gradient-dark-to: #555;
--color-scrim: rgba(255, 255, 255, 0.9);
--color-scrim: rgba(255, 255, 255, 0.9);
--color-btn-front: var(--color-foreground);
--color-btn-back: var(--shade-3-color);
--color-btn-front: var(--color-foreground);
--color-btn-back: var(--shade-3-color);
--row-height: 50px;
--row-height: 50px;
--gutter-size: 8px;
--border-radius: 8px;
--border-color: rgba(0, 0, 0, 0.1);
--gutter-size: 8px;
--border-radius: 8px;
--border-color: rgba(0, 0, 0, 0.1);
--toaster-easing: cubic-bezier(1, -0.3, 0, 1.3);
--toaster-easing: cubic-bezier(1, -0.3, 0, 1.3);
--shade-1-color: transparent;
--shade-2-color: var(--color-shade-1);
--shade-3-color: var(--color-shade-2);
--shade-4-color: var(--color-shade-3);
--shade-5-color: var(--color-shade-4);
}
</style>
--shade-1-color: transparent;
--shade-2-color: var(--color-shade-1);
--shade-3-color: var(--color-shade-2);
--shade-4-color: var(--color-shade-3);
--shade-5-color: var(--color-shade-4);
--narrow-width: 700px;
--wide-width: 1200px;
}
`;

View File

@ -1,7 +1,6 @@
import * as mixins from "./mixins";
import * as config from "./config";
import { listLayout } from "./list-layout";
import { shared } from "./shared";
export { mixins, config, listLayout, shared };
export { mixins, config, shared };

View File

@ -1,63 +0,0 @@
import { html } from "@polymer/lit-element";
import * as mixins from "./mixins";
import * as config from "./config";
export const listLayout = html`
<style>
.list-layout {
${mixins.fullbleed()}
perspective: 1000px;
}
.list-layout > * {
border-radius: var(--border-radius);
overflow: hidden;
will-change: transform;
}
@media (min-width: ${config.narrowWidth}px) {
.list-layout {
display: flex;
}
.list-layout > :first-child {
width: 350px;
margin-right: var(--gutter-size);
}
.list-layout > :last-child {
flex: 1;
}
.list-layout:not([show-detail]) > :last-child {
display: none;
}
}
@media (max-width: ${config.narrowWidth}px) {
.list-layout > :first-child {
margin: 0;
${mixins.fullbleed()}
}
.list-layout > :last-child {
${mixins.fullbleed()}
z-index: 10;
}
.list-layout > :first-child,
.list-layout > :last-child {
transition: transform 0.3s cubic-bezier(0.6, 0, 0.2, 1);
}
.list-layout[show-detail] > :first-child {
transform: translate3d(0, 0, -50px);
}
.list-layout:not([show-detail]) > :last-child {
transform: translate(100%, 0);
}
}
`;

View File

@ -1,9 +1,11 @@
export const unselectable = () => `
import { css } from "lit-element";
export const unselectable = () => css`
cursor: default;
user-select: none;
`;
export const positionSticky = () => `
export const positionSticky = () => css`
position: -webkit-sticky;
position: -moz-sticky;
position: -o-sticky;
@ -11,37 +13,7 @@ export const positionSticky = () => `
position: sticky;
`;
export const tapHighlight = () => `
position: relative;
cursor: pointer;
`;
export const tapHighlightAfter = () => `
content: "";
display: block;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: currentColor;
opacity: 0;
transition: opacity 1s;
pointer-events: none;
border-radius: inherit;
`;
export const tapHighlightActiveAfter = () => `
opacity: 0.3;
transition: none;
`;
export const tapHighlightHoverAfter = () => `
opacity: 0.1;
transition: none;
`;
export const fullbleed = () => `
export const fullbleed = () => css`
position: absolute;
top: 0;
left: 0;
@ -50,43 +22,43 @@ export const fullbleed = () => `
overflow: hidden;
`;
export const scroll = (direction?: "vertical" | "horizontal") => `
${direction === "vertical" ? "overflow-y" : direction === "horizontal" ? "overflow-x" : "overflow"}: auto;
export const scroll = (direction?: "vertical" | "horizontal") => css`
${direction === "vertical" ? css`overflow-y` : direction === "horizontal" ? css`overflow-x` : css`overflow`}: auto;
-webkit-overflow-scrolling: touch;
`;
export const ellipsis = () => `
export const ellipsis = () => css`
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
`;
export const absoluteCenter = () => `
export const absoluteCenter = () => css`
${fullbleed()};
margin: auto;
`;
export const shade1 = () => `
export const shade1 = () => css`
background: var(--shade-1-color);
`;
export const shade2 = () => `
export const shade2 = () => css`
background: var(--shade-2-color);
`;
export const shade3 = () => `
export const shade3 = () => css`
background: var(--shade-3-color);
`;
export const shade4 = () => `
export const shade4 = () => css`
background: var(--shade-4-color);
`;
export const shade5 = () => `
export const shade5 = () => css`
background: var(--shade-5-color);
`;
export const card = () => `
export const card = () => css`
background: var(--color-background);
/* box-shadow: rgba(0, 0, 0, 0.2) 0 0 1px; */
border-radius: var(--border-radius);
@ -95,24 +67,30 @@ export const card = () => `
overflow: hidden;
`;
export const gradientHighlight = (horizontal = false) => `
background: linear-gradient(${
horizontal ? "90deg" : "0"
}, var(--color-gradient-highlight-from) 0%, var(--color-gradient-highlight-to) 100%);
export const gradientHighlight = (horizontal = false) => css`
background: linear-gradient(
${horizontal ? css`90deg` : css`0`},
var(--color-gradient-highlight-from) 0%,
var(--color-gradient-highlight-to) 100%
);
`;
export const gradientWarning = (horizontal = false) => `
background: linear-gradient(${
horizontal ? "90deg" : "0"
}, var(--color-gradient-warning-from) 0%, var(--color-gradient-warning-to) 100%);
export const gradientWarning = (horizontal = false) => css`
background: linear-gradient(
${horizontal ? css`90deg` : css`0`},
var(--color-gradient-warning-from) 0%,
var(--color-gradient-warning-to) 100%
);
`;
export const gradientDark = (horizontal = false) => `
background: linear-gradient(${
horizontal ? "90deg" : "0"
}, var(--color-gradient-dark-from) 0%, var(--color-gradient-dark-to) 100%);
export const gradientDark = (horizontal = false) => css`
background: linear-gradient(
${horizontal ? css`90deg` : css`0`},
var(--color-gradient-dark-from) 0%,
var(--color-gradient-dark-to) 100%
);
`;
export const textShadow = () => `
export const textShadow = () => css`
text-shadow: rgba(0, 0, 0, 0.2) 0px 2px 0px;
`;

View File

@ -1,9 +1,7 @@
import { html } from "@polymer/lit-element";
import { css } from "lit-element";
import * as mixins from "./mixins";
import * as config from "./config";
export const shared = html`
<style>
export const shared = css`
:host {
user-select: none;
-webkit-user-select: none;
@ -280,19 +278,46 @@ export const shared = html`
}
.tap {
${mixins.tapHighlight()}
position: relative;
cursor: pointer;
}
.tap::after {
${mixins.tapHighlightAfter()}
content: "";
display: block;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: currentColor;
opacity: 0;
transition: opacity 1s;
pointer-events: none;
border-radius: inherit;
}
.tap:active::after {
${mixins.tapHighlightActiveAfter()}
opacity: 0.3;
transition: none;
}
.tap:not(:active):hover::after {
${mixins.tapHighlightHoverAfter()}
.tap::before {
content: "";
display: block;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: currentColor;
opacity: 0;
pointer-events: none;
border-radius: inherit;
}
.tap:not(:active):hover::before {
opacity: 0.1;
}
.tiles > :nth-child(8n + 1), .tiles-1 {
@ -550,29 +575,19 @@ export const shared = html`
align-items: center;
justify-content: center;
padding: 10px 15px;
border-bottom: solid 3px var(--color-shade-1);
}
.tabs > * > pl-icon {
margin-left: -12px;
margin-left: -10px;
font-size: 90%;
width: 35px;
height: 35px;
}
.tabs > *[active] {
color: var(--color-highlight);
}
.tabs > *[active]::before {
content: "";
display: block;
width: 100%;
height: 3px;
background: currentColor;
position: absolute;
left: 0;
bottom: 0;
}
header > .tabs {
margin: -10px -10px -12px -10px;
border-color: var(--color-highlight);
}
.search-wrapper {
@ -622,7 +637,7 @@ export const shared = html`
75% {transform: translate(5px, 2px);}
}
@media (min-width: ${config.narrowWidth + 1}px) {
@media (min-width: 701px) {
.menu-button {
visibility: hidden;
}
@ -632,10 +647,9 @@ export const shared = html`
}
}
@media (max-width: ${config.narrowWidth}px) {
@media (max-width: 700px) {
.wide {
display: none;
}
}
</style>
`;