Compare commits

..

18 Commits

Author SHA1 Message Date
0af54a0d52 Merge pull request 'Prise en compte de la pagination sur la récupération des projets' (#44) from feature/fix-projects-fetch into develop
Reviewed-on: #44
2021-04-02 10:57:23 +02:00
d8dcf636ea Prise en compte de la pagination sur la récupération des projets 2021-04-02 10:54:49 +02:00
6bb8afd914 Merge branch 'issue-35' of wpetit/gengitkan into develop 2020-06-17 11:36:21 +02:00
3eb96fc75e Amelioration affichage colonnes fermees 2020-06-17 11:33:09 +02:00
d6597270dd Fixe erreur lors du deplacement d'un ticket d'une colonne a une autre 2020-06-17 11:15:55 +02:00
fbad143bed Factorisation de l'ouverture/fermeture d'une colonne 2020-06-16 11:31:11 +02:00
e3459d136e Ajout mode compacté 2020-06-16 11:23:57 +02:00
fe0e2667a0 Correction affichage issue ajouter depuis l'UI 2020-06-16 10:54:11 +02:00
0e93e7c52f Permettre la réduction des colonnes d'un tableau 2020-06-05 17:14:55 +02:00
832cca1c66 Refonte de l'UI des tableaux 2020-06-05 16:18:53 +02:00
94bfb77d87 Merge branch 'issue-31' of wpetit/gengitkan into develop 2020-06-05 10:34:47 +02:00
5a677d2491 Ajout commande d'installation des dépendances client #31 2020-06-05 10:33:36 +02:00
fbb2b3f8da Ajout d'une redirection automatique sur la page "referer" en cas de
perte de session
2020-05-19 22:22:37 +02:00
44182fd1cd Modification nom cookie 2020-05-19 22:22:24 +02:00
4e9298f5b6 Filtrage des PRs de la liste des issues 2020-05-19 17:21:46 +02:00
9dce43fd58 Intégration de github.com/zalmoxisus/redux-devtools-extension 2020-05-19 17:20:31 +02:00
3fa2b5905a Correction auto-logout 2020-05-06 11:30:26 +02:00
b456fe9f65 Correction algorithme de détection des tickets non collectés
Voir #22
2020-04-30 17:27:58 +02:00
25 changed files with 376 additions and 206 deletions

View File

@ -9,6 +9,7 @@
### Procédure ### Procédure
```bash ```bash
cd client && npm install # Installation des dépendances client
make watch # Surveiller les modifications sur le sources et compiler/démarrer le serveur make watch # Surveiller les modifications sur le sources et compiler/démarrer le serveur
``` ```

View File

@ -1162,22 +1162,6 @@
} }
} }
}, },
"@babel/runtime-corejs2": {
"version": "7.7.4",
"resolved": "https://registry.npmjs.org/@babel/runtime-corejs2/-/runtime-corejs2-7.7.4.tgz",
"integrity": "sha512-hKNcmHQbBSJFnZ82ewYtWDZ3fXkP/l1XcfRtm7c8gHPM/DMecJtFFBEp7KMLZTuHwwb7RfemHdsEnd7L916Z6A==",
"requires": {
"core-js": "^2.6.5",
"regenerator-runtime": "^0.13.2"
},
"dependencies": {
"regenerator-runtime": {
"version": "0.13.3",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz",
"integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw=="
}
}
},
"@babel/template": { "@babel/template": {
"version": "7.7.0", "version": "7.7.0",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.7.0.tgz", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.7.0.tgz",
@ -1259,11 +1243,11 @@
"dev": true "dev": true
}, },
"@lourenci/react-kanban": { "@lourenci/react-kanban": {
"version": "0.15.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/@lourenci/react-kanban/-/react-kanban-0.15.0.tgz", "resolved": "https://registry.npmjs.org/@lourenci/react-kanban/-/react-kanban-2.0.0.tgz",
"integrity": "sha512-/2XjB26iXcvpwDwlT3sz8/ptQ7QyTpMGlrPf1f02+V1Z4jdbVMo6Luz1sGlHe/TP68N8yz69/YT9qwqHZ6YYmQ==", "integrity": "sha512-ieNi7d/01wgT9t8kN7Z/RBubyZq9VvKXyU/5sj+UlrY8h4GPRNrN11jLV/ykg55lhyXGKgXDk3ObYurOfrmu3w==",
"requires": { "requires": {
"react-beautiful-dnd": "^11.0.0" "react-beautiful-dnd": "^13.0.0"
} }
}, },
"@redux-saga/core": { "@redux-saga/core": {
@ -2530,7 +2514,8 @@
"core-js": { "core-js": {
"version": "2.6.10", "version": "2.6.10",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.10.tgz", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.10.tgz",
"integrity": "sha512-I39t74+4t+zau64EN1fE5v2W31Adtc/REhzWN+gWRRXg6WH5qAsZm62DHpQ1+Yhe4047T55jvzz7MUqF/dBBlA==" "integrity": "sha512-I39t74+4t+zau64EN1fE5v2W31Adtc/REhzWN+gWRRXg6WH5qAsZm62DHpQ1+Yhe4047T55jvzz7MUqF/dBBlA==",
"dev": true
}, },
"core-js-compat": { "core-js-compat": {
"version": "3.4.1", "version": "3.4.1",
@ -2635,9 +2620,9 @@
} }
}, },
"css-box-model": { "css-box-model": {
"version": "1.2.0", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.0.tgz", "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz",
"integrity": "sha512-lri0br+jSNV0kkkiGEp9y9y3Njq2PmpqbeGWRFQJuZteZzY9iC9GZhQ8Y4WpPwM/2YocjHePxy14igJY7YKzkA==", "integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==",
"requires": { "requires": {
"tiny-invariant": "^1.0.6" "tiny-invariant": "^1.0.6"
} }
@ -6201,18 +6186,32 @@
} }
}, },
"react-beautiful-dnd": { "react-beautiful-dnd": {
"version": "11.0.5", "version": "13.0.0",
"resolved": "https://registry.npmjs.org/react-beautiful-dnd/-/react-beautiful-dnd-11.0.5.tgz", "resolved": "https://registry.npmjs.org/react-beautiful-dnd/-/react-beautiful-dnd-13.0.0.tgz",
"integrity": "sha512-7llby9U+jIfkINcyxPHVWU0HFYzqxMemUYgGHsFsbx4fZo1n/pW6sYKYzhxGxR3Ap5HxqswcQkKUZX4uEUWhlw==", "integrity": "sha512-87It8sN0ineoC3nBW0SbQuTFXM6bUqM62uJGY4BtTf0yzPl8/3+bHMWkgIe0Z6m8e+gJgjWxefGRVfpE3VcdEg==",
"requires": { "requires": {
"@babel/runtime-corejs2": "^7.4.5", "@babel/runtime": "^7.8.4",
"css-box-model": "^1.1.2", "css-box-model": "^1.2.0",
"memoize-one": "^5.0.4", "memoize-one": "^5.1.1",
"raf-schd": "^4.0.0", "raf-schd": "^4.0.2",
"react-redux": "^7.0.3", "react-redux": "^7.1.1",
"redux": "^4.0.1", "redux": "^4.0.4",
"tiny-invariant": "^1.0.4", "use-memo-one": "^1.1.1"
"use-memo-one": "^1.1.0" },
"dependencies": {
"@babel/runtime": {
"version": "7.10.2",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.2.tgz",
"integrity": "sha512-6sF3uQw2ivImfVIl62RZ7MXhO2tap69WeWK57vAaimT6AZbE4FbqjdEJIN1UqoD6wI6B+1n9UiagafH1sxjOtg==",
"requires": {
"regenerator-runtime": "^0.13.4"
}
},
"regenerator-runtime": {
"version": "0.13.5",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz",
"integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA=="
}
} }
}, },
"react-dom": { "react-dom": {

View File

@ -48,7 +48,7 @@
"ts-loader": "^7.0.2" "ts-loader": "^7.0.2"
}, },
"dependencies": { "dependencies": {
"@lourenci/react-kanban": "^0.15.0", "@lourenci/react-kanban": "^2.0.0",
"bulma": "^0.7.2", "bulma": "^0.7.2",
"bulma-switch": "^2.0.0", "bulma-switch": "^2.0.0",
"react": "^16.12.0", "react": "^16.12.0",

View File

@ -1,7 +1,7 @@
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import { Page } from '../Page'; import { Page } from '../Page';
import { connect, DispatchProp } from 'react-redux'; import { connect, DispatchProp } from 'react-redux';
import Board from '@lourenci/react-kanban'; import Board, { addColumn } from '@lourenci/react-kanban';
import { fetchBoards } from '../../store/actions/boards'; import { fetchBoards } from '../../store/actions/boards';
import { createIssue } from '../../store/actions/issues'; import { createIssue } from '../../store/actions/issues';
import { buildKanboard, moveCard } from '../../store/actions/kanboards'; import { buildKanboard, moveCard } from '../../store/actions/kanboards';
@ -13,6 +13,7 @@ export interface BoardPageProps extends DispatchProp {
board: any board: any
kanboard: any kanboard: any
} }
export class BoardPage extends React.Component<BoardPageProps> { export class BoardPage extends React.Component<BoardPageProps> {
state = { state = {
@ -24,6 +25,7 @@ export class BoardPage extends React.Component<BoardPageProps> {
project: "" project: ""
}, },
compactMode: true, compactMode: true,
hasError: false,
} }
onNewCardTitleChange: (evt: any) => void; onNewCardTitleChange: (evt: any) => void;
@ -41,19 +43,22 @@ export class BoardPage extends React.Component<BoardPageProps> {
this.onNewCardSaveClick = this.onNewCardSaveClick.bind(this); this.onNewCardSaveClick = this.onNewCardSaveClick.bind(this);
} }
componentDidCatch(error, errorInfo) {
// You can also log the error to an error reporting service
console.error(error, errorInfo);
}
render() { render() {
const { board } = this.props; const { board } = this.props;
return ( return (
<Page title={`${board ? (board.title + ' - ') : ''}GenGitKan`}> <Page title={`${board ? (board.title + ' - ') : ''}GenGitKan`}>
<div className="container is-fluid"> {this.renderBoard()}
{this.renderBoard()}
</div>
</Page> </Page>
); );
} }
renderBoard() { renderBoard() {
const { kanboard } = this.props; const { kanboard, board } = this.props;
if (!kanboard) { if (!kanboard) {
return <Loader></Loader> return <Loader></Loader>
@ -61,28 +66,48 @@ export class BoardPage extends React.Component<BoardPageProps> {
return ( return (
<Fragment> <Fragment>
<div className="is-clearfix has-margin-top-normal"> <nav className="navbar is-light">
<div className="is-pulled-right"> <div className="container is-fluid">
<div className="field"> <div className="navbar-start">
<input id="compactMode" <div className="navbar-item">
checked={this.state.compactMode} {board.title}
onChange={this.onCompactModeChange.bind(this)} </div>
type="checkbox" </div>
className="switch is-rtl" <div className="navbar-end">
name="compactMode" <div className="navbar-item">
/>
<label htmlFor="compactMode">Mode compact</label> <div className="field">
<input id="compactMode"
checked={this.state.compactMode}
onChange={this.onCompactModeChange.bind(this)}
type="checkbox"
className="switch is-outlined is-success"
name="compactMode"
/>
<label htmlFor="compactMode">Mode compact</label>
</div>
</div>
<a href={`#/boards/${board.id}/edit`} className="navbar-item">
<span className="icon">
<i className="fa fa-edit fa-fw"></i>
</span>
<span>Modifier le tableau</span>
</a>
</div> </div>
</div> </div>
</div> </nav>
<div className="kanboard-container is-fullheight"> <div className="container is-fluid">
<Board disableLaneDrag={true} <div className="kanboard-container is-fullheight">
renderCard={this.renderCard.bind(this)} <Board
renderLaneHeader={this.renderLaneHeader} renderCard={this.renderCard.bind(this)}
onCardDragEnd={this.onCardDragEnd.bind(this)}> renderColumnHeader={this.renderLaneHeader.bind(this)}
{kanboard} onCardDragEnd={this.onCardDragEnd.bind(this)}
</Board> disableColumnDrag={true}
{ this.renderNewCardModal() } >
{kanboard}
</Board>
{ this.renderNewCardModal() }
</div>
</div> </div>
</Fragment> </Fragment>
@ -167,28 +192,49 @@ export class BoardPage extends React.Component<BoardPageProps> {
<div className="kanboard-lane"> <div className="kanboard-lane">
<div className="level"> <div className="level">
<div className="level-left"> <div className="level-left">
<h3 className="level-item is-size-3">{lane.title}</h3> <div className="level-item">
<span className="tag is-primary is-light is-normal">{lane.cards.length}</span>
</div>
<button className="button is-light level-item is-small expand"
onClick={this.onMinimizeColumn}>
<span className="icon">
<i className="fas fa-chevron-right" aria-hidden="true"></i>
</span>
</button>
<h3 className="level-item is-size-5">
{lane.title}
</h3>
</div> </div>
<div className="level-right"> <div className="level-right is-show-expand">
<button className="button is-light level-item is-medium" <button className="button is-light level-item is-small"
onClick={this.onNewCardClick.bind(this, lane.id)}> onClick={this.onNewCardClick.bind(this, lane.id)}>
<span className="icon"> <span className="icon">
<i className="fas fa-plus" aria-hidden="true"></i> <i className="fas fa-plus" aria-hidden="true"></i>
</span> </span>
</button> </button>
<button className="button is-light level-item is-small"
onClick={this.onMinimizeColumn}>
<span className="icon">
<i className="fas fa-chevron-left" aria-hidden="true"></i>
</span>
</button>
</div> </div>
</div> </div>
</div> </div>
) )
} }
onCardDragEnd(source: any, dest: any) { onMinimizeColumn(e: any) {
e.currentTarget.closest('.react-kanban-column').classList.toggle('minimized');
}
onCardDragEnd(card: any, source: any, dest: any) {
const { board } = this.props; const { board } = this.props;
this.props.dispatch(moveCard( this.props.dispatch(moveCard(
board.id, board.id,
source.fromLaneId, source.fromColumnId,
source.fromPosition, source.fromPosition,
dest.toLaneId, dest.toColumnId,
dest.toPosition dest.toPosition
)); ));
} }

View File

@ -16,41 +16,50 @@ export class IssueCard extends React.PureComponent<IssueCardProps> {
return ( return (
<div className="kanboard-card"> <div className="kanboard-card">
<div className={`box ${compact ? 'has-padding-small': ''}`}> <div className="box has-padding-small is-radiusless">
<div className="media"> <div className="media">
{
card.issue.assignee && !compact ?
<div className="media-left">
<figure className="image is-64x64">
<img src={card.issue.assignee.avatar_url} alt="Image" />
</figure>
<small>{`@${card.issue.assignee.login}`}</small>
</div>
: null
}
<div className="media-content"> <div className="media-content">
<div className="content"> <div className="content">
<p> { !compact &&
<a target="_blank" href={issueURL}><strong>{`#${card.issue.number}`}</strong></a>&nbsp; <nav className="level">
{ !compact && card.issue.milestone ? <small>{`- ${card.issue.milestone.title}`}</small> : null } <div className="level-left">
{ !compact ? <br /> : ' - ' } <div className="level-item">
<span className="is-size-6">{card.issue.title}</span> <a target="_blank" href={issueURL}><strong>{`#${card.issue.number}`}</strong></a>
</p> </div>
{ !compact &&
<div className="level-item">
<a target="_blank" href={projectURL}>{card.project}</a>
</div>
}
</div>
<div className="level-right">
{
card.issue.assignee && !compact ?
<div className="level-item">
<small>{`@${card.issue.assignee.login}`}</small>
</div>
: null
}
</div>
</nav>
}
{ compact &&
<a target="_blank" className="mr-1" href={issueURL}><strong>{`#${card.issue.number}`}</strong></a>
}
<span>{card.issue.title ? card.issue.title : ''}</span>
</div> </div>
{ !compact &&
<nav className="level">
<div className="level-left"></div>
<div className="level-right">
<div className="level-item is-size-7">
{card.issue.milestone ? card.issue.milestone.title : ''}
</div>
</div>
</nav>
}
</div> </div>
</div> </div>
{
!compact ?
<div className="level is-mobile" style={{marginTop:'1rem'}}>
<div className="level-left">
<small className="level-item"><a href={projectURL}>{card.project}</a></small>
</div>
<div className="level-right">
</div>
</div> :
null
}
</div> </div>
</div> </div>
); );

View File

@ -1,4 +1,5 @@
@import 'bulma/bulma.sass'; @import 'bulma/bulma.sass';
@import '../../node_modules/@lourenci/react-kanban/dist/styles.css';
@import 'bulma-switch/dist/css/bulma-switch.sass'; @import 'bulma-switch/dist/css/bulma-switch.sass';
@import '_base.scss'; @import '_base.scss';
@import '_loader.scss'; @import '_loader.scss';

View File

@ -19,3 +19,7 @@ html, body {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
.mr-1 {
margin-right: 5px;
}

View File

@ -44,6 +44,12 @@
.kanboard-lane { .kanboard-lane {
margin-bottom: $size-small; margin-bottom: $size-small;
background: $white;
top: 0;
.expand {
display: none;
}
} }
} }
@ -52,3 +58,141 @@
align-items: center; align-items: center;
width: 50% !important; width: 50% !important;
} }
.react-kanban-board {
max-height: calc(100vh - calc(52px * 2));
overflow: hidden;
overflow-x: scroll;
scrollbar-color: $grey-lighter, #f1f1f1;
scrollbar-width: 5px;
&::-webkit-scrollbar {
width: 5px;
height: 5px;
}
/* Track */
&::-webkit-scrollbar-track {
background: #f1f1f1;
}
/* Handle */
&::-webkit-scrollbar-thumb {
background: $grey-lighter;
}
/* Handle on hover */
&::-webkit-scrollbar-thumb:hover {
background: $green;
}
}
.react-kanban-column {
transition: width ease .2s;
max-width: 305px;
min-width: 305px;
position: relative;
max-height: 100%;
overflow-x: hidden;
overflow-y: scroll;
scrollbar-color: $grey-lighter, #f1f1f1;
scrollbar-width: 5px;
.kanboard-card {
display: block;
}
&.minimized {
max-width: 70px;
min-width: 70px;
writing-mode: vertical-rl;
text-orientation: sideways-right;
.level-item {
margin-right: 0;
}
.level-left {
margin-right: 0;
}
.level-right.is-show-expand {
display: none;
}
.kanboard-lane {
/*margin-right: -1em;*/
h3 {
margin-top: .5em;
/*margin-right: -.5em;*/
}
.tag {
writing-mode: horizontal-tb;
}
.expand {
display: block !important;
margin-top: .5em;
}
}
.kanboard-card {
display: none;
}
}
&::-webkit-scrollbar {
width: 5px;
}
/* Track */
&::-webkit-scrollbar-track {
background: #f1f1f1;
}
/* Handle */
&::-webkit-scrollbar-thumb {
background: $grey-lighter;
}
/* Handle on hover */
&::-webkit-scrollbar-thumb:hover {
background: $green;
}
}
.react-kanban-card__title {
position: sticky;
position: -webkit-sticky;
}
.react-kanban-column {
background: white !important;
}
.kanboard-card {
overflow-wrap: break-word;
a {
display: inline-block;
}
.level {
margin-bottom: 0;
}
.level-item {
max-width: 100%;
}
}

View File

@ -37,22 +37,22 @@ function handleMoveCard(state: any, action: any) {
const kanboard = state.byID[boardID]; const kanboard = state.byID[boardID];
const lanes = [ ...kanboard.lanes ]; const columns = [ ...kanboard.columns ];
const fromLane = lanes[fromLaneID]; const fromLane = columns[fromLaneID];
const toLane = lanes[toLaneID]; const toLane = columns[toLaneID];
const card = fromLane.cards[fromPosition]; const card = fromLane.cards[fromPosition];
const fromCards = [ ...fromLane.cards ]; const fromCards = [ ...fromLane.cards ];
if (fromLaneID !== toLaneID) { if (fromLaneID !== toLaneID) {
fromCards.splice(fromPosition, 1); fromCards.splice(fromPosition, 1);
lanes[fromLaneID] = { columns[fromLaneID] = {
...fromLane, ...fromLane,
cards: fromCards, cards: fromCards,
}; };
const toCards = [ ...toLane.cards ]; const toCards = [ ...toLane.cards ];
toCards.splice(toPosition, 0, card); toCards.splice(toPosition, 0, card);
lanes[toLaneID] = { columns[toLaneID] = {
...toLane, ...toLane,
cards: toCards, cards: toCards,
}; };
@ -67,7 +67,7 @@ function handleMoveCard(state: any, action: any) {
...state.byID, ...state.byID,
[boardID]: { [boardID]: {
...state.byID[boardID], ...state.byID[boardID],
lanes, columns,
}, },
} }
}; };

View File

@ -1,10 +1,11 @@
import { GiteaUnauthorizedError } from "../../util/gitea"; import { GiteaUnauthorizedError } from "../../util/gitea";
import { put } from 'redux-saga/effects'; import { put } from 'redux-saga/effects';
import { logout } from '../actions/logout'; import { logout } from '../actions/logout';
import { saveReferer } from "../../util/referer";
export function* failuresSaga(action) { export function* failuresSaga(action) {
const err = action.error; if (action.error instanceof GiteaUnauthorizedError) {
if (err instanceof GiteaUnauthorizedError) { saveReferer();
yield put(logout()); yield put(logout());
} }
} }

View File

@ -18,7 +18,7 @@ export function* fetchIssuesSaga(action: any) {
if (pageIssues.length === 0) { if (pageIssues.length === 0) {
break; break;
} }
issues.push(...pageIssues); issues.push(...pageIssues.filter(issue => issue.pull_request === null));
page++; page++;
} }

View File

@ -22,7 +22,7 @@ export function* moveCardSaga(action: any) {
} }
}); });
const toLane = kanboard.lanes[toLaneID]; const toLane = kanboard.columns[toLaneID];
const card = toLane.cards[toPosition]; const card = toLane.cards[toPosition];
if (!card) return; if (!card) return;
@ -75,13 +75,13 @@ function createCards(projects: Project[], issues: any, lane: BoardLane, rest: Se
return projectIssues.reduce((projectCards: KanboardCard[], issue: any) => { return projectIssues.reduce((projectCards: KanboardCard[], issue: any) => {
const hasLabel = issue.labels.some((l: any) => l.name === lane.issueLabel); const hasLabel = issue.labels.some((l: any) => l.name === lane.issueLabel);
const card = getMemoizedKanboardCard(issue.id, issue.title, p, issue); const { card, memoized } = getMemoizedKanboardCard(issue.id, issue.title, p, issue);
if (hasLabel) { if (hasLabel) {
projectCards.push(card); projectCards.push(card);
rest.delete(card); rest.delete(card);
} else { } else {
rest.add(card); if (!memoized) rest.add(card);
} }
return projectCards; return projectCards;
@ -95,11 +95,19 @@ function createCards(projects: Project[], issues: any, lane: BoardLane, rest: Se
const kanboardCardMemo: {[key: string]: KanboardCard} = {}; const kanboardCardMemo: {[key: string]: KanboardCard} = {};
function getMemoizedKanboardCard(id: number, title: string, project: Project, issue: Issue): KanboardCard { function getKanboardCardMemoizationKey(id: number, project: Project, issue: Issue) {
const key = `${project.id}-${issue.id}-${id}`; return `${project.id}-${issue.id}-${id}`;
if (kanboardCardMemo.hasOwnProperty(key)) return kanboardCardMemo[key]; }
function isKanboardCardMemoized(key: string) {
return kanboardCardMemo.hasOwnProperty(key)
}
function getMemoizedKanboardCard(id: number, title: string, project: Project, issue: Issue) {
const key = getKanboardCardMemoizationKey(id, project, issue);
if (isKanboardCardMemoized(key)) return { card: kanboardCardMemo[key], memoized: true };
kanboardCardMemo[key] = { id, title, project, issue }; kanboardCardMemo[key] = { id, title, project, issue };
return kanboardCardMemo[key]; return { card: kanboardCardMemo[key], memoized: false };
} }
function resetKandboarCardMemo() { function resetKandboarCardMemo() {
@ -137,7 +145,7 @@ function createKanboard(board: Board, issues: any) {
const kanboard = { const kanboard = {
id: board.id, id: board.id,
lanes: createKanboardLanes(board, issues), columns: createKanboardLanes(board, issues),
}; };
return kanboard; return kanboard;

View File

@ -3,10 +3,18 @@ import { FETCH_PROJECTS_SUCCESS, FETCH_PROJECTS_FAILURE } from '../actions/proje
import { gitea } from '../../util/gitea'; import { gitea } from '../../util/gitea';
export function* fetchProjectsSaga() { export function* fetchProjectsSaga() {
let projects = [];
let projects;
try { try {
projects = yield call(gitea.fetchUserProjects.bind(gitea)) let page = 1;
while(true) {
let pageProjects = yield call(gitea.fetchUserProjects.bind(gitea), page);
if (pageProjects.length === 0) {
break;
}
projects.push(...pageProjects);
page++;
}
} catch(error) { } catch(error) {
yield put({ type: FETCH_PROJECTS_FAILURE, error }); yield put({ type: FETCH_PROJECTS_FAILURE, error });
return; return;

View File

@ -0,0 +1,9 @@
import { hasReferer, getReferer, clearReferer } from '../../util/referer';
export function* navigateToRefererSaga() {
if (!hasReferer()) return;
const referer = getReferer();
console.log("Redirecting to referer", referer);
clearReferer();
window.location.hash = referer;
}

View File

@ -10,9 +10,11 @@ import { LOGOUT_REQUEST, LOGOUT_SUCCESS } from '../actions/logout';
import { logoutSaga, logoutSuccessSaga } from './logout'; import { logoutSaga, logoutSuccessSaga } from './logout';
import { BUILD_KANBOARD_REQUEST, MOVE_CARD } from '../actions/kanboards'; import { BUILD_KANBOARD_REQUEST, MOVE_CARD } from '../actions/kanboards';
import { buildKanboardSaga, moveCardSaga, refreshKanboardSaga } from './kanboards'; import { buildKanboardSaga, moveCardSaga, refreshKanboardSaga } from './kanboards';
import { navigateToRefererSaga } from './referer';
export function* rootSaga() { export function* rootSaga() {
yield all([ yield all([
navigateToRefererSaga(),
takeEvery(patternFromRegExp(/^.*_FAILURE/), failuresSaga), takeEvery(patternFromRegExp(/^.*_FAILURE/), failuresSaga),
takeLatest(FETCH_BOARDS_REQUEST, fetchBoardsSaga), takeLatest(FETCH_BOARDS_REQUEST, fetchBoardsSaga),
takeLatest(BUILD_KANBOARD_REQUEST, buildKanboardSaga), takeLatest(BUILD_KANBOARD_REQUEST, buildKanboardSaga),

View File

@ -1,4 +1,4 @@
import { createStore, applyMiddleware } from 'redux' import { createStore, applyMiddleware, compose } from 'redux'
import createSagaMiddleware from 'redux-saga' import createSagaMiddleware from 'redux-saga'
import { rootReducer } from './reducers/root' import { rootReducer } from './reducers/root'
import { rootSaga } from './sagas/root' import { rootSaga } from './sagas/root'
@ -14,6 +14,8 @@ if (process.env.NODE_ENV !== 'production') {
reduxMiddlewares.push(loggerMiddleware); reduxMiddlewares.push(loggerMiddleware);
} }
const composeEnhancers = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
// create the saga middleware // create the saga middleware
const sagaMiddleware = createSagaMiddleware() const sagaMiddleware = createSagaMiddleware()
reduxMiddlewares.push(sagaMiddleware); reduxMiddlewares.push(sagaMiddleware);
@ -21,7 +23,7 @@ reduxMiddlewares.push(sagaMiddleware);
// mount it on the Store // mount it on the Store
export const store = createStore( export const store = createStore(
rootReducer, rootReducer,
applyMiddleware(...reduxMiddlewares) composeEnhancers(applyMiddleware(...reduxMiddlewares)),
) )
// then run the saga // then run the saga

View File

@ -2,6 +2,7 @@
export class GiteaUnauthorizedError extends Error { export class GiteaUnauthorizedError extends Error {
constructor(...args: any[]) { constructor(...args: any[]) {
super(...args) super(...args)
Object.setPrototypeOf(this, GiteaUnauthorizedError.prototype);
} }
} }
@ -15,8 +16,8 @@ export class GiteaClient {
; ;
} }
fetchUserProjects() { fetchUserProjects(page = 1) {
return fetch(`/gitea/api/v1/user/repos`) return fetch(`/gitea/api/v1/user/repos?page=${page}`)
.then(this.assertAuthorization) .then(this.assertAuthorization)
.then(this.assertOk) .then(this.assertOk)
.then(res => res.json()) .then(res => res.json())

View File

@ -0,0 +1,19 @@
const localStorage = window.localStorage;
const refererKey = 'referer';
export function getReferer() {
return localStorage.getItem(refererKey);
}
export function saveReferer() {
console.log("Saving referer", window.location.hash);
localStorage.setItem(refererKey, window.location.hash);
}
export function hasReferer() {
return !!getReferer();
}
export function clearReferer() {
localStorage.removeItem(refererKey);
}

View File

@ -28,7 +28,7 @@ func getServiceContainer(conf *config.Config) (*service.Container, error) {
ctn.Provide( ctn.Provide(
session.ServiceName, session.ServiceName,
gorilla.ServiceProvider("gitea-kan", cookieStore), gorilla.ServiceProvider("gengitkan", cookieStore),
) )
// Create and expose config service provider // Create and expose config service provider

1
debian/compat vendored
View File

@ -1 +0,0 @@
9

14
debian/control vendored
View File

@ -1,14 +0,0 @@
Source: gengitkan
Section: unknown
Priority: optional
Maintainer: William Petit <wpetit@cadoles.com>
Build-Depends: debhelper (>= 8.0.0), wget, ca-certificates, tar
Standards-Version: 3.9.4
Homepage: http://forge.cadoles.com/wpetit/gengitkan
Vcs-Git: http://forge.cadoles.com/wpetit/gengitkan.git
Vcs-Browser: http://forge.cadoles.com/wpetit/gengitkan
Package: gengitkan
Architecture: amd64
Depends: ${shlibs:Depends}, ${misc:Depends}
Description: Application type "Kanboard" connectée à Gitea

View File

@ -1 +0,0 @@
var/lib/gengitkan

View File

@ -1,13 +0,0 @@
[Unit]
Description=Application type "Kanboard" connectée à Gitea
After=network-online.target
[Service]
Type=simple
Environment=GENGITKAN_HTTP_PUBDIR=/usr/share/gengitkan/public
Environment=GENGITKAN_DATA_DBPATH=/var/lib/gengitkan/data.db
ExecStart=/usr/bin/gengitkan -workdir /usr/share/gengitkan -config /etc/gengitkan/server.conf
Restart=on-failure
[Install]
WantedBy=multi-user.target

54
debian/rules vendored
View File

@ -1,54 +0,0 @@
#!/usr/bin/make -f
# -*- makefile -*-
# Uncomment this to turn on verbose mode.
export DH_VERBOSE=1
GO_VERSION := 1.13.5
OS := linux
ARCH := amd64
GOPATH=$(HOME)/go
ifeq (, $(shell which go 2>/dev/null))
override_dh_auto_build: install-go
endif
ifeq (, $(shell which node 2>/dev/null))
override_dh_auto_build: install-nodejs
endif
%:
dh $@ --with systemd
override_dh_auto_build: $(GOPATH)
cd client && npm install
GOPATH=$(GOPATH) PATH="$(PATH):/usr/local/go/bin:$(GOPATH)/bin" go mod vendor
GOPATH=$(GOPATH) PATH="$(PATH):/usr/local/go/bin:$(GOPATH)/bin" ARCH_TARGETS=$(ARCH) make release
$(GOPATH):
mkdir -p $(GOPATH)
install-go:
wget https://dl.google.com/go/go$(GO_VERSION).$(OS)-$(ARCH).tar.gz
tar -C /usr/local -xzf go$(GO_VERSION).$(OS)-$(ARCH).tar.gz
install-nodejs:
wget -O- https://deb.nodesource.com/setup_12.x | bash -
apt-get install -y nodejs
override_dh_auto_install:
mkdir -p debian/gengitkan/usr/share/gengitkan
mkdir -p debian/gengitkan/etc/gengitkan
mkdir -p debian/gengitkan/usr/bin
cp -r release/server-$(OS)-$(ARCH)/* debian/gengitkan/usr/share/gengitkan/
mv debian/gengitkan/usr/share/gengitkan/bin/server debian/gengitkan/usr/bin/gengitkan
mv debian/gengitkan/usr/share/gengitkan/server.conf debian/gengitkan/etc/gengitkan/server.conf
install -d debian/gengitkan
override_dh_strip:
override_dh_auto_test:

View File

@ -1 +0,0 @@
3.0 (native)