Telekonsultacje

Michał Zdunek

Funkcjonalność aplikacji

  • Zdalna analiza i oglądanie serii zdjęć
  • Tagowanie zdjęć
  • Manipulacje na zdjęciach (rozjaśnianie, wyostrzanie itp.)
  • Możliwość globalnej lub lokalnej widoczności zmian
  • Komunikacja tekstowa (czat)
  • Komunikacja audio-wideo

Typ aplikacji - desktopowa czy webowa?

  • Aplikacja webowa jest dostosowana do komunikacji przez internet
  • Łatwość tworzenia interfejsu w aplikacji webowej (HTML, CSS)
  • Ograniczenie do środowiska przeglądarki
  • Problemy z komunikacją w czasie rzeczywistym

Komunikacja przez websockets

Technologia WebRTC

  • Przesyłanie danych peer-to-peer między przeglądarkami bez użycia zewnętrznych wtyczek
  • Wspierana w najnowszych wersjach przeglądarek
  • getUserMedia - umożliwia przeglądarce dostęp do kamery oraz mikrofonu
  • RTCPeerConnection - uruchamia komunikację audio i wideo
  • RTCDataChannel - umożliwia komunikację peer-to-peer między przeglądarkami

Architektura aplikacji po stronie serwera

  • Środowisko Ruby on Rails
  • Rozbudowany i sprawdzony framework do tworzenia aplikacji internetowych
  • Architektura Model-Widok-Kontroler (ang. MVC)
  • Duża ilość dostępnych dodatków

Ruby on Rails 5 - Action Cable

class ChatChannel < ApplicationCable::Channel
  def subscribed
    stream_from "chat_#{params[:room]}"
  end

  def receive(data)
    ActionCable.server.broadcast "chat_#{params[:room]}", data
  end
end
App.chatChannel = App.cable.subscriptions.create { channel: "ChatChannel", 
  room: "Best Room" },
  received: (data) ->
    # data => { sent_by: "Paul", body: "This is a cool chat app." }

App.chatChannel.send({ sent_by: "Paul", body: "This is a cool chat app." })

ReactJS

  • Biblioteka do tworzenia komponentów, z których składa się interfejs użytkownika
  • Przykład:
var CommentBox = React.createClass({
  render: function() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList />
        <CommentForm />
      </div>
    );
  }
});

CamanJS

  • Biblioteka do modyfikowania obrazków wygenerowanych w znaczniku <canvas>
  • Przykład:
  Caman('#my-image', function () {
    this.brightness(10);
    this.contrast(30);
    this.sepia(60);
    this.saturation(-30);
    this.render();
  });

Logowanie

  • Zaimplementowane przy użyciu dodatku Devise, rozszerzone o nazwę użytkownika

Zdjęcia

  • Jeden model dla zdjęć, przechowujący ich nazwę, treść oraz stan ich modyfikacji

Interfejs użytkownika

Wideoczat

  • Zaimplementowany z pomocą dodatku TalkingStick

Kanał PhotoChannel

class PhotoChannel < ApplicationCable::Channel
  def subscribed
    ...
    ActionCable.server.broadcast "photo_#{params[:id]}", data
  end

  def update(data)
    ...
  end

  def add_tag(data)
    ...
  end

  def delete_tag(data)
    ...
  end

  def add_message(data)
    ...
  end
end

Komponent InputRange

<div className="range-label">Powiększenie:</div>
<InputRange
  minValue={100}
  maxValue={300}
  value={this.state.scale}
  onChange={this.handleScaleChange.bind(this)}
  onChangeComplete={this.finishChange.bind(this)}
/>

Skalowanie oraz przesuwanie zdjęcia

<canvas
  id="img-canvas"
  width={default_width}
  height={default_height}
  style={{transform: `scale(${this.state.scale / 100})`,
    position: "absolute",
    left: this.state.x,
    top: this.state.y,
  }}
  onMouseDown={this.startDragging.bind(this)}
  onMouseUp={this.stopDragging.bind(this)}
></canvas>
getWithinBoundaries() {
  let sideMargin = (this.state.width * (this.state.scale/100 - 1)) / 2;
  let verticalMargin = (this.state.height * (this.state.scale/100 - 1)) / 2;
  x = Math.max(Math.min(this.state.x, sideMargin), -sideMargin);
  y = Math.max(Math.min(this.state.y, verticalMargin), -verticalMargin);
  this.setState({x, y});
  return {x, y};
}

Wyświetlanie tagów

renderTag(tag) {
  let middleWidth = this.state.width / 2;
  let middleHeight = this.state.height / 2;
  let multiplier = this.state.scale / 100;
  let x = (tag.x - middleWidth) * multiplier + middleWidth + this.state.x;
  let y = (tag.y - middleHeight) * multiplier + middleHeight + this.state.y;
  return (
    <div key={tag.id} className="tag" style={{left: x, top: y}}>
      <a href="#" onClick={this.deleteTag.bind(this, tag)}>x</a> {tag.text}
    </div>
  )
}

Możliwości rozwoju

  • Tworzenie prywatnych zdjęć
  • Bardziej rozbudowane modyfikacje - np. modyfikowanie części obrazka
  • Komunikacja jeden na jeden
Made with Slides.com