Projekt

Allgemein

Profil

« Zurück | Weiter » 

Revision 3cae447e

Von Moritz Bunkus vor mehr als 10 Jahren hinzugefügt

  • ID 3cae447edd6ab367173b9aca8a168be236f9078c
  • Vorgänger c805dfd9
  • Nachfolger 237b14f3

Pflichtenhefte: Copy & Paste für Textblöcke & Items

Unterschiede anzeigen:

SL/Controller/RequirementSpecItem.pm
9 9
use List::Util qw(first);
10 10
use Time::HiRes ();
11 11

  
12
use SL::Clipboard;
12 13
use SL::DB::RequirementSpec;
13 14
use SL::DB::RequirementSpecComplexity;
14 15
use SL::DB::RequirementSpecItem;
......
19 20

  
20 21
use Rose::Object::MakeMethods::Generic
21 22
(
22
  scalar                  => [ qw(item visible_item visible_section) ],
23
  scalar                  => [ qw(item visible_item visible_section clicked_item sections) ],
23 24
  'scalar --get_set_init' => [ qw(complexities risks) ],
24 25
);
25 26

  
26
__PACKAGE__->run_before('load_requirement_spec_item', only => [ qw(dragged_and_dropped ajax_update ajax_edit ajax_delete ajax_flag) ]);
27
__PACKAGE__->run_before('load_requirement_spec_item', only => [ qw(dragged_and_dropped ajax_update ajax_edit ajax_delete ajax_flag ajax_copy) ]);
27 28
__PACKAGE__->run_before('init_visible_section');
28 29

  
29 30
#
......
52 53
  $self->render($js);
53 54
}
54 55

  
56
sub insert_new_item_in_section_view {
57
  my ($self, $js) = @_;
58

  
59
  $js->hide('#section-list-empty');
60

  
61
  my $new_type  = $self->item->item_type;
62
  my $id_prefix = $new_type eq 'sub-function-block' ? 'sub-' : '';
63
  my $template  = 'requirement_spec_item/_' . (apply { s/-/_/g; $_ } $new_type);
64
  my $html      = "" . $self->render($template, { output => 0 }, requirement_spec_item => $self->item);
65
  my $next_item = $self->item->get_next_in_list;
66

  
67
  if ($next_item) {
68
    $js->insertBefore($html, '#' . $id_prefix . 'function-block-' . $next_item->id);
69
  } else {
70
    my $parent_is_section = $self->item->parent->item_type eq 'section';
71
    $js->appendTo($html, $parent_is_section ? '#section-list' : '#sub-function-block-container-' . $self->item->parent_id);
72
    $js->show('#sub-function-block-container-' . $self->item->parent_id) if !$parent_is_section;
73
  }
74

  
75
  $self->replace_bottom($js, $self->item->parent) if $new_type eq 'sub-function-block';
76
}
77

  
55 78
sub action_dragged_and_dropped {
56 79
  my ($self)              = @_;
57 80

  
......
105 128
  }
106 129

  
107 130
  if ($old_visible_section->id == $new_section->id) {
108
    $js->hide('#section-list-empty');
109

  
110
    my $id_prefix = $new_type eq 'sub-function-block' ? 'sub-' : '';
111
    my $template  = 'requirement_spec_item/_' . (apply { s/-/_/g; $_ } $new_type);
112
    my $html      = "" . $self->render($template, { output => 0 }, requirement_spec_item => $self->item);
113
    my $next_item = $self->item->get_next_in_list;
114

  
115
    if ($next_item) {
116
      $js->insertBefore($html, '#' . $id_prefix . 'function-block-' . $next_item->id);
117
    } else {
118
      my $parent_is_section = $self->item->parent->item_type eq 'section';
119
      $js->appendTo($html, $parent_is_section ? '#section-list' : '#sub-function-block-container-' . $self->item->parent_id);
120
      $js->show('#sub-function-block-container-' . $self->item->parent_id) if !$parent_is_section;
121
    }
122

  
123
    $self->replace_bottom($js, $self->item->parent) if $new_type eq 'sub-function-block';
131
    $self->insert_new_item_in_section_view($js);
124 132
  }
125 133

  
126 134
  # $::lxdebug->dump(0, "js", $js->to_array);
......
203 211

  
204 212
  my $js = SL::ClientJS->new;
205 213

  
206
  if (!$self->visible_section || ($self->visible_section->id != $self->item->section->id)) {
214
  if (!$self->is_item_visible) {
207 215
    # Show section/item to edit if it is not visible.
208 216

  
209 217
    my $html = $self->render('requirement_spec_item/_section', { output => 0 }, requirement_spec_item => $self->item);
......
315 323
         ->val('#current_content_id', '')
316 324
    }
317 325

  
318
  } elsif ($self->visible_section && ($self->visible_section->id == $self->item->section->id)) {
326
  } elsif ($self->is_item_visible) {
319 327
    # Item in currently visible section is deleted.
320 328

  
321 329
    my $type = $self->item->item_type;
......
342 350

  
343 351
  $self->item->update_attributes(is_flagged => !$self->item->is_flagged);
344 352

  
345
  my $is_visible = $self->visible_section && ($self->visible_section->id == $self->item->section->id);
346

  
347 353
  SL::ClientJS->new
348
   ->action_if($is_visible, 'toggleClass', '#' . $self->item->item_type . '-' . $self->item->id, 'flagged')
354
   ->action_if($self->is_item_visible, 'toggleClass', '#' . $self->item->item_type . '-' . $self->item->id, 'flagged')
349 355
   ->toggleClass('#fb-' . $self->item->id, 'flagged')
350 356
   ->render($self);
351 357
}
352 358

  
359
sub action_ajax_copy {
360
  my ($self, %params) = @_;
361

  
362
  SL::Clipboard->new->copy($self->item);
363
  SL::ClientJS->new->render($self);
364
}
365

  
366
sub determine_paste_position {
367
  my ($self) = @_;
368

  
369
  if ($self->item->item_type eq 'section') {
370
    # Sections are always pasted either directly after the
371
    # clicked-upon section or at the very end.
372
    return $self->clicked_item ? (undef, $self->clicked_item->section->id) : ();
373

  
374
  } elsif ($self->item->item_type eq 'function-block') {
375
    # A function block:
376
    # - paste on section list: insert into last section as last element
377
    # - paste on section: insert into that section as last element
378
    # - paste on function block: insert after clicked-upon element
379
    # - paste on sub function block: insert after parent function block of clicked-upon element
380
    return !$self->clicked_item                                ? ( $self->sections->[-1]->id,              undef                          )
381
         :  $self->clicked_item->item_type eq 'section'        ? ( $self->clicked_item->id,                undef                          )
382
         :  $self->clicked_item->item_type eq 'function-block' ? ( $self->clicked_item->parent_id,         $self->clicked_item->id        )
383
         :                                                       ( $self->clicked_item->parent->parent_id, $self->clicked_item->parent_id );
384

  
385
  } else {                      # sub-function-block
386
    # A sub function block:
387
    # - paste on section list: promote to function block and insert into last section as last element
388
    # - paste on section: promote to function block and insert into that section as last element
389
    # - paste on function block: insert as last element in clicked-upon element
390
    # - paste on sub function block: insert after clicked-upon element
391

  
392
    # Promote sub function blocks to function blocks when pasting on a
393
    # section or the section list.
394
    $self->item->item_type('function-block') if !$self->clicked_item || ($self->clicked_item->item_type eq 'section');
395

  
396
    return !$self->clicked_item                                ? ( $self->sections->[-1]->id,      undef                   )
397
         :  $self->clicked_item->item_type eq 'section'        ? ( $self->clicked_item->id,        undef                   )
398
         :  $self->clicked_item->item_type eq 'function-block' ? ( $self->clicked_item->id,        undef                   )
399
         :                                                       ( $self->clicked_item->parent_id, $self->clicked_item->id );
400
  }
401
}
402

  
403
sub assign_requirement_spec_id_rec {
404
  my ($self, $item) = @_;
405

  
406
  $item->requirement_spec_id($::form->{requirement_spec_id});
407
  $self->assign_requirement_spec_id_rec($_) for @{ $item->children || [] };
408

  
409
  return $item;
410
}
411

  
412
sub create_and_insert_node_rec {
413
  my ($self, $js, $item, $new_parent_id, $insert_after) = @_;
414

  
415
  my $node = $self->presenter->requirement_spec_item_jstree_data($item);
416
  $js->jstree->create_node('#tree', $insert_after ? ('#fb-' . $insert_after, 'after') : $new_parent_id ? ('#fb-' . $new_parent_id, 'last') : ('#sections', 'last'), $node);
417

  
418
  $self->create_and_insert_node_rec($js, $_, $item->id) for @{ $item->children || [] };
419

  
420
  $js->jstree->open_node('#tree', '#fb-' . $item->id);
421
}
422

  
423
sub action_ajax_paste {
424
  my ($self, %params) = @_;
425

  
426
  my $js     = SL::ClientJS->new;
427
  my $copied = SL::Clipboard->new->get_entry(qr/^RequirementSpecItem$/);
428

  
429
  if (!$copied) {
430
    return $js->error(t8("The clipboard does not contain anything that can be pasted here."))
431
              ->render($self);
432
  }
433

  
434
  $self->item($self->assign_requirement_spec_id_rec($copied->to_object));
435
  my $req_spec = SL::DB::RequirementSpec->new(id => $::form->{requirement_spec_id})->load;
436
  $self->sections($req_spec->sections);
437

  
438
  if (($self->item->item_type ne 'section') && !@{ $self->sections }) {
439
    return $js->error(t8("You cannot paste function blocks or sub function blocks if there is no section."))
440
              ->render($self);
441
  }
442

  
443
  $self->clicked_item($::form->{id} ? SL::DB::RequirementSpecItem->new(id => $::form->{id})->load : undef);
444

  
445
  my ($new_parent_id, $insert_after) = $self->determine_paste_position;
446

  
447
  # Store result in database.
448
  $self->item->update_attributes(requirement_spec_id => $::form->{requirement_spec_id}, parent_id => $new_parent_id);
449
  $self->item->add_to_list(position => 'after', reference => $insert_after) if $insert_after;
450

  
451
  # Update the tree: create the node for all pasted objects.
452
  $self->create_and_insert_node_rec($js, $self->item, $new_parent_id, $insert_after);
453

  
454
  # Pasting the very first section?
455
  if (!@{ $self->sections }) {
456
    my $html = $self->render('requirement_spec_item/_section', { output => 0 }, requirement_spec_item => $self->item);
457
    $js->html('#column-content', $html)
458
       ->jstree->select_node('#tree', '#fb-' . $self->item->id)
459
  }
460

  
461
  # Update the current view if required.
462
  $self->insert_new_item_in_section_view($js) if $self->is_item_visible;
463

  
464
  $js->render($self);
465
}
466

  
353 467
#
354 468
# filters
355 469
#
......
483 597
  my $js = SL::ClientJS->new;
484 598

  
485 599
  my $new_section = $self->item->section;
486
  if (!$self->visible_section || ($self->visible_section->id != $new_section->id)) {
600
  if (!$self->is_item_visible) {
487 601
    # Show section/item to edit if it is not visible.
488 602

  
489 603
    $html = $self->render('requirement_spec_item/_section', { output => 0 }, requirement_spec_item => $new_section);
......
503 617
  $js->render($self);
504 618
}
505 619

  
620
sub is_item_visible {
621
  my ($self, $item) = @_;
622

  
623
  $item ||= $self->item;
624
  return $self->visible_section && ($self->visible_section->id == $item->section->id);
625
}
626

  
506 627
1;
SL/Controller/RequirementSpecTextBlock.pm
7 7
use Time::HiRes ();
8 8

  
9 9
use SL::ClientJS;
10
use SL::Clipboard;
10 11
use SL::DB::RequirementSpec;
11 12
use SL::DB::RequirementSpecPredefinedText;
12 13
use SL::DB::RequirementSpecTextBlock;
......
19 20
  'scalar --get_set_init' => [ qw(predefined_texts) ],
20 21
);
21 22

  
22
__PACKAGE__->run_before('load_requirement_spec_text_block', only => [qw(ajax_edit ajax_update ajax_delete ajax_flag dragged_and_dropped)]);
23
__PACKAGE__->run_before('load_requirement_spec_text_block', only => [qw(ajax_edit ajax_update ajax_delete ajax_flag dragged_and_dropped ajax_copy)]);
23 24

  
24 25
#
25 26
# actions
......
248 249
  $self->render($js);
249 250
}
250 251

  
252
sub action_ajax_copy {
253
  my ($self, %params) = @_;
254

  
255
  SL::Clipboard->new->copy($self->text_block);
256
  SL::ClientJS->new->render($self);
257
}
258

  
259
sub action_ajax_paste {
260
  my ($self, %params) = @_;
261

  
262
  my $copied = SL::Clipboard->new->get_entry(qr/^RequirementSpecTextBlock$/);
263
  if (!$copied) {
264
    return SL::ClientJS->new
265
      ->error(t8("The clipboard does not contain anything that can be pasted here."))
266
      ->render($self);
267
  }
268

  
269
  my $js                      = SL::ClientJS->new;
270
  my $current_output_position = $self->output_position_from_id($::form->{current_content_id}, $::form->{current_content_type});
271
  my $new_output_position     = $::form->{id} ? $self->output_position_from_id($::form->{id}) : $::form->{output_position};
272
  my $front_back              = 0 == $new_output_position ? 'front' : 'back';
273

  
274
  $self->text_block($copied->to_object);
275
  $self->text_block->update_attributes(requirement_spec_id => $::form->{requirement_spec_id}, output_position => $new_output_position);
276
  $self->text_block->add_to_list(position => 'after', reference => $::form->{id}) if $::form->{id};
277

  
278
  if ($current_output_position == $new_output_position) {
279
    my $html = $self->render('requirement_spec_text_block/_text_block', { output => 0 }, text_block => $self->text_block);
280
    $js->action($::form->{id} ? 'insertAfter' : 'appendTo', $html, '#text-block-' . ($::form->{id} || 'list'));
281
  }
282

  
283
  my $node = $self->presenter->requirement_spec_text_block_jstree_data($self->text_block);
284
  $js->jstree->create_node('#tree', $::form->{id} ? ('#tb-' . $::form->{id}, 'after') : ("#tb-${front_back}", 'last'), $node)
285
     ->render($self);
286
}
287

  
251 288
#
252 289
# filters
253 290
#
js/requirement_spec.js
287 287
      , sep1:   "---------"
288 288
      , flag:   { name: kivi.t8('Toggle marker'),     icon: "flag",   callback: standard_text_block_ajax_call, disabled: disable_edit_text_block_commands }
289 289
      , sep2:   "---------"
290
      , copy:   { name: kivi.t8('Copy'),              icon: "copy",                                            disabled: function() { return true; } }
291
      , paste:  { name: kivi.t8('Paste'),             icon: "paste",                                           disabled: function() { return true; } }
292
      // , copy:   { name: kivi.t8('Copy'),              icon: "copy",                                            disabled: disable_edit_text_block_commands }
293
      // , paste:  { name: kivi.t8('Paste'),             icon: "paste",                                           disabled: disable_edit_text_block_commands }
290
      , copy:   { name: kivi.t8('Copy'),              icon: "copy",   callback: standard_text_block_ajax_call, disabled: disable_edit_text_block_commands }
291
      , paste:  { name: kivi.t8('Paste'),             icon: "paste",  callback: standard_text_block_ajax_call  }
294 292
    }
295 293
  });
296 294

  
......
311 309
      , sep2:               "---------"
312 310
      , flag:               { name: kivi.t8('Toggle marker'),      icon: "flag",   callback: standard_item_ajax_call, disabled: disable_edit_item_commands }
313 311
      , sep3:               "---------"
314
      , copy:               { name: kivi.t8('Copy'),               icon: "copy",                                      disabled: function() { return true; } }
315
      , paste:              { name: kivi.t8('Paste'),              icon: "paste",                                     disabled: function() { return true; } }
316
      // , copy:               { name: kivi.t8('Copy'),               icon: "copy",                                      disabled: disable_edit_item_commands }
317
      // , paste:              { name: kivi.t8('Paste'),              icon: "paste",                                     disabled: disable_edit_item_commands }
312
      , copy:               { name: kivi.t8('Copy'),               icon: "copy",   callback: standard_item_ajax_call, disabled: disable_edit_item_commands }
313
      , paste:              { name: kivi.t8('Paste'),              icon: "paste",  callback: standard_item_ajax_call }
318 314
    }
319 315
  });
320 316

  
......
330 326
      , sep2:                   "---------"
331 327
      , flag:                   { name: kivi.t8('Toggle marker'),          icon: "flag",   callback: standard_item_ajax_call, disabled: disable_edit_item_commands }
332 328
      , sep3:                   "---------"
333
      , copy:                   { name: kivi.t8('Copy'),                   icon: "copy",                                      disabled: function() { return true; } }
334
      , paste:                  { name: kivi.t8('Paste'),                  icon: "paste",                                     disabled: function() { return true; } }
335
      // , copy:                   { name: kivi.t8('Copy'),                   icon: "copy",                                      disabled: disable_edit_item_commands }
336
      // , paste:                  { name: kivi.t8('Paste'),                  icon: "paste",                                     disabled: disable_edit_item_commands }
329
      , copy:                   { name: kivi.t8('Copy'),                   icon: "copy",   callback: standard_item_ajax_call, disabled: disable_edit_item_commands }
330
      , paste:                  { name: kivi.t8('Paste'),                  icon: "paste",  callback: standard_item_ajax_call }
337 331
    }
338 332
  });
339 333
}

Auch abrufbar als: Unified diff