Source code for kanban

"""
############################################################
Kanban - Agile Workflow
############################################################

:Author: *Carlo E. T. Oliveira*
:Contact: carlo@nce.ufrj.br
:Date: 2013/03/29
:Status: This is a "work in progress"
:Revision: 0.1.9
:Home: `Labase <http://labase.selfip.org/>`__
:Copyright: 2013, `GPL <http://is.gd/3Udt>`__. 
"""
__author__  = "Carlo E. T. Oliveira (carlo@nce.ufrj.br)"
__version__ = "0.1"
__date__    = "2013/03/29"

REPO = '/studio/activlets/%s'
BRYTHON = False

def _logger(*a):
    print(a)
        
if not '__package__' in dir():
    import svg
    import html
    logger = log
    BRYTHON = False
    pass
else:
    logger = _logger
    pass

COLORS = """#CCFF66 #669933 #CCFFCC #99CC33 #CC6699 #993366 #FFCCCC #CC3399
#FFCC66 #996633 #FFCCCC #CC9933 #6666CC #336699 #CCCCFF #3366CC""".split()
COLORS = ['#F9D1D1', '#F9D9D1', '#F9E2D1', '#F9EAD1', '#F9F3D1', '#F8F9D1',
          '#EFF9D1', '#E7F9D1', '#DEF9D1', '#D6F9D1', '#D1F9D4', '#D1F9DD',
          '#D1F9E5', '#D1F9EE', '#D1F9F6', '#D1F4F9', '#D1ECF9', '#D1E3F9',
          '#D1DBF9', '#D1D2F9', '#D7D1F9', '#E0D1F9', '#E8D1F9', '#F1D1F9']

BACKGROUNDS ="#666666 #999999 #CCCCCC #EEEEEE".split()
BACKGROUNDS =["rgba(%d,%d,%d,0.4)"%(color,color,color) for color in [102,153,204,238]]

PANELS = len(BACKGROUNDS)
HEAD, LABELS, PANEL, ITEM = 'head labels panel item'.split()

[docs]class GUI: """ A factory for html elements. :ref:`GUI` """ def __init__(self, doc, gui): self.doc, self.gui = doc, gui pass
[docs] def div(self, text, node, draggable=False, id='nono', Class="rounded-corners", nodename= None): """Create a HTML DIV element""" element = self.gui.DIV(text, draggable=draggable, id=id, Class=Class) thenode = nodename and self.doc[nodename] or node if isinstance(node, str): self.doc[node] <= element else: node <= element return element
[docs] def cling(self,level, element): """Cling a element to a DOM node""" level <= element
[docs] def remove(self, node, element): """Remove a element from a DOM node""" node.removeChild(element)
[docs] def confirmation(self, query_text): """Open a confimation dialog and return the choice""" return confirm(query_text)
[docs] def set_style(self, target, **kwargs): """Set the style attribute of a DOM element""" #logger('styyle %s'%kwargs) target.style= kwargs
#setattr( target, 'style', kwargs)
[docs] def set_attrs(self, target, **kwargs): """Set the attributes defined as key arguments of a target DOM element""" for attr, value in kwargs.items(): setattr( target, attr, value)
[docs]class Draggable: """ Interface for something that can be dragged. :ref:`Draggable` """ def _drag_start(self, ev): ev.data[ITEM]=self.ob_id ev.data.effectAllowed = 'move' def _mouse_over(self,ev): ev.target.style.cursor = "pointer" def _drag_over(self,ev): ev.data.dropEffect = 'move' ev.preventDefault() def _drop(self,ev): ev.preventDefault() item_id = ev.data[ITEM] item = self.container.get_item(item_id) self.do_drop(item)
[docs] def do_drop(self,item): """Template action when element receives a dropped item""" pass
[docs]class Composite(Draggable): """ Interface of containement, contained by and contains peers. :ref:`Composite` """
[docs] def get_dimensions(self): """get dimensions for component.""" pass
[docs] def arrange(self, left=0, top=0, width=0, height=0, margin = 4, offy = 40, overflowY = 'hidden', overflowX = 'hidden', opacity = 0.7 ): """Deploy this component in a container""" dims = {'position':'absolute', 'overflowX':'hidden', 'left':left + margin/2, 'top':top, # * height + offy 'width':width - margin, 'height':height - margin, 'backgroundColor':self.color, 'opacity':opacity} if overflowY: dims['overflowY'] = overflowY self.gui.set_style(self.avatar, **dims)
def _build_avatar(self,gui, obid, color, node, clazz, drag, events): self.avatar = self.gui.div('', node = node, draggable= drag , id=obid, Class=clazz) dims = self.get_dimensions() self.arrange(**dims) gui.set_attrs(self.avatar, **events)
[docs] def append(self, component): """Append a new component.""" self.items.append(component)
[docs] def remove(self, component): """Remove a component.""" self.items.remove(component) self._rearrange_components(component)
[docs] def attach(self, container, dims): """attach to a new container.""" self.container.remove(self) print (self.container.avatar,self.avatar) self.gui.remove(self.container.avatar,self.avatar) self.container = container self.top, self.width = dims['top'],dims['width'] self.arrange(dims['left'],self.top*68+42,self.width,dims['height'], dims['margin']) self.arrange(0,2+self.top*68,self.width,dims['height'], dims['margin']) container.append(self) self.gui.cling(container.avatar,self.avatar)
def _rearrange_components(self, component): """dettach from this container and sort components.""" pass
OBID = TMID = 0
[docs]class Timer(Composite): """ A Clock to set and account task time. :ref:`Task` """
[docs] def delete_object(self): """Delete this item from Board and Dom""" self.container.remove(self) logger('delete_object %s'%self.ob_id) self.gui.remove(self, self.container.avatar)
def __init__(self, gui, container, color, left, top, width): global TMID self.gui, self.container, self.color = gui, container, 'black' self.ob_id, self.top, self.width = 'clock_%d'%TMID, top, 64 self.left = left #container.register(self) TMID += 1 events = dict(ondragover = self._drag_over, ondrop = self._drop, ondragstart = self._drag_start,onmouseover = self._mouse_over, onclick = self._mouse_click) self._build_avatar(gui, self.ob_id, color, container.avatar, "task-timer", False, events)
[docs] def get_dimensions(self): """get dimensions for component.""" #dims = dict(left = self.left, top = 42 + self.top*68, # width = self.width, height = 68, margin=4) dims = dict(left = 0, top = 0, width = self.width, height = 16, margin=0, opacity=0.7) return dims
def _mouse_click(self, ev): logger('click') self.avatar.text = prompt('New text',self.avatar.text)
[docs]class Task(Composite): """ A Task represented by a colored note. :ref:`Task` """
[docs] def delete_object(self): """Delete this item from Board and Dom""" if self.gui.confirmation('Deseja realmente apagar %s?'% self.ob_id): self.container.remove(self) logger('delete_object %s'%self.ob_id) self.gui.remove(self, PANEL)
def __init__(self, gui, container, color, left, top, width): global OBID self.gui, self.container, self.color = gui, container, color self.ob_id, self.top, self.width = 'task_%d'%OBID, top, width self.left, self.offy, self.height, self.stepy = left, 2, 68, 68 container.register(self) OBID += 1 self._build_avatar(gui, self.ob_id, color, container.avatar, "task-div", True, {}) self.task = self.avatar self.timer = Timer(gui,self,'black', left, top-16, width) self.offy, self.height, self.width, self.stepy = 18, '100%', '100%', 0 events = dict(ondragover = self._drag_over, ondrop = self._drop, ondragstart = self._drag_start,onmouseover = self._mouse_over, onclick = self._mouse_click) self._build_note(gui, 'note-'+self.ob_id, color, self.task, "task-note", True, events) #self.avatar, self.note = self.task , self.avatar def _build_note(self,gui, obid, color, node, clazz, drag, events): self.note = self.gui.div('', node = node, draggable= drag , id=obid, Class=clazz) dims = {'position':'absolute', 'overflow':'hidden', 'left':2, 'top':self.offy, # * height + offy 'width':'96%', 'height':46, 'backgroundColor':self.color, 'opacity':0.9} self.gui.set_style(self.note, **dims) gui.set_attrs(self.note, **events)
[docs] def get_dimensions(self): """get dimensions for component.""" #dims = dict(left = self.left, top = 42 + self.top*68, # width = self.width, height = 68, margin=4) dims = dict(left = 0, top = self.offy + self.top*self.stepy, width = self.width, height = self.height, margin=4, opacity=0.8) return dims
def _mouse_click(self, ev): logger('click') self.note.text = prompt('New text',self.avatar.text)
[docs]class Task_panel: """ A panel representing several steps of the task flow. :ref:`Task_panel` """ def __init__(self,gui, container): self.gui, self.left, self.width = gui, 80, 100 self.panels = [self._build_panel(i,color, container) for i, color in enumerate(BACKGROUNDS)] def _build_panel(self,i, color, container): self.width = 80 + (PANELS - i) * 60 panel_div = Step_board(self.gui, i, color, self.left, self.width, container) BRYTHON and panel_div.__init__(self.gui, i, color, self.left, self.width, container) self.left += self.width return panel_div
[docs]class Color_tab(Draggable): """ A color markers for new tasks. :ref:`Color_tab` """ def __init__(self,gui, color, left, top, width, height, container): if left < 50: #: TODO remove Brython fix return self.gui, self.color, self.ob_id, self.container = gui, color, color, container self.tab = self._build_tab(gui, color, left, top, width, height) container.register(self) #logger('Color_tab init top %s color %s tab %s'%(top,color, self)) def _build_tab(self,gui, color, left, top, width, height): avatar = gui.div('', node= HEAD, draggable=True, id=color, Class="color-tabs") args = {'position':'absolute','left':left, 'top':top, 'width':width, 'height':height, 'backgroundColor':color} gui.set_style(avatar, **args) gui.set_attrs(avatar, ondragstart = self._drag_start,onmouseover = self._mouse_over, ondragover = self._drag_over, ondrop = self._drop) return avatar
[docs] def get_color(self): """Get the color of the tab""" return self.color
[docs] def attach(self, container, dims): """Deploy a new task in a stepboard""" left, top, width = dims['left'],dims['top'],dims['width'] task = Task(self.gui, container, self.color, left, top, width) BRYTHON and task.__init__(self.gui, container, self.color, left, top, width) container.append(task)
[docs] def new_proj(self, container, dims): """Deploy a new project in a projectboard""" print('_pr_dropnew_proj') left, top, width = dims['left'],dims['top'],dims['width'] proj = Project(self.gui, self.color, left , top, width, 64, container) #proj.__init__(self.gui, self.color, left , top, width, 64, container) container.append(proj)
[docs] def delete_object(self, ev): pass
[docs] def do_drop(self,item): logger(' delete_object item %s id %s del %s'%(item, item.ob_id,dir(item))) item.delete_object()
[docs]class Project(Color_tab): """ A Project selector for new tasks. :ref:`Color_tab` """ def __init__(self,gui, color, left, top, width, height, container): self.gui, self.color, self.ob_id, self.container = gui, color, color, container self.avatar = self._build_tab(gui, color, left, top, width, height) print('_pr_dropnew_projProject') container.register(self) def _build_tab(self,gui, color, left, top, width, height): avatar = gui.div('', node= self.container.avatar, draggable=True, id='Proj-%s'%color, Class="Project-tab") args = {'position':'absolute','left':left,'top':top*68, 'width':width, 'height':height, 'backgroundColor':color, 'opacity':0.9} gui.set_style(avatar, **args) gui.set_attrs(avatar, ondragstart = self._drag_start,onmouseover = self._mouse_over, ondragover = self._drag_over, ondrop = self._drop) logger('Project init top %s color %s tab %s'%(top,color, self)) return avatar
[docs]class Color_pallete: """ A collecion of color markers for new tasks. :ref:`Color_pallete` """ def __init__(self, gui, container): self.gui, self.container = gui, container #self.colors = [ self._build_color(i,color, container) # for i, color in enumerate(COLORS)] #self.colors = { color:self._build_color(i,color, container) # for i, color in enumerate(COLORS)} colors = {} for i, color in enumerate(COLORS): cl =self._build_color(i,color, container) colors[color] = cl self.colors = colors def _build_color(self,i, color, container): # create a DIV for each color label left = 80 +36*i top = 470 #(i//15) width, height = 36, 16 gui = self.gui color_tab = Color_tab(gui, color, left , top, width, height, container) BRYTHON and color_tab.__init__(gui, color, left , top, width, height, container) #logger('Color_pallete build top %s color %s tab %s'%(top,color, color_tab)) #color_tab._build_tab(self.gui,color, left, top, 16) return color_tab #(color, color_tab)
[docs]class Step_board(Composite): """ A board to hold tasks within a step in the task workflow. :ref:`Step_board` """ def __init__(self, gui, i, color, left, width, panel): # create a DIV for each AREA (ie each country) self.gui, self.left, self.width, self.container = gui, left, width, panel self.color = color self.items = [] if i >= 100: return events = dict(ondragover = self._drag_over, ondrop = self._drop) print('Step_board build_avatar %s'%left) self._build_avatar(gui, 'panel%d'%i, color, PANEL, "task-panel", False, events)
[docs] def get_dimensions(self): """get dimensions for component.""" dims = dict(left = self.left, top = 40, opacity=0.9, width = self.width, height = 420, margin=0, overflowY= 'auto') return dims
[docs] def get_item(self, item): """Get a tab with the given object id key""" return self.container.get_item(item)
[docs] def register(self, item): """Register an item with the given object id key""" self.container.register(item)
[docs] def remove(self, task): """Remove a task from a stepboard""" def ts (t=task): return t ind = [i for i in self.items if i is not task] logger('task indesx %s'%ind) self.items= ind
#self.items.remove(task) def _next_position(self): return dict(left=self.left, top=len(self.items), width=self.width, height = 68, margin=4) return (self.left, len(self.items), self.width) def _drag_start(self, ev): pass def _mouse_over(self,ev): pass
[docs] def do_drop(self,item): item.attach(self,self._next_position())
pass
[docs]class Project_board(Step_board): """ A board to hold tasks within a step in the task workflow. :ref:`Step_board` """ def __init__(self, gui, i, color, left, width, panel): self.gui, self.left, self.width, self.container = gui, left, width, panel self.color = color self.items = [] events = dict(ondragover = self._drag_over, ondrop = self._pr_drop) print('Project_board build_avatar %s'%panel) self._build_avatar(gui, 'board%d'%i, color, PANEL, "board-panel", False, events)
[docs] def get_dimensions(self): """get dimensions for component.""" dims = dict(left = self.left, top = 40, opacity=0.9, width = self.width, height = 420, margin=0, overflowY= 'auto') return dims
def _pr_drop(self,ev): ev.preventDefault() item_id = ev.data[ITEM] item = self.container.get_item(item_id) #self.do_drop(item) print('_pr_drop items: %s'%self.items) item.new_proj(self,self._next_position())
[docs]class Dust_bin: """ A Kanban workflow plugin for the Activ platform. :ref:`Dust_bin` """ pass
[docs]class Kanban: """ A Kanban workflow plugin for the Activ platform. :ref:`kanban` """
[docs] def get_tab(self, color): """Get a tab with the given key color""" return self.head_bar.colors[color]
[docs] def register(self, item): """Register an item with the given object id key""" self.items[item.ob_id] = item
[docs] def remove(self, item): """Remove an item with the given object id key""" self.items.pop(item.ob_id)
[docs] def get_item(self, ob_id): """Get a tab with the given object id key""" return self.items[ob_id]
def _build_project_selector(self): return Color_pallete(self.gui, self) def _build_label_selector(self): return Project_board(self.gui, 100, 'rgba(255,255,255,0.1)', 0, 70, self) def _build_workflow_area(self): return Task_panel(self.gui, self) pass def __init__(self,gui): self.gui = gui self.items = {} self.head_bar = self._build_project_selector() self.label_bar = self._build_label_selector() self.task_bar = self._build_workflow_area()
[docs]def main(dc, gui, div_ids, repo): """ Starting point """ global REPO REPO = repo return Kanban(gui)