diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index e37c07882d..6b1ea80d08 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -1,14 +1,9 @@ categories: - - title: "Breaking Changes" - labels: - - "BC-break" - - title: "Major Features" + - title: "Major features" labels: - "MAJOR" - - title: "Documentation enhancements" + - title: "Breaking changes" labels: - - "Documentation :books:" -template: | - ## What’s Changed - - $CHANGES + - "BC-break" + - title: "Other changes" +template: $CHANGES diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-changelog.yml similarity index 72% rename from .github/workflows/build-docs.yml rename to .github/workflows/build-changelog.yml index ab60038a44..65b915189e 100644 --- a/.github/workflows/build-docs.yml +++ b/.github/workflows/build-changelog.yml @@ -1,4 +1,4 @@ -name: Build Docs +name: Build changelog on: push: @@ -6,10 +6,11 @@ on: - develop jobs: - update_release_draft: + update: + name: Update runs-on: ubuntu-latest steps: - - name: Run Release Drafter + - name: Run uses: release-drafter/release-drafter@v5 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 88865f78c6..ac84495540 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -1,4 +1,4 @@ -name: Build Release +name: Build release on: push: diff --git a/.github/workflows/test-unit.yml b/.github/workflows/test-unit.yml index 410576ec06..0e9e3debee 100644 --- a/.github/workflows/test-unit.yml +++ b/.github/workflows/test-unit.yml @@ -34,10 +34,10 @@ jobs: - name: Setup cache 1/2 id: composer-cache run: | - echo "::set-output name=dir::$(composer config cache-files-dir)" + echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - name: Setup cache 2/2 - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-smoke-${{ matrix.php }}-${{ matrix.type }}-${{ hashFiles('composer.json') }} @@ -82,7 +82,7 @@ jobs: php: ['7.4', '8.0', '8.1', '8.2'] type: ['Phpunit', 'Phpunit Lowest'] include: - - php: '8.1' # TODO replace with 'latest' once it represents at least PHP 8.1 + - php: 'latest' type: 'Phpunit Burn' env: LOG_COVERAGE: "${{ fromJSON('{true: \"1\", false: \"\"}')[matrix.php == '8.0' && matrix.type == 'Phpunit' && (github.event_name == 'pull_request' || (github.event_name == 'push' && (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master')))] }}" @@ -92,7 +92,7 @@ jobs: options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=5 -e MYSQL_ROOT_PASSWORD=atk4_pass_root -e MYSQL_USER=atk4_test_user -e MYSQL_PASSWORD=atk4_pass -e MYSQL_DATABASE=atk4_test --entrypoint sh mysql:8 -c "exec docker-entrypoint.sh mysqld --default-authentication-plugin=mysql_native_password" mariadb: image: mariadb - options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=5 -e MYSQL_ROOT_PASSWORD=atk4_pass_root -e MYSQL_USER=atk4_test_user -e MYSQL_PASSWORD=atk4_pass -e MYSQL_DATABASE=atk4_test + options: --health-cmd="mariadb-admin ping" --health-interval=10s --health-timeout=5s --health-retries=5 -e MYSQL_ROOT_PASSWORD=atk4_pass_root -e MYSQL_USER=atk4_test_user -e MYSQL_PASSWORD=atk4_pass -e MYSQL_DATABASE=atk4_test postgres: image: postgres:12-alpine env: @@ -121,10 +121,10 @@ jobs: - name: Setup cache 1/2 id: composer-cache run: | - echo "::set-output name=dir::$(composer config cache-files-dir)" + echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - name: Setup cache 2/2 - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ matrix.php }}-${{ matrix.type }}-${{ hashFiles('composer.json') }} @@ -224,10 +224,11 @@ jobs: - name: Upload coverage logs 2/2 (only for latest Phpunit) if: env.LOG_COVERAGE - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v3 with: token: ${{ secrets.CODECOV_TOKEN }} - file: coverage/merged.xml + fail_ci_if_error: true + files: coverage/merged.xml behat-test: name: Behat @@ -252,7 +253,7 @@ jobs: options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=5 -e MYSQL_ROOT_PASSWORD=atk4_pass_root -e MYSQL_USER=atk4_test_user -e MYSQL_PASSWORD=atk4_pass -e MYSQL_DATABASE=atk4_test --entrypoint sh mysql:8 -c "exec docker-entrypoint.sh mysqld --default-authentication-plugin=mysql_native_password" mariadb: image: mariadb - options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=5 -e MYSQL_ROOT_PASSWORD=atk4_pass_root -e MYSQL_USER=atk4_test_user -e MYSQL_PASSWORD=atk4_pass -e MYSQL_DATABASE=atk4_test + options: --health-cmd="mariadb-admin ping" --health-interval=10s --health-timeout=5s --health-retries=5 -e MYSQL_ROOT_PASSWORD=atk4_pass_root -e MYSQL_USER=atk4_test_user -e MYSQL_PASSWORD=atk4_pass -e MYSQL_DATABASE=atk4_test postgres: image: postgres:12-alpine env: @@ -281,54 +282,64 @@ jobs: - name: Setup cache 1/2 id: composer-cache run: | - echo "::set-output name=dir::$(composer config cache-files-dir)" + echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - name: Setup cache 2/2 - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-behat-${{ matrix.php }}-${{ matrix.type }}-${{ hashFiles('composer.json') }} restore-keys: | ${{ runner.os }}-composer- - - name: Install JS dependencies (only for Slow) - if: matrix.type == 'Chrome Slow' - run: | - mv public public.orig - mkdir public public/external - cp public.orig/.gitignore public - cp public.orig/agileui.less public - cp public.orig/logo.png public - cp public.orig/external/.gitignore public/external - cp public.orig/external/package.json public/external - cp public.orig/external/package-lock.json public/external - cp public.orig/external/postinstall.js public/external - npm install --loglevel=error -g pug-cli less less-plugin-clean-css uglify-js - (cd js && npm ci --loglevel=error) - # uncomment and remove line below once Fomantic-UI 2.9.0 is released (cd public/external && npm ci --loglevel=error && git clean -dxfq .) - (cd public/external && npm ci --ignore-scripts --loglevel=error && npm run postinstall && git clean -dxfq .) + - name: Install JS dependencies (only for coverage or Slow) + if: env.LOG_COVERAGE || matrix.type == 'Chrome Slow' + run: | + if [ -n "$LOG_COVERAGE" ]; then + (cd js && npm install --package-lock-only --save-dev babel-plugin-istanbul nyc && npm ci --loglevel=error) + else + mv public public.orig + mkdir public public/css public/external + cp public.orig/.gitattributes public + cp public.orig/.gitignore public + cp public.orig/logo.png public + cp public.orig/css/agileui.less public/css + cp public.orig/external/.gitignore public/external + cp public.orig/external/package.json public/external + cp public.orig/external/package-lock.json public/external + cp public.orig/external/postinstall.js public/external + npm install --loglevel=error -g pug-cli less less-plugin-clean-css uglify-js + (cd js && npm ci --loglevel=error) + (cd public/external && npm ci --loglevel=error && git clean -dxfq .) + fi - name: Lint JS files (only for Slow) if: matrix.type == 'Chrome Slow' run: | - (cd js && npm run lint ../public/external/\*.js) + cp public/external/postinstall.js js + (cd js && npm run lint) - name: Compile HTML files (only for Slow) if: matrix.type == 'Chrome Slow' run: | cp -r template template.orig find template -not -type d -not -name '*.pug' -delete - (cd template && pug --silent --pretty .) + (cd template && pug --doctype html --pretty --silent .) - name: Compile CSS files (only for Slow) if: matrix.type == 'Chrome Slow' run: | - lessc public/agileui.less public/agileui.min.css --clean-css="--s1 --advanced" --source-map + lessc public/css/agileui.less public/css/agileui.min.css --clean-css="--s1 --advanced" --source-map - - name: Compile JS files (only for Slow) - if: matrix.type == 'Chrome Slow' + - name: Compile JS files (only for coverage or Slow) + if: env.LOG_COVERAGE || matrix.type == 'Chrome Slow' run: | - (cd js && npm run build) + if [ -n "$LOG_COVERAGE" ]; then + rm -r public/js + (cd js && ISTANBUL_COVERAGE=1 npm run build) + else + (cd js && npm run build) + fi - name: Diff compiled files (only for Slow) if: matrix.type == 'Chrome Slow' @@ -352,7 +363,7 @@ jobs: php -r '(new PDO("mysql:host=mariadb", "root", "atk4_pass_root"))->exec("ALTER USER '"'"'atk4_test_user'"'"'@'"'"'%'"'"' WITH MAX_USER_CONNECTIONS 5");' php -r '(new PDO("pgsql:host=postgres;dbname=atk4_test", "atk4_test_user", "atk4_pass"))->exec("ALTER ROLE atk4_test_user CONNECTION LIMIT 1");' /usr/lib/oracle/setup.sh - if [ -n "$LOG_COVERAGE" ]; then mkdir coverage; fi + if [ -n "$LOG_COVERAGE" ]; then mkdir coverage coverage/js; fi ci_wait_until () { timeout 30 sh -c "until { $1 2> /dev/null; }; do sleep 0.02; done" || timeout 15 sh -c "$1" || { echo "health timeout: $1"; exit 1; }; } php -d opcache.enable_cli=1 -S 127.0.0.1:8888 > /dev/null 2>&1 & ci_wait_until 'nc -w 1 127.0.0.1 8888' @@ -425,15 +436,47 @@ jobs: php demos/_demo-data/create-db.php vendor/bin/behat -vv --config behat.yml.dist - - name: Upload coverage logs 1/2 (only for latest Chrome) + - name: Upload coverage logs 1/2 (only for coverage) if: env.LOG_COVERAGE run: | ls -l coverage | wc -l php -d memory_limit=2G vendor/bin/phpcov merge coverage/ --clover coverage/merged.xml - - - name: Upload coverage logs 2/2 (only for latest Chrome) + ls -l coverage/js | wc -l + (cd js && npx nyc report --temp-dir ../coverage/js --report-dir ../coverage/js -e vue --reporter=clover) + # fix never reached condition is rendered to clover with falsecount > 0 + # https://github.com/istanbuljs/istanbuljs/issues/695 + sed -i -E 's~count="0" type="cond" truecount="0" falsecount="[1-9]+[0-9]*"~count="0" type="cond" truecount="0" falsecount="0"~' coverage/js/clover.xml + sed -i -E 's~count="[0-9]+" type="cond" truecount="[1-9]+[0-9]*" falsecount="[0-9]+"~count="1" type="cond" truecount="1" falsecount="1"~' coverage/js/clover.xml + + - name: Upload coverage logs 2/2 (only for coverage) if: env.LOG_COVERAGE - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v3 with: token: ${{ secrets.CODECOV_TOKEN }} - file: coverage/merged.xml + fail_ci_if_error: true + files: coverage/merged.xml,coverage/js/clover.xml + + docs-test: + name: Docs + runs-on: ubuntu-latest + container: + image: ghcr.io/mvorisek/image-php:latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Install Python and dependencies + run: | + apk add python3 py3-pip + python --version + (cd docs && pip install -r requirements.txt) + + - name: Build + run: | + mv docs/baseline.txt docs/baseline.orig.txt + (cd docs && python -m sphinx -T -b html . out 2>&1 | tee baseline.txt) + sed -i -r 's~[^:]*/docs/([^:]*:)([0-9]+:)?~\1~;t;d' docs/baseline.txt + + - name: Diff build baseline + run: | + diff -u docs/baseline.orig.txt docs/baseline.txt diff --git a/.gitignore b/.gitignore index f91a9d78d2..6dfde935bf 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ cache *.cache.* /demos/db.php +/demos/db-behat-rw.txt /demos/_demo-data/db.sqlite /demos/_demo-data/db.sqlite-journal /phpunit.xml diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 5797add99b..97a57e9e77 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -9,8 +9,8 @@ ->setRules([ '@PhpCsFixer' => true, '@PhpCsFixer:risky' => true, - '@PHP74Migration:risky' => true, '@PHP74Migration' => true, + '@PHP74Migration:risky' => true, // required by PSR-12 'concat_space' => [ @@ -18,11 +18,6 @@ ], // disable some too strict rules - 'phpdoc_types' => [ - // keep enabled, but without "alias" group to not fix - // "Callback" to "callback" in phpdoc - 'groups' => ['simple', 'meta'], - ], 'phpdoc_types_order' => [ 'null_adjustment' => 'always_last', 'sort_algorithm' => 'none', diff --git a/README.md b/README.md index f129c86712..6535f8cd25 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -# Agile UI - User Interface framework for Agile Toolkit +# Agile UI - Robust and easy to use PHP Framework for Web Apps -[Agile Toolkit](https://agiletoolkit.org/) is a Low Code framework written in PHP. Agile UI implement server side rendering engine and over 50 UI generic components for interacting with your Data Model. +Agile UI implement server side rendering engine and over 50 UI generic components for interacting with your data. -Agile UI is quickest way for building back-end UI, admin interfaces, data management systems for medium and large projects designed around roles, complex logic, formulas. +Agile UI is the quickest way for building back-end UI, admin interfaces, data management systems for medium and large projects designed around roles, complex logic, formulas... - Agile UI relies on abstract data. It could be stored in SQL, NoSQL or in external API. - Agile UI adjusts to your data model. If you change your model structure, UI will reflect that. @@ -11,12 +11,12 @@ Agile UI is quickest way for building back-end UI, admin interfaces, data manage - Agile UI is compact - single file, several lines of code - that's all it takes. - Agile UI is extensible - integrates VueJS for custom components and interactive behaviours. -![Build](https://github.com/atk4/ui/workflows/Unit%20Testing/badge.svg) +[![Build](https://github.com/atk4/ui/workflows/Unit/badge.svg)](https://github.com/atk4/ui/actions?query=workflow%3AUnit+branch%3Adevelop) [![CodeCov](https://codecov.io/gh/atk4/ui/branch/develop/graph/badge.svg)](https://codecov.io/gh/atk4/ui) [![GitHub release](https://img.shields.io/github/release/atk4/ui.svg)](CHANGELOG.md) [![Code Climate](https://codeclimate.com/github/atk4/ui/badges/gpa.svg)](https://codeclimate.com/github/atk4/ui) -Quick-Links: [Documentation](https://agile-ui.readthedocs.io). [Demo-site](https://ui.agiletoolkit.org). [ATK Data](https://github.com/atk4/data). [Forum](https://forum.agiletoolkit.org/). [Chat](https://gitter.im/atk4/atk4). [Commercial support](https://www.agiletoolkit.org/contact). +Quick-Links: [Documentation](https://atk4-ui.readthedocs.io/). [Demo-site](https://ui.atk4.org/). [ATK Data](https://github.com/atk4/data). [Forum](https://forum.agiletoolkit.org/). [Chat](https://gitter.im/atk4/atk4). [Commercial support](https://www.agiletoolkit.org/contact). ## How does Agile Toolkit work? @@ -37,19 +37,15 @@ high-level projects developed entirely on Agile Toolkit. ### Who uses Agile Toolkit? -Companies use Agile Toolkit to implement admin interface and in some cases even user-facing interface. - - - www.linkedfinance.com - - www.sortmybooks.com - - If you have a project built with Agile Toolkit - add it here! +Many companies use Agile Toolkit to implement admin interface and in some cases even user-facing interface. ### How does it work? -Download from www.agiletoolkit.org or Install ATK UI with `composer require atk4/ui` +Download from https://ui.atk4.org/ or install ATK UI with `composer require atk4/ui` Create "index.php" file with: -``` php +```php addField('email'); $form->onSubmit(function (Form $form) { // implement subscribe here - return $form->success('Subscribed ' . $form->model->get('email') . ' to newsletter.'); + return $form->jsSuccess('Subscribed ' . $form->model->get('email') . ' to newsletter.'); }); // decorate anything @@ -76,16 +72,16 @@ Open PHP in the browser and observe a fully working and good looking form: ![subscribe](docs/images/subscribe.png) -ATK UI relies on https://fomantic-ui.com CSS framework to render the form beautifully. It also implements submission call-back in a very straightforward way. The demo also demonstrates use of JavaScript action, which can make objects interract with each-other (e.g. Form submit reloads Table). +ATK UI relies on https://fomantic-ui.com CSS framework to render the form beautifully. It also implements submission callback in a very straightforward way. The demo also demonstrates use of JavaScript action, which can make objects interact with each-other (e.g. Form submit reloads Table). ### Database Integration with ATK Data To get most of ATK UI, use [ATK Data](https://github.com/atk4/data) to describe your business models such as "User" or "Purchase". When you define models, you can start using some more advanced components: -[Crud](https://ui.agiletoolkit.org/demos/crud.php) is a fully-interractive component that supports pagination, reloading, conditions, data formatting, sorting, quick-search, ordering, custom actions and modals, but at the same time is very easy to use: +[Crud](https://ui.atk4.org/demos/crud.php) is a fully-interactive component that supports pagination, reloading, conditions, data formatting, sorting, quick-search, ordering, custom actions and modals, but at the same time is very easy to use: -``` php -$app = new \Atk4\Ui\App('hello world'); +```php +$app = new \Atk4\Ui\App(['title' => 'hello world']); $app->initLayout([\Atk4\Ui\Layout\Admin::class]); $app->db = \Atk4\Data\Persistence::connect('mysql://user:pass@localhost/atk'); @@ -95,7 +91,7 @@ $app->db = \Atk4\Data\Persistence::connect('mysql://user:pass@localhost/atk'); ATK Data allows you to set up relations between models: -``` php +```php class User extends Model { protected function init(): void @@ -111,16 +107,15 @@ class User extends Model Conventional Crud works only with a single model, but with add-on you can take advantage this relationship information: https://github.com/atk4/mastercrud -``` php +```php use \Atk4\Mastercrud\MasterCrud; // set up $app here -$master_crud = MasterCrud::addTo($app) +$masterCrud = MasterCrud::addTo($app) ->setModel(new User($app->db), [ 'Purchases' => [], ]); - ``` ### Agile UI can be styled @@ -133,7 +128,7 @@ It's easy to create your own application styling. Here are some example UI: As of version 2.0 - Agile Toolkit offers support for User Actions. Those are easy to define in your Data Model declaration: -``` php +```php $this->addUserAction('archive', function (Model $m) { $m->set('is_archived', true); $this->saveAndUnload(); @@ -151,7 +146,7 @@ Agile UI has some unique features: One of the fundamental features of ATK is Callback - ability to dynamically generate a route then have JS part of the component invoke it. Thanks to this approach, code can be fluid, simple and readable: -``` php +```php $tabs = \Atk4\Ui\Tabs::addTo($app); \Atk4\Ui\Message::addTo($tabs->addTab('Intro'), ['Other tabs are loaded dynamically!']); @@ -178,13 +173,13 @@ Another component implementation using a very friendly PHP syntax: ![wizard](docs/images/wizard.png) -You get most benefit when you use various ATK UI Components together. Try the following demo: https://ui.agiletoolkit.org/demos/interactive/wizard.php. The demo implements: +You get most benefit when you use various ATK UI Components together. Try the following demo: https://ui.atk4.org/demos/interactive/wizard.php. The demo implements: -- Multi-step wizard with ability to navigate forward and backward -- Form with validation -- Data memorization in the session -- Table with column formatter, Messages -- Real-time output console +- Multi-step wizard with ability to navigate forward and backward +- Form with validation +- Data memorization in the session +- Table with column formatter, Messages +- Real-time output console With ATK it [takes about 50 lines of PHP code only](https://github.com/atk4/ui/blob/develop/demos/interactive/wizard.php) to build it all. @@ -192,7 +187,7 @@ With ATK it [takes about 50 lines of PHP code only](https://github.com/atk4/ui/b It's really easy to put together a complex Admin system. Add this code to a new PHP file (tweak it with your database details, table and fields): -``` php +```php =7.4 <8.3", "atk4/data": "dev-develop", - "symfony/filesystem": "^4.4 || ^5.3", - "symfony/http-foundation": "^4.4 || ^5.3" + "nyholm/psr7": "^1.4", + "nyholm/psr7-server": "^1.0", + "symfony/filesystem": "^4.4 || ^5.3 || ^6.0", + "symfony/http-foundation": "^4.4 || ^5.3 || ^6.0" }, "require-release": { "php": ">=7.4 <8.3", - "atk4/data": "~4.0.0", - "symfony/filesystem": "^4.4 || ^5.3", - "symfony/http-foundation": "^4.4 || ^5.3" + "atk4/data": "~5.0.0", + "nyholm/psr7": "^1.4", + "nyholm/psr7-server": "^1.0", + "symfony/filesystem": "^4.4 || ^5.3 || ^6.0", + "symfony/http-foundation": "^4.4 || ^5.3 || ^6.0" }, "require-dev": { + "atk4/behat-mink-selenium2-driver": "^1.6.1", "behat/mink-extension": "^2.3.1", - "behat/mink-selenium2-driver": "^1.5", "ergebnis/composer-normalize": "^2.13", "friendsofphp/php-cs-fixer": "^3.0", "fzaninotto/faker": "^1.6", - "guzzlehttp/guzzle": "^6.3", + "guzzlehttp/guzzle": "^7.3", "johnkary/phpunit-speedtrap": "^3.3", "phpstan/extension-installer": "^1.1", "phpstan/phpstan": "^1.0", "phpstan/phpstan-deprecation-rules": "^1.0", "phpstan/phpstan-strict-rules": "^1.3", "phpunit/phpunit": "^9.5.5", - "symfony/process": "^4.4.30 || ^5.3.7" + "symfony/process": "^4.4.30 || ^5.3.7 || ^6.0" }, "conflict": { "behat/behat": "<3.9", "behat/mink": "<1.9", - "instaclick/php-webdriver": "<1.4.13", + "guzzlehttp/psr7": "<2.4", "symfony/console": "<4.4.30 || >=5 <5.3.7", "symfony/css-selector": "<4.4.24 || >=5 <5.2.9", "symfony/filesystem": "<4.4.30 || >=5 <5.3.7", diff --git a/demos/Dockerfile b/demos/Dockerfile index ac7b982eca..aeb0d1b13e 100644 --- a/demos/Dockerfile +++ b/demos/Dockerfile @@ -13,7 +13,7 @@ RUN apt-get update && apt-get install -y \ && docker-php-ext-install intl \ && docker-php-ext-install pdo pdo_mysql -RUN curl -sL https://deb.nodesource.com/setup_12.x | bash - \ +RUN curl -sL https://deb.nodesource.com/setup_current.x | bash - \ && apt-get update && apt-get -y install nodejs \ && npm install -g npm @@ -27,7 +27,7 @@ COPY js js COPY public public RUN cd js && npm ci && npm run build -RUN cd public && lessc agileui.less agileui.css +RUN cd public/css && lessc agileui.less agileui.css ADD composer.json . RUN jq 'del(."require-release")|del(."require-dev")' < composer.json > tmp && mv tmp composer.json \ @@ -40,6 +40,8 @@ COPY src src COPY template template COPY demos demos RUN echo " index.php +RUN sed -E "s/(\\\$minified = )true;/\\1false;/g" -i src/App.php RUN php demos/_demo-data/create-db.php +RUN chown -R www-data:www-data demos/_demo-data RUN sed -E "s/\(('sqlite:.+)\);/(\$_ENV['DB_DSN'] ?? \\1, \$_ENV['DB_USER'] ?? null, \$_ENV['DB_PASSWORD'] ?? null);/g" -i demos/db.default.php diff --git a/demos/_demo-data/create-db.php b/demos/_demo-data/create-db.php index 02904219f2..c91c58aaa9 100644 --- a/demos/_demo-data/create-db.php +++ b/demos/_demo-data/create-db.php @@ -62,7 +62,12 @@ public function import(array $rowsMulti) return parent::import(array_map(function (array $rows): array { $rowsPrefixed = []; foreach ($rows as $k => $v) { - $rowsPrefixed[$this->prefixFieldName($k)] = $v; + $field = $this->getField($this->prefixFieldName($k)); + if (in_array($field->type, ['date', 'time', 'datetime'], true)) { + $v = new \DateTime($v . ' GMT'); + } + + $rowsPrefixed[$field->shortName] = $v; } return $rowsPrefixed; @@ -1100,11 +1105,6 @@ public function import(array $rowsMulti) ['id' => 3, 'project_name' => 'Agile Data', 'project_code' => 'at03', 'description' => 'Agile Data implements an entirely new pattern for data abstraction, that is specifically designed for remote databases such as RDS, Cloud SQL, BigQuery and other distributed data storage architectures. It focuses on reducing number of requests your App have to send to the Database by using more sophisticated queries while also offering full Domain Model mapping and Database vendor abstraction.', 'client_name' => 'Agile Toolkit', 'client_address' => 'Some Street,' . "\n" . 'Garden City' . "\n" . 'UK', 'client_country_iso' => 'GB', 'is_commercial' => 0, 'currency' => 'GBP', 'is_completed' => 1, 'project_budget' => 12000, 'project_invoiced' => 0, 'project_paid' => 0, 'project_hour_cost' => 0, 'project_hours_est' => 300, 'project_hours_reported' => 394, 'project_expenses_est' => 600, 'project_expenses' => 430, 'project_mgmt_cost_pct' => 0.2, 'project_qa_cost_pct' => 0.3, 'start_date' => '2016-04-17', 'finish_date' => '2016-06-20', 'finish_time' => '03:04:00', 'created' => '2017-04-06 10:30:15', 'updated' => '2017-04-06 10:35:04'], ['id' => 4, 'project_name' => 'Agile UI', 'project_code' => 'at04', 'description' => 'Web UI Component library.', 'client_name' => 'Agile Toolkit', 'client_address' => 'Some Street,' . "\n" . 'Garden City' . "\n" . 'UK', 'client_country_iso' => 'GB', 'is_commercial' => 0, 'currency' => 'GBP', 'is_completed' => 0, 'project_budget' => 20000, 'project_invoiced' => 0, 'project_paid' => 0, 'project_hour_cost' => 0, 'project_hours_est' => 600, 'project_hours_reported' => 368, 'project_expenses_est' => 1200, 'project_expenses' => 0, 'project_mgmt_cost_pct' => 0.3, 'project_qa_cost_pct' => 0.4, 'start_date' => '2016-09-17', 'finish_date' => '', 'finish_time' => '', 'created' => '2017-04-06 10:30:15', 'updated' => '2017-04-06 10:35:04'], ]; -foreach ($data as $rowIndex => $row) { - foreach (['start_date', 'finish_date', 'finish_time', 'created', 'updated'] as $k) { - $data[$rowIndex][$k] = new \DateTime($row[$k] . ' GMT'); - } -} $model->import($data); $model = new ImportModelWithPrefixedFields($db, ['table' => 'product_category']); @@ -1148,4 +1148,27 @@ public function import(array $rowsMulti) ['id' => 7, 'name' => 'Ice Cream', 'brand' => 'Milk Corp.', 'product_category_id' => 3, 'product_sub_category_id' => 8], ]); +$model = new ImportModelWithPrefixedFields($db, ['table' => 'multiline_item']); +$model->addField('item', ['type' => 'string']); +$model->addField('inv_date', ['type' => 'date']); +$model->addField('inv_time', ['type' => 'time']); +$model->addField('country_id', ['type' => 'bigint']); +$model->addField('qty', ['type' => 'integer']); +$model->addField('box', ['type' => 'integer']); +(new Migrator($model))->create(); +$model->import([ + ['id' => 1, 'item' => 'Chocolate', 'inv_date' => '2020-02-20', 'inv_time' => '7:20', 'country_id' => 80, 'qty' => 7, 'box' => 5], + ['id' => 2, 'item' => 'DAP delivery', 'inv_date' => '2020-02-01', 'inv_time' => '8:33', 'country_id' => 223, 'qty' => 2, 'box' => 100], +]); + +$model = new ImportModelWithPrefixedFields($db, ['table' => 'multiline_delivery']); +$model->addField('name', ['type' => 'string']); +$model->addField('country', ['type' => 'json']); +$model->addField('items', ['type' => 'json']); +(new Migrator($model))->create(); +$model->import([ + // TODO Model::containsXxx support + // https://github.com/atk4/ui/issues/1860 +]); + echo 'import complete!' . "\n\n"; diff --git a/demos/_includes/Counter.php b/demos/_includes/Counter.php index c08657b41f..3d65543429 100644 --- a/demos/_includes/Counter.php +++ b/demos/_includes/Counter.php @@ -6,7 +6,7 @@ use Atk4\Ui\Button; use Atk4\Ui\Form; -use Atk4\Ui\JsExpression; +use Atk4\Ui\Js\JsExpression; class Counter extends Form\Control\Line { @@ -19,7 +19,7 @@ protected function init(): void $this->actionLeft = new Button(['icon' => 'minus']); $this->action = new Button(['icon' => 'plus']); - $this->actionLeft->js('click', $this->jsInput()->val(new JsExpression('parseInt([]) - 1', [$this->jsInput()->val()]))); - $this->action->js('click', $this->jsInput()->val(new JsExpression('parseInt([]) + 1', [$this->jsInput()->val()]))); + $this->actionLeft->on('click', $this->jsInput()->val(new JsExpression('parseInt([]) - 1', [$this->jsInput()->val()]))); + $this->action->on('click', $this->jsInput()->val(new JsExpression('parseInt([]) + 1', [$this->jsInput()->val()]))); } } diff --git a/demos/_includes/Demo.php b/demos/_includes/Demo.php index 7c8658d4d3..384d6f807b 100644 --- a/demos/_includes/Demo.php +++ b/demos/_includes/Demo.php @@ -6,7 +6,8 @@ use Atk4\Ui\Columns; use Atk4\Ui\Exception; -use Atk4\Ui\JsChain; +use Atk4\Ui\Js\JsExpression; +use Atk4\Ui\Js\JsFunction; use Atk4\Ui\View; class Demo extends Columns @@ -19,9 +20,6 @@ class Demo extends Columns /** @var bool */ public static $isInitialized = false; - /** @var string */ - public $highlightDefaultStyle = 'dark'; - /** @var int */ public $leftWidth = 8; /** @var int */ @@ -59,12 +57,18 @@ protected function extractCodeFromClosure(\Closure $fx): string }, $codeArr)); } + /** + * @param \Closure(View): void $fx + */ public function setCodeAndCall(\Closure $fx, string $lang = 'php'): void { $code = $this->extractCodeFromClosure($fx); $this->highLightCode(); - View::addTo(View::addTo($this->left, ['element' => 'pre']), ['element' => 'code'])->addClass($lang)->set($code); + View::addTo(View::addTo($this->left, ['element' => 'pre']), ['element' => 'code']) + ->addClass('language-' . $lang) + ->set($code) + ->js(true)->each(new JsFunction(['i, el'], [new JsExpression('hljs.highlightElement(el)')])); $fx($this->right); } @@ -72,9 +76,8 @@ public function setCodeAndCall(\Closure $fx, string $lang = 'php'): void public function highLightCode(): void { if (!self::$isInitialized) { - $this->getApp()->requireCss('https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.16.2/styles/' . $this->highlightDefaultStyle . '.min.css'); - $this->getApp()->requireJs('https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.16.2/highlight.min.js'); - $this->js(true, (new JsChain('hljs'))->initHighlighting()); + $this->getApp()->requireCss($this->getApp()->cdn['highlight.js'] . '/styles/github-dark-dimmed.min.css'); + $this->getApp()->requireJs($this->getApp()->cdn['highlight.js'] . '/highlight.min.js'); self::$isInitialized = true; } } diff --git a/demos/_includes/DemoActionsUtil.php b/demos/_includes/DemoActionsUtil.php index b19fa332a0..4617cb3e6d 100644 --- a/demos/_includes/DemoActionsUtil.php +++ b/demos/_includes/DemoActionsUtil.php @@ -56,7 +56,7 @@ public static function setupDemoActions(Country $country): void }, ]); - $country->addUserAction('edit_argument_prev', [ + $country->addUserAction('edit_argument_preview', [ 'caption' => 'Argument/Preview', 'description' => 'Ask for argument "Age" and display preview prior to execute', 'args' => [ @@ -99,7 +99,7 @@ public static function setupDemoActions(Country $country): void 'caption' => 'User Confirmation', 'description' => 'Confirm the action using a ConfirmationExecutor', 'confirmation' => function (UserAction $a) { - $iso3 = $a->getEntity()->get(Country::hinting()->fieldName()->iso3); + $iso3 = Country::assertInstanceOf($a->getEntity())->iso3; return 'Are you sure you want to perform this action on: ' . $a->getEntity()->getTitle() . ' (' . $iso3 . ')'; }, diff --git a/demos/_includes/DemoLookup.php b/demos/_includes/DemoLookup.php deleted file mode 100644 index 6422dc8e87..0000000000 --- a/demos/_includes/DemoLookup.php +++ /dev/null @@ -1,66 +0,0 @@ -plus) { - return; - } - - if ($this->plus === true) { - $this->plus = 'Add New'; - } - - if (is_string($this->plus)) { - $this->plus = ['button' => $this->plus]; - } - - $buttonSeed = $this->plus['button'] ?? []; - if (is_string($buttonSeed)) { - $buttonSeed = ['content' => $buttonSeed]; - } - - $defaultSeed = [Button::class, 'class.disabled' => $this->disabled || $this->readOnly]; - $this->action = Factory::factory(array_merge($defaultSeed, $buttonSeed)); - - $vp = VirtualPage::addTo($this->form ?? $this->getOwner()); - $vp->set(function (VirtualPage $vp) { - $form = Form::addTo($vp); - - $entity = $this->model->createEntity(); - $form->setModel($entity, $this->plus['fields'] ?? null); - - $form->onSubmit(function (Form $form) { - $form->model->save(); - - $ret = [ - new JsToast('Form submit!. Data are not save in demo mode.'), - (new Jquery('.atk-modal'))->modal('hide'), - ]; - - $row = $this->renderRow($form->model); - $chain = new Jquery('#' . $this->name . '-ac'); - $chain->dropdown('set value', $row['value'])->dropdown('set text', $row['title']); - $ret[] = $chain; - - return $ret; - }); - }); - - $caption = $this->plus['caption'] ?? 'Add New ' . $this->model->getModelCaption(); - $this->action->js('click', new JsModal($caption, $vp)); - } -} diff --git a/demos/_includes/FlyersForm.php b/demos/_includes/FlyersForm.php index 65f9ba5851..64698313e1 100644 --- a/demos/_includes/FlyersForm.php +++ b/demos/_includes/FlyersForm.php @@ -7,7 +7,7 @@ use Atk4\Data\Model; use Atk4\Data\Persistence; use Atk4\Ui\Form; -use Atk4\Ui\JsToast; +use Atk4\Ui\Js\JsToast; class FlyersForm extends Form { diff --git a/demos/_includes/PromotionText.php b/demos/_includes/PromotionText.php index 3eca0128d7..084938a149 100644 --- a/demos/_includes/PromotionText.php +++ b/demos/_includes/PromotionText.php @@ -17,22 +17,18 @@ protected function init(): void parent::init(); $t = Text::addTo($this); - $t->addParagraph( - <<< 'EOF' - Agile Toolkit base package includes: - EOF - ); - - $t->addHtml( - <<< 'HTML' -
{{ JSON.stringify(this.query, null, 2) }}- -