Init part 3

This commit is contained in:
Snoweuph 2023-05-05 20:53:21 +02:00
parent 635ec65d07
commit ce88550539
45 changed files with 4124 additions and 0 deletions

20
.checkout/.env Normal file
View file

@ -0,0 +1,20 @@
# In all environments, the following files are loaded if they exist,
# the latter taking precedence over the former:
#
# * .env contains default values for the environment variables needed by the app
# * .env.local uncommitted file with local overrides
# * .env.$APP_ENV committed environment-specific defaults
# * .env.$APP_ENV.local uncommitted environment-specific overrides
#
# Real environment variables win over .env files.
#
# DO NOT DEFINE PRODUCTION SECRETS IN THIS FILE NOR IN ANY OTHER COMMITTED FILES.
# https://symfony.com/doc/current/configuration/secrets.html
#
# Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2).
# https://symfony.com/doc/current/best_practices.html#use-environment-variables-for-infrastructure-configuration
###> symfony/framework-bundle ###
APP_ENV=dev
APP_SECRET=5d752842d639735b4175d1abd3d86748
###< symfony/framework-bundle ###

17
.checkout/.gitignore vendored Normal file
View file

@ -0,0 +1,17 @@
###> symfony/framework-bundle ###
/.env.local
/.env.local.php
/.env.*.local
/config/secrets/prod/prod.decrypt.private.php
/public/bundles/
/var/
/vendor/
###< symfony/framework-bundle ###
###> symfony/webpack-encore-bundle ###
/node_modules/
/public/build/
npm-debug.log
yarn-error.log
###< symfony/webpack-encore-bundle ###

7
.checkout/assets/app.ts Normal file
View file

@ -0,0 +1,7 @@
import './styles/app.scss';
// start the Stimulus application
import './bootstrap';
import cards_controller from './controllers/cards_controller';
cards_controller.bindAllCardDragAndDropEvents();

View file

@ -0,0 +1,8 @@
import { startStimulusApp } from '@symfony/stimulus-bridge';
// Registers Stimulus controllers from controllers.json and in the controllers/ directory
export const app = startStimulusApp(require.context(
'@symfony/stimulus-bridge/lazy-controller-loader!./controllers',
true,
/\.[jt]sx?$/
));

View file

@ -0,0 +1,4 @@
{
"controllers": [],
"entrypoints": []
}

View file

@ -0,0 +1,58 @@
import { Controller } from '@hotwired/stimulus';
import cards_controller from './cards_controller';
export default class extends Controller {
static targets = ['value', 'textarea', 'save', 'revert', 'edit', 'delete']
edit_card(){
this.textareaTarget.style.display = 'block'
this.saveTarget.style.display = 'block'
this.revertTarget.style.display = 'block'
this.valueTarget.style.display = 'none'
this.editTarget.style.display = 'none'
this.deleteTarget.style.display = 'none'
this.textareaTarget.value = this.valueTarget.innerText
this.element.draggable = false;
}
edit_card_save(){
var value = this.textareaTarget.value.replace(/(?:\r\n|\r|\n)/g, '%5Cr%5Cn');
console.log(this.textareaTarget.value);
if(value.length === 0){
return
}
const xhttp = new XMLHttpRequest()
xhttp.open("GET", location.origin + `/kanban/api/card/set/value/${this.element.getAttribute('db_id')}/${value}`, true)
xhttp.onreadystatechange = (ev) => {
if (xhttp.readyState === XMLHttpRequest.DONE && xhttp.status === 200) {
this.valueTarget.innerText = this.textareaTarget.value;
this.valueTarget.style.display = 'block'
this.editTarget.style.display = 'block'
this.deleteTarget.style.display = 'block'
this.textareaTarget.style.display = 'none'
this.saveTarget.style.display = 'none'
this.revertTarget.style.display = 'none'
this.element.draggable = true;
}
}
xhttp.send()
}
edit_card_revert(){
this.valueTarget.style.display = 'block'
this.editTarget.style.display = 'block'
this.deleteTarget.style.display = 'block'
this.textareaTarget.style.display = 'none'
this.saveTarget.style.display = 'none'
this.revertTarget.style.display = 'none'
this.element.draggable = true;
}
delete_card(){
const xhttp = new XMLHttpRequest()
xhttp.open("GET", location.origin + `/kanban/api/card/delete/${this.element.getAttribute('db_id')}`, true)
xhttp.onreadystatechange = (ev) => {
if (xhttp.readyState === XMLHttpRequest.DONE && xhttp.status === 200) {
this.element.remove()
}
}
xhttp.send()
}
}

View file

@ -0,0 +1,57 @@
export default class{
static bindAllCardDragAndDropEvents(){
var cards = document.getElementsByClassName("card")
for (let i = 0; i < cards.length; i++) {
cards[i].addEventListener("dragstart", this.onDragStart);
var card = cards[i] as HTMLElement
card.draggable = true
}
var cardLists = document.getElementsByClassName("card-list")
for (let i = 0; i < cardLists.length; i++) {
cardLists[i].addEventListener("drop", this.onDrop)
cardLists[i].addEventListener("dragover", this.allowDrop)
}
}
static bindSpecificCardDragAndDropEvents(card){
card.addEventListener("dragstart", this.onDragStart)
}
static onDragStart(event) {
event.dataTransfer.clearData();
event.dataTransfer.setData("text/plain", event.target.id)
}
static allowDrop(event) {
event.preventDefault()
}
static onDrop(event) {
const data = event.dataTransfer.getData("text")
event.preventDefault()
const cardlist = event.target.closest(".card-list")
const card = document.getElementById(data)
const origin_columnId = card.closest(".column").id
const target_columnId = cardlist.closest(".column").id
const cardId = card.getAttribute("db_id");
if(target_columnId == origin_columnId){
return
}
const xhttp = new XMLHttpRequest()
const xhttp2 = new XMLHttpRequest()
if(target_columnId == "-2"){
xhttp2.open("GET", `${location.origin}/kanban/api/card/set/done/${cardId}/true`, true)
xhttp2.send()
} else if(origin_columnId == "-2"){
xhttp2.open("GET", `${location.origin}/kanban/api/card/set/done/${cardId}/false`, true)
xhttp2.send()
}
xhttp.open("GET", `${location.origin}/kanban/api/card/move/${cardId}/${target_columnId}`, true)
xhttp.onreadystatechange = (ev) => {
if (xhttp.readyState !== XMLHttpRequest.DONE || xhttp.status !== 200){
return
}
cardlist.appendChild(card)
}
xhttp.send()
}
}

View file

@ -0,0 +1,27 @@
import { Controller } from '@hotwired/stimulus';
import cards_controller from './cards_controller';
export default class extends Controller {
static targets = ['value', 'output']
create_card(){
var value = this.valueTarget.value.replace(/(?:\r\n|\r|\n)/g, '%5Cr%5Cn')
if(value.length == 0){
return
}
const xhttp = new XMLHttpRequest()
xhttp.open("GET", location.origin + `/kanban/api/card/create/${this.element.id}/${value}`, true)
xhttp.onreadystatechange = (ev) => {
if (xhttp.readyState === XMLHttpRequest.DONE) {
var template = document.createElement('template')
var response = xhttp.responseText.trim();
response = response.replace(/(?:\\r\\n|\\r|\\n)/g, '<br>\n')
template.innerHTML = response
const card = template.content.firstChild
cards_controller.bindSpecificCardDragAndDropEvents(card)
this.outputTarget.appendChild(card)
this.valueTarget.value = ""
}
}
xhttp.send()
}
}

View file

@ -0,0 +1,11 @@
@import "theme";
@import "components/wrapper";
@import "components/column";
@import "components/card";
:root,html,body{
margin: 0;
height: fit-content;
}

View file

@ -0,0 +1,52 @@
@import '../theme';
.card{
min-height: 5rem;
text-align: left;
padding: 0.5rem;
border-radius: 0.5rem;
background: $bg-3;
outline: $fg-3 solid 0.1rem;
@media (prefers-color-scheme: light) {
background: $bga-3;
outline-color: $fga-3;
}
.card-button{
background: transparent;
border: none;
float: right;
color: $fg-4;
&:hover{
color: $fg-2;
}
@media (prefers-color-scheme: light) {
color: $fga-5;
&:hover{
color: $fga-3;
}
}
}
p{
margin: 0;
}
.card-edit-textarea{
box-sizing: border-box;
width: 100%;
min-width: 100%;
max-width: 100%;
height: 7rem;
min-height: 7rem;
max-height: 15rem;
border-radius: 0.5rem;
border: none;
background: $bg-4;
color: $fg-2;
@media (prefers-color-scheme: light) {
background: $bga-4;
color: $fga-2;
}
}
}

View file

@ -0,0 +1,95 @@
@import '../theme';
.column{
width: 20rem;
min-width: 20rem;
display: flex;
flex-direction: column;
text-align: center;
background: $bg-2;
@media (prefers-color-scheme: light) {
background: $bga-2;
}
border-radius: 0.5rem;
.column-title-bar{
display: flex;
align-items: center;
padding: 0.5rem;
h2{
}
.add-card-button{
margin-left: auto;
width: 3rem;
height: 3rem;
background: transparent;
color: $fg-2;
border: $fg-2 solid 2px;
border-radius: 0.5rem;
&:hover{
box-shadow: 0 0 0.5rem 0 $fg-1 ;
}
@media (prefers-color-scheme: light) {
color: $fga-2;
border-color: $fga-2;
&:hover {
box-shadow: inset 0 0 0.5rem 0 $fga-2;
}
}
}
}
.add-card-textarea-wrapper{
width: 100%;
padding: 0.5rem;
box-sizing: border-box;
.add-card-textarea{
box-sizing: border-box;
width: 100%;
min-width: 100%;
max-width: 100%;
height: 7rem;
min-height: 7rem;
max-height: 15rem;
border-radius: 0.5rem;
border: none;
background: $bg-4;
color: $fg-2;
@media (prefers-color-scheme: light) {
background: $bga-4;
color: $fga-2;
}
}
}
.card-list-wrapper{
padding: 0.5rem;
height: 100%;
.card-list {
list-style-type: none;
display: flex;
padding: 0;
margin: 0;
flex-direction: column;
border-radius: 0.55rem;
gap: 0.5rem;
height: 100%;
}
}
&:first-of-type, &:last-of-type {
.card-list-wrapper > .card-list {
background: repeating-linear-gradient(-45deg, $fg-5, $fg-5 0.5rem, $bg-3 0.5rem, $bg-3 1.5rem);
@media (prefers-color-scheme: light) {
background: repeating-linear-gradient(-45deg, $fga-5, $fga-5 0.5rem, $bga-3 0.5rem, $bga-3 1.5rem);
border-color: $fga-2;
}
}
}
}

View file

@ -0,0 +1,9 @@
.wrapper{
width: 80%;
height: 100%;
margin: 0;
display: flex;
gap: 1rem;
padding: 1rem;
min-height: calc(100vh - 5rem);
}

View file

@ -0,0 +1,49 @@
$white: #fefefe;
$light-1: #e0e0e0;
$light-2: #c0c0c0;
$light-3: #a0a0a0;
$light-4: #808080;
$dark-4: #5c5c5c;
$dark-3: #4c4c4c;
$dark-2: #3c3c3c;
$dark-1: #2c2c2c;
$black: #181818;
//Darkmode (default)
$fg-1: $white;
$fg-2: $light-1;
$fg-3: $light-2;
$fg-4: $light-3;
$fg-5: $light-4;
$bg-5: $dark-4;
$bg-4: $dark-3;
$bg-3: $dark-2;
$bg-2: $dark-1;
$bg-1: $black;
//lightmode
$fga-1: $black;
$fga-2: $dark-1;
$fga-3: $dark-2;
$fga-4: $dark-3;
$fga-5: $dark-4;
$bga-5: $light-4;
$bga-4: $light-3;
$bga-3: $light-2;
$bga-2: $light-1;
$bga-1: $white;
:root,html{
font-family: Helvetica;
background: $bg-1;
color: $fg-2;
@media (prefers-color-scheme: light) {
background: $bga-1;
color: $fga-2;
}
}

17
.checkout/bin/console Normal file
View file

@ -0,0 +1,17 @@
#!/usr/bin/env php
<?php
use App\Kernel;
use Symfony\Bundle\FrameworkBundle\Console\Application;
if (!is_file(dirname(__DIR__).'/vendor/autoload_runtime.php')) {
throw new LogicException('Symfony Runtime is missing. Try running "composer require symfony/runtime".');
}
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
return function (array $context) {
$kernel = new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
return new Application($kernel);
};

68
.checkout/composer.json Normal file
View file

@ -0,0 +1,68 @@
{
"type": "project",
"license": "proprietary",
"minimum-stability": "stable",
"prefer-stable": true,
"require": {
"php": ">=8.1",
"ext-ctype": "*",
"ext-iconv": "*",
"symfony/console": "6.2.*",
"symfony/dotenv": "6.2.*",
"symfony/flex": "^2",
"symfony/framework-bundle": "6.2.*",
"symfony/runtime": "6.2.*",
"symfony/twig-bundle": "6.2.*",
"symfony/webpack-encore-bundle": "^1.16",
"symfony/yaml": "6.2.*",
"ext-pdo": "*"
},
"config": {
"allow-plugins": {
"php-http/discovery": true,
"symfony/flex": true,
"symfony/runtime": true
},
"sort-packages": true
},
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"App\\Tests\\": "tests/"
}
},
"replace": {
"symfony/polyfill-ctype": "*",
"symfony/polyfill-iconv": "*",
"symfony/polyfill-php72": "*",
"symfony/polyfill-php73": "*",
"symfony/polyfill-php74": "*",
"symfony/polyfill-php80": "*",
"symfony/polyfill-php81": "*"
},
"scripts": {
"auto-scripts": {
"cache:clear": "symfony-cmd",
"assets:install %PUBLIC_DIR%": "symfony-cmd"
},
"post-install-cmd": [
"@auto-scripts"
],
"post-update-cmd": [
"@auto-scripts"
]
},
"conflict": {
"symfony/symfony": "*"
},
"extra": {
"symfony": {
"allow-contrib": false,
"require": "6.2.*"
}
}
}

2899
.checkout/composer.lock generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,9 @@
<?php
class Config
{
public static string $database_host = "localhost";
public static string $database_name = "dominik_php_kanban";
public static string $database_user = "root";
public static string $database_password = "";
}

View file

@ -0,0 +1,7 @@
<?php
return [
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
Symfony\WebpackEncoreBundle\WebpackEncoreBundle::class => ['all' => true],
];

View file

@ -0,0 +1,19 @@
framework:
cache:
# Unique name of your app: used to compute stable namespaces for cache keys.
#prefix_seed: your_vendor_name/app_name
# The "app" cache stores to the filesystem by default.
# The data in this cache should persist between deploys.
# Other options include:
# Redis
#app: cache.adapter.redis
#default_redis_provider: redis://localhost
# APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues)
#app: cache.adapter.apcu
# Namespaced pools use the above "app" backend by default
#pools:
#my.dedicated.cache: null

View file

@ -0,0 +1,25 @@
# see https://symfony.com/doc/current/reference/configuration/framework.html
framework:
secret: '%env(APP_SECRET)%'
#csrf_protection: true
http_method_override: false
handle_all_throwables: true
# Enables session support. Note that the session will ONLY be started if you read or write from it.
# Remove or comment this section to explicitly disable session support.
session:
handler_id: null
cookie_secure: auto
cookie_samesite: lax
storage_factory_id: session.storage.factory.native
#esi: true
#fragments: true
php_errors:
log: true
when@test:
framework:
test: true
session:
storage_factory_id: session.storage.factory.mock_file

View file

@ -0,0 +1,12 @@
framework:
router:
utf8: true
# Configure how to generate URLs in non-HTTP contexts, such as CLI commands.
# See https://symfony.com/doc/current/routing.html#generating-urls-in-commands
#default_uri: http://localhost
when@prod:
framework:
router:
strict_requirements: null

View file

@ -0,0 +1,6 @@
twig:
default_path: '%kernel.project_dir%/templates'
when@test:
twig:
strict_variables: true

View file

@ -0,0 +1,45 @@
webpack_encore:
# The path where Encore is building the assets - i.e. Encore.setOutputPath()
output_path: '%kernel.project_dir%/public/build'
# If multiple builds are defined (as shown below), you can disable the default build:
# output_path: false
# Set attributes that will be rendered on all script and link tags
script_attributes:
defer: true
# Uncomment (also under link_attributes) if using Turbo Drive
# https://turbo.hotwired.dev/handbook/drive#reloading-when-assets-change
# 'data-turbo-track': reload
# link_attributes:
# Uncomment if using Turbo Drive
# 'data-turbo-track': reload
# If using Encore.enableIntegrityHashes() and need the crossorigin attribute (default: false, or use 'anonymous' or 'use-credentials')
# crossorigin: 'anonymous'
# Preload all rendered script and link tags automatically via the HTTP/2 Link header
# preload: true
# Throw an exception if the entrypoints.json file is missing or an entry is missing from the data
# strict_mode: false
# If you have multiple builds:
# builds:
# frontend: '%kernel.project_dir%/public/frontend/build'
# pass the build name as the 3rd argument to the Twig functions
# {{ encore_entry_script_tags('entry1', null, 'frontend') }}
framework:
assets:
json_manifest_path: '%kernel.project_dir%/public/build/manifest.json'
#when@prod:
# webpack_encore:
# # Cache the entrypoints.json (rebuild Symfony's cache when entrypoints.json changes)
# # Available in version 1.2
# cache: true
#when@test:
# webpack_encore:
# strict_mode: false

View file

@ -0,0 +1,5 @@
<?php
if (file_exists(dirname(__DIR__).'/var/cache/prod/App_KernelProdContainer.preload.php')) {
require dirname(__DIR__).'/var/cache/prod/App_KernelProdContainer.preload.php';
}

View file

@ -0,0 +1,5 @@
controllers:
resource:
path: ../src/Controller/
namespace: App\Controller
type: attribute

View file

@ -0,0 +1,32 @@
app_kanban:
path: /kanban
controller: App\Controller\Kanban\KanbanController::render_board
app_create_card:
path: /kanban/api/card/create/{column_id}/{value}
controller: App\Controller\Kanban\CardController::create_card
requirements:
column_id: '-?\d+'
value: '.+'
app_delete_card:
path: /kanban/api/card/delete/{id}
controller: App\Controller\Kanban\CardController::delete_card
requirements:
id: '\d+'
app_move_card:
path: /kanban/api/card/move/{card_id}/{column_id}
controller: App\Controller\Kanban\CardController::move_card
requirements:
card_id: '\d+'
column_id: '-?\d+'
app_set_card_value:
path: /kanban/api/card/set/value/{card_id}/{value}
controller: App\Controller\Kanban\CardController::set_card_value
requirements:
card_id: '\d+'
value: '.+'
app_set_card_done:
path: /kanban/api/card/set/done/{card_id}/{is_done}
controller: App\Controller\Kanban\CardController::set_card_done
requirements:
card_id: '\d+'
is_done: true|false

View file

@ -0,0 +1,4 @@
when@dev:
_errors:
resource: '@FrameworkBundle/Resources/config/routing/errors.xml'
prefix: /_error

View file

@ -0,0 +1,24 @@
# This file is the entry point to configure your own services.
# Files in the packages/ subdirectory configure your dependencies.
# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
parameters:
services:
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/'
exclude:
- '../src/DependencyInjection/'
- '../src/Entity/'
- '../src/Kernel.php'
# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones

27
.checkout/package.json Normal file
View file

@ -0,0 +1,27 @@
{
"devDependencies": {
"@babel/core": "^7.17.0",
"@babel/preset-env": "^7.16.0",
"@hotwired/stimulus": "^3.0.0",
"@symfony/stimulus-bridge": "^3.2.0",
"@symfony/webpack-encore": "^4.0.0",
"core-js": "^3.23.0",
"fork-ts-checker-webpack-plugin": "^7.0.0",
"regenerator-runtime": "^0.13.9",
"sass": "^1.62.1",
"sass-loader": "^13.0.0",
"ts-loader": "^9.0.0",
"typescript": "^5.0.4",
"webpack": "^5.74.0",
"webpack-cli": "^4.10.0",
"webpack-notifier": "^1.15.0"
},
"license": "UNLICENSED",
"private": true,
"scripts": {
"dev-server": "encore dev-server",
"dev": "encore dev",
"watch": "encore dev --watch",
"build": "encore production --progress"
}
}

View file

@ -0,0 +1,9 @@
<?php
use App\Kernel;
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
return function (array $context) {
return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
};

25
.checkout/schema.sql Normal file
View file

@ -0,0 +1,25 @@
CREATE TABLE `cards` (
`id` bigint(20) UNSIGNED NOT NULL,
`value` varchar(256) DEFAULT NULL,
`column_id` int(10) UNSIGNED DEFAULT NULL,
`is_done` tinyint(1) NOT NULL DEFAULT 0
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
CREATE TABLE `columns` (
`id` int(10) UNSIGNED NOT NULL,
`title` varchar(64) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
ALTER TABLE `cards`
ADD PRIMARY KEY (`id`);
ALTER TABLE `columns`
ADD PRIMARY KEY (`id`);
ALTER TABLE `cards`
MODIFY `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT;
ALTER TABLE `columns`
MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;
COMMIT;

0
.checkout/src/Controller/.gitignore vendored Normal file
View file

View file

@ -0,0 +1,44 @@
<?php
namespace App\Controller\Kanban;
use App\Model\Card;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
class CardController extends AbstractController
{
public function create_card(int $column_id, string $value): Response
{
Card::initPDO();
$new_card = Card::createCard($value, $column_id);
return $this->render('kanban/card.html.twig', [
"card" => $new_card
]);
}
public function delete_card(int $id): Response
{
Card::initPDO();
$response_code = $new_card = Card::deleteCard($id) ? 200 : 417;
return new Response($response_code);
}
public function move_card(int $card_id, int $column_id): Response
{
Card::initPDO();
$response_code = Card::moveCard($card_id, $column_id) ? 200 : 417;
return new Response($response_code);
}
public function set_card_value(int $card_id, string $value): Response
{
Card::initPDO();
$response_code = Card::setCardValue($card_id, $value) ? 200 : 417;
return new Response($response_code);
}
public function set_card_done(int $card_id, string $is_done): Response
{
Card::initPDO();
$response_code = Card::setCardDone($card_id, $is_done == "true") ? 200 : 417;
return new Response($response_code);
}
}

View file

@ -0,0 +1,25 @@
<?php
namespace App\Controller\Kanban;
use App\Model\Card;
use App\Model\Column;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
class KanbanController extends AbstractController
{
public function render_board(): Response
{
Column::initPDO();
Card::initPDO();
$columns = Column::getColumns();
$cards = Card::getCards($columns);
return $this->render('kanban/board.html.twig', [
"cards" => $cards,
"columns" => $columns
]);
}
}

13
.checkout/src/Kernel.php Normal file
View file

@ -0,0 +1,13 @@
<?php
namespace App;
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
require __DIR__ . '/../config/Config.php';
class Kernel extends BaseKernel
{
use MicroKernelTrait;
}

View file

@ -0,0 +1,96 @@
<?php
namespace App\Model;
use Config;
use PDO;
use Symfony\Component\Console\Logger\ConsoleLogger;
class Card
{
private int $id;
public Column $column;
public string $value;
private bool $is_done;
private static PDO $pdo;
private function __construct(int $id)
{
$this->id = $id;
}
public static function initPDO(){
self::$pdo = new PDO("mysql:host=" . Config::$database_host . ";dbname=" . Config::$database_name, Config::$database_user, Config::$database_password);
}
public static function getCards(array $columns) : array{
$cards = array();
$sql = "SELECT * FROM `cards` WHERE 1";
foreach (self::$pdo->query($sql) as $row) {
$card = new Card($row["id"]);
$card->value = $row["value"];
$card->is_done = $row["is_done"];
$column_id = $row["column_id"];
if($column_id == null&& $column_id !== 0){
$card->column = $columns[$card->is_done ? "done" : "unsorted"];
$columns[$card->is_done ? "done" : "unsorted"]->cards[$card->id] = $card;
$cards[] = $card;
continue;
}
$card->column = $columns[$column_id];
$columns[$column_id]->cards[$card->id] = $card;
$cards[] = $card;
}
return $cards;
}
public static function createCard(string $value, int $column_id) : Card{
$lines = explode("\\r\\n", $value);
$value = "";
foreach ($lines as $line){
$value .= $line . PHP_EOL;
}
if($column_id < 0){
$sql = self::$pdo->prepare("INSERT INTO `cards`(`value`) VALUES (:value)");
$sql->bindParam(':value', $value);
}else{
$sql = self::$pdo->prepare("INSERT INTO `cards`(`value`, `column_id`) VALUES (:value, :column_id)");
$sql->bindParam(':value', $value);
$sql->bindParam(':column_id', $column_id);
}
$sql->execute();
$new_id = self::$pdo->lastInsertId();
$card = new Card($new_id);
$card->value = $value;
$fake_column = new Column($column_id);
$card->column = $fake_column;
return $card;
}
public static function deleteCard(int $id): bool{
$sql = self::$pdo->prepare("DELETE FROM `cards` WHERE `id`=:id");
$sql->bindParam(':id', $id);
return $sql->execute();
}
public static function moveCard(int $card_id, int $column_id): bool{
$sql = self::$pdo->prepare("UPDATE `cards` SET `column_id` = :column_id WHERE `id`=:id");
$new_column_id = $column_id >= 0 ? $column_id : null;
$sql->bindParam(':column_id', $new_column_id);
$sql->bindParam(':id', $card_id);
return $sql->execute();
}
public static function setCardValue(int $card_id, string $value): bool{
$sql = self::$pdo->prepare("UPDATE `cards` SET `value` = '$value' WHERE `id`=:id");
$sql->bindParam(':id', $card_id);
return $sql->execute();
}
public static function setCardDone(int $card_id, bool $is_done): bool{
$sql = self::$pdo->prepare("UPDATE `cards` SET `is_done` = :is_done WHERE `id`=:id");
$sql->bindParam(':is_done', $is_done);
$sql->bindParam(':id', $card_id);
return $sql->execute();
}
public function getId(): int
{
return $this->id;
}
}

View file

@ -0,0 +1,51 @@
<?php
namespace App\Model;
use Config;
use PDO;
class Column
{
private int $id;
public array $cards;
public string $title;
private static PDO $pdo;
public function __construct(int $id)
{
$this->id = $id;
}
public static function initPDO(){
self::$pdo = new PDO("mysql:host=" . Config::$database_host . ";dbname=" . Config::$database_name, Config::$database_user, Config::$database_password);
}
public static function getColumns() : array{
$columns = array();
$unsorted_column = new Column(-1);
$unsorted_column->title = "Unsorted";
$unsorted_column->cards = array();
$columns["unsorted"] = $unsorted_column;
$sql = "SELECT * FROM `columns` WHERE 1";
foreach (self::$pdo->query($sql) as $row) {
$column_id = "" . $row["id"];
$column = new Column($row["id"]);
$column->title = $row["title"];
$column->cards = array();
$columns[$column_id] = $column;
}
$done_column = new Column(-2);
$done_column->title = "Done";
$done_column->cards = array();
$columns["done"] = $done_column;
return $columns;
}
public function getId(): int
{
return $this->id;
}
public function getCards(): array{
return $this->cards;
}
}

90
.checkout/symfony.lock Normal file
View file

@ -0,0 +1,90 @@
{
"symfony/console": {
"version": "6.2",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "5.3",
"ref": "da0c8be8157600ad34f10ff0c9cc91232522e047"
},
"files": [
"./bin/console"
]
},
"symfony/flex": {
"version": "2.2",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "1.0",
"ref": "146251ae39e06a95be0fe3d13c807bcf3938b172"
},
"files": [
"./.env"
]
},
"symfony/framework-bundle": {
"version": "6.2",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "6.2",
"ref": "af47254c5e4cd543e6af3e4508298ffebbdaddd3"
},
"files": [
"./config/packages/cache.yaml",
"./config/packages/framework.yaml",
"./config/preload.php",
"./config/routes/framework.yaml",
"./config/services.yaml",
"./public/index.php",
"./src/Controller/.gitignore",
"./src/Kernel.php"
]
},
"symfony/routing": {
"version": "6.2",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "6.2",
"ref": "e0a11b4ccb8c9e70b574ff5ad3dfdcd41dec5aa6"
},
"files": [
"./config/packages/routing.yaml",
"./config/routes.yaml"
]
},
"symfony/twig-bundle": {
"version": "6.2",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "5.4",
"ref": "bb2178c57eee79e6be0b297aa96fc0c0def81387"
},
"files": [
"./config/packages/twig.yaml",
"./templates/base.html.twig"
]
},
"symfony/webpack-encore-bundle": {
"version": "1.16",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "1.10",
"ref": "f8fc53f1942f76679e9ee3c25fd44865355707b5"
},
"files": [
"./assets/app.js",
"./assets/bootstrap.ts",
"./assets/controllers.json",
"./assets/controllers/hello_controller.js",
"./assets/styles/app.css",
"./config/packages/webpack_encore.yaml",
"./package.json",
"./webpack.config.js"
]
}
}

View file

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{% block title %}Welcome!{% endblock %}</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 128 128%22><text y=%221.2em%22 font-size=%2296%22>⚫️</text></svg>">
{# Run `composer require symfony/webpack-encore-bundle` to start using Symfony UX #}
{% block stylesheets %}
{{ encore_entry_link_tags('app') }}
{% endblock %}
{% block javascripts %}
{{ encore_entry_script_tags('app') }}
{% endblock %}
</head>
<body>
{% block body %}{% endblock %}
</body>
</html>

View file

@ -0,0 +1,20 @@
<!doctype html>
<head>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.2/css/all.min.css">
{% block stylesheets %}
{{ encore_entry_link_tags('app') }}
{% endblock %}
{% block javascripts %}
{{ encore_entry_script_tags('app') }}
{% endblock %}
</head>
<body>
<div class="wrapper">
{% for column in columns %}
{{ include('kanban/column.html.twig') }}
{% endfor %}
</div>
</body>

View file

@ -0,0 +1,8 @@
<li id="card{{ card.getId }}" db_id="{{ card.getId }}" class="card" {{ stimulus_controller('card') }}>
<button class="card-button card-edit-button fa-solid fa-rotate-left" {{ stimulus_action('card', 'edit_card_revert') }} {{ stimulus_target('card', 'revert') }} style="display: none"></button>
<button class="card-button card-edit-button fa-solid fa-floppy-disk" {{ stimulus_action('card', 'edit_card_save') }} {{ stimulus_target('card', 'save') }} style="display: none"></button>
<button class="card-button fa-solid fa-trash" {{ stimulus_action('card', 'delete_card') }} {{ stimulus_target('card', 'delete') }}></button>
<button class="card-button fa-solid fa-pen" {{ stimulus_action('card', 'edit_card') }} {{ stimulus_target('card', 'edit') }}></button>
<p {{ stimulus_target('card', 'value') }}>{{ card.value | nl2br }}</p>
<textarea class="card-edit-textarea" {{ stimulus_target('card', 'textarea') }} style="display: none"></textarea>
</li>

View file

@ -0,0 +1,20 @@
<div class='column' id='{{ column.getId}}' {{ stimulus_controller('column') }}>
<div class="column-title-bar">
<h2>{{ column.title }}</h2>
{% if column.getId != -2 %}
<button class="add-card-button fa-solid fa-plus fa-xl" {{ stimulus_action('column', 'create_card') }}></button>
{%endif %}
</div>
{% if column.getId != -2 %}
<div class="add-card-textarea-wrapper">
<textarea class="add-card-textarea" {{ stimulus_target('column', 'value') }}></textarea>
</div>
{%endif %}
<div class="card-list-wrapper">
<ul class='card-list' {{ stimulus_target('column', 'output') }}>
{% for card in column.getCards %}
{{ include('kanban/card.html.twig') }}
{% endfor %}
</ul>
</div>
</div>

1
.checkout/tsconfig.json Normal file
View file

@ -0,0 +1 @@
{}

View file

@ -0,0 +1,77 @@
const Encore = require('@symfony/webpack-encore');
// Manually configure the runtime environment if not already configured yet by the "encore" command.
// It's useful when you use tools that rely on webpack.config.js file.
if (!Encore.isRuntimeEnvironmentConfigured()) {
Encore.configureRuntimeEnvironment(process.env.NODE_ENV || 'dev');
}
Encore
// directory where compiled assets will be stored
.setOutputPath('public/build/')
// public path used by the web server to access the output path
.setPublicPath('/build')
// only needed for CDN's or subdirectory deploy
//.setManifestKeyPrefix('build/')
/*
* ENTRY CONFIG
*
* Each entry will result in one JavaScript file (e.g. app.js)
* and one CSS file (e.g. app.css) if your JavaScript imports CSS.
*/
.addEntry('app', './assets/app.ts')
// enables the Symfony UX Stimulus bridge (used in assets/bootstrap.js)
.enableStimulusBridge('./assets/controllers.json')
// When enabled, Webpack "splits" your files into smaller pieces for greater optimization.
.splitEntryChunks()
// will require an extra script tag for runtime.js
// but, you probably want this, unless you're building a single-page app
.enableSingleRuntimeChunk()
/*
* FEATURE CONFIG
*
* Enable & configure other features below. For a full
* list of features, see:
* https://symfony.com/doc/current/frontend.html#adding-more-features
*/
.cleanupOutputBeforeBuild()
.enableBuildNotifications()
.enableSourceMaps(!Encore.isProduction())
// enables hashed filenames (e.g. app.abc123.css)
.enableVersioning(Encore.isProduction())
// configure Babel
// .configureBabel((config) => {
// config.plugins.push('@babel/a-babel-plugin');
// })
// enables and configure @babel/preset-env polyfills
.configureBabelPresetEnv((config) => {
config.useBuiltIns = 'usage';
config.corejs = '3.23';
})
// enables Sass/SCSS support
.enableSassLoader()
// uncomment if you use TypeScript
.enableTypeScriptLoader()
// uncomment if you use React
//.enableReactPreset()
// uncomment to get integrity="..." attributes on your script & link tags
// requires WebpackEncoreBundle 1.4 or higher
//.enableIntegrityHashes(Encore.isProduction())
// uncomment if you're having problems with a jQuery plugin
//.autoProvidejQuery()
.enableForkedTypeScriptTypesChecking()
;
module.exports = Encore.getWebpackConfig();

8
.idea/.gitignore vendored Normal file
View file

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml