Compare commits

...

47 Commits

Author SHA1 Message Date
Alice Gaudon af72cb6799 Version 1.1.0 3 months ago
Alice Gaudon 47f0a45cb1 install dependencies 3 months ago
Alice Gaudon 0a0a8a8952 Merge remote-tracking branch 'boilerplate/develop' into develop 3 months ago
Alice Gaudon a5b7cf9268 upgrade dependencies 3 months ago
Alice Gaudon 3062960a4b fix(front/file-uploader): properly disable forms when uploading files to avoid interrupting a file upload 3 months ago
Alice Gaudon f54438e9dd chore(front/NavMenu): move Auth Tokens link to new Account dropdown from swaf 3 months ago
Alice Gaudon 848c952073 fix(back/AuthTokenController): fix auth tokens pagination size 3 months ago
Alice Gaudon e3f7b5e7c7 feat(front/auth-token): make token revoke action button mode to gain space 3 months ago
Alice Gaudon 8ddcd50c35 feat(front/auth-token): use better icon for new token generation button 3 months ago
Alice Gaudon 8d13bd0c8d chore(back/AuthToken): update to new AuthProof use method from swaf 3 months ago
Alice Gaudon c54a201e98 chore(front): convert auth-tokens to svelte 3 months ago
Alice Gaudon 3abd692ce3 feat(front): improve about page, remove redundant link from nav menu 3 months ago
Alice Gaudon eaac5f3ace chore(front): remove old asset files 3 months ago
Alice Gaudon 5cb13c221d feat(front/url-shrinker): update url-shrinker icon to shrink 3 months ago
Alice Gaudon 7c65f1b6ae chore(front/url-shrinker): convert url-shrinker to svelte 3 months ago
Alice Gaudon c025aa2539 chore(front/file-uploader): remove unnecessary css, container, update col-grow class names 3 months ago
Alice Gaudon 1b5e088d74 chore(front/global css): remove old scss files and add new and updated _vars.scss 3 months ago
Alice Gaudon 53c7423115 fix(front/file-uploader): remove unnecessary data-table-container usage 3 months ago
Alice Gaudon c2182dbbca chore(back/scripts): move linux scripts to new assets folder 3 months ago
Alice Gaudon 27f760d503 feat(front/file-uploader): make copy button in file list work again 3 months ago
Alice Gaudon a8f82564a5 feat(front/file-uploader): display human readable time in file list 3 months ago
Alice Gaudon 9807158bb9 feat(front/file-uploader): improve file list table density and looks 3 months ago
Alice Gaudon 11fccbfc0d fix sass->scss asset dir name 3 months ago
Alice Gaudon 15441a0941 feat(front/FileUpload): display errors and allow retrying or canceling the upload 3 months ago
Alice Gaudon 7ad0eac6db feat(front/file-uploader): reduce page height changes/unnecessary visual changes, improve ux 3 months ago
Alice Gaudon c906827fe1 fix(front/file-uploader): do not add failed uploads to finished uploads list 3 months ago
Alice Gaudon 5477c858ef fix(front/file-uploader): prevent starting upload if another upload is running 3 months ago
Alice Gaudon 79fb4e28c8 chore(front/file-uploader): convert file-uploader view to svelte 3 months ago
Alice Gaudon e94b98aee0 Merge remote-tracking branch 'origin/develop' into develop 3 months ago
Alice Gaudon 47156c9a77 Remove feather icons in favor of lucide icons (included with swaf) 3 months ago
Alice Gaudon 79d23a4604 swaf upgrade: convert views to svelte 3 months ago
Alice Gaudon 5571737d93 swaf upgrade: fix eslint 3 months ago
Alice Gaudon 377c073cd2 swaf upgrade: move views to new assets folder 3 months ago
Alice Gaudon b1ba6b9106 swaf upgrade: make code compile 3 months ago
Alice Gaudon 69b234d95c upgrade dependencies 3 months ago
Alice Gaudon 17c7674c3e Merge remote-tracking branch 'boilerplate/develop' into develop 3 months ago
Alice Gaudon 5f5275ceb1 upgrade dependencies 5 months ago
Alice Gaudon eb0364a2f7 Fix eslint and fix linting issues 6 months ago
Alice Gaudon f2f34dd00f Update eslint config with swaf 6 months ago
Alice Gaudon 8c1230d501 Remove reference to deleted tsconfig.frontend.json from eslint config 6 months ago
Alice Gaudon 6002ad5290 Remove webpack and unused dependencies 6 months ago
Alice Gaudon 257a407faf Remove unused old assets 6 months ago
Alice Gaudon 569a50e43a Upgrade swaf and dependencies 6 months ago
Alice Gaudon 938e8b4ebb Add imagemin-webp 12 months ago
Alice Gaudon 7e260ebde2 Upgrade dependencies, replace img-loader with image-minimizer-webpack-plugin 12 months ago
Alice Gaudon efbc895ba3 Bump @types/node 1 year ago
Alice Gaudon 2cb5b6f8f9 Replace node-sass with sass 1 year ago
  1. 135
      .eslintrc.cjs
  2. 115
      .eslintrc.json
  3. 3
      .gitignore
  4. 12
      assets/config.json
  5. 81
      assets/sass/_fonts.scss
  6. 33
      assets/sass/_vars.scss
  7. 18
      assets/sass/app.scss
  8. 90
      assets/sass/error.scss
  9. 11
      assets/sass/fm.scss
  10. 977
      assets/sass/layout.scss
  11. 19
      assets/sass/responsivity_tools.scss
  12. 39
      assets/ts/PersistentWebSocket.ts
  13. 10
      assets/ts/app.ts
  14. 23
      assets/ts/copyable_text.ts
  15. 11
      assets/ts/external_links.ts
  16. 193
      assets/ts/fm.ts
  17. 4
      assets/ts/font-awesome.ts
  18. 43
      assets/ts/forms.ts
  19. 21
      assets/ts/main_menu.ts
  20. 26
      assets/ts/message_icons.ts
  21. 31
      assets/ts/tooltips-and-dropdowns.ts
  22. 24
      assets/ts/url-shrinker.ts
  23. 72
      package.json
  24. 22
      scripts/_functions.js
  25. 2
      scripts/clean.js
  26. 22
      scripts/dist.js
  27. 26
      scripts/prepare-sources.js
  28. 107
      src/App.ts
  29. 15
      src/DeleteOldFilesJobComponent.ts
  30. 5
      src/SlugGenerator.ts
  31. 0
      src/assets/files/shrink_url.sh
  32. 0
      src/assets/files/upload_file.sh
  33. 0
      src/assets/img/logo.svg
  34. 0
      src/assets/img/logox1024.png
  35. 0
      src/assets/img/logox128.png
  36. 3
      src/assets/package.json
  37. 0
      src/assets/scss/.gitkeep
  38. 117
      src/assets/scss/_vars.scss
  39. 6
      src/assets/ts/tsconfig.eslint.json
  40. 27
      src/assets/ts/tsconfig.json
  41. 25
      src/assets/views/about.svelte
  42. 84
      src/assets/views/auth-tokens.svelte
  43. 223
      src/assets/views/components/FileUpload.svelte
  44. 217
      src/assets/views/file-uploader.svelte
  45. 7
      src/assets/views/templates/base/BaseNavMenuAuthAccountDropdownAdditionalLinks.svelte
  46. 10
      src/assets/views/templates/base/BaseNavMenuLinks.svelte
  47. 15
      src/assets/views/tsconfig.json
  48. 83
      src/assets/views/url-shrinker.svelte
  49. 1
      src/common/dummy.ts
  50. 3
      src/common/package.json
  51. 20
      src/common/tsconfig.json
  52. 2
      src/controllers/AboutController.ts
  53. 24
      src/controllers/AuthTokenController.ts
  54. 62
      src/controllers/FileController.ts
  55. 5
      src/controllers/HomeController.ts
  56. 31
      src/controllers/LinkController.ts
  57. 25
      src/controllers/URLRedirectController.ts
  58. 14
      src/main.ts
  59. 3
      src/migrations/CreateAuthTokensTable.ts
  60. 3
      src/migrations/CreateFilesTable.ts
  61. 3
      src/migrations/CreateUrlRedirectsTable.ts
  62. 3
      src/migrations/ReplaceTtlWithExpiresAtFilesTable.ts
  63. 23
      src/models/AuthToken.ts
  64. 14
      src/models/FileModel.ts
  65. 20
      src/models/URLRedirect.ts
  66. 30
      src/tsconfig.json
  67. 0
      src/types/.gitkeep
  68. 19
      tsconfig.frontend.json
  69. 44
      tsconfig.json
  70. 2
      tsconfig.test.json
  71. 14
      views/about.njk
  72. 104
      views/file-uploader.njk
  73. 63
      views/layouts/base.njk
  74. 75
      views/url-shrinker.njk
  75. 100
      webpack.config.js
  76. 7656
      yarn.lock

135
.eslintrc.cjs

@ -0,0 +1,135 @@
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
plugins: [
'svelte3',
'@typescript-eslint',
'import',
'simple-import-sort',
],
parserOptions: {
tsconfigRootDir: __dirname,
project: [
'./tsconfig.test.json',
'./src/tsconfig.json',
'./src/common/tsconfig.json',
'./src/assets/ts/tsconfig.eslint.json',
'./src/assets/views/tsconfig.json',
]
},
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
],
rules: {
indent: [
'error',
4,
{
SwitchCase: 1
}
],
'no-trailing-spaces': 'error',
'max-len': [
'error',
{
code: 120,
ignoreTemplateLiterals: true,
ignoreRegExpLiterals: true
}
],
semi: 'off',
'@typescript-eslint/semi': [
'error'
],
'no-extra-semi': 'error',
'eol-last': 'error',
'comma-dangle': 'off',
'simple-import-sort/imports': 'error',
'no-extra-parens': 'off',
'no-nested-ternary': 'error',
'no-return-await': 'off',
'no-useless-return': 'error',
'no-useless-constructor': 'off',
'import/extensions': ['error', 'ignorePackages'],
'@typescript-eslint/comma-dangle': [
'error',
{
arrays: 'always-multiline',
objects: 'always-multiline',
imports: 'always-multiline',
exports: 'always-multiline',
functions: 'always-multiline',
enums: 'always-multiline',
generics: 'always-multiline',
tuples: 'always-multiline'
}
],
'@typescript-eslint/no-extra-parens': [
'error'
],
'@typescript-eslint/no-inferrable-types': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'error',
'@typescript-eslint/no-unnecessary-condition': 'error',
'@typescript-eslint/no-unused-vars': [
'error',
{
argsIgnorePattern: '^_'
}
],
'@typescript-eslint/no-non-null-assertion': 'error',
'@typescript-eslint/no-useless-constructor': [
'error'
],
'@typescript-eslint/return-await': [
'error',
'always'
],
'@typescript-eslint/explicit-member-accessibility': [
'error',
{
accessibility: 'explicit'
}
],
'@typescript-eslint/no-floating-promises': 'error',
},
ignorePatterns: [
'.eslintrc.js',
'rollup.config.js',
'jest.config.js',
'dist/**/*',
'config/**/*',
'intermediates/**/*',
'public/**/*',
'scripts/**/*',
'src/frontend/register_svelte/register_svelte.js',
],
overrides: [
{
files: [
'test/**/*'
],
rules: {
'max-len': [
'error',
{
code: 120,
ignoreTemplateLiterals: true,
ignoreRegExpLiterals: true,
ignoreStrings: true
}
]
}
},
{
files: ['*.svelte'],
processor: 'svelte3/svelte3'
}
],
settings: {
'svelte3/typescript': require('typescript'),
'svelte3/ignore-styles': function (attributes) {
return !!(attributes['lang'] && attributes['lang'] !== 'css');
}
},
}

115
.eslintrc.json

@ -1,115 +0,0 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"plugins": [
"@typescript-eslint"
],
"parserOptions": {
"project": [
"./tsconfig.json",
"./tsconfig.test.json",
"./tsconfig.frontend.json"
]
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended"
],
"rules": {
"indent": [
"error",
4,
{
"SwitchCase": 1
}
],
"no-trailing-spaces": "error",
"max-len": [
"error",
{
"code": 120,
"ignoreStrings": true,
"ignoreTemplateLiterals": true,
"ignoreRegExpLiterals": true
}
],
"semi": "off",
"@typescript-eslint/semi": [
"error"
],
"no-extra-semi": "error",
"eol-last": "error",
"comma-dangle": "off",
"@typescript-eslint/comma-dangle": [
"error",
{
"arrays": "always-multiline",
"objects": "always-multiline",
"imports": "always-multiline",
"exports": "always-multiline",
"functions": "always-multiline",
"enums": "always-multiline",
"generics": "always-multiline",
"tuples": "always-multiline"
}
],
"no-extra-parens": "off",
"@typescript-eslint/no-extra-parens": [
"error"
],
"no-nested-ternary": "error",
"@typescript-eslint/no-inferrable-types": "off",
"@typescript-eslint/explicit-module-boundary-types": "error",
"@typescript-eslint/no-unnecessary-condition": "error",
"@typescript-eslint/no-unused-vars": [
"error",
{
"argsIgnorePattern": "^_"
}
],
"@typescript-eslint/no-non-null-assertion": "error",
"no-useless-return": "error",
"no-useless-constructor": "off",
"@typescript-eslint/no-useless-constructor": [
"error"
],
"no-return-await": "off",
"@typescript-eslint/return-await": [
"error",
"always"
],
"@typescript-eslint/explicit-member-accessibility": [
"error",
{
"accessibility": "explicit"
}
],
"@typescript-eslint/no-floating-promises": "error"
},
"ignorePatterns": [
"jest.config.js",
"scripts/**/*",
"webpack.config.js",
"dist/**/*",
"public/**/*",
"config/**/*"
],
"overrides": [
{
"files": [
"test/**/*"
],
"rules": {
"max-len": [
"error",
{
"code": 120,
"ignoreTemplateLiterals": true,
"ignoreRegExpLiterals": true,
"ignoreStrings": true
}
]
}
}
]
}

3
.gitignore vendored

@ -8,3 +8,6 @@ storage/uploads
config/local.*
src/package.json
intermediates/
dist/

12
assets/config.json

@ -1,12 +0,0 @@
{
"bundles": {
"app": "ts/app.ts",
"fm": "ts/fm.ts",
"url-shrinker": "ts/url-shrinker.ts",
"layout": "sass/layout.scss",
"error": "sass/error.scss",
"logo": "img/logo.svg",
"logo_png": "img/logox128.png",
"logo_png_xxl": "img/logox1024.png"
}
}

81
assets/sass/_fonts.scss

@ -1,81 +0,0 @@
/* vietnamese */
@font-face {
font-family: 'Nunito Sans';
font-style: normal;
font-weight: 300;
font-display: swap;
src: local('Nunito Sans Light'), local('NunitoSans-Light'), url(https://fonts.gstatic.com/s/nunitosans/v5/pe03MImSLYBIv1o4X1M8cc8WAc5iU1EQVg.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Nunito Sans';
font-style: normal;
font-weight: 300;
font-display: swap;
src: local('Nunito Sans Light'), local('NunitoSans-Light'), url(https://fonts.gstatic.com/s/nunitosans/v5/pe03MImSLYBIv1o4X1M8cc8WAc5jU1EQVg.woff2) format('woff2');
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Nunito Sans';
font-style: normal;
font-weight: 300;
font-display: swap;
src: local('Nunito Sans Light'), local('NunitoSans-Light'), url(https://fonts.gstatic.com/s/nunitosans/v5/pe03MImSLYBIv1o4X1M8cc8WAc5tU1E.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* vietnamese */
@font-face {
font-family: 'Nunito Sans';
font-style: normal;
font-weight: 400;
font-display: swap;
src: local('Nunito Sans Regular'), local('NunitoSans-Regular'), url(https://fonts.gstatic.com/s/nunitosans/v5/pe0qMImSLYBIv1o4X1M8cceyI9tScg.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Nunito Sans';
font-style: normal;
font-weight: 400;
font-display: swap;
src: local('Nunito Sans Regular'), local('NunitoSans-Regular'), url(https://fonts.gstatic.com/s/nunitosans/v5/pe0qMImSLYBIv1o4X1M8ccezI9tScg.woff2) format('woff2');
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Nunito Sans';
font-style: normal;
font-weight: 400;
font-display: swap;
src: local('Nunito Sans Regular'), local('NunitoSans-Regular'), url(https://fonts.gstatic.com/s/nunitosans/v5/pe0qMImSLYBIv1o4X1M8cce9I9s.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* vietnamese */
@font-face {
font-family: 'Nunito Sans';
font-style: normal;
font-weight: 700;
font-display: swap;
src: local('Nunito Sans Bold'), local('NunitoSans-Bold'), url(https://fonts.gstatic.com/s/nunitosans/v5/pe03MImSLYBIv1o4X1M8cc8GBs5iU1EQVg.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Nunito Sans';
font-style: normal;
font-weight: 700;
font-display: swap;
src: local('Nunito Sans Bold'), local('NunitoSans-Bold'), url(https://fonts.gstatic.com/s/nunitosans/v5/pe03MImSLYBIv1o4X1M8cc8GBs5jU1EQVg.woff2) format('woff2');
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Nunito Sans';
font-style: normal;
font-weight: 700;
font-display: swap;
src: local('Nunito Sans Bold'), local('NunitoSans-Bold'), url(https://fonts.gstatic.com/s/nunitosans/v5/pe03MImSLYBIv1o4X1M8cc8GBs5tU1E.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

33
assets/sass/_vars.scss

@ -1,33 +0,0 @@
$primary: darken(#242b33, 2%);
$primaryForeground: #f0f0f0;
$secondary: lighten(#842cff, 10%);
$secondaryForeground: $primaryForeground;
$backgroundColor: darken($primary, 4%);
$defaultTextColor: #ffffff;
$headerBackground: transparent;
$headerContainer: true;
$footerBackground: transparent;
$panelBackground: darken($backgroundColor, 3.2%);
$inputBackground: darken($panelBackground, 4%);
$info: #4499ff;
$infoText: darken($info, 42%);
$infoColor: desaturate($infoText, 50%);
$success: #55ff55;
$successText: darken($success, 45%);
$successColor: desaturate($successText, 50%);
$warning: #ffcc00;
$warningText: darken($warning, 30%);
$warningColor: desaturate($warningText, 50%);
$error: #ff0000;
$errorText: darken($error, 30%);
$errorColor: desaturate($errorText, 50%);
// Responsivity
$mobileThreshold: 850px;
$desktopThreshold: 940px;

18
assets/sass/app.scss

@ -1,18 +0,0 @@
@import "layout";
@import "fm";
.file-upload-table {
@media (max-width: 550px) {
> thead > tr > th:nth-child(3),
> tbody > tr > td:nth-child(3) {
display: none;
}
}
@media (max-width: 785px) {
> thead > tr > th:nth-child(4),
> tbody > tr > td:nth-child(4) {
display: none;
}
}
}

90
assets/sass/error.scss

@ -1,90 +0,0 @@
@import "layout";
header, footer {
margin: 0;
padding: 0;
height: 0;
}
main {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.messages {
margin-bottom: 32px;
}
.error-code {
font-size: 36px;
}
.error-message {
font-size: 32px;
}
.error-instructions {
margin-top: 32px;
font-size: 20px;
}
nav {
margin-top: 32px;
}
&::before {
content: "Oops";
position: absolute;
z-index: -1;
font-size: #{'min(50vh, 40vw)'};
opacity: 0.025;
}
}
.contact {
text-align: center;
padding: 8px;
}
.logo {
position: absolute;
top: 0;
left: 0;
width: 100%;
margin-top: 24px;
text-align: center;
a {
position: relative;
padding: 16px;
color: $defaultTextColor;
&:hover {
color: #fff;
&::before {
opacity: 0.2;
}
}
&::before {
content: "";
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
background-image: url(../img/logo.svg);
background-repeat: no-repeat;
background-position: center;
background-size: 64px;
opacity: 0.075;
filter: contrast(0);
}
}
}

11
assets/sass/fm.scss

@ -1,11 +0,0 @@
@import "vars";
#file-upload {
padding: 8px;
background: $infoColor;
border-radius: 5px;
.name, .status {
text-align: center;
}
}

977
assets/sass/layout.scss

@ -1,977 +0,0 @@
@import "vars";
@import 'fonts';
@import "responsivity_tools";
* {
box-sizing: border-box;
}
html, body {
height: 100%;
}
body {
display: flex;
flex-direction: column;
margin: 0;
font-family: "Nunito Sans", sans-serif;
font-size: 16px;
color: $defaultTextColor;
background-color: $backgroundColor;
}
@mixin tip {
position: relative;
.tip {
visibility: hidden;
position: absolute;
z-index: 10000;
pointer-events: none;
display: block;
width: max-content;
height: 30px;
padding: 4px 8px;
line-height: 22px;
top: calc(100% + 8px);
left: 50%;
transform: translateX(-50%);
text-align: center;
font-size: 18px;
color: $defaultTextColor;
opacity: 0;
transition: opacity ease-out 100ms, visibility step-end 150ms;
transition-delay: 0ms;
background-color: #000;
border-radius: 5px;
text-transform: initial;
font-weight: initial;
&.top {
top: auto;
bottom: calc(100% + 8px);
}
}
&:hover, &:active {
.tip {
visibility: visible;
opacity: 1;
transition: opacity ease-out 100ms;
transition-delay: 150ms;
}
}
}
body > header {
z-index: 50;
display: flex;
flex-direction: row-reverse;
justify-content: space-between;
align-items: center;
$headerHeight: 64px;
height: $headerHeight;
line-height: $headerHeight;
background: $headerBackground;
@if $headerContainer {
@include container;
}
@media (max-width: $mobileThreshold) {
padding: 0;
}
.logo {
display: flex;
flex-direction: row;
align-items: center;
padding: 0 16px 0 8px;
font-size: 24px;
color: $defaultTextColor;
&:hover {
color: lighten($defaultTextColor, 10%);
}
img {
width: initial;
height: calc(#{$headerHeight} - 16px);
margin-right: 8px;
flex-shrink: 0;
}
}
nav {
> ul {
position: fixed;
z-index: -1;
top: 0;
left: 0;
height: 100%;
transform: translateX(-100%);
transition: transform ease-out 150ms;
display: flex;
flex-direction: column;
margin: 0;
padding: $headerHeight 8px 8px;
font-size: 20px;
background: $panelBackground;
li {
position: relative;
list-style: none;
margin-top: 8px;
a, button {
position: relative;
margin: 0;
display: flex;
flex-direction: row;
align-items: center;
height: auto;
padding: 8px;
border-radius: 3px;
&:hover, &:active {
&:not(button) {
background-color: rgba(255, 255, 255, 0.07);
}
}
.feather {
--icon-size: 16px;
}
.tip {
position: static;
visibility: visible;
opacity: 1;
display: block;
height: auto;
margin-left: 8px;
padding: 0 0 0 4px;
transform: none;
font-size: 16px;
line-height: 16px;
color: inherit;
text-transform: uppercase;
font-weight: inherit;
background: transparent;
}
&:hover {
.tip {
visibility: visible;
opacity: 1;
transition: opacity ease-out 100ms;
transition-delay: 150ms;
}
}
}
button {
margin: 0;
height: 32px;
.feather {
margin-right: 0;
}
}
form {
display: flex;
justify-content: center;
align-items: center;
padding: 0;
}
&.auth-user {
img {
width: 48px;
height: 48px;
border-radius: 3px;
margin-right: 8px;
}
}
.dropdown {
position: initial;
display: block;
padding-left: 0;
}
}
> li:not(:first-child) {
border-top: 1px solid transparentize($defaultTextColor, 0.8);
padding-top: 8px;
}
&.open {
transform: translateX(0%);
box-shadow: 0 0 5px darken($panelBackground, 20%);
}
}
#menu-button {
position: fixed;
top: 0;
left: 0;
display: block;
margin: 0;
padding: 0 16px;
line-height: $headerHeight;
cursor: pointer;
background: transparent;
border-radius: 0;
.feather {
--icon-size: 28px;
margin: 0 8px;
}
}
hr {
border: 0;
border-bottom: 1px solid $defaultTextColor;
opacity: 0.2;
}
}
@media (min-width: $mobileThreshold) {
flex-direction: row;
nav {
#menu-button {
display: none;
}
ul {
position: static;
flex-direction: row;
transform: none;
padding: 0;
background: transparent;
li {
margin-top: 0;
margin-left: 8px;
&:last-child {
a, button, .button {
.tip {
left: unset;
right: 4px;
transform: none;
}
}
}
.dropdown {
position: absolute;
z-index: -1;
top: 100%;
right: 0;
display: none;
padding: 8px;
white-space: nowrap;
background: $panelBackground;
border-radius: 0 0 3px 3px;
box-shadow: 0 2px 2px transparentize(darken($panelBackground, 20%), 0.75);
border-top: 4px solid lighten($panelBackground, 5%);
li {
margin-left: 0;
&:not(:first-child) {
margin-top: 8px;
}
}
}
&:hover .dropdown {
display: block;
}
}
> li:not(:first-child) {
border-top: 0;
padding-top: 0;
}
}
}
}
}
body > footer {
padding: 8px;
margin-top: 8px;
text-align: center;
background-color: $footerBackground;
}
main {
flex: 1;
padding: 8px 0;
button, .button {
@include tip;
}
}
h1 {
text-align: center;
font-size: 32px;
& + p {
text-align: center;
font-size: 20px;
}
}
h1, h2 {
font-weight: 100;
}
h3, h4 {
font-weight: 300;
}
section > h2, .panel > h2 {
display: flex;
flex-direction: row;
align-items: center;
position: relative;
text-align: center;
margin-top: 4px;
font-size: 24px;
line-height: 1;
.feather {
margin: 0 16px 0 0;
opacity: 0.1;
}
&::after {
content: "";
flex: 1;
margin: 0 16px;
height: 0;
border-bottom: 1px solid $defaultTextColor;
opacity: 0.1;
}
}
section > hr, .panel > hr {
border: 0;
border-bottom: 1px solid $defaultTextColor;
opacity: 0.2;
margin: 8px 32px;
}
a {
color: $secondary;
text-decoration: none;
&:hover {
color: lighten($secondary, 30%);
}
.feather.feather-external-link {
--icon-size: 16px;
margin-left: 4px;
margin-top: -3px;
}
}
form {
padding: 8px 16px;
text-align: center;
.form-field:not(.hidden) {
display: flex;
flex-direction: column;
margin: 16px auto;
.control {
position: relative;
background: $inputBackground;
border-radius: 5px;
}
.feather.icon {
position: absolute;
top: 50%;
right: 8px;
transform: translateY(-50%);
z-index: 0;
--icon-size: 24px;
opacity: 0.75;
}
label {
position: absolute;
left: 8px;
top: 20px;
user-select: none;
font-size: 16px;
opacity: 0.75;
transition-property: top, font-size;
transition-duration: 150ms;
transition-timing-function: ease-out;
cursor: text;
}
[disabled] {
opacity: 0.5;
& ~ label {
opacity: 0.5;
cursor: default;
}
}
input, select, textarea, .input-group {
z-index: 1;
border: 0;
color: $defaultTextColor;
background: transparent;
font-size: 16px;
&:focus, &:not([value=""]), &[type="file"] {
~ label {
top: 8px;
font-size: 14px;
}
}
}
input, select, textarea, .form-display {
display: block;
padding: 32px 8px 8px 8px;
width: 100%;
height: 60px;
}
select {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
&::-ms-expand {
display: none;
}
& + .feather {
position: absolute;
pointer-events: none;
right: 8px;
top: 30px;
transition: transform 150ms ease-out;
}
// Temporary
&:focus + .feather {
transform: rotateX(180deg);
}
}
textarea {
resize: vertical;
min-height: 100px;
font-family: inherit;
}
input[type=color] {
height: calc(32px + 8px + 32px);
}
&.inline {
display: flex;
flex-direction: row;
.control {
display: flex;
flex-direction: row;
align-items: center;
flex-grow: 1;
input[type=checkbox] {
width: min-content;
height: min-content;
margin: 8px;
text-align: left;
& ~ label {
position: static;
flex-grow: 1;
display: inline;
padding: 8px;
font-size: 16px;
text-align: left;
}
}
}
}
.input-group {
display: flex;
flex-grow: 1;
flex-direction: row;
div {
position: relative;
flex: 1;
input {
width: 100%;
border: 0;
background: transparent;
}
}
}
}
.inline-fields {
display: flex;
flex-direction: row;
align-items: start;
margin: 16px auto;
.form-field {
flex: 1;
margin: 0;
}
> :not(.form-field) {
padding: 32px 8px 8px 8px;
}
+ {
.error, .hint {
margin-top: -16px;
margin-bottom: 16px;
}
}
}
.form-field, .inline-fields + {
.error, .hint {
padding: 2px;
text-align: left;
font-size: 14px;
.feather {
--icon-size: 14px;
}
}
.error {
color: $error;
}
}
}
button, .button {
display: inline-flex;
margin: 8px;
padding: 12px 16px;
border: 0;
border-radius: 5px;
cursor: pointer;
text-transform: uppercase;
font-size: 16px;
font-weight: bolder;
line-height: 16px;
.feather {
--icon-size: 16px;
margin-right: 8px;
}
.feather.last {
margin-right: 0;
margin-left: 8px;
}
&, &.primary {
color: $primaryForeground;
background-color: darken($secondary, 10%);
&:hover {
background-color: $secondary;
}
}
&.info {
background-color: $infoColor;
}
&.success {
background-color: $successColor;
}
&.warning {
background-color: $warningColor;
&:hover {
background-color: lighten($warningColor, 10%);
}
}
&.error, &.danger {
background-color: $errorColor;
&:hover {
background-color: lighten($errorColor, 10%);
}
}
&.transparent {
background-color: transparent;
}
&:hover {
color: $primaryForeground;
}
}
// ---
// --- Tables
// ---
td.actions {
> * {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
form {
padding: 0;
display: inline;
}
button, .button {
margin: 0;
padding: 8px;
.feather {
margin-right: 0;
}
}
> *:not(:first-child) {
margin-left: 8px;
}
}
}
.data-table {
width: 100%;
text-align: left;
border-collapse: collapse;
th, td {
padding: 8px;
}
th {
border-bottom: 1px solid #39434a;
white-space: nowrap;
}
tr:nth-child(even) {
background-color: rgba(255, 255, 255, 0.03);
}
tr:hover {
background-color: rgba(255, 255, 255, 0.09);
}
thead tr:hover {
background-color: transparent;
}
th.shrink-col {
width: 0;
}
}
// ---
// --- Breadcrumb widget
// ---
.breadcrumb {
list-style: none;
display: flex;
flex-direction: row;
margin: 0;
padding: 8px;
> *:not(:first-child)::before {
content: '';
padding: 0 8px;
}
}
// ---
// --- Layout helpers
// ---
.center {
text-align: center;
}
.panel {
position: relative;
margin: 16px 0 48px;
padding: 8px;
background-color: $panelBackground;
border-radius: 5px;
p {
margin: 16px 8px;
}
> .feather:first-child {
</