diff --git a/.github/workflows/cypress.yml b/.github/workflows/cypress.yml new file mode 100644 index 00000000000..93bf55ad011 --- /dev/null +++ b/.github/workflows/cypress.yml @@ -0,0 +1,98 @@ +name: Cypress + +on: + pull_request: + push: + branches: + - master + - stable* + +env: + APP_NAME: viewer + BRANCH: ${{ github.base_ref }} + TESTING: true + +jobs: + init: + runs-on: ubuntu-latest + + steps: + - name: Checkout server + uses: actions/checkout@v3 + + - name: Read package.json node and npm engines version + uses: skjnldsv/read-package-engines-version-actions@v1.2 + id: versions + with: + fallbackNode: "^12" + fallbackNpm: "^6" + + - name: Set up node ${{ steps.versions.outputs.nodeVersion }} + uses: actions/setup-node@v3 + with: + cache: 'npm' + node-version: ${{ steps.versions.outputs.nodeVersion }} + + - name: Set up npm ${{ steps.versions.outputs.npmVersion }} + run: npm i -g npm@"${{ steps.versions.outputs.npmVersion }}" + + - name: Install dependencies & build app + run: | + npm ci + TESTING=true npm run build --if-present + + - name: Save context + uses: actions/cache@v3 + with: + key: cypress-context-${{ github.run_id }} + path: /home/runner/work/server + + cypress: + runs-on: ubuntu-latest + needs: init + + strategy: + fail-fast: false + matrix: + # run multiple copies of the current job in parallel + containers: [1] + + name: runner ${{ matrix.containers }} + + steps: + - name: Restore context + uses: actions/cache@v3 + with: + key: cypress-context-${{ github.run_id }} + path: /home/runner/work/server + + - name: Run E2E cypress tests + uses: cypress-io/github-action@v4 + with: + record: true + parallel: true + # cypress env + ci-build-id: ${{ github.sha }}-${{ github.run_number }} + tag: ${{ github.event_name }} + env: + # Needs to be prefixed with CYPRESS_ + CYPRESS_BRANCH: ${{ env.BRANCH }} + CYPRESS_GH: true + # https://github.com/cypress-io/github-action/issues/124 + COMMIT_INFO_MESSAGE: ${{ github.event.pull_request.title }} + # Needed for some specific code workarounds + TESTING: true + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} + + summary: + runs-on: ubuntu-latest + needs: [init, cypress] + + if: always() + + name: cypress-summary + + steps: + - name: Summary status + run: if ${{ needs.init.result != 'success' || ( needs.cypress.result != 'success' && needs.cypress.result != 'skipped' ) }}; then exit 1; fi diff --git a/.gitignore b/.gitignore index a964fae1f92..5845018f9c9 100644 --- a/.gitignore +++ b/.gitignore @@ -163,3 +163,7 @@ composer.phar ./.htaccess core/js/mimetypelist.js + +# Tests - cypress +cypress/snapshots +cypress/videos diff --git a/apps/theming/css/default.css b/apps/theming/css/default.css index d028364ec7b..7b62656c4f9 100644 --- a/apps/theming/css/default.css +++ b/apps/theming/css/default.css @@ -54,9 +54,6 @@ --background-invert-if-dark: no; --background-invert-if-bright: invert(100%); --background-image-invert-if-bright: no; - --image-background: url('/core/img/app-background.jpg'); - --image-background-default: url('/core/img/app-background.jpg'); - --color-background-plain: #0082c9; --primary-invert-if-bright: no; --color-primary: #006aa3; --color-primary-default: #0082c9; @@ -75,4 +72,6 @@ --color-primary-element-light-hover: #dbe5ea; --color-primary-element-text-dark: #ededed; --gradient-primary-background: linear-gradient(40deg, var(--color-primary) 0%, var(--color-primary-hover) 100%); + --image-background-default: url('/apps/theming/img/background/kamil-porembinski-clouds.jpg'); + --color-background-plain: #0082c9; } diff --git a/apps/theming/css/settings-admin.css b/apps/theming/css/settings-admin.css deleted file mode 100644 index 1979387c5dd..00000000000 --- a/apps/theming/css/settings-admin.css +++ /dev/null @@ -1,148 +0,0 @@ -#theming input { - width: 230px; -} -#theming input:focus, -#theming input:active { - padding-right: 30px; -} -#theming .fileupload { - display: none; -} -#theming div > label { - position: relative; -} -#theming .theme-undo { - position: absolute; - top: -7px; - right: 4px; - cursor: pointer; - opacity: 0.3; - padding: 7px; - vertical-align: top; - display: inline-block; - visibility: hidden; - height: 32px; - width: 32px; -} -#theming form.uploadButton { - width: 411px; - display: flex; - align-items: center; -} -#theming form .theme-undo, -#theming .theme-remove-bg { - cursor: pointer; - opacity: 0.3; - padding: 7px; - vertical-align: top; - display: inline-block; - float: right; - position: relative; - top: 4px; - right: 0px; - visibility: visible; - height: 32px; - width: 32px; - margin-left: auto; -} -#theming form .theme-undo:not([style*="display:"]) ~ .theme-remove-bg { - margin-left: 0; -} -#theming input[type=text]:hover + .theme-undo, -#theming input[type=text] + .theme-undo:hover, -#theming input[type=text]:focus + .theme-undo, -#theming input[type=text]:active + .theme-undo, -#theming input[type=url]:hover + .theme-undo, -#theming input[type=url] + .theme-undo:hover, -#theming input[type=url]:focus + .theme-undo, -#theming input[type=url]:active + .theme-undo { - visibility: visible; -} -#theming label span { - display: inline-block; - min-width: 175px; - max-width: 175px; - white-space: wrap; - padding: 8px 0px; - vertical-align: top; -} -#theming .icon-upload, -#theming .uploadButton .icon-loading-small { - padding: 8px 20px; - width: 20px; - margin: 2px 0px; - min-height: 32px; - display: inline-block; -} -#theming #theming_settings_status { - height: 26px; - margin: 10px; -} -#theming #theming_settings_loading { - display: inline-block; - vertical-align: middle; - margin-right: 10px; -} -#theming #theming_settings_msg { - vertical-align: middle; - border-radius: 3px; -} -#theming #theming-preview { - width: 230px; - height: 140px; - background-size: cover; - background-position: center center; - text-align: center; - margin-left: 178px; - margin-top: 10px; - margin-bottom: 20px; - cursor: pointer; - background-color: var(--color-primary-default); - background-image: var(--image-background-default, var(--image-background-plain, linear-gradient(40deg, #0082c9 0%, #30b6ff 100%))); -} -#theming #theming-preview #theming-preview-logo { - cursor: pointer; - width: 20%; - height: 20%; - margin-top: 20px; - display: inline-block; - background-position: center; - background-repeat: no-repeat; - background-size: contain; - background-image: var(--image-logo, url("../../../core/img/logo/logo.svg")); -} -#theming .theming-hints { - margin-top: 20px; -} -#theming .image-preview { - display: inline-block; - width: 80px; - height: 36px; - background-position: center; - background-repeat: no-repeat; - background-size: contain; -} -#theming #theming-preview-logoheader { - background-image: var(--image-logoheader); -} -#theming #theming-preview-favicon { - background-image: var(--image-favicon); -} -#theming #user-theming { - margin-top: 44px; - display: flex; -} -#theming #user-theming > div { - max-width: 400px; - margin-bottom: 44px; -} - -/* transition effects for theming value changes */ -#header { - transition: background-color 500ms linear; -} -#header svg, #header img { - transition: 500ms filter linear; -} - -/*# sourceMappingURL=settings-admin.css.map */ diff --git a/apps/theming/css/settings-admin.scss b/apps/theming/css/settings-admin.scss deleted file mode 100644 index f34dea52698..00000000000 --- a/apps/theming/css/settings-admin.scss +++ /dev/null @@ -1,168 +0,0 @@ -#theming { - input { - width: 230px; - } - - input:focus, - input:active { - padding-right: 30px; - } - - .fileupload { - display: none; - } - - div > label { - position: relative; - } - - .theme-undo { - position: absolute; - top: -7px; // input padding - right: 4px; // input right margin + border - cursor: pointer; - opacity: .3; - padding: 7px; - vertical-align: top; - display: inline-block; - visibility: hidden; - height: 32px; // height of input - width: 32px; // height of input - } - form.uploadButton { - width: 411px; - display: flex; - align-items: center; - } - form .theme-undo, - .theme-remove-bg { - cursor: pointer; - opacity: .3; - padding: 7px; - vertical-align: top; - display: inline-block; - float: right; - position: relative; - top: 4px; - right: 0px; - visibility: visible; - height: 32px; - width: 32px; - // right align - margin-left: auto; - } - form .theme-undo:not([style*="display:"]) ~ .theme-remove-bg { - // Only align the undo button if both are shown - margin-left: 0; - } - - input[type='text']:hover + .theme-undo, - input[type='text'] + .theme-undo:hover, - input[type='text']:focus + .theme-undo, - input[type='text']:active + .theme-undo, - input[type='url']:hover + .theme-undo, - input[type='url'] + .theme-undo:hover, - input[type='url']:focus + .theme-undo, - input[type='url']:active + .theme-undo{ - visibility: visible; - } - - label span { - display: inline-block; - min-width: 175px; - max-width: 175px; - white-space: wrap; - padding: 8px 0px; - vertical-align: top; - } - - .icon-upload, - .uploadButton .icon-loading-small { - padding: 8px 20px; - width: 20px; - margin: 2px 0px; - min-height: 32px; - display: inline-block; - } - - #theming_settings_status { - height: 26px; - margin: 10px; - } - - #theming_settings_loading { - display: inline-block; - vertical-align: middle; - margin-right: 10px; - } - - #theming_settings_msg { - vertical-align: middle; - border-radius: 3px; - } - - #theming-preview { - width: 230px; - height: 140px; - background-size: cover; - background-position: center center; - text-align: center; - margin-left: 178px; - margin-top: 10px; - margin-bottom: 20px; - cursor: pointer; - background-color: var(--color-primary-default); - background-image: var(--image-background-default, var(--image-background-plain, linear-gradient(40deg, #0082c9 0%, #30b6ff 100%))); - - #theming-preview-logo { - cursor: pointer; - width: 20%; - height: 20%; - margin-top: 20px; - display: inline-block; - background-position: center; - background-repeat: no-repeat; - background-size: contain; - background-image: var(--image-logo, url('../../../core/img/logo/logo.svg')); - } - } - - .theming-hints { - margin-top: 20px; - } - - .image-preview { - display: inline-block; - width: 80px; - height: 36px; - background-position: center; - background-repeat: no-repeat; - background-size: contain; - } - - #theming-preview-logoheader { - // Only using --image-logoheader to show the custom value only - background-image: var(--image-logoheader); - } - - #theming-preview-favicon { - background-image: var(--image-favicon); - } - - #user-theming { - margin-top: 44px; - display: flex; - & > div { - max-width: 400px; - margin-bottom: 44px; - } - } -} - -/* transition effects for theming value changes */ -#header { - transition: background-color 500ms linear; - svg, img { - transition: 500ms filter linear; - } -} diff --git a/apps/theming/lib/Controller/UserThemeController.php b/apps/theming/lib/Controller/UserThemeController.php index 888ab9a0ca8..112a8a23638 100644 --- a/apps/theming/lib/Controller/UserThemeController.php +++ b/apps/theming/lib/Controller/UserThemeController.php @@ -168,9 +168,15 @@ class UserThemeController extends OCSController { /** * @NoAdminRequired */ - public function setBackground(string $type = BackgroundService::BACKGROUND_DEFAULT, string $value = ''): JSONResponse { + public function setBackground(string $type = BackgroundService::BACKGROUND_DEFAULT, string $value = '', string $color = null): JSONResponse { $currentVersion = (int)$this->config->getUserValue($this->userId, Application::APP_ID, 'userCacheBuster', '0'); + // Set color if provided + if ($color) { + $this->backgroundService->setColorBackground($color); + } + + // Set background image if provided try { switch ($type) { case BackgroundService::BACKGROUND_SHIPPED: @@ -179,14 +185,13 @@ class UserThemeController extends OCSController { case BackgroundService::BACKGROUND_CUSTOM: $this->backgroundService->setFileBackground($value); break; - case 'color': - $this->backgroundService->setColorBackground($value); - break; case BackgroundService::BACKGROUND_DEFAULT: $this->backgroundService->setDefaultBackground(); break; default: - return new JSONResponse(['error' => 'Invalid type provided'], Http::STATUS_BAD_REQUEST); + if (!$color) { + return new JSONResponse(['error' => 'Invalid type provided'], Http::STATUS_BAD_REQUEST); + } } } catch (\InvalidArgumentException $e) { return new JSONResponse(['error' => $e->getMessage()], Http::STATUS_BAD_REQUEST); diff --git a/apps/theming/lib/ImageManager.php b/apps/theming/lib/ImageManager.php index ce9c2525802..d4f4353b8a8 100644 --- a/apps/theming/lib/ImageManager.php +++ b/apps/theming/lib/ImageManager.php @@ -94,7 +94,7 @@ class ImageManager { case 'favicon': return $this->urlGenerator->imagePath('core', 'logo/logo.png') . '?v=' . $cacheBusterCounter; case 'background': - return $this->urlGenerator->linkTo(Application::APP_ID, "img/background/" . BackgroundService::DEFAULT_BACKGROUND); + return $this->urlGenerator->linkTo(Application::APP_ID, 'img/background/' . BackgroundService::DEFAULT_BACKGROUND); } return ''; } diff --git a/apps/theming/lib/Service/BackgroundService.php b/apps/theming/lib/Service/BackgroundService.php index 667ca99a1f9..002ca169a83 100644 --- a/apps/theming/lib/Service/BackgroundService.php +++ b/apps/theming/lib/Service/BackgroundService.php @@ -30,7 +30,7 @@ namespace OCA\Theming\Service; use InvalidArgumentException; use OC\User\NoUserException; use OCA\Theming\AppInfo\Application; -use OCP\Files\AppData\IAppDataFactory; +use OCA\Theming\ThemingDefaults; use OCP\Files\File; use OCP\Files\IAppData; use OCP\Files\IRootFolder; @@ -140,13 +140,13 @@ class BackgroundService { private IAppData $appData; private IConfig $config; private string $userId; - private IAppDataFactory $appDataFactory; + private ThemingDefaults $themingDefaults; public function __construct(IRootFolder $rootFolder, IAppData $appData, IConfig $config, ?string $userId, - IAppDataFactory $appDataFactory) { + ThemingDefaults $themingDefaults) { if ($userId === null) { return; } @@ -155,11 +155,12 @@ class BackgroundService { $this->config = $config; $this->userId = $userId; $this->appData = $appData; - $this->appDataFactory = $appDataFactory; + $this->themingDefaults = $themingDefaults; } public function setDefaultBackground(): void { $this->config->deleteUserValue($this->userId, Application::APP_ID, 'background_image'); + $this->config->setUserValue($this->userId, Application::APP_ID, 'background_color', $this->themingDefaults->getDefaultColorPrimary()); } /** @@ -171,7 +172,7 @@ class BackgroundService { * @throws NoUserException */ public function setFileBackground($path): void { - $this->config->setUserValue($this->userId, Application::APP_ID, 'background_image', self::BACKGROUND_DEFAULT); + $this->config->setUserValue($this->userId, Application::APP_ID, 'background_image', self::BACKGROUND_CUSTOM); $userFolder = $this->rootFolder->getUserFolder($this->userId); /** @var File $file */ diff --git a/apps/theming/lib/Themes/CommonThemeTrait.php b/apps/theming/lib/Themes/CommonThemeTrait.php index c58a3fd43e3..40feddd2b38 100644 --- a/apps/theming/lib/Themes/CommonThemeTrait.php +++ b/apps/theming/lib/Themes/CommonThemeTrait.php @@ -97,7 +97,7 @@ trait CommonThemeTrait { if ($backgroundDeleted) { $variables['--color-background-plain'] = $this->themingDefaults->getColorPrimary(); if ($this->themingDefaults->isUserThemingDisabled() || $user === null) { - $variables['--image-background-plain'] = 'true'; + $variables['--image-background-plain'] = 'yes'; } } @@ -108,13 +108,12 @@ trait CommonThemeTrait { if ($image === 'background') { // If background deleted is set, ignoring variable if ($backgroundDeleted) { - $variables['--image-background-default'] = 'no'; continue; } $variables['--image-background-size'] = 'cover'; $variables['--image-background-default'] = "url('" . $imageUrl . "')"; } - // --image-background is overriden by user theming + // --image-background is overridden by user theming $variables["--image-$image"] = "url('" . $imageUrl . "')"; } } diff --git a/apps/theming/lib/ThemingDefaults.php b/apps/theming/lib/ThemingDefaults.php index 42965ca6795..954b1be651b 100644 --- a/apps/theming/lib/ThemingDefaults.php +++ b/apps/theming/lib/ThemingDefaults.php @@ -247,7 +247,7 @@ class ThemingDefaults extends \OC_Defaults { * Return the default color primary */ public function getDefaultColorPrimary(): string { - $color = $this->config->getAppValue(Application::APP_ID, 'color'); + $color = $this->config->getAppValue(Application::APP_ID, 'color', ''); if (!preg_match('/^\#([0-9a-f]{3}|[0-9a-f]{6})$/i', $color)) { $color = '#0082c9'; } diff --git a/apps/theming/src/AdminTheming.vue b/apps/theming/src/AdminTheming.vue index 1d9f5b69512..4b1877ccf7d 100644 --- a/apps/theming/src/AdminTheming.vue +++ b/apps/theming/src/AdminTheming.vue @@ -285,8 +285,15 @@ export default { background-position: center; text-align: center; margin-top: 10px; - background-color: var(--color-primary-default); - background-image: var(--image-background-default, var(--image-background-plain, url('../../../core/img/app-background.jpg'), linear-gradient(40deg, #0082c9 0%, #30b6ff 100%))); + /* This is basically https://github.com/nextcloud/server/blob/master/core/css/guest.css + But without the user variables. That way the admin can preview the render as guest*/ + /* As guest, there is no user color color-background-plain */ + background-color: var(--color-primary-default, #0082c9); + /* As guest, there is no user background (--image-background) + 1. Empty background if defined + 2. Else default background + 3. Finally default gradient (should not happened, the background is always defined anyway) */ + background-image: var(--image-background-plain, var(--image-background-default, linear-gradient(40deg, #0082c9 0%, #30b6ff 100%))); &-logo { width: 20%; diff --git a/apps/theming/src/components/BackgroundSettings.vue b/apps/theming/src/components/BackgroundSettings.vue index 9890f9ad3f0..e1d8b731bc4 100644 --- a/apps/theming/src/components/BackgroundSettings.vue +++ b/apps/theming/src/components/BackgroundSettings.vue @@ -1,10 +1,10 @@