15 June 2010

Realaxy Actionscript Editor Tutorial — «Первое знакомство»

Adobe Flash
RASE Poster

ВСТУПЛЕНИЕ


Мы вышли на этап раннего доступа и ранней беты.
Опыт показал, что в основном пользователь видит в Realaxy еще один редактор AS и воспринимает через призму других редакторов. Привычки, потребление памяти, предпочтения во всем том, как это должно работать. Пользователь не хочет революции — он лишь хочет чуть-чуть лучше. Ну уж, конечно, не видит всей той «громадины», которая стоит за Realaxy и MPS. Отчасти виноваты мы — у нас просто не было времени и сил на документацию. Если смотреть глазами пользователя, то мы говорим общие фразы и бросаемся красивыми словами. Но нет конкретики. Поэтому я принял решение: никакой разработки ближайшую неделю. Я буду писать серию статей про работу в редакторе. Цель номер один — читатель статей должен найти понимание. Цель номер два — читатель должен обрести чувство, которое испытываю я сам — чувство, что что-то начинается, чувство близости революции в программировании и в целом во всем том, что называется «написание программ».

ДЛЯ ХАБРА-ЧИТАТЕЛЕЙ


Сегодня подумалось, что для нашей совместной пользы будет, если я буду выставлять статьи сначала здесь, на Хабре. Это поможет находить проблемные места и непонимания в тексте. Тема редактирования в MPS и RASE, и тема мета-программирования, действительно сложно описать понятно, привычными терминами, поэтому мне действительно нужна ваша помощь. Просто и понятно донести до читателя иногда сложные, а скорее просто незнакомые вещи. Если будут комментарии и мысли по делу — очень прошу — подскажите. Одна голова хорошо, а несколько сотен — лучше. Кто не в теме — зайдите на сайт realaxy.com и посмотрите видео ролики на первой странице. Потом начните читать этот урок. Следуйте инструкциям и возможно вы даже проникнитесь. Если будет интерес дальше — почитайте статьи на сайте.

Огромное спасибо. Итак, начнем.

ИНСТАЛЛЯЦИЯ RASE


Если редактор не установлен, устанавливаем. Первое открытие в бета-версии занимает достаточное количество времени. В первом открытии происходят некие процессы, от которых мы избавимся в будущем — компиляции и создание индексов — это бета, мы об этом знаем и знаем как это решить.

До билда 8000 (переход на платформу MPS 1.5) возможны серьезные проблемы с памятью и производительностью. Обратите внимание на датчик потребления памяти внизу-справа — если потребление памяти больше 500 mb и есть значительное «подтормаживание», то RASE стоит перегрузить. Это ранняя бета и не все еще так хорошо, как хотелось бы. Корректно закройте редактор и запустите его снова. Опять же, при экстренном закрытии редактора, запуск его после «падения» может занять значительное время.


НОВЫЙ ПРОЕКТ


Создадим новый RASE проект — File->New ActionScript Project.

image

1. В первом окне нажимаем «Next»
2. На втором шаге назовем проект Tutorial и назовем наш модуль ActionScript
3. В третьем окне оставим так же — нам нужен Main class.
4. Нажимаем «Finish» и ждем некоторое время (в бете это может занять несколько минут, поэтому наливаем чай).
5. Ожидание закончилось. Перед нами наш первый проект в Realaxy ActionScript Editor. В левой панели разворачиваем дерево и открываем наш класс Main.

image

Опять же это бета и отмечены сбои. Если класса нет, правый клик на модуле «ActionScript» — создадим пакет «com.example». Правый клик на пакете — создаем класс «Main»

image

ПРЕДВАРИТЕЛЬНАЯ ПОДГОТОВКА


Перемещаем курсор на класс и делаем небольшую подготовку.
Нажимаем ctrl+R и пишем Sprite. Нажимаем enter.
Мы сделали наш первый импорт — flash.display.

image

Специально отметил этот момент, так как в бете как оказалось проблема «как импортировать класс или в RASE нет 90% классов AS3» вторая после «долгой загрузкой проекта» — мы это решим дайте нам только немного времени.
И важное замечание — в «коде» ничего не должно происходить :) «Импорт» прошел вполне правильно после нажатие ENTER. Это не ошибка — читайте дальше и все прояснится буквально в следующем обзаце. В этой точке текста отмечены случаи вхождения в бесконечную рекурсию.


Поимпортируем еще чего нибудь для тренировки — Event — неплохой вариант.

КОД ЭТО НЕ КОД


Теперь смотрим в «код» и никаких импортов не видим. Где же они? Выделяем в панели проекта наш пакет «com.example» и правой кнопкой вызываем панель свойств (можно просто нажать alt+enter). В закладке «Common» видим импортированные модели.

image

Почему модели? В терминах MPS нет понятия класса и пакета. Это сущности ActionScript. (сущности конкретного языка, а языков может быть много). С точки зрения редактора есть руты и модели. Мы используем слово пакет или класс, но все же подразумеваем, что класс — это такой вид рута (интерфейс, глобальная функция — это тоже руты) А пакет — это модель, которая хранит руты ActionScript. Надеюсь никого не запутал. В общем, если видите упоминание про «модель» — можете читать «пакет». Если это root, то это то, что может лежать в модели (пакете).

Так почему нет импортов в «коде»? Вот этот момент я люблю больше всего. Это не код. Это не текст. Это такой GUI. Пауза.

Как работает обычный ТЕКСТОВОЙ редактор кода? Вы пишете некий текст, который должен соответствовать синтаксическим правилам определенного языка. Т.е. есть текст, и есть некий контролирующий инструмент — «парсер», который помогает писать «правильно».

Наш редактор строится на совершенно других принципах. Нет больше текста. Есть специально разработанный для «написания» кода GUI. Пользовательский интерфейс. Нет «парсера», а есть правила работы с кодом, которые решают не столько вопрос соответствия неким правилам конкретного языка программирования, сколько решают вопросы юзабилити — работы с интерфейсом.

Еще Саша меня попросил написать, что мы работаем с AST. Привет, Саша!

Почему так сделано? В первую очередь — это языковые расширения. Писать каждый раз «парсер» для языка — это та еще работа. Отказавшись от «парсера», мы развязываем себе руки в расширяемости языков. А вторая (а может быть и первая) причина — а ведь GUI это удобнее чем текст, черт побери. Большинство слов в коде программы не для нас, а для компьютера — для компилятора. Мы доносим компилятору свои мысли. RASE же, наоборот, изначально имеет понимание структуры, он работает с синтаксическим деревом. Вы просто не можете ввести неверный код. Поэтому поначалу написание кода видится таким непривычным. Но поверьте — сначала не привычно, но поработайте в этой среде пару дней — вы привыкните, а через неделю я уверен, что вы озвучите мысль как и я когда-то — «как было раньше — писать код в виде текста — это неправильно!»

Как поется в известной в России песне о будущем — «вкалывают роботы, а не человек!». Это уже для западного читателя.

Возвращаемся к нашему туториалу.

УЧИМСЯ ПРЫГАТЬ ПО КОДУ — КЛАВИША ТАБ


Откройте класс, поставьте курсор на код и нажмите несколько раз клавишу TAB.
Это важный момент понимания навигации по коду в Realaxy.
В отличии от перемещения стрелками клавиатуры, мы переходим в точки редактирования. Все точки кода, которые можно редактировать, «приглашают» наш курсор. Eсли нажать TAB. TAB — ближайшая точка редактирования впереди, Shift+TAB позади. Таб — важная комбинация при редактировании кода в RASE. Ну и стрелки никто не отменяет — два-три нажатия стрелок вниз — достигли примерной зоны редактирования. Нажали несколько раз TAB — «прицелились».

ДОБАВИМ ЧЕГО-НИБУДЬ — клавиша ENTER


Второй по важности принцип работы с кодом в Realaxy — добавление дочернего элемента.
Такими элементами могут являться методы, поля, параметры метода. Есть «отец» класс, а у него есть дети: методы и поля. Метод имеет детей-параметры. Тело метода имеет детей-строчки кода. Строчки кода имеют детей-expression. Клавиша Enter добавляет новых детей в точке редактирования.

Нажимая Tab, переместитесь в точку с методом — там написано <<methods>>.

image

Мы переместились в точку, которая может содержать методы экземпляра. Нажимаем Enter — добавили новый метод и наш курсор переместился в точку, где нужно ввести название метода. Название красное — RASE нам сигнализирует, что нужно что-то добавить. Пишем название метода. Тут же нажимаем таб и перемещаемся в точку параметров — (|). Нажимаем Enter — опять мы добавили дочерний код, называем параметр «a» ( a: <type>). Нажимаем опять TAB и попадаем в место, где нужно указывать тип (<type>), пишем «int» — параметр добавлен. Добавим еще один параметр — b: int (enter, «b», tab, «int»). Добавим третий параметр — c: int (ну вы поняли).

image

Если нужно добавить ребенка «до», нажимаем shift+enter (mac) или insert (win). Нажмем shift+tab несколько раз и переместимся в точку редактирования. В название первого параметра метода a: int. Добавляем новый параметр «до» (на ваш выбор).

УЧИМСЯ ВЫДЕЛЯТЬ ЭЛЕМЕНТЫ
РОДИТЕЛЬ-РЕБЕНОК ctrl+UP/DOWN


В начале знакомства с RASE необходимо понять принципы выделения элементов. Это не текст, мы работаем с ячейками. Ячейки включены друг в друга. Чтобы выделить родительскую ячейку, нужно нажать комбинацию ctrl+UP. Добавьте метод и напишите if(ctrl+space). Переместите курсор в тело if(){|} и еще добавьте один if block. Внутри тела метода нажмите несколько раз ctrl+UP. Когда весь класс станет синим, нажмите комбинацию клавиш clrl+DOWN несколько раз и вернитесь к исходному положению. Я думаю, на этом остановимся.

image

Выделяя элементы, мы переходим в разные контексты редактирования и нам становятся доступны действия для конкретного элемента. Один из примеров — у нас параметр метода со значением по умолчанию. Мы хотим добавить новый параметр. Значение — это выражение и, нажав на ENTER, мы, скорее всего, изменим выражение, но не добавим новый парамер. Нажав ctrl+UP несколько раз, мы выделим именно параметр метода и нажатие клавиши ENTER приведет к добавлению именно его соседа. Итак ctrl+UP/DOWN.

УЧИМСЯ ВЫДЕЛЯТЬ ЭЛЕМЕНТЫ (2)
дочерние элементы shift+UP/DOWN


Чтобы выделить элементы, представленные списком, нужно нажать комбинацию клавиш shift+UP/DOWN. Зайдите в тело метода и добавьте два комментария ("//", «first line», ENTER, "//", «second line»),
получили две строчки. Чтобы выделить обе, нужно нажать два раза shift+UP, если мы находимся на второй строке или shift+DOWN, если находимся на первой.
Добавим массив в виде строки кода квадратная скобка и четыре элемента — [1, 2, 3, 4]. Это тоже список. Попрактикуемся с выделением элементов.
Так же работает выделение и с параметрами метода (a: int, b: int, c: int).

image

УДАЛЕНИЕ ЭЛЕМЕНТА — BACKSPACE


По-моему и так все понятно :) Вкупе с умным выделением удалять элементы удобно. Да.
Но на самом деле все еще интереснее — в Realaxy удаление элементов более умное, чем в каком-либо другом редакторе.
Например, создайте блок if и добавьте в него какой-нибудь код. Теперь поставьте курсор на сам «if» и нажмите backspace. Будет выполнено поведение, противоположное оборачиванию (surround) кода — unwrap. Часто от блоков нужно отказаться — код нужно вынести за пределы блока и удалить сам блок. В RASE самый простой способ это сделать. Попробуйте с блоком with(){} — там все еще интереснее.
Кроме блоков кода многие элементы реагируют на удаление. Такое умное поведение кода очень экономит наше время.

ПЕРЕМЕЩЕНИЕ КОДА alt+UP/DOWN


Поначалу это поведение выглядит как фокус, но когда понимаешь, что это такой GUI, то все встает на свои места. Выделяем строки и параметры и перемещаем их в другой метод, в другой блок кода. Лучше посмотреть видео.

НАЧИНАЕМ ПИСАТЬ КОД


Мы создали метод и у метода есть параметры. Даже написали пару блоков условия. Настала пора написать уж чего-нибудь.

image

Уже не плохо. Выделим код (ctrl+UP), чтобы было выделено 1+2+3+4 (без точки с запятой), и нажмем комбинацию клавиш ctrl+alt+V.
Получилась переменная —

image

Тут можно хвататься за голову и кричать — «еще одно клавиатурное сокращение!». Ответ — с этого места нашего туториала пошли дополнительные клавиатурные сокращения для рефакторинга и трасформации ячеек, и если вы что-то забыли, вы можете посмотреть это в меню либо нажать правую кнопку и посмотреть «cell actions» — там много всего. Сразу все не запомнишь — это понятно.

И опять немного теории. Здесь нужно чуть-чуть понимания. Код в RASE представлен ячейками. Потому он такой умный и систематизированный. Код «сверстан» ячейками. Есть разные ячейки: одни могут содержать просто текст, другие могут содержать другие ячейки. Есть специальные ячейки для ссылок. Есть коллекции, которые объединяют ячейки в вертикальные и горизонтальные группы.

Вернемся к теме, которая уже обсуждалась: чем отличается ввод текста в текстовом редакторе и редакторе на платформе MPS. При вводе текста в текстовом редакторе пользователь может ввести что угодно, а потом «парсер» проверяет, правильно ли это. В MPS редактор принимает текст, вводимый пользователем, и реагирует на ввод — создает ячейки — код-GUI.

При вводе текста пользователем работают механизмы, которые «понимают», что пользователь ввел нужный текст, и его можно интерпретировать как код. Таких «хитрых механизмов» тут два — правило замены и правило трансформации. Давайте рассмотрим процессы, которые происходят при написании нашего кода.

При вводе выражения произошли три замены и две трансформации:

image

Сначала мы ввели «var» — редактор видит контекст — пустая строка и получает список замен: на что он может заменить пустую строку.

image

При этом поиске он смотрит текстовое представление таких элементов и проверяет соответствие «введенный текст» — необходимый элемент. Такой список можно посмотреть полностью, если не вводить текст, а просто нажать ctrl+space — мы увидим список возможных для ЗАМЕНЫ элементов. И только один из них начинается на «var». Редактор понимает, что пользователь хочет добавить переменную, и сразу заменяет строку на шаблон — декларацию локальной переменной, которая выглядит как строка.

image

Курсор переместился на имя переменной — так сделано разработчиком языка специально, потому что я — как разработчик — разрабатываю сценарий работы с кодом и думаю, что пользователь после создания переменной захочет ее назвать. Назовем ее «name». Далее мы нажмем TAB и переместимся в ячейку, в которой нужно указывать тип переменной. Тут мы сразу нажмем ctrl+space и начнем писать «Str» — мы уже понимаем как работает наш интерфейс и в списке видем String и нажимаем ENTER.

image

Заменяем пустой тип на ячейку содержащую ссылку на класс String (примерно так).

Некоторые классы могут быть недоступны в списке автокомплита. В этом случае нужно нажать комбинацию клавиш ctrl+R и добавить импорт нужного пакета

Мы имеем выражение

image

Мы создали переменную и теперь нам нужно указать значение переменной. Ставим курсор после «String» и вводим знак равенства "=". Наше выражение приобретает вид:

image

Курсор стоит на значении переменной. Здесь был запущен и выполнен второй механизм, понимание которого так важного для комфортной работы в RASE — трансформация. Список всех трансформаций можно увидеть нажав пробел после/до элемента, и ctrl+space. Давайте вернемся назад (нажмем ctrl+Z или просто BACKSPACE, когда курсор у нас находится в <intializer>).
Опять поставим курсор после String и нажмем пробел.

image

Появится временная ячейка, которая нам подсказывает, что редактор находится в состоянии ожидания трансформации. Нажмем ctrl+space. Видим список. У меня в списке только один элемент — "= initializer".

image

Возможно, кто-то сделает языковое расширение и в списке может появится и второй элемент. Но языковые расширения — это тема другого туториала. Кстати, давайте пошалим. Нажмите ctrl+L и добавим язык «generics». В списке транформаций у нас уже больше — три элемента!

Итак, трансформация — это изменение кода через ввод текста после или до исходного элемента. Замена и трансформация — вот и все принципы работы с кодом в RASE. Важно знать, как это происходит с самого начала, чтобы понимать, как код трансформируется при вводе текста. Внешне выглядит, как будто вы вводите обычный текст, в обычном редакторе, а на самом деле умный интерфейс работает за нас.

Двигаемся дальше: нажимаем один раз открывающуюся кавычку — ‘"‘ и получаем шаблон строки (произошла замена). Пишем «hello».

Теперь добавляем плюс ("+")

image

и у нас происходит еще одна трансформация — мы создали новое выражение.

Добавляем еще одну строку (", «world»). Создание выражения закончено.

Мы получили нашу первую строку с первым выражением

image

И мы начали понимать, как наш редактор «думает». По-моему, отличное начало.

Замена и трансформация. Замена относительно привычное понятие для людей, пишущих код в редакторах. Ctrl+Space — ага, это понятно. Как бы замена или подобное поведение, есть в любом другом редакторе. Трансформация, я полагаю, это новый принцип редактирования кода, которого нет нигде, кроме основанных на MPS редакторах.

Еще пример трансформации — создайте блок if(){}. («if», в код был помещен блок if, курсор на условии). Теперь переместите курсор после закрывающей скобки. Можно просто ввести «else{», но ведь мы понимаем, что происходит — нажмем пробел и ctrl+space. В списке автокомплита мы видим два элемента — «else» и «else if».

image

Полюбите ctrl+space как самого себя! :) Данный туториал я пишу на берегу моря в открытом кафе в Черногории. Для меня это самый лучший способ сконцентрироваться. Спокойно и шумно одновременно. Дочка хочет сбить рукой мое пиво, я его отставил подальше от ее локтя. Мой макбук не залит :)


Итак, код писать мы уже умеем. Давайте сделаем что-нибудь реальное.

Удалим наш тестовый метод, и будем писать код в конструкторе. Пишем «new» и код принял вид

image

это шаблон создания экземпляра класса. Нам нужно создать экземпляр текстового поля. Нажимаем ctrl+space и пытаемся ввести «TextField» и мы видим, что его нет в списке автокомплита. У нас не импортирован пакет flash.text. Нажимаем комбинацию клавиш ctrl+R и пишем «TextField».

Небольшой tip&trick работы автокомплитом в RASE — чтобы быстро найти что-то в списке элементов, и мы точно знаем, что этот элемент именован в стиле «camel case», мы можем ввести заглавные буквы его названия — то есть «TextField» равно «TF»

Нажимаем ENTER — импорт добавлен. Повторяем действие добавления текстового поля в шаблон создания нового экземпляра («TF», ctrl+space) и код приобретает следующий вид:

image

Теперь выделяем выражение создания экземпляра текстового поля (да, нажимаем ctrl+UP неслько раз). Далее нажимаем клавиатурное сокращение ctrl+alt+V («introduce variable») и наш код приобретает вид

image

Практика написание кода через простые рефакторинги — очень продуктивна. Зачем создавать переменную и указывать ее тип, если сначала можно создать значение, и только потом создать переменную через introduce? Пусть редактор работает за нас! Еще один tip&trick создания переменной: напишите «TextField» и нажмите ctrl+space — в списке выбора есть шаблон для переменной — нужно нажать лишь один раз стрелку вниз и мы имем декларацию переменной с нужным нам типом. Но все же introduce, на мой взгляд, продуктивнее.


Имя нашей переменной выделено и мы можем его изменить.
Нажимаем ctrl+space и видим в выборе «textFild» — нажимаем ENTER.

image

Создадим новую строку кода, где мы будем указывать параметры нашего текстового поля.
Для этого опять выделим наш код и нажмем клавишу ENTER — добавлена новая строка.

Mы уже знаем, что строка кода — это элемент списка. Напомним себе, что мы выполнили одно из важнейших действий в редакторе RASE — добавление элемента в список нажатием ENTER

Пишем текст — «textField» и у нас появилась ячейка ссылки на переменную. Нажимаем точку "." и у нас прошла трансформация выражения — наша строка приняла вид

image

это шаблон доступа к свойствам и методам нашего текстового поля.
Нажимаем ctrl+space или просто пишем текст «text». Добавляем знак равенства (трансформация) и выражение приобретает вид:

image

Указываем наш текст «Hello World!».

image

Создадим новую строку и вызываем метод — addChild(textField).

image

По умолчанию класс Main был унаследован от Sprite. Если это не так, перейдите в ячейку, находящуюся после ключевого слова «extends», и добавьте суперкласс «Sprite»

Отлично!
Запустим наше приложение. Нам нужно создать run-configuration. Меню «Run» — «Edit Configuration» — добавляем новую конфигурацию (нажать иконку с плюсом). Назовем конфигурацию «First Tutorial» и сохраним.
Теперь нажмем зеленую иконку «Run» в меню действий или нажмем комбинацию клавиш shift+F10.

Код в Realaxy представлен синтаксическим деревом (AST), а не текстом. Поэтому перед компиляцией редактор запускает процесс генерации текстового представления кода из синтаксического дерева. Работа с синтаксическим деревом — большое преимущество по сравнению с другими (обычными текстовыми) редакторами. Мы можем очень много сделать в процессе генерации. Оптимизировать код, делать трансформации, и тут огромное поле для языковых расширений. И даже мы можем получить на выходе не AS, а HTML5 или же Object-C

В первый раз процесс генерации может занять некоторое время.
Потом редактор будет делать это быстрее и «генерить» только измененный код.

Через некоторое время (если мы все сделали правильно) появится окно flash-плеера. И в этом окне виден текст — «Hello World!».

Закончилась первая часть нашего знакомства с Realaxy ActionScritp Editor, которую обычно называют «Hello World!». Осталось описать совсем немного и можно сказать, что мы опиcали основы работы с RASE.

image

Небольшое резюме —
  1. Tab и стрелки клавиатуры — отличный способ навигации.
  2. Код в RASE — это не текст.
  3. Нужно понимать структуру кода. Чуть-чуть. Чтобы добавить элемент в список — нужно нажать ENTER.
  4. При написании кода в RASE нужно помнить два принципа — замена (subtitute) и трансформация (side transform).
  5. В RASE нужно много нажимать ctrl+space
  6. BACKSPACE — очень умная клавиша
  7. Пишем код через простые рефакторинги. Вместо создания переменной сделаем introduce variable

Перечислим основные клавиатурные сокращения при работе с RASE:

TAB — переместиться на следующую «точку редактирования»
SHIFT+TAB — переместиться в предыдущую «точку редактирования»
CTRL+UP — выделить элемент кода выше в иерархии вложенности ячеек
CTRL+DOWN — вернуться в истории выделенных нодов назад
SHIFT+UP/DOWN — выделить элементы списка
ALT+UP — переместить элемент списка в ближайшую (возможную) позицию влево
ALT+DOWN — переместить элемент списка в ближайшую правую позицию
ENTER — добавить элемент в список
SHIFT+ENTER (mac) или INSERT (win) — добавить элемент списка перед элементом на котором находится фокус
CTRL+SPACE — показать список замены элемента находящего в фокусе или выделенного
SPACE (справа или слева элемента) — показать ячейку трансформации (последующее нажатие CTRL+SPACE покажет список доступных трансформаций)

Базовые рефакторинги —
ctrl+alt+V — introduce variable — выносит выделенное значение в переменную
ctlr+alt+N (cmd+alt+N) — inline variable — производит действие обратное introduce — все ссылки на переменную заменяются на значения. Inline может вести себя по-разному, если курсор находится на декларации переменной или ссылкой на него (тут нужно по экcпериментировать либо подождать туториал)

Дополнительные клавиатурные полезности —
ctrl+alt+T — вывести меню Surround With
alt+Enter — меню квикфиксов и intentions — помощников (если вы видите лампочку в поле слева — то жмите эту комбинацию — возможно, там то, что вы ищите)
Shift+F10 — запустить выбранный (последний) RUN-configuration

ОКОНЧАНИЕ


Подошел к концу первый урок, я думаю он самый важный из серии. Он дает понимание основополагающих принципов в RASE. Я надеюсь, что дейстительно помог.
Через две недели мы готовим новую бету и все будет работать быстрее в несколько раз, и нарекания по производительности, я надеюсь уже не будет так много.
Следующий урок будет посвящен работе с языковыми расширениями. Это уже не так скучно, поверьте.

Удачи и до следующего урока.

Автор идеи, и руководитель разработки Realaxy ActionScript Editor
Евгений Потапенко

UPD: Объявление. Требуется переводчик на английский для долгосрочного сотрудничества. Пожалуйста, если вы потянете перевод таких текстов (подчеркиваю — для native читателей), напишите в комментариях либо приватом мне. Спасибо.
Tags:flashrealaxymps
Hubs: Adobe Flash
+84
2k 49
Comments 82
Popular right now
Дизайнер digital
from 80,000 ₽CreativePeopleМоскваRemote job
Дизайнер интерфейсов
from 70,000 ₽IdaprojectRemote job
Junior email-маркетолог
from 40,000 ₽Retail RocketМоскваRemote job
Дизайнер (UX/UI)
from 80,000 ₽IZIBOOKМоскваRemote job