From 7b59026d393f2784e4f29628d061f4d6506c57c9 Mon Sep 17 00:00:00 2001 From: YunJae Jang Date: Tue, 23 Dec 2014 00:15:57 +0900 Subject: New Korean Translation New Korean Translation 2014-11-07 version --- lang-ko.js | 492 +++++++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 394 insertions(+), 98 deletions(-) mode change 100644 => 100755 lang-ko.js diff --git a/lang-ko.js b/lang-ko.js old mode 100644 new mode 100755 index a1e50eb..3241c7f --- a/lang-ko.js +++ b/lang-ko.js @@ -179,13 +179,13 @@ SnapTranslator.dict.ko = { // translations meta information 'language_name': - 'Korean', // the name as it should appear in the language menu + '한국어', // the name as it should appear in the language menu 'language_translator': 'Yunjae Jang', // your name for the Translators tab 'translator_e-mail': - 'yunjae.jang@inc.korea.ac.kr', // optional + 'janggoons@gmail.com', // optional 'last_changed': - '2012-11-18', // this, too, will appear in the Translators tab + '2014-11-07', // this, too, will appear in the Translators tab // GUI // control bar: @@ -218,7 +218,7 @@ SnapTranslator.dict.ko = { // editor: 'draggable': - '드래그 가능?', + '마우스로 직접 움직이기', // tabs: 'Scripts': @@ -243,8 +243,16 @@ SnapTranslator.dict.ko = { '왼쪽에서 오른쪽으로만', // new sprite button: - 'add a new sprite': - '새로운 스프라이트 추가', + 'add a new Turtle sprite': + '새로운 스프라이트 추가하기', + + // new paint sprite button: + 'paint a new sprite': + '새로운 스프라이트 그리기', + + // new paint costume button: + 'Paint a new costume': + '새로운 모양 그리기', // tab help 'costumes tab help': @@ -300,19 +308,19 @@ SnapTranslator.dict.ko = { 'point towards %dst': '%dst 쪽 보기', 'go to x: %n y: %n': - 'x: %n 、y: %n 쪽으로 가기', + 'x: %n 、y: %n 쪽으로 이동하기', 'go to %dst': - '%dst 위치로 가기', + '%dst 위치로 이동하기', 'glide %n secs to x: %n y: %n': - '%n 초 동안 x: %n 、y: %n 쪽으로 움직이기', + '%n 초 동안 x: %n 、y: %n 쪽으로 이동하기', 'change x by %n': 'x좌표 %n 만큼 바꾸기', 'set x to %n': - 'x좌표 %n 로 정하기', + 'x좌표 %n (으)로 정하기', 'change y by %n': 'y좌표 %n 만큼 바꾸기', 'set y to %n': - 'y좌표 %n 로 정하기', + 'y좌표 %n (으)로 정하기', 'if on edge, bounce': '벽에 닿으면 튕기기', 'x position': @@ -328,13 +336,13 @@ SnapTranslator.dict.ko = { 'next costume': '다음 모양', 'costume #': - '모양 #', + '모양 번호', 'say %s for %n secs': - '%s %n 초 동안 말하기', + '%s 을(를) %n 초 동안 말하기', 'say %s': '%s 말하기', 'think %s for %n secs': - '%s %n 초간 생각하기', + '%s 을(를) %n 초 동안 생각하기', 'think %s': '%s 생각하기', 'Hello!': @@ -342,15 +350,15 @@ SnapTranslator.dict.ko = { 'Hmm...': '흠…', 'change %eff effect by %n': - '%eff 효과 %n 만큼 바꾸기', + '%eff 효과를 %n 만큼 바꾸기', 'set %eff effect to %n': - '%eff 효과 %n 만큼 주기', + '%eff 효과를 %n 만큼 정하기', 'clear graphic effects': '그래픽 효과 지우기', 'change size by %n': - '크기 %n 만큼 바꾸기', + '크기를 %n 만큼 바꾸기', 'set size to %n %': - '크기 %n % 로 정하기', + '크기를 %n % 로 정하기', 'size': '크기', 'show': @@ -373,82 +381,96 @@ SnapTranslator.dict.ko = { 'play sound %snd': '%snd 소리내기', 'play sound %snd until done': - '끝날때까지 %snd 소리내기', + '%snd 을(를) 끝까지 소리내기', 'stop all sounds': '모든 소리 끄기', 'rest for %n beats': - '%n 비트 동안 쉬기', + '%n 박자 동안 쉬기', 'play note %n for %n beats': - '%n 음을 %n 비트로 연주하기', + '%n 음을 %n 박자로 연주하기', 'change tempo by %n': - '템포를 %n 만큼 바꾸기', + '빠르기를 %n 만큼 바꾸기', 'set tempo to %n bpm': - '템포를 %n bpm으로 맞추기', + '빠르기를 %n bpm으로 정하기', 'tempo': - '템포', + '빠르기', // pen: 'clear': - '지우기', + '펜 자국 지우기', 'pen down': '펜 내리기', 'pen up': '펜 올리기', 'set pen color to %clr': - '펜의 색 %clr 으로 정하기', + '펜 색깔을 %clr 으로 정하기', 'change pen color by %n': - '펜의 색 %n 만큼 바꾸기', + '펜 색깔을 %n 만큼 바꾸기', 'set pen color to %n': - '펜의 색 %n 으로 정하기', + '펜 색깔을 %n (으)로 정하기', 'change pen shade by %n': - '펜의 그림자 %n 만큼 바꾸기', + '펜 음영을 %n 만큼 바꾸기', 'set pen shade to %n': - '펜의 그림자 %n 으로 정하기', + '펜 음영을 %n 으로 정하기', 'change pen size by %n': - '펜의 크기 %n 만큼 바꾸기', + '펜 굵기를 %n 만큼 바꾸기', 'set pen size to %n': - '펜의 크기 %n 으로 정하기', + '펜 굵기를 %n (으)로 정하기', 'stamp': - '스탬프', + '도장찍기', // control: 'when %greenflag clicked': - '%greenflag 클릭되었을 때', + '%greenflag 클릭했을 때', 'when %keyHat key pressed': - '%keyHat 키 눌렀을 때', + '%keyHat 키를 눌렀을 때', 'when I am clicked': - '자신이 클릭되었을 때', + '이 스프라이트를 클릭했을 때', 'when I receive %msgHat': - '%msgHat 받을 때', + '%msgHat 을(를) 받았을 때', 'broadcast %msg': '%msg 방송하기', 'broadcast %msg and wait': '%msg 방송하고 기다리기', 'Message name': - '메세지 이름', + '메시지 이름', + 'message': + '메시지', + 'any message': + '어떤 메시지', 'wait %n secs': '%n 초 기다리기', 'wait until %b': '%b 까지 기다리기', 'forever %c': - '무한반복 %c', + '무한 반복하기 %c', 'repeat %n %c': - '반복 %n 회 %c', + '%n 번 반복하기 %c', 'repeat until %b %c': - '반복 %b 계속 확인 %c', + '%b 까지 반복하기 %c', 'if %b %c': '만약 %b 라면 %c', 'if %b %c else %c': '만약 %b 라면 %c 아니면 %c', 'report %s': '%s 출력하기', - 'stop block': - '블록 멈추기', - 'stop script': - '스크립트 멈추기', - 'stop all %stop': - '모두 멈추기 %stop', + 'stop %stopChoices': + '%stopChoices 멈추기', + 'all': + '모두', + 'this script': + '이 스크립트', + 'this block': + '이 블록', + 'stop %stopOthersChoices': + '%stopOthersChoices 멈추기', + 'all but this script': + '이 스크립트를 제외한 모두', + 'other scripts in sprite': + '이 스프라이트에 있는 다른 스크립트', + 'pause all %pause': + '모두 잠시 멈추기 %pause', 'run %cmdRing %inputs': '%cmdRing 을(를) %inputs 으로 실행하기', 'launch %cmdRing %inputs': @@ -461,14 +483,23 @@ SnapTranslator.dict.ko = { '반복해서 %cmdRing 을(를) 호출하기', 'warp %c': '워프 %c', + 'when I start as a clone': + '복제되었을 때', + 'create a clone of %cln': + '%cln 을(를) 복제하기', + 'myself': + '나 자신', + 'delete this clone': + '이 복제본 삭제하기', + // sensing: 'touching %col ?': - '%col 에 닿기?', + '%col 에 닿았는가?', 'touching %clr ?': - '%clr 색에 닿기?', + '%clr 색에 닿았는가?', 'color %clr is touching %clr ?': - '%clr 색이 %clr 색에 닿기?', + '%clr 색이 %clr 색에 닿았는가?', 'ask %s and wait': '%s 을(를) 묻고 기다리기', 'what\'s your name?': @@ -476,36 +507,59 @@ SnapTranslator.dict.ko = { 'answer': '대답', 'mouse x': - '마우스 x좌표', + '마우스의 x좌표', 'mouse y': - '마우스 y좌표', + '마우스의 y좌표', 'mouse down?': - '마우스 클릭하기?', + '마우스를 클릭했는가?', 'key %key pressed?': - '%key 키 클릭하기?', + '%key 키를 눌렀는가?', 'distance to %dst': '%dst 까지 거리', 'reset timer': '타이머 초기화', 'timer': '타이머', + '%att of %spr': + '%att ( %spr 에 대한)', 'http:// %s': 'http:// %s', - + 'turbo mode?': + '터보 모드인가?', + 'set turbo mode to %b': + '터보 모드 %b 으로 설정하기', 'filtered for %clr': '%clr 색 추출하기', 'stack size': '스택 크기', 'frames': '프레임', + 'current %dates': + '현재 %dates', + 'year': + '연도', + 'month': + '월', + 'date': + '일', + 'day of week': + '요일(1~7)', + 'hour': + '시간', + 'minute': + '분', + 'second': + '초', + 'time in milliseconds': + '밀리세컨드초', // operators: '%n mod %n': - '%n 나누기 %n 의 나머지', + '( %n / %n ) 의 나머지', 'round %n': '%n 반올림', '%fun of %n': - '%n 의 %fun', + '%fun ( %n 에 대한)', 'pick random %n to %n': '%n 부터 %n 사이의 난수', '%b and %b': @@ -520,12 +574,14 @@ SnapTranslator.dict.ko = { '거짓', 'join %words': '%words 결합하기', + 'split %s by %delim': + '%s 를 %delim 기준으로 나누기', 'hello': '안녕', 'world': - '세계', + '세상', 'letter %n of %s': - '%n 의 %s 번째 글자', + '%n 번째 글자 ( %s 에 대한)', 'length of %s': '%s 의 길이', 'unicode of %s': @@ -533,9 +589,11 @@ SnapTranslator.dict.ko = { 'unicode %n as letter': '유니코드 %n 에 대한 문자', 'is %s a %typ ?': - '%s 이(가) %typ 인가요?', + '%s 이(가) %typ 인가?', + 'is %s identical to %s ?': + '%s 와(과) %s 가 동일한가?', 'type of %s': - '%s 타입', + '%s 의 타입', // variables: 'Make a variable': @@ -543,12 +601,12 @@ SnapTranslator.dict.ko = { 'Variable name': '변수 이름', 'Delete a variable': - '변수 삭제', + '변수 삭제하기', 'set %var to %s': - '%var 을(를) %s 로 저장', + '변수 %var 에 %s 저장하기', 'change %var by %n': - '%var 에 %n 씩 누적하기', + '변수 %var 을(를) %n 만큼 바꾸기', 'show variable %var': '변수 %var 보이기', 'hide variable %var': @@ -560,38 +618,78 @@ SnapTranslator.dict.ko = { 'list %exp': '리스트 %exp', '%s in front of %l': - '%s 을(를) %l 처음에 추가하기 ', + '%s 을(를) 리스트 %l 의 맨 앞에 추가하기 ', 'item %idx of %l': - '%idx 항목 %l', + '%idx 번째 항목 (리스트 %l 에 대한)', 'all but first of %l': - '%l 첫번째 아이템 제외한 모든 아이템', + '리스트 %l 에서 첫 번째 항목 제외하기', 'length of %l': - '%l 의 크기', + '리스트 %l 의 항목 갯수', '%l contains %s': - '%l 에 %s 포함?', + '리스트 %l 에 %s 포함되었는가?', 'thing': - '아이템', + '어떤 것', 'add %s to %l': - '%s 을(를) %l 마지막에 추가하기 ', + '%s 을(를) 리스트 %l 의 마지막에 추가하기 ', 'delete %ida of %l': - '%ida 을(를) %l 에서 삭제하기 ', + '%ida 번째 항목 삭제하기 (리스트 %l 에 대한)', 'insert %s at %idx of %l': - '%s 을(를) %idx 위치에 추가하기 %l', + '%s 을(를) %idx 위치에 추가하기 (리스트 %l 에 대한)', 'replace item %idx of %l with %s': - '%idx 항목 %l 에 %s 로 교체하기', + '%idx 번째 (리스트 %l 에 대한) 를 %s (으)로 바꾸기', // other 'Make a block': '블록 만들기', + // Paint Editor + 'Paint Editor': + '그림 편집기', + 'undo': + '되돌리기', + 'grow': + '확대', + 'shrink': + '축소', + 'flip ↔': + '↔ 반전', + 'flip ↕': + '↕ 반전', + 'Brush size': + '펜 크기', + 'Constrain proportions of shapes?\n(you can also hold shift)': + '도형 크기 비율을 고정하는가?\n(shift 키를 눌러서 사용할 수 있습니다.)', + 'Paintbrush tool\n(free draw)': + '붓 도구', + 'Stroked Rectangle\n(shift: square)': + '사각형 그리기 도구\n(shift: 정사각형)', + 'Stroked Ellipse\n(shift: circle)': + '타원 그리기 도구\n(shift: 원)', + 'Eraser tool': + '지우개 도구', + 'Set the rotation center': + '회전축 설정하기', + 'Line tool\n(shift: vertical/horizontal)': + '선 그리기 도구\n(shift: 수평/수직)', + 'Filled Rectangle\n(shift: square)': + '채워진 사각형 그리기 도구\n(shift: 정사각형)', + 'Filled Ellipse\n(shift: circle)': + '채워진 타원 그리기 도구\n(shift: 원)', + 'Fill a region': + '색 채우기', + 'Pipette tool\n(pick a color anywhere)': + '스포이드 도구\n(원하는 색 선택하기)', + // menus // snap menu 'About...': 'Snap! 에 대해서...', + 'Reference manual': + '참고자료 다운로드', 'Snap! website': 'Snap! 웹사이트', 'Download source': - '소스 다운로드', + '소스코드 다운로드', 'Switch back to user mode': '사용자 모드로 전환', 'disable deep-Morphic\ncontext menus\nand show user-friendly ones': @@ -609,15 +707,18 @@ SnapTranslator.dict.ko = { 'Open...': '열기...', 'Save': - '저장', + '저장하기', + 'Save to disk': + '내 컴퓨터에 저장하기', + 'experimental - store this project\nin your downloads folder': + '실험적 - 이 프로젝트를\n 다운로드 폴더에 저장합니다.', 'Save As...': - '다른 이름으로 저장...', + '다른 이름으로 저장하기...', 'Import...': '가져오기...', 'file menu import hint': '내보낸 프로젝트 파일, 블록 라이브러리\n' - + '스프라이트 모양 또는 소리를 가져옵니다.\n\n' - + '일부 웹브라우저에서는 지원되지 않습니다.', + + '스프라이트 모양 또는 소리를 가져옵니다.', 'Export project as plain text...': '프로젝트를 텍스트 파일로 내보내기...', 'Export project...': @@ -628,58 +729,171 @@ SnapTranslator.dict.ko = { '블록 내보내기...', 'show global custom block definitions as XML\nin a new browser window': '새롭게 정의한 전역 블록 데이터를\n새로운 윈도우에 XML 형태로 보여주기', + 'Export all scripts as pic...': + '모든 스크립트를 그림파일로 내보내기', + 'show a picture of all scripts\nand block definitions': + '모든 스크립트와 정의된 블록을 그림파일로 보여줍니다.', + 'Import tools': + '추가 도구 가져오기', + 'load the official library of\npowerful blocks': + '강력한 기능을 제공하는\n 블록들을 가져옵니다.', + 'Libraries...': + '라이브러리...', + 'Select categories of additional blocks to add to this project.': + '추가적인 블록을 선택해서\n 사용할 수 있습니다.', + 'Import library': + '라이브러리 가져오기', + + // cloud menu + 'Login...': + '로그인...', + 'Signup...': + '계정만들기...', + 'Reset Password...': + '비밀번호 재설정...', + 'url...': + 'url...', + 'export project media only...': + 'export project media only...', + 'export project without media...': + 'export project without media...', + 'export project as cloud data...': + 'export project as cloud data...', + 'open shared project from cloud...': + 'open shared project from cloud...', // settings menu 'Language...': '언어선택...', + 'Zoom blocks...': + '블록 크기 설정...', + 'Stage size...': + '무대 크기 설정...', + 'Stage size': + '무대 크기', + 'Stage width': + '가로(너비)', + 'Stage height': + '세로(높이)', + 'Default': + '기본설정', + 'Blurred shadows': '반투명 그림자', 'uncheck to use solid drop\nshadows and highlights': '체크해제하면, 그림자와 하이라이트가\n불투명 상태로 됩니다.', 'check to use blurred drop\nshadows and highlights': '체크하면, 그림자와 하이라이트가\n반투명 상태로 됩니다.', + 'Zebra coloring': '중첩 블록 구분하기', 'check to enable alternating\ncolors for nested blocks': '체크하면, 중첩된 블록을\n다른 색으로 구분할 수 있습니다.', 'uncheck to disable alternating\ncolors for nested block': '체크해제하면, 중첩된 블록을\n다른 색으로 구분할 수 없습니다.', + + 'Dynamic input labels': + 'Dynamic input labels', + 'uncheck to disable dynamic\nlabels for variadic inputs': + 'uncheck to disable dynamic\nlabels for variadic inputs', + 'check to enable dynamic\nlabels for variadic inputs': + 'check to enable dynamic\nlabels for variadic inputs', + 'Prefer empty slot drops': '빈 슬롯에 입력 가능', 'settings menu prefer empty slots hint': '설정 메뉴에 빈 슬롯의\n힌트를 사용할 수 있습니다.', 'uncheck to allow dropped\nreporters to kick out others': '체크해제하면, 기존 리포터 블록에\n새로운 리포터 블록으로 대체할 수 있습니다.', + 'Long form input dialog': '긴 형태의 입력 대화창', 'check to always show slot\ntypes in the input dialog': '체크하면, 입력 대화창에\n항상 슬롯의 형태를 보여줍니다.', 'uncheck to use the input\ndialog in short form': '체크해제하면, 입력 대화창을\n짧은 형태로 사용합니다.', + + 'Plain prototype labels': + '새로 만든 블록 인수 설정', + 'uncheck to always show (+) symbols\nin block prototype labels': + '체크해제하면, 블록 편집기에서\n 블록 인수 추가 버튼(+)을\n 보입니다.', + 'check to hide (+) symbols\nin block prototype labels': + '체크하면, 블록 편집기에서\n 블록 인수 추가 버튼(+)을\n 숨깁니다.', + 'Virtual keyboard': '가상 키보드', 'uncheck to disable\nvirtual keyboard support\nfor mobile devices': '체크해제하면, 모바일 기기에서\n가상 키보드를 사용할 수 없습니다.', 'check to enable\nvirtual keyboard support\nfor mobile devices': '체크하면, 모바일 기기에서\n가상 키보드를 사용할 수 있습니다.', + 'Input sliders': '입력창에서 슬라이더 사용', 'uncheck to disable\ninput sliders for\nentry fields': '체크해제하면, 입력창에서\n슬라이더를 사용할 수 없습니다.', 'check to enable\ninput sliders for\nentry fields': '체크하면, 입력창에서\n슬라이더를 사용할 수 있습니다.', + 'Clicking sound': '블록 클릭시 소리', 'uncheck to turn\nblock clicking\nsound off': '체크해제하면, 블록 클릭시\n소리가 꺼집니다.', 'check to turn\nblock clicking\nsound on': '체크하면, 블록 클릭시\n소리가 켜집니다.', + + 'Animations': + '애니메이션', + 'uncheck to disable\nIDE animations': + '체크해제하면, IDE 애니메이션을\n 사용할 수 없습니다.', + + 'Turbo mode': + '터보 모드', + 'check to prioritize\nscript execution': + '체크하면, 스크립트를\n 빠르게 실행합니다.', + 'uncheck to run scripts\nat normal speed': + '체크해제하면, 스크립트 실행 속도를\n 보통으로 합니다.', + + 'Flat design': + '플랫(Flat) 디자인', + 'uncheck for default\nGUI design': + '체크해제하면,\n 기본 GUI 디자인으로\n 변경합니다.', + 'check for alternative\nGUI design': + '체크하면, 플랫(Flat)\n 디자인으로 변경합니다.', + + 'Sprite Nesting': + 'Sprite Nesting', + 'uncheck to disable\nsprite composition': + 'uncheck to disable\nsprite composition', + 'check to enable\nsprite composition': + 'check to enable\nsprite composition', + 'Thread safe scripts': '스레드 안전 스크립트', - 'uncheck to allow\nscript reentrancy': + 'uncheck to allow\nscript reentrance': '체크해제하면, 스크립트\n재진입성을 허락합니다.', - 'check to disallow\nscript reentrancy': + 'check to disallow\nscript reentrance': '체크하면, 스크립트\n재진입성을 허락하지 않습니다.', + + 'Prefer smooth animations': + '자연스러운 애니메이션', + 'uncheck for greater speed\nat variable frame rates': + '체크해제하면, 프레임\n 전환 비율이 빨라집니다.', + 'check for smooth, predictable\nanimations across computers': + '체크하면, 애니메이션이\n 자연스러워 집니다.', + + 'Flat line ends': + '선 끝을 평평하게 만들기', + 'check for flat ends of lines': + '체크하면, 선 끝을\n 평평하게 만듭니다.', + 'uncheck for round ends of lines': + '체크해제하면, 선 끝을\n 둥글게 만듭니다.', + + 'Codification support': + '체계화 지원', + 'uncheck to disable\nblock to text mapping features': + 'uncheck to disable\nblock to text mapping features', + 'check for block\nto text mapping features': + '체크하면, check for block\nto text mapping features', // inputs 'with inputs': @@ -692,35 +906,55 @@ SnapTranslator.dict.ko = { // context menus: 'help': '도움말', + + // palette: + 'find blocks...': + '블록 찾기...', + 'hide primitives': + '기본 블록 숨기기', + 'show primitives': + '기본 블록 보이기', // blocks: 'help...': '블록 도움말...', + 'relabel...': + '블록 바꾸기...', 'duplicate': - '복사', + '복사하기', 'make a copy\nand pick it up': - '복사해서\n그 블록을 들고 있습니다.', + '복사해서\n들고 있습니다.', 'delete': - '삭제', + '삭제하기', 'script pic...': - '스크립트 그림...', + '이 스크립트를 그림파일로 내보내기...', 'open a new window\nwith a picture of this script': '이 스크립트 그림을\n새로운 윈도우에서 엽니다.', 'ringify': - '형태변환', + '블록형태 변환하기', + 'unringify': + 'unringify', // custom blocks: 'delete block definition...': - '블록 삭제', + '블록 삭제하기', 'edit...': '편집…', // sprites: 'edit': - '편집', + '스크립트 편집하기', 'export...': '내보내기...', + // stage: + 'show all': + '모든 스프라이트 나타내기', + 'pic...': + '그림파일로 내보내기...', + 'open a new window\nwith a picture of the stage': + '새로운 창을 열고\n무대의 화면을\n그림파일로 저장한다.', + // scripting area 'clean up': '스크립트 정리하기', @@ -728,12 +962,20 @@ SnapTranslator.dict.ko = { '스크립트를\n수직으로 정렬한다.', 'add comment': '주석 추가하기', + 'undrop': + '마지막으로 가져온 블록', + 'undo the last\nblock drop\nin this pane': + '마지막으로\n 가져온 블록을\n 확인한다.', + 'scripts pic...': + '모든 스크립트를 그림파일로 내보내기...', + 'open a new window\nwith a picture of all scripts': + '새로운 창을 열어서\n 모든 스크립트를\n 그림으로 저장한다.', 'make a block...': '블록 만들기...', // costumes 'rename': - '이름수정', + '이름 수정하기', 'export': '내보내기', @@ -750,7 +992,9 @@ SnapTranslator.dict.ko = { // dialogs // buttons 'OK': - 'OK', + '확인', + 'Ok': + '확인', 'Cancel': '취소', 'Yes': @@ -762,6 +1006,31 @@ SnapTranslator.dict.ko = { 'Help': '도움말', + // zoom blocks + 'Zoom blocks': + '블록 크기 설정', + 'build': + '만들기', + 'your own': + '나만의', + 'blocks': + '블록', + 'normal (1x)': + '기본 크기 (1x)', + 'demo (1.2x)': + '데모 크기 (1.2x)', + 'presentation (1.4x)': + '발표용 크기 (1.4x)', + 'big (2x)': + '큰 크기 (2x)', + 'huge (4x)': + '매우 큰 크기(4x)', + 'giant (8x)': + '정말 큰 크기 (8x)', + 'monstrous (10x)': + '믿을 수 없는 크기 (10x)', + + // costume editor 'Costume Editor': '모양 편집기', @@ -822,7 +1091,7 @@ SnapTranslator.dict.ko = { // block deletion dialog 'Delete Custom Block': - '블록 삭제', + '블록 삭제하기', 'block deletion dialog text': '이 블록과 모든 인스턴스를\n 삭제해도 괜찮습니까?', @@ -911,6 +1180,8 @@ SnapTranslator.dict.ko = { // coments 'add comment here...': '여기에 주석 추가…', + 'comment pic...': + '주석을 그림파일로 내보내기...', // drow downs // directions @@ -933,11 +1204,22 @@ SnapTranslator.dict.ko = { // costumes 'Turtle': - '터틀', + '화살표', + 'Empty': + 'Leer', - // graphical effects + // graphical effects + 'brightness': + '밝기', 'ghost': '유령', + 'negative': + '반전', + 'comic': + '코믹', + 'confetti': + '색종이', + // keys 'space': @@ -1029,9 +1311,11 @@ SnapTranslator.dict.ko = { // math functions 'abs': - '절대값', + '절대값(abs)', 'sqrt': - '제곱근', + '제곱근(sqrt)', + 'floor': + '바닥(floor)', 'sin': 'sin', 'cos': @@ -1049,11 +1333,23 @@ SnapTranslator.dict.ko = { 'e^': 'e^', + // delimiters + 'letter': + '글자', + 'whitespace': + '빈칸', + 'line': + '줄', + 'tab': + '탭', + 'cr': + '새줄(cr)', + // data types 'number': '숫자', 'text': - '텍스트', + '문자', 'Boolean': '불리언', 'list': @@ -1087,12 +1383,12 @@ SnapTranslator.dict.ko = { 'Are you sure you want to delete': '정말로 삭제합니까?', 'unringify': - '형태변환취소', + '블록형태변환 취소하기', 'rename...': '이름수정...', '(180) down': '(180) 아래', 'Ok': - 'OK' + '확인' }; -- cgit v1.3.1 From 947906aab9a8bb684ae6d1ce1ddd0c3ed2505119 Mon Sep 17 00:00:00 2001 From: jmoenig Date: Mon, 12 Jan 2015 10:13:49 +0100 Subject: Backend load balancing support also: * eliminate now obsolete authentication roundtrip, * Cloud error message tweaks --- cloud.js | 1281 +++++++++++++++++++++++++++++++------------------------------- 1 file changed, 642 insertions(+), 639 deletions(-) diff --git a/cloud.js b/cloud.js index 54189d8..ed7233f 100644 --- a/cloud.js +++ b/cloud.js @@ -1,639 +1,642 @@ -/* - - cloud.js - - a backend API for SNAP! - - written by Jens Mönig - - Copyright (C) 2014 by Jens Mönig - - This file is part of Snap!. - - Snap! is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as - published by the Free Software Foundation, either version 3 of - the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . - -*/ - -// Global settings ///////////////////////////////////////////////////// - -/*global modules, IDE_Morph, SnapSerializer, hex_sha512, alert, nop, -localize*/ - -modules.cloud = '2014-May-26'; - -// Global stuff - -var Cloud; - -var SnapCloud = new Cloud( - 'https://snapcloud.miosoft.com/miocon/app/login?_app=SnapCloud' -); - -// Cloud ///////////////////////////////////////////////////////////// - -function Cloud(url) { - this.username = null; - this.password = null; // hex_sha512 hashed - this.url = url; - this.session = null; - this.api = {}; -} - -Cloud.prototype.clear = function () { - this.username = null; - this.password = null; - this.session = null; - this.api = {}; -}; - -Cloud.prototype.hasProtocol = function () { - return this.url.toLowerCase().indexOf('http') === 0; -}; - -// Cloud: Snap! API - -Cloud.prototype.signup = function ( - username, - email, - callBack, - errorCall -) { - // both callBack and errorCall are two-argument functions - var request = new XMLHttpRequest(), - myself = this; - try { - request.open( - "GET", - (this.hasProtocol() ? '' : 'http://') - + this.url + 'SignUp' - + '&Username=' - + encodeURIComponent(username) - + '&Email=' - + encodeURIComponent(email), - true - ); - request.setRequestHeader( - "Content-Type", - "application/x-www-form-urlencoded" - ); - request.withCredentials = true; - request.onreadystatechange = function () { - if (request.readyState === 4) { - if (request.responseText) { - if (request.responseText.indexOf('ERROR') === 0) { - errorCall.call( - this, - request.responseText, - 'Signup' - ); - } else { - callBack.call( - null, - request.responseText, - 'Signup' - ); - } - } else { - errorCall.call( - null, - myself.url + 'SignUp', - localize('could not connect to:') - ); - } - } - }; - request.send(null); - } catch (err) { - errorCall.call(this, err.toString(), 'Snap!Cloud'); - } -}; - -Cloud.prototype.getPublicProject = function ( - id, - callBack, - errorCall -) { - // id is Username=username&projectName=projectname, - // where the values are url-component encoded - // callBack is a single argument function, errorCall take two args - var request = new XMLHttpRequest(), - responseList, - myself = this; - try { - request.open( - "GET", - (this.hasProtocol() ? '' : 'http://') - + this.url + 'Public' - + '&' - + id, - true - ); - request.setRequestHeader( - "Content-Type", - "application/x-www-form-urlencoded" - ); - request.withCredentials = true; - request.onreadystatechange = function () { - if (request.readyState === 4) { - if (request.responseText) { - if (request.responseText.indexOf('ERROR') === 0) { - errorCall.call( - this, - request.responseText - ); - } else { - responseList = myself.parseResponse( - request.responseText - ); - callBack.call( - null, - responseList[0].SourceCode - ); - } - } else { - errorCall.call( - null, - myself.url + 'Public', - localize('could not connect to:') - ); - } - } - }; - request.send(null); - } catch (err) { - errorCall.call(this, err.toString(), 'Snap!Cloud'); - } -}; - -Cloud.prototype.resetPassword = function ( - username, - callBack, - errorCall -) { - // both callBack and errorCall are two-argument functions - var request = new XMLHttpRequest(), - myself = this; - try { - request.open( - "GET", - (this.hasProtocol() ? '' : 'http://') - + this.url + 'ResetPW' - + '&Username=' - + encodeURIComponent(username), - true - ); - request.setRequestHeader( - "Content-Type", - "application/x-www-form-urlencoded" - ); - request.withCredentials = true; - request.onreadystatechange = function () { - if (request.readyState === 4) { - if (request.responseText) { - if (request.responseText.indexOf('ERROR') === 0) { - errorCall.call( - this, - request.responseText, - 'Reset Password' - ); - } else { - callBack.call( - null, - request.responseText, - 'Reset Password' - ); - } - } else { - errorCall.call( - null, - myself.url + 'ResetPW', - localize('could not connect to:') - ); - } - } - }; - request.send(null); - } catch (err) { - errorCall.call(this, err.toString(), 'Snap!Cloud'); - } -}; - -Cloud.prototype.connect = function ( - callBack, - errorCall -) { - // both callBack and errorCall are two-argument functions - var request = new XMLHttpRequest(), - myself = this; - try { - request.open( - "GET", - (this.hasProtocol() ? '' : 'http://') + this.url, - true - ); - request.setRequestHeader( - "Content-Type", - "application/x-www-form-urlencoded" - ); - request.withCredentials = true; - request.onreadystatechange = function () { - if (request.readyState === 4) { - if (request.responseText) { - myself.api = myself.parseAPI(request.responseText); - myself.session = request.getResponseHeader('MioCracker') - .split(';')[0]; - if (myself.api.login) { - callBack.call(null, myself.api, 'Snap!Cloud'); - } else { - errorCall.call( - null, - 'connection failed' - ); - } - } else { - errorCall.call( - null, - myself.url, - localize('could not connect to:') - ); - } - } - }; - request.send(null); - } catch (err) { - errorCall.call(this, err.toString(), 'Snap!Cloud'); - } -}; - - -Cloud.prototype.login = function ( - username, - password, - callBack, - errorCall -) { - var myself = this; - this.connect( - function () { - myself.rawLogin(username, password, callBack, errorCall); - myself.disconnect(); - }, - errorCall - ); -}; - -Cloud.prototype.rawLogin = function ( - username, - password, - callBack, - errorCall -) { - // both callBack and errorCall are two-argument functions - var myself = this, - pwHash = hex_sha512("miosoft%20miocon," - + this.session.split('=')[1] + "," - + encodeURIComponent(username.toLowerCase()) + "," - + password // alreadey hex_sha512 hashed - ); - this.callService( - 'login', - function (response, url) { - if (myself.api.logout) { - myself.username = username; - myself.password = password; - callBack.call(null, response, url); - } else { - errorCall.call( - null, - 'Service catalog is not available,\nplease retry', - 'Connection Error:' - ); - } - }, - errorCall, - [username, pwHash] - ); -}; - -Cloud.prototype.reconnect = function ( - callBack, - errorCall -) { - if (!(this.username && this.password)) { - this.message('You are not logged in'); - return; - } - this.login( - this.username, - this.password, - callBack, - errorCall - ); -}; - -Cloud.prototype.saveProject = function (ide, callBack, errorCall) { - var myself = this, - pdata, - media; - - ide.serializer.isCollectingMedia = true; - pdata = ide.serializer.serialize(ide.stage); - media = ide.hasChangedMedia ? - ide.serializer.mediaXML(ide.projectName) : null; - ide.serializer.isCollectingMedia = false; - ide.serializer.flushMedia(); - - // check if serialized data can be parsed back again - try { - ide.serializer.parse(pdata); - } catch (err) { - ide.showMessage('Serialization of program data failed:\n' + err); - throw new Error('Serialization of program data failed:\n' + err); - } - if (media !== null) { - try { - ide.serializer.parse(media); - } catch (err) { - ide.showMessage('Serialization of media failed:\n' + err); - throw new Error('Serialization of media failed:\n' + err); - } - } - ide.serializer.isCollectingMedia = false; - ide.serializer.flushMedia(); - - myself.reconnect( - function () { - myself.callService( - 'saveProject', - function (response, url) { - callBack.call(null, response, url); - myself.disconnect(); - ide.hasChangedMedia = false; - }, - errorCall, - [ - ide.projectName, - pdata, - media, - pdata.length, - media ? media.length : 0 - ] - ); - }, - errorCall - ); -}; - -Cloud.prototype.getProjectList = function (callBack, errorCall) { - var myself = this; - this.reconnect( - function () { - myself.callService( - 'getProjectList', - function (response, url) { - callBack.call(null, response, url); - myself.disconnect(); - }, - errorCall - ); - }, - errorCall - ); -}; - -Cloud.prototype.changePassword = function ( - oldPW, - newPW, - callBack, - errorCall -) { - var myself = this; - this.reconnect( - function () { - myself.callService( - 'changePassword', - function (response, url) { - callBack.call(null, response, url); - myself.disconnect(); - }, - errorCall, - [oldPW, newPW] - ); - }, - errorCall - ); -}; - -Cloud.prototype.logout = function (callBack, errorCall) { - this.clear(); - this.callService( - 'logout', - callBack, - errorCall - ); -}; - -Cloud.prototype.disconnect = function () { - this.callService( - 'logout', - nop, - nop - ); -}; - -// Cloud: backend communication - -Cloud.prototype.callURL = function (url, callBack, errorCall) { - // both callBack and errorCall are optional two-argument functions - var request = new XMLHttpRequest(), - myself = this; - try { - request.open('GET', url, true); - request.withCredentials = true; - request.setRequestHeader( - "Content-Type", - "application/x-www-form-urlencoded" - ); - request.setRequestHeader('MioCracker', this.session); - request.onreadystatechange = function () { - if (request.readyState === 4) { - if (request.responseText) { - var responseList = myself.parseResponse( - request.responseText - ); - callBack.call(null, responseList, url); - } else { - errorCall.call( - null, - url, - 'no response from:' - ); - } - } - }; - request.send(null); - } catch (err) { - errorCall.call(this, err.toString(), url); - } -}; - -Cloud.prototype.callService = function ( - serviceName, - callBack, - errorCall, - args -) { - // both callBack and errorCall are optional two-argument functions - var request = new XMLHttpRequest(), - service = this.api[serviceName], - myself = this, - postDict; - - if (!this.session) { - errorCall.call(null, 'You are not connected', 'Cloud'); - return; - } - if (!service) { - errorCall.call( - null, - 'service ' + serviceName + ' is not available', - 'API' - ); - return; - } - if (args && args.length > 0) { - postDict = {}; - service.parameters.forEach(function (parm, idx) { - postDict[parm] = args[idx]; - }); - } - try { - request.open(service.method, service.url, true); - request.withCredentials = true; - request.setRequestHeader( - "Content-Type", - "application/x-www-form-urlencoded" - ); - request.setRequestHeader('MioCracker', this.session); - request.onreadystatechange = function () { - if (request.readyState === 4) { - var responseList = []; - if (request.responseText && - request.responseText.indexOf('ERROR') === 0) { - errorCall.call( - this, - request.responseText, - localize('Service:') + ' ' + localize(serviceName) - ); - return; - } - if (serviceName === 'login') { - myself.api = myself.parseAPI(request.responseText); - } - responseList = myself.parseResponse( - request.responseText - ); - callBack.call(null, responseList, service.url); - } - }; - request.send(this.encodeDict(postDict)); - } catch (err) { - errorCall.call(this, err.toString(), service.url); - } -}; - -// Cloud: payload transformation - -Cloud.prototype.parseAPI = function (src) { - var api = {}, - services; - services = src.split(" "); - services.forEach(function (service) { - var entries = service.split("&"), - serviceDescription = {}, - parms; - entries.forEach(function (entry) { - var pair = entry.split("="), - key = decodeURIComponent(pair[0]).toLowerCase(), - val = decodeURIComponent(pair[1]); - if (key === "service") { - api[val] = serviceDescription; - } else if (key === "parameters") { - parms = val.split(","); - if (!(parms.length === 1 && !parms[0])) { - serviceDescription.parameters = parms; - } - } else { - serviceDescription[key] = val; - } - }); - }); - return api; -}; - -Cloud.prototype.parseResponse = function (src) { - var ans = [], - lines; - if (!src) {return ans; } - lines = src.split(" "); - lines.forEach(function (service) { - var entries = service.split("&"), - dict = {}; - entries.forEach(function (entry) { - var pair = entry.split("="), - key = decodeURIComponent(pair[0]), - val = decodeURIComponent(pair[1]); - dict[key] = val; - }); - ans.push(dict); - }); - return ans; -}; - -Cloud.prototype.parseDict = function (src) { - var dict = {}; - if (!src) {return dict; } - src.split("&").forEach(function (entry) { - var pair = entry.split("="), - key = decodeURIComponent(pair[0]), - val = decodeURIComponent(pair[1]); - dict[key] = val; - }); - return dict; -}; - -Cloud.prototype.encodeDict = function (dict) { - var str = '', - pair, - key; - if (!dict) {return null; } - for (key in dict) { - if (dict.hasOwnProperty(key)) { - pair = encodeURIComponent(key) - + '=' - + encodeURIComponent(dict[key]); - if (str.length > 0) { - str += '&'; - } - str += pair; - } - } - return str; -}; - -// Cloud: user messages (to be overridden) - -Cloud.prototype.message = function (string) { - alert(string); -}; +/* + + cloud.js + + a backend API for SNAP! + + written by Jens Mönig + + Copyright (C) 2015 by Jens Mönig + + This file is part of Snap!. + + Snap! is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +*/ + +// Global settings ///////////////////////////////////////////////////// + +/*global modules, IDE_Morph, SnapSerializer, hex_sha512, alert, nop, +localize*/ + +modules.cloud = '2015-January-12'; + +// Global stuff + +var Cloud; +var SnapCloud = new Cloud( + 'https://snap.apps.miosoft.com/SnapCloud' +); + +// Cloud ///////////////////////////////////////////////////////////// + +function Cloud(url) { + this.username = null; + this.password = null; // hex_sha512 hashed + this.url = url; + this.session = null; + this.limo = null; + this.route = null; + this.api = {}; +} + +Cloud.prototype.clear = function () { + this.username = null; + this.password = null; + this.session = null; + this.limo = null; + this.route = null; + this.api = {}; +}; + +Cloud.prototype.hasProtocol = function () { + return this.url.toLowerCase().indexOf('http') === 0; +}; + +Cloud.prototype.setRoute = function (username) { + var routes = 10, + userNum = 0, + i; + + for (i = 0; i < username.length; i += 1) { + userNum += username.charCodeAt(i); + } + userNum = userNum % routes + 1; + this.route = '.sc1m' + + (userNum < 10 ? '0' : '') + + userNum; +}; + +// Cloud: Snap! API + +Cloud.prototype.signup = function ( + username, + email, + callBack, + errorCall +) { + // both callBack and errorCall are two-argument functions + var request = new XMLHttpRequest(), + myself = this; + try { + request.open( + "GET", + (this.hasProtocol() ? '' : 'http://') + + this.url + 'SignUp' + + '?Username=' + + encodeURIComponent(username) + + '&Email=' + + encodeURIComponent(email), + true + ); + request.setRequestHeader( + "Content-Type", + "application/x-www-form-urlencoded" + ); + request.withCredentials = true; + request.onreadystatechange = function () { + if (request.readyState === 4) { + if (request.responseText) { + if (request.responseText.indexOf('ERROR') === 0) { + errorCall.call( + this, + request.responseText, + 'Signup' + ); + } else { + callBack.call( + null, + request.responseText, + 'Signup' + ); + } + } else { + errorCall.call( + null, + myself.url + 'SignUp', + localize('could not connect to:') + ); + } + } + }; + request.send(null); + } catch (err) { + errorCall.call(this, err.toString(), 'Snap!Cloud'); + } +}; + +Cloud.prototype.getPublicProject = function ( + id, + callBack, + errorCall +) { + // id is Username=username&projectName=projectname, + // where the values are url-component encoded + // callBack is a single argument function, errorCall take two args + var request = new XMLHttpRequest(), + responseList, + myself = this; + try { + request.open( + "GET", + (this.hasProtocol() ? '' : 'http://') + + this.url + 'Public' + + '?' + + id, + true + ); + request.setRequestHeader( + "Content-Type", + "application/x-www-form-urlencoded" + ); + request.withCredentials = true; + request.onreadystatechange = function () { + if (request.readyState === 4) { + if (request.responseText) { + if (request.responseText.indexOf('ERROR') === 0) { + errorCall.call( + this, + request.responseText + ); + } else { + responseList = myself.parseResponse( + request.responseText + ); + callBack.call( + null, + responseList[0].SourceCode + ); + } + } else { + errorCall.call( + null, + myself.url + 'Public', + localize('could not connect to:') + ); + } + } + }; + request.send(null); + } catch (err) { + errorCall.call(this, err.toString(), 'Snap!Cloud'); + } +}; + +Cloud.prototype.resetPassword = function ( + username, + callBack, + errorCall +) { + // both callBack and errorCall are two-argument functions + var request = new XMLHttpRequest(), + myself = this; + try { + request.open( + "GET", + (this.hasProtocol() ? '' : 'http://') + + this.url + 'ResetPW' + + '?Username=' + + encodeURIComponent(username), + true + ); + request.setRequestHeader( + "Content-Type", + "application/x-www-form-urlencoded" + ); + request.withCredentials = true; + request.onreadystatechange = function () { + if (request.readyState === 4) { + if (request.responseText) { + if (request.responseText.indexOf('ERROR') === 0) { + errorCall.call( + this, + request.responseText, + 'Reset Password' + ); + } else { + callBack.call( + null, + request.responseText, + 'Reset Password' + ); + } + } else { + errorCall.call( + null, + myself.url + 'ResetPW', + localize('could not connect to:') + ); + } + } + }; + request.send(null); + } catch (err) { + errorCall.call(this, err.toString(), 'Snap!Cloud'); + } +}; + +Cloud.prototype.login = function ( + username, + password, + callBack, + errorCall +) { + // both callBack and errorCall are two-argument functions + var request = new XMLHttpRequest(), + usr = JSON.stringify({'__h': password, '__u': username}), + myself = this; + this.setRoute(username); + try { + request.open( + "POST", + (this.hasProtocol() ? '' : 'http://') + + this.url + + '?SESSIONGLUE=' + + this.route, + true + ); + request.setRequestHeader( + "Content-Type", + "application/json; charset=utf-8" + ); + // glue this session to a route: + request.setRequestHeader('SESSIONGLUE', this.route); + request.withCredentials = true; + request.onreadystatechange = function () { + if (request.readyState === 4) { + if (request.responseText) { + myself.api = myself.parseAPI(request.responseText); + myself.session = request.getResponseHeader('MioCracker') + .split(';')[0]; + // set the cookie identifier: + myself.limo = this.getResponseHeader("miocracker") + .substring( + 9, + this.getResponseHeader("miocracker").indexOf("=") + ); + if (myself.api.logout) { + myself.username = username; + myself.password = password; + callBack.call(null, myself.api, 'Snap!Cloud'); + } else { + errorCall.call( + null, + request.responseText, + 'connection failed' + ); + } + } else { + errorCall.call( + null, + myself.url, + localize('could not connect to:') + ); + } + } + }; + request.send(usr); + } catch (err) { + errorCall.call(this, err.toString(), 'Snap!Cloud'); + } +}; + +Cloud.prototype.reconnect = function ( + callBack, + errorCall +) { + if (!(this.username && this.password)) { + this.message('You are not logged in'); + return; + } + this.login( + this.username, + this.password, + callBack, + errorCall + ); +}; + +Cloud.prototype.saveProject = function (ide, callBack, errorCall) { + var myself = this, + pdata, + media; + + ide.serializer.isCollectingMedia = true; + pdata = ide.serializer.serialize(ide.stage); + media = ide.hasChangedMedia ? + ide.serializer.mediaXML(ide.projectName) : null; + ide.serializer.isCollectingMedia = false; + ide.serializer.flushMedia(); + + // check if serialized data can be parsed back again + try { + ide.serializer.parse(pdata); + } catch (err) { + ide.showMessage('Serialization of program data failed:\n' + err); + throw new Error('Serialization of program data failed:\n' + err); + } + if (media !== null) { + try { + ide.serializer.parse(media); + } catch (err) { + ide.showMessage('Serialization of media failed:\n' + err); + throw new Error('Serialization of media failed:\n' + err); + } + } + ide.serializer.isCollectingMedia = false; + ide.serializer.flushMedia(); + + myself.reconnect( + function () { + myself.callService( + 'saveProject', + function (response, url) { + callBack.call(null, response, url); + myself.disconnect(); + ide.hasChangedMedia = false; + }, + errorCall, + [ + ide.projectName, + pdata, + media, + pdata.length, + media ? media.length : 0 + ] + ); + }, + errorCall + ); +}; + +Cloud.prototype.getProjectList = function (callBack, errorCall) { + var myself = this; + this.reconnect( + function () { + myself.callService( + 'getProjectList', + function (response, url) { + callBack.call(null, response, url); + myself.disconnect(); + }, + errorCall + ); + }, + errorCall + ); +}; + +Cloud.prototype.changePassword = function ( + oldPW, + newPW, + callBack, + errorCall +) { + var myself = this; + this.reconnect( + function () { + myself.callService( + 'changePassword', + function (response, url) { + callBack.call(null, response, url); + myself.disconnect(); + }, + errorCall, + [hex_sha512(oldPW), hex_sha512(newPW)] + ); + }, + errorCall + ); +}; + +Cloud.prototype.logout = function (callBack, errorCall) { + this.clear(); + this.callService( + 'logout', + callBack, + errorCall + ); +}; + +Cloud.prototype.disconnect = function () { + this.callService( + 'logout', + nop, + nop + ); +}; + +// Cloud: backend communication + +Cloud.prototype.callURL = function (url, callBack, errorCall) { + // both callBack and errorCall are optional two-argument functions + var request = new XMLHttpRequest(), + stickyUrl, + myself = this; + try { + // set the Limo. Also set the glue as a query paramter for backup. + stickyUrl = url + + '&SESSIONGLUE=' + + this.route + + '&_Limo=' + + this.limo; + request.open('GET', stickyUrl, true); + request.withCredentials = true; + request.setRequestHeader( + "Content-Type", + "application/x-www-form-urlencoded" + ); + request.setRequestHeader('MioCracker', this.session); + // Set the glue as a request header. + request.setRequestHeader('SESSIONGLUE', this.route); + request.onreadystatechange = function () { + if (request.readyState === 4) { + if (request.responseText) { + var responseList = myself.parseResponse( + request.responseText + ); + callBack.call(null, responseList, url); + } else { + errorCall.call( + null, + url, + 'no response from:' + ); + } + } + }; + request.send(null); + } catch (err) { + errorCall.call(this, err.toString(), url); + } +}; + +Cloud.prototype.callService = function ( + serviceName, + callBack, + errorCall, + args +) { + // both callBack and errorCall are optional two-argument functions + var request = new XMLHttpRequest(), + service = this.api[serviceName], + myself = this, + stickyUrl, + postDict; + + if (!this.session) { + errorCall.call(null, 'You are not connected', 'Cloud'); + return; + } + if (!service) { + errorCall.call( + null, + 'service ' + serviceName + ' is not available', + 'API' + ); + return; + } + if (args && args.length > 0) { + postDict = {}; + service.parameters.forEach(function (parm, idx) { + postDict[parm] = args[idx]; + }); + } + try { + stickyUrl = this.url + + '/' + + service.url + + '&SESSIONGLUE=' + + this.route + + '&_Limo=' + + this.limo; + request.open(service.method, stickyUrl, true); + request.withCredentials = true; + request.setRequestHeader( + "Content-Type", + "application/x-www-form-urlencoded" + ); + request.setRequestHeader('MioCracker', this.session); + request.setRequestHeader('SESSIONGLUE', this.route); + request.onreadystatechange = function () { + if (request.readyState === 4) { + var responseList = []; + if (request.responseText && + request.responseText.indexOf('ERROR') === 0) { + errorCall.call( + this, + request.responseText, + localize('Service:') + ' ' + localize(serviceName) + ); + return; + } + if (serviceName === 'login') { + myself.api = myself.parseAPI(request.responseText); + } + responseList = myself.parseResponse( + request.responseText + ); + callBack.call(null, responseList, service.url); + } + }; + request.send(this.encodeDict(postDict)); + } catch (err) { + errorCall.call(this, err.toString(), service.url); + } +}; + +// Cloud: payload transformation + +Cloud.prototype.parseAPI = function (src) { + var api = {}, + services; + services = src.split(" "); + services.forEach(function (service) { + var entries = service.split("&"), + serviceDescription = {}, + parms; + entries.forEach(function (entry) { + var pair = entry.split("="), + key = decodeURIComponent(pair[0]).toLowerCase(), + val = decodeURIComponent(pair[1]); + if (key === "service") { + api[val] = serviceDescription; + } else if (key === "parameters") { + parms = val.split(","); + if (!(parms.length === 1 && !parms[0])) { + serviceDescription.parameters = parms; + } + } else { + serviceDescription[key] = val; + } + }); + }); + return api; +}; + +Cloud.prototype.parseResponse = function (src) { + var ans = [], + lines; + if (!src) {return ans; } + lines = src.split(" "); + lines.forEach(function (service) { + var entries = service.split("&"), + dict = {}; + entries.forEach(function (entry) { + var pair = entry.split("="), + key = decodeURIComponent(pair[0]), + val = decodeURIComponent(pair[1]); + dict[key] = val; + }); + ans.push(dict); + }); + return ans; +}; + +Cloud.prototype.parseDict = function (src) { + var dict = {}; + if (!src) {return dict; } + src.split("&").forEach(function (entry) { + var pair = entry.split("="), + key = decodeURIComponent(pair[0]), + val = decodeURIComponent(pair[1]); + dict[key] = val; + }); + return dict; +}; + +Cloud.prototype.encodeDict = function (dict) { + var str = '', + pair, + key; + if (!dict) {return null; } + for (key in dict) { + if (dict.hasOwnProperty(key)) { + pair = encodeURIComponent(key) + + '=' + + encodeURIComponent(dict[key]); + if (str.length > 0) { + str += '&'; + } + str += pair; + } + } + return str; +}; + +// Cloud: user messages (to be overridden) + +Cloud.prototype.message = function (string) { + alert(string); +}; -- cgit v1.3.1 From 50e84f8890079c45bb32e001eece4c3544fd1641 Mon Sep 17 00:00:00 2001 From: jmoenig Date: Mon, 12 Jan 2015 10:15:56 +0100 Subject: Check project for compatibility notify users of potential incompatibilities when opening projects created in other forks (e.g. BeetleBlocks) --- gui.js | 43 ++++++++++++++++++++++++++----------------- store.js | 29 +++++++++++++++++++++++------ 2 files changed, 49 insertions(+), 23 deletions(-) diff --git a/gui.js b/gui.js index 8cd9403..d862463 100644 --- a/gui.js +++ b/gui.js @@ -9,7 +9,7 @@ written by Jens Mönig jens@moenig.org - Copyright (C) 2014 by Jens Mönig + Copyright (C) 2015 by Jens Mönig This file is part of Snap!. @@ -69,7 +69,7 @@ SpeechBubbleMorph*/ // Global stuff //////////////////////////////////////////////////////// -modules.gui = '2014-December-04'; +modules.gui = '2015-January-12'; // Declarations @@ -300,7 +300,6 @@ IDE_Morph.prototype.openIn = function (world) { this.inform('Snap!', motd); } */ - function interpretUrlAnchors() { var dict; if (location.hash.substr(0, 6) === '#open:') { @@ -2509,7 +2508,7 @@ IDE_Morph.prototype.aboutSnap = function () { world = this.world(); aboutTxt = 'Snap! 4.0\nBuild Your Own Blocks\n\n--- beta ---\n\n' - + 'Copyright \u24B8 2014 Jens M\u00F6nig and ' + + 'Copyright \u24B8 2015 Jens M\u00F6nig and ' + 'Brian Harvey\n' + 'jens@moenig.org, bh@cs.berkeley.edu\n\n' @@ -2943,12 +2942,18 @@ IDE_Morph.prototype.rawOpenProjectString = function (str) { StageMorph.prototype.enableCodeMapping = false; if (Process.prototype.isCatchingErrors) { try { - this.serializer.openProject(this.serializer.load(str), this); + this.serializer.openProject( + this.serializer.load(str, this), + this + ); } catch (err) { this.showMessage('Load failed: ' + err); } } else { - this.serializer.openProject(this.serializer.load(str), this); + this.serializer.openProject( + this.serializer.load(str, this), + this + ); } this.stopFastTracking(); }; @@ -2980,7 +2985,10 @@ IDE_Morph.prototype.rawOpenCloudDataString = function (str) { model = this.serializer.parse(str); this.serializer.loadMediaModel(model.childNamed('media')); this.serializer.openProject( - this.serializer.loadProjectModel(model.childNamed('project')), + this.serializer.loadProjectModel( + model.childNamed('project'), + this + ), this ); } catch (err) { @@ -2990,7 +2998,10 @@ IDE_Morph.prototype.rawOpenCloudDataString = function (str) { model = this.serializer.parse(str); this.serializer.loadMediaModel(model.childNamed('media')); this.serializer.openProject( - this.serializer.loadProjectModel(model.childNamed('project')), + this.serializer.loadProjectModel( + model.childNamed('project'), + this + ), this ); } @@ -3974,6 +3985,9 @@ IDE_Morph.prototype.cloudResponse = function () { IDE_Morph.prototype.cloudError = function () { var myself = this; + // try finding an eplanation what's going on + // has some issues, commented out for now + /* function getURL(url) { try { var request = new XMLHttpRequest(); @@ -3987,13 +4001,15 @@ IDE_Morph.prototype.cloudError = function () { return null; } } + */ return function (responseText, url) { // first, try to find out an explanation for the error // and notify the user about it, // if none is found, show an error dialog box var response = responseText, - explanation = getURL('http://snap.berkeley.edu/cloudmsg.txt'); + // explanation = getURL('http://snap.berkeley.edu/cloudmsg.txt'), + explanation = null; if (myself.shield) { myself.shield.destroy(); myself.shield = null; @@ -4044,14 +4060,7 @@ IDE_Morph.prototype.setCloudURL = function () { null, { 'Snap!Cloud' : - 'https://snapcloud.miosoft.com/miocon/app/' + - 'login?_app=SnapCloud', - 'local network lab' : - '192.168.2.107:8087/miocon/app/login?_app=SnapCloud', - 'local network office' : - '192.168.186.146:8087/miocon/app/login?_app=SnapCloud', - 'localhost dev' : - 'localhost/miocon/app/login?_app=SnapCloud' + 'https://snap.apps.miosoft.com/SnapCloud' } ); }; diff --git a/store.js b/store.js index 8cbc10f..9dd851d 100644 --- a/store.js +++ b/store.js @@ -7,7 +7,7 @@ written by Jens Mönig jens@moenig.org - Copyright (C) 2014 by Jens Mönig + Copyright (C) 2015 by Jens Mönig This file is part of Snap!. @@ -61,7 +61,7 @@ SyntaxElementMorph, Variable*/ // Global stuff //////////////////////////////////////////////////////// -modules.store = '2014-December-17'; +modules.store = '2015-January-12'; // XML_Serializer /////////////////////////////////////////////////////// @@ -306,16 +306,33 @@ XML_Serializer.prototype.mediaXML = function (name) { return xml + ''; }; - // SnapSerializer loading: -SnapSerializer.prototype.load = function (xmlString) { +SnapSerializer.prototype.load = function (xmlString, ide) { // public - answer a new Project represented by the given XML String - return this.loadProjectModel(this.parse(xmlString)); + return this.loadProjectModel(this.parse(xmlString), ide); }; -SnapSerializer.prototype.loadProjectModel = function (xmlNode) { +SnapSerializer.prototype.loadProjectModel = function (xmlNode, ide) { // public - answer a new Project represented by the given XML top node + // show a warning if the origin apps differ + + var appInfo = xmlNode.attributes.app, + app = appInfo ? appInfo.split(' ')[0] : null; + + if (ide && app !== this.app.split(' ')[0]) { + ide.inform( + app + ' Project', + 'This project has been created by a different app:\n\n' + + app + + '\n\nand may be incompatible or fail to load here.' + ); + } + return this.rawLoadProjectModel(xmlNode); +}; + +SnapSerializer.prototype.rawLoadProjectModel = function (xmlNode) { + // private var myself = this, project = {sprites: {}}, model, -- cgit v1.3.1 From 04ffda276e0294996431da8aca3584918873ae4f Mon Sep 17 00:00:00 2001 From: jmoenig Date: Mon, 12 Jan 2015 10:17:13 +0100 Subject: Speed up messages received by clones MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Don’t highlight scripts running inside clones (boosts performance), Thanks, @aranlunzer, for the hint! --- threads.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/threads.js b/threads.js index 791d81c..555e531 100644 --- a/threads.js +++ b/threads.js @@ -9,7 +9,7 @@ written by Jens Mönig jens@moenig.org - Copyright (C) 2014 by Jens Mönig + Copyright (C) 2015 by Jens Mönig This file is part of Snap!. @@ -83,7 +83,7 @@ ArgLabelMorph, localize, XML_Element, hex_sha512*/ // Global stuff //////////////////////////////////////////////////////// -modules.threads = '2014-December-17'; +modules.threads = '2014-January-12'; var ThreadManager; var Process; @@ -159,9 +159,11 @@ ThreadManager.prototype.startProcess = function ( active.stop(); this.removeTerminatedProcesses(); } - top.addHighlight(); newProc = new Process(block.topBlock(), callback); newProc.exportResult = exportResult; + if (!newProc.homeContext.receiver.isClone) { + top.addHighlight(); + } this.processes.push(newProc); return newProc; }; -- cgit v1.3.1 From a0b39a4dafd41214ebf52f2db149486a2bdf27b7 Mon Sep 17 00:00:00 2001 From: jmoenig Date: Mon, 12 Jan 2015 10:17:58 +0100 Subject: Make clones non-editable Disable clones from being edited via their context menus or double-click --- objects.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/objects.js b/objects.js index b155b05..78545a5 100644 --- a/objects.js +++ b/objects.js @@ -9,7 +9,7 @@ written by Jens Mönig jens@moenig.org - Copyright (C) 2014 by Jens Mönig + Copyright (C) 2015 by Jens Mönig This file is part of Snap!. @@ -125,7 +125,7 @@ PrototypeHatBlockMorph*/ // Global stuff //////////////////////////////////////////////////////// -modules.objects = '2014-December-17'; +modules.objects = '2015-January-12'; var SpriteMorph; var StageMorph; @@ -2642,7 +2642,9 @@ SpriteMorph.prototype.userMenu = function () { menu.addItem("duplicate", 'duplicate'); menu.addItem("delete", 'remove'); menu.addItem("move", 'move'); - menu.addItem("edit", 'edit'); + if (!this.isClone) { + menu.addItem("edit", 'edit'); + } menu.addLine(); if (this.anchor) { menu.addItem( @@ -2658,6 +2660,7 @@ SpriteMorph.prototype.userMenu = function () { }; SpriteMorph.prototype.exportSprite = function () { + if (this.isCoone) {return; } var ide = this.parentThatIsA(IDE_Morph); if (ide) { ide.exportSprite(this); @@ -3530,6 +3533,7 @@ SpriteMorph.prototype.mouseClickLeft = function () { }; SpriteMorph.prototype.mouseDoubleClick = function () { + if (this.isClone) {return; } this.edit(); }; -- cgit v1.3.1 From 91838819c479ce96dc7a0c2a772912354c9b3988 Mon Sep 17 00:00:00 2001 From: jmoenig Date: Mon, 12 Jan 2015 10:18:20 +0100 Subject: Italian translation update, thanks, Alberto Firpo! --- lang-it.js | 103 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- locale.js | 10 +++--- 2 files changed, 105 insertions(+), 8 deletions(-) diff --git a/lang-it.js b/lang-it.js index 44111aa..85600a0 100644 --- a/lang-it.js +++ b/lang-it.js @@ -181,11 +181,11 @@ SnapTranslator.dict.it = { 'language_name': 'Italiano', // the name as it should appear in the language menu 'language_translator': - 'Stefano Federici', // your name for the Translators tab + 'Stefano Federici, Alberto Firpo', // your name for the Translators tab 'translator_e-mail': - 's_federici@yahoo.com', // optional + 's_federici@yahoo.com, albertofirpo12@gmail.com', // optional 'last_changed': - '2012-10-16', // this, too, will appear in the Translators tab + '2015-01-12', // this, too, will appear in the Translators tab // GUI // control bar: @@ -426,6 +426,10 @@ SnapTranslator.dict.it = { 'invia a tutti %msg e attendi', 'Message name': 'Nome messaggio', + 'message': + 'messaggio', + 'any message': + 'qualunque messaggio', 'wait %n secs': 'attendi %n secondi', 'wait until %b': @@ -444,10 +448,22 @@ SnapTranslator.dict.it = { 'risultato %s', 'stop block': 'ferma il blocco', + 'all': + 'tutti', + 'this script': + 'questo script', + 'this block': + 'questo Blocco', 'stop script': 'ferma lo script', 'stop all %stop': 'ferma tutto %stop', + 'all but this script': + 'tutto tranne questo script', + 'other scripts in sprite': + 'altri script dello sprite', + 'pause all %pause': + 'pausa tutto %pause', 'run %cmdRing %inputs': 'esegui %cmdRing %inputs', 'launch %cmdRing %inputs': @@ -533,6 +549,8 @@ SnapTranslator.dict.it = { 'falso', 'join %words': 'unione di %words', + 'split %s by %delim': + 'separa %s di %delim', 'hello': 'ciao', 'world': @@ -558,6 +576,8 @@ SnapTranslator.dict.it = { 'Nuova variabile', 'Variable name': 'Nome della variabile?', + 'Script variable name': + 'Nome della variabile locale?', 'Delete a variable': 'Cancella variabile', @@ -604,6 +624,8 @@ SnapTranslator.dict.it = { // snap menu 'About...': 'Informazioni su Snap!...', + 'Reference manual': + 'Manuale', 'Snap! website': 'Sito web di Snap!', 'Download source': @@ -648,6 +670,10 @@ SnapTranslator.dict.it = { 'Importa tools', 'load the official library of\npowerful blocks': 'carica la libreria ufficiale di\nblocchi Snap', + 'Libraries...': + 'Modulo...', + 'Import library': + 'Importa modulo', // cloud menu 'Login...': @@ -660,6 +686,16 @@ SnapTranslator.dict.it = { 'Lingua...', 'Zoom blocks...': 'Zoom dei blocchi...', + 'Stage size...': + 'Dimensione pannello...', + 'Stage size': + 'Dimensione pannello', + 'Stage width': + 'Larghezza pannello', + 'Stage height': + 'Altezza pannello', + 'Default': + 'Default', 'Blurred shadows': 'Ombreggiature attenuate', 'uncheck to use solid drop\nshadows and highlights': @@ -688,6 +724,13 @@ SnapTranslator.dict.it = { 'disabilitare per permettere agli slot di espellere\ni reporter inclusi al loro interno', 'Long form input dialog': 'Usa finestra degli input estesa', + 'Plain prototype labels': + 'Etichetta prototipo base', + 'uncheck to always show (+) symbols\nin block prototype labels': + 'disabilitare per visualizzare sempre (+) \nnelle etichette dei blocchi prototipo', + 'check to hide (+) symbols\nin block prototype labels': + 'abilitare per visualizzare sempre (+) \nnelle etichette dei blocchi prototipo', + 'check to always show slot\ntypes in the input dialog': 'abilitare per mostrare sempre i tipi degli slot\nnella finestra di creazione degli input', 'uncheck to use the input\ndialog in short form': @@ -734,6 +777,13 @@ SnapTranslator.dict.it = { 'disabilitare per massima velocità\na framerate variabile', 'check for smooth, predictable\nanimations across computers': 'abilitare per avere animazioni\nfluide su tutti i computer', + 'Flat line ends': + 'fine linea piana', + 'check for flat ends of lines': + 'abilitare per fine linea netti', + 'uncheck for round ends of lines': + 'disabilitare per fine linea arrotondati', + // inputs 'with inputs': @@ -742,11 +792,20 @@ SnapTranslator.dict.it = { 'con variabili:', 'Input Names:': 'Con Variabili:', + 'input list:': + 'con liste:', + // context menus: 'help': 'aiuto', + // palette: + 'hide primitives': + 'nascondi primitive', + 'show primitives': + 'mostra primitive', + // blocks: 'help...': 'aiuto...', @@ -778,6 +837,12 @@ SnapTranslator.dict.it = { // sprites: 'edit': 'modifica', + 'move': + 'muovi', + 'detach from': + 'stacca da', + 'detach all parts': + 'stacca tutte le parti', 'export...': 'esporta...', @@ -796,6 +861,15 @@ SnapTranslator.dict.it = { 'riordina gli script\nuno sotto l\'altro', 'add comment': 'aggiungi un commento', + 'undrop': + 'annulla cancellazione', + 'undo the last\nblock drop\nin this pane': + 'annulla ultima cancellazione\ndi blocco\n in questo pannello', + 'scripts pic...': + 'immagine script...', + 'open a new window\nwith a picture of all scripts': + 'apri una nuova finestra\ncon immagine dello script', + 'make a block...': 'crea un blocco...', @@ -823,6 +897,8 @@ SnapTranslator.dict.it = { // buttons 'OK': 'OK', + 'Ok': + 'OK', 'Cancel': 'Annulla', 'Yes': @@ -1057,8 +1133,17 @@ SnapTranslator.dict.it = { 'Vuoto', // graphical effects + 'brightness': + 'Luminosita', 'ghost': 'fantasma', + 'negative': + 'negativo', + 'comic': + 'comic', + 'confetti': + 'confetti', + // keys 'space': @@ -1170,6 +1255,18 @@ SnapTranslator.dict.it = { 'e^': 'e^', + // delimiters + 'letter': + 'lettera', + 'whitespace': + 'spazio', + 'line': + 'linea', + 'tab': + 'tabulatore', + 'cr': + 'A capo', + // data types 'number': 'numero', diff --git a/locale.js b/locale.js index b3c2ce5..613ead9 100644 --- a/locale.js +++ b/locale.js @@ -6,7 +6,7 @@ written by Jens Mönig - Copyright (C) 2014 by Jens Mönig + Copyright (C) 2015 by Jens Mönig This file is part of Snap!. @@ -42,7 +42,7 @@ /*global modules, contains*/ -modules.locale = '2014-December-15'; +modules.locale = '2015-January-12'; // Global stuff @@ -157,11 +157,11 @@ SnapTranslator.dict.it = { 'language_name': 'Italiano', 'language_translator': - 'Stefano Federici', + 'Stefano Federici, Alberto Firpo', 'translator_e-mail': - 's_federici@yahoo.com', + 's_federici@yahoo.com, albertofirpo12@gmail.com', 'last_changed': - '2013-04-08' + '2015-01-12' }; SnapTranslator.dict.ja = { -- cgit v1.3.1 From 68e5feb8cef74904b0a20af6ab1455c7ce586c04 Mon Sep 17 00:00:00 2001 From: jmoenig Date: Mon, 12 Jan 2015 10:18:32 +0100 Subject: Update history --- history.txt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/history.txt b/history.txt index 6d6f0ff..c3c4068 100755 --- a/history.txt +++ b/history.txt @@ -2404,3 +2404,11 @@ ______ * Objects, Store: Experimental “processes” count watcher (hidden in dev mode) * Threads: Remove terminated processes from expired clones * Threads: Let “zombifying” scripts access receivers’ local vars + +150112 +------ +* Cloud, GUI: Backend load balancing support, eliminate now obsolete authentication roundtrip, Cloud error message tweaks +* Store: notify users of potential incompatibilities when opening projects created in other forks (e.g. BeetleBlocks) +* Threads: Don’t highlight scripts running inside clones (boosts performance), Thanks, @aranlunzer, for the hint! +* Objects: Disable clones from being edited via their context menus or double-click +* Italian translation update, thanks, Alberto Firpo! -- cgit v1.3.1 From ff3eed45eae2f4af3df5b636cdd59cabf9581e9b Mon Sep 17 00:00:00 2001 From: jmoenig Date: Mon, 12 Jan 2015 10:45:21 +0100 Subject: correct threads.js version happy New Year, duh :-) --- threads.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/threads.js b/threads.js index 555e531..e07059f 100644 --- a/threads.js +++ b/threads.js @@ -83,7 +83,7 @@ ArgLabelMorph, localize, XML_Element, hex_sha512*/ // Global stuff //////////////////////////////////////////////////////// -modules.threads = '2014-January-12'; +modules.threads = '2015-January-12'; var ThreadManager; var Process; -- cgit v1.3.1 From 667193b9f02331b3372fefbb9fd4a424d5b86d7e Mon Sep 17 00:00:00 2001 From: jmoenig Date: Mon, 12 Jan 2015 13:05:14 +0100 Subject: Force Chrome to show GUI messages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit add additional yields to nextSteps() to work around a bug recently introduced to Chrome (other browsers don’t need this kludge). Remember to take those yields out again when and if Chrome (ever) fixes this (which, for all I know, may be never) --- gui.js | 6 ++++++ history.txt | 1 + 2 files changed, 7 insertions(+) diff --git a/gui.js b/gui.js index d862463..521f27e 100644 --- a/gui.js +++ b/gui.js @@ -352,6 +352,7 @@ IDE_Morph.prototype.openIn = function (world) { function () { msg = myself.showMessage('Opening project...'); }, + function () {nop(); }, // yield (bug in Chrome) function () { if (projectData.indexOf(' Date: Tue, 13 Jan 2015 08:36:41 +0100 Subject: fixed #702 --- byob.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/byob.js b/byob.js index b5905ce..9e7dfc8 100644 --- a/byob.js +++ b/byob.js @@ -9,7 +9,7 @@ written by Jens Mönig jens@moenig.org - Copyright (C) 2014 by Jens Mönig + Copyright (C) 2015 by Jens Mönig This file is part of Snap!. @@ -106,7 +106,7 @@ SymbolMorph, isNil*/ // Global stuff //////////////////////////////////////////////////////// -modules.byob = '2014-November-20'; +modules.byob = '2015-January-13'; // Declarations @@ -209,12 +209,14 @@ CustomBlockDefinition.prototype.copyAndBindTo = function (sprite) { c.receiver = sprite; // only for (kludgy) serialization c.declarations = copy(this.declarations); // might have to go deeper - c.body = Process.prototype.reify.call( - null, - this.body.expression, - new List(this.inputNames()) - ); - c.body.outerContext = null; + if (c.body) { + c.body = Process.prototype.reify.call( + null, + this.body.expression, + new List(this.inputNames()) + ); + c.body.outerContext = null; + } return c; }; -- cgit v1.3.1 From 4a3cf0aa3bc4778821801ec325fb2fd9dbbd038b Mon Sep 17 00:00:00 2001 From: jmoenig Date: Tue, 13 Jan 2015 08:37:33 +0100 Subject: fixed #680 --- gui.js | 11 ++++++++++- history.txt | 5 +++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/gui.js b/gui.js index 521f27e..46d47e5 100644 --- a/gui.js +++ b/gui.js @@ -69,7 +69,7 @@ SpeechBubbleMorph*/ // Global stuff //////////////////////////////////////////////////////// -modules.gui = '2015-January-12'; +modules.gui = '2015-January-13'; // Declarations @@ -3343,6 +3343,15 @@ IDE_Morph.prototype.toggleAppMode = function (appMode) { }).forEach(function (s) { s.adjustScrollBars(); }); + // prevent rotation and draggability controls from + // showing for the stage + if (this.currentSprite === this.stage) { + this.spriteBar.children.forEach(function (child) { + if (child instanceof PushButtonMorph) { + child.hide(); + } + }); + } } this.setExtent(this.world().extent()); // resume trackChanges }; diff --git a/history.txt b/history.txt index 3ca41f8..b621772 100755 --- a/history.txt +++ b/history.txt @@ -2413,3 +2413,8 @@ ______ * Objects: Disable clones from being edited via their context menus or double-click * Italian translation update, thanks, Alberto Firpo! * GUI: add additional yields to nextSteps() (work around a bug in Chrome) + +150113 +------ +* BYOB: fixed #702 +* GUI: fixed #680 -- cgit v1.3.1 From eb3345966e6e660b814d1e0bd3f3eb280c7c3e08 Mon Sep 17 00:00:00 2001 From: Bernat Romagosa Date: Fri, 16 Jan 2015 11:22:18 +0100 Subject: updated Catalan translation --- lang-ca.js | 84 +++++++++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 59 insertions(+), 25 deletions(-) diff --git a/lang-ca.js b/lang-ca.js index 8991d45..1c9838e 100644 --- a/lang-ca.js +++ b/lang-ca.js @@ -6,7 +6,7 @@ written by Jens Mönig - Copyright (C) 2013 by Jens Mönig + Copyright (C) 2014 by Jens Mönig This file is part of Snap!. @@ -183,9 +183,9 @@ SnapTranslator.dict.ca = { 'language_translator': 'Bernat Romagosa Carrasquer', // your name for the Translators tab 'translator_e-mail': - 'tibabenfortlapalanca@gmail.com', // optional + 'bromagosa@citilab.eu', // optional 'last_changed': - '2013-11-26', // this, too, will appear in the Translators tab + '2014-01-16', // this, too, will appear in the Translators tab // GUI // control bar: @@ -447,12 +447,20 @@ SnapTranslator.dict.ca = { 'si %b llavors %c si no %c', 'report %s': 'retorna %s', - 'stop block': - 'atura el bloc', - 'stop script': - 'atura aquest programa', - 'stop all %stop': - 'atura-ho tot %stop', + 'stop %stopChoices': + 'atura %stopChoices', + 'all': + 'tot', + 'this script': + 'aquest programa', + 'this block': + 'aquest block', + 'stop %stopOthersChoices': + 'atura %stopOthersChoices', + 'all but this script': + 'tot excepte aquest programa', + 'other scripts in sprite': + 'els altres programes d\'aquest objecte', 'pause all %pause': 'pausa-ho tot %pause', 'run %cmdRing %inputs': @@ -645,7 +653,7 @@ SnapTranslator.dict.ca = { 'Import...': 'Importar...', 'file menu import hint': - 'pistes del menú d\'importació', + 'carrega una llibreria de projecte\no de blocs exportada, un vestit\no un so', 'Export project as plain text...': 'Exportar projecte en text pla...', 'Export project...': @@ -676,6 +684,16 @@ SnapTranslator.dict.ca = { 'Idioma...', 'Zoom blocks...': 'Mida dels blocs...', + 'Stage size...': + 'Mida de l\'escenari...', + 'Stage size': + 'Mida de l\'escenari', + 'Stage width': + 'Amplada de l\'escenari', + 'Stage height': + 'Alçada de l\'escenari', + 'Default': + 'Per defecte', 'Blurred shadows': 'Ombres suavitzades', 'uncheck to use solid drop\nshadows and highlights': @@ -744,9 +762,9 @@ SnapTranslator.dict.ca = { 'marca\'m per habilitar\nles animacions de la interfície', 'Thread safe scripts': 'Fil d\'execució segur', - 'uncheck to allow\nscript reentrancy': + 'uncheck to allow\nscript reentrance': 'desmarca\'m per permetre\nla re-entrada als programes', - 'check to disallow\nscript reentrancy': + 'check to disallow\nscript reentrance': 'marca\'m per no permetre\nla re-entrada als programes', 'Prefer smooth animations': 'Suavitza les animacions', @@ -754,6 +772,12 @@ SnapTranslator.dict.ca = { 'desmarca\'m per augmentar la velocitat de\nles animacions fins la màxima capacitat d\'aquesta màquina', 'check for smooth, predictable\nanimations across computers': 'marca\'m per aconseguir unes animacions\nmés suaus i a velocitat predible en màquines diferents', + 'Flat line ends': + 'Línies del llapis rectes', + 'check for flat ends of lines': + 'marca\'m per fer que els\nextrems de les línies del\nllapis siguin rectes', + 'uncheck for round ends of lines': + 'desmarca\'m per fer que\nels extrems de les línies\ndel llapis siguin arrodonits', // inputs 'with inputs': @@ -806,6 +830,8 @@ SnapTranslator.dict.ca = { // sprites: 'edit': 'editar', + 'move': + 'moure', 'detach from': 'desenganxa de', 'detach all parts': @@ -878,9 +904,9 @@ SnapTranslator.dict.ca = { // zoom blocks 'Zoom blocks': - 'Canvia la mida dels blocs', + 'Canvia la mida dels blocs', 'build': - 'fes', + 'construeix', 'your own': 'els teus propis', 'blocks': @@ -934,7 +960,7 @@ SnapTranslator.dict.ca = { // save project 'Save Project As...': - 'Anomena i desa projecte...', + 'Anomena i desa projecte...', // export blocks 'Export blocks': @@ -945,8 +971,6 @@ SnapTranslator.dict.ca = { 'aquest projecte encara no\nté cap bloc personalitzat', 'select': 'seleccionar', - 'all': - 'tots els blocs', 'none': 'cap bloc', @@ -1024,7 +1048,7 @@ SnapTranslator.dict.ca = { 'About Snap': 'Sobre Snap', 'Back...': - 'Enrera...', + 'Enrere...', 'License...': 'Llicència...', 'Modules...': @@ -1094,8 +1118,16 @@ SnapTranslator.dict.ca = { 'Buit', // graphical effects + 'brightness': + 'brillantor', 'ghost': 'fantasma', + 'negative': + 'negatiu', + 'comic': + 'còmic', + 'confetti': + 'confeti', // keys 'space': @@ -1187,29 +1219,31 @@ SnapTranslator.dict.ca = { // math functions 'abs': - 'abs', + 'valor absolut', 'floor': 'part entera', 'sqrt': 'arrel quadrada', 'sin': - 'sin', + 'sinus', 'cos': - 'cos', + 'cosinus', 'tan': - 'tan', + 'tangent', 'asin': - 'asin', + 'arcsinus', 'acos': - 'acos', + 'arccosinus', 'atan': - 'atan', + 'arctangent', 'ln': 'ln', 'e^': 'e^', // delimiters + 'letter': + 'lletra', 'whitespace': 'espai en blanc', 'line': -- cgit v1.3.1 From 3a24dacfa4063a65d92d5a9e681db1754d6930e1 Mon Sep 17 00:00:00 2001 From: Bernat Romagosa Date: Wed, 21 Jan 2015 09:42:27 +0100 Subject: updated Catalan translation --- lang-ca.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lang-ca.js b/lang-ca.js index 1c9838e..b555b15 100644 --- a/lang-ca.js +++ b/lang-ca.js @@ -1225,17 +1225,17 @@ SnapTranslator.dict.ca = { 'sqrt': 'arrel quadrada', 'sin': - 'sinus', + 'sin', 'cos': - 'cosinus', + 'cos', 'tan': - 'tangent', + 'tan', 'asin': - 'arcsinus', + 'asin', 'acos': - 'arccosinus', + 'acos', 'atan': - 'arctangent', + 'atan', 'ln': 'ln', 'e^': -- cgit v1.3.1 From f1fbb38b8715639e6025c130de70ef269cb0c0cd Mon Sep 17 00:00:00 2001 From: jmoenig Date: Wed, 21 Jan 2015 10:23:02 +0100 Subject: Keep layering of nested sprites thru drag & drop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit it used to be that dragging an anchor always brought it to the front, altering the nested sprite’s internal layering order --- history.txt | 4 ++++ objects.js | 27 ++++++++++++++++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/history.txt b/history.txt index b621772..1880e45 100755 --- a/history.txt +++ b/history.txt @@ -2418,3 +2418,7 @@ ______ ------ * BYOB: fixed #702 * GUI: fixed #680 + +150121 +------ +* Objects: Keep layering of nested sprites thru drag & drop diff --git a/objects.js b/objects.js index 78545a5..e59726f 100644 --- a/objects.js +++ b/objects.js @@ -125,7 +125,7 @@ PrototypeHatBlockMorph*/ // Global stuff //////////////////////////////////////////////////////// -modules.objects = '2015-January-12'; +modules.objects = '2015-January-21'; var SpriteMorph; var StageMorph; @@ -1315,6 +1315,7 @@ SpriteMorph.prototype.init = function (globals) { this.anchor = null; this.nestingScale = 1; this.rotatesWithAnchor = true; + this.layers = null; // cache for dragging nested sprites, don't serialize this.blocksCache = {}; // not to be serialized (!) this.paletteCache = {}; // not to be serialized (!) @@ -3158,6 +3159,7 @@ SpriteMorph.prototype.positionTalkBubble = function () { SpriteMorph.prototype.prepareToBeGrabbed = function (hand) { var bubble = this.talkBubble(); + this.recordLayers(); if (!bubble) {return null; } this.removeShadow(); bubble.hide(); @@ -3168,6 +3170,7 @@ SpriteMorph.prototype.prepareToBeGrabbed = function (hand) { }; SpriteMorph.prototype.justDropped = function () { + this.restoreLayers(); this.positionTalkBubble(); }; @@ -4045,6 +4048,28 @@ SpriteMorph.prototype.allAnchors = function () { return result; }; +SpriteMorph.prototype.recordLayers = function () { + var stage = this.parentThatIsA(StageMorph); + if (!stage) { + this.layerCache = null; + return; + } + this.layers = this.allParts(); + this.layers.sort(function (x, y) { + return stage.children.indexOf(x) < stage.children.indexOf(y) ? + -1 : 1; + }); +}; + +SpriteMorph.prototype.restoreLayers = function () { + if (this.layers && this.layers.length > 1) { + this.layers.forEach(function (sprite) { + sprite.comeToFront(); + }); + } + this.layers = null; +}; + // SpriteMorph highlighting SpriteMorph.prototype.addHighlight = function (oldHighlight) { -- cgit v1.3.1 From fee92b65f292ee46b83cc499bf623eb269dc6565 Mon Sep 17 00:00:00 2001 From: jmoenig Date: Wed, 21 Jan 2015 12:18:46 +0100 Subject: Generate ScriptsPaneTexture programmatically --- byob.js | 4 ++-- gui.js | 24 ++++++++++++++++++++---- history.txt | 1 + manifest.mf | 3 +-- scriptsPaneTexture.gif | Bin 155 -> 0 bytes store.js | 4 ++-- 6 files changed, 26 insertions(+), 10 deletions(-) delete mode 100755 scriptsPaneTexture.gif diff --git a/byob.js b/byob.js index 9e7dfc8..ec714f1 100644 --- a/byob.js +++ b/byob.js @@ -106,7 +106,7 @@ SymbolMorph, isNil*/ // Global stuff //////////////////////////////////////////////////////// -modules.byob = '2015-January-13'; +modules.byob = '2015-January-21'; // Declarations @@ -1651,7 +1651,7 @@ BlockEditorMorph.prototype.init = function (definition, target) { scripts = new ScriptsMorph(target); scripts.isDraggable = false; scripts.color = IDE_Morph.prototype.groupColor; - scripts.texture = IDE_Morph.prototype.scriptsPaneTexture; + scripts.cachedTexture = IDE_Morph.prototype.scriptsPaneTexture; scripts.cleanUpMargin = 10; proto = new PrototypeHatBlockMorph(this.definition); diff --git a/gui.js b/gui.js index 46d47e5..9b8b51a 100644 --- a/gui.js +++ b/gui.js @@ -69,7 +69,7 @@ SpeechBubbleMorph*/ // Global stuff //////////////////////////////////////////////////////// -modules.gui = '2015-January-13'; +modules.gui = '2015-January-21'; // Declarations @@ -119,7 +119,7 @@ IDE_Morph.prototype.setDefaultDesign = function () { ]; IDE_Morph.prototype.rotationStyleColors = IDE_Morph.prototype.tabColors; IDE_Morph.prototype.appModeColor = new Color(); - IDE_Morph.prototype.scriptsPaneTexture = 'scriptsPaneTexture.gif'; + IDE_Morph.prototype.scriptsPaneTexture = this.scriptsTexture(); IDE_Morph.prototype.padding = 5; SpriteIconMorph.prototype.labelColor @@ -172,6 +172,22 @@ IDE_Morph.prototype.setFlatDesign = function () { = IDE_Morph.prototype.buttonLabelColor; }; +IDE_Morph.prototype.scriptsTexture = function () { + var pic = newCanvas(new Point(100, 100)), // bigger scales faster + ctx = pic.getContext('2d'), + i; + for (i = 0; i < 100; i += 4) { + ctx.fillStyle = this.frameColor.toString(); + ctx.fillRect(i, 0, 1, 100); + ctx.fillStyle = this.groupColor.lighter(6).toString(); + ctx.fillRect(i + 1, 0, 1, 100); + ctx.fillRect(i + 3, 0, 1, 100); + ctx.fillStyle = this.groupColor.toString(); + ctx.fillRect(i + 2, 0, 1, 100); + } + return pic; +}; + IDE_Morph.prototype.setDefaultDesign(); // IDE_Morph instance creation: @@ -1152,7 +1168,7 @@ IDE_Morph.prototype.createSpriteEditor = function () { if (this.currentTab === 'scripts') { scripts.isDraggable = false; scripts.color = this.groupColor; - scripts.texture = this.scriptsPaneTexture; + scripts.cachedTexture = this.scriptsPaneTexture; this.spriteEditor = new ScrollFrameMorph( scripts, @@ -3527,7 +3543,7 @@ IDE_Morph.prototype.userSetBlocksScale = function () { sample = new FrameMorph(); sample.acceptsDrops = false; - sample.texture = this.scriptsPaneTexture; + sample.cachedTexture = this.scriptsPaneTexture; sample.setExtent(new Point(250, 180)); scrpt.setPosition(sample.position().add(10)); sample.add(scrpt); diff --git a/history.txt b/history.txt index 1880e45..ff472c7 100755 --- a/history.txt +++ b/history.txt @@ -2422,3 +2422,4 @@ ______ 150121 ------ * Objects: Keep layering of nested sprites thru drag & drop +* GUI, Store, BYOB: Generate ScriptsPaneTexture programmatically diff --git a/manifest.mf b/manifest.mf index 353d47f..d040961 100644 --- a/manifest.mf +++ b/manifest.mf @@ -10,5 +10,4 @@ threads.js widgets.js store.js xml.js -scriptsPaneTexture.gif -snap_logo_sm.gif +snap_logo_sm.png diff --git a/scriptsPaneTexture.gif b/scriptsPaneTexture.gif deleted file mode 100755 index 846c77f..0000000 Binary files a/scriptsPaneTexture.gif and /dev/null differ diff --git a/store.js b/store.js index 9dd851d..056767f 100644 --- a/store.js +++ b/store.js @@ -61,7 +61,7 @@ SyntaxElementMorph, Variable*/ // Global stuff //////////////////////////////////////////////////////// -modules.store = '2015-January-12'; +modules.store = '2015-January-21'; // XML_Serializer /////////////////////////////////////////////////////// @@ -861,7 +861,7 @@ SnapSerializer.prototype.loadScripts = function (scripts, model) { // private var myself = this, scale = SyntaxElementMorph.prototype.scale; - scripts.texture = 'scriptsPaneTexture.gif'; + scripts.cachedTexture = IDE_Morph.prototype.scriptsPaneTexture; model.children.forEach(function (child) { var element; if (child.tag === 'script') { -- cgit v1.3.1 From 386ff338951135bed9d8b7bd33c973b46e63e89f Mon Sep 17 00:00:00 2001 From: jmoenig Date: Wed, 21 Jan 2015 12:26:54 +0100 Subject: Fix Zoom Dialog’s sample background in “flat” design MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gui.js | 1 + history.txt | 1 + 2 files changed, 2 insertions(+) diff --git a/gui.js b/gui.js index 9b8b51a..73fffe9 100644 --- a/gui.js +++ b/gui.js @@ -3543,6 +3543,7 @@ IDE_Morph.prototype.userSetBlocksScale = function () { sample = new FrameMorph(); sample.acceptsDrops = false; + sample.color = IDE_Morph.prototype.groupColor; sample.cachedTexture = this.scriptsPaneTexture; sample.setExtent(new Point(250, 180)); scrpt.setPosition(sample.position().add(10)); diff --git a/history.txt b/history.txt index ff472c7..4de8ee1 100755 --- a/history.txt +++ b/history.txt @@ -2423,3 +2423,4 @@ ______ ------ * Objects: Keep layering of nested sprites thru drag & drop * GUI, Store, BYOB: Generate ScriptsPaneTexture programmatically +* GUI: Fix Zoom Dialog’s sample background in “flat” design -- cgit v1.3.1 From 669704a90adf772f534c89fb4c3732236fef2b51 Mon Sep 17 00:00:00 2001 From: jmoenig Date: Wed, 21 Jan 2015 12:51:08 +0100 Subject: Integrated Korean and Catalan translation updates --- history.txt | 1 + lang-ca.js | 2 +- lang-ko.js | 2 +- locale.js | 10 +++++----- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/history.txt b/history.txt index 4de8ee1..ad0f53d 100755 --- a/history.txt +++ b/history.txt @@ -2424,3 +2424,4 @@ ______ * Objects: Keep layering of nested sprites thru drag & drop * GUI, Store, BYOB: Generate ScriptsPaneTexture programmatically * GUI: Fix Zoom Dialog’s sample background in “flat” design +* Updated Korean and Catalan translations, thanks, Yunjae Jang and Bernat Romagosa! diff --git a/lang-ca.js b/lang-ca.js index b555b15..c57d0d2 100644 --- a/lang-ca.js +++ b/lang-ca.js @@ -185,7 +185,7 @@ SnapTranslator.dict.ca = { 'translator_e-mail': 'bromagosa@citilab.eu', // optional 'last_changed': - '2014-01-16', // this, too, will appear in the Translators tab + '2015-01-21', // this, too, will appear in the Translators tab // GUI // control bar: diff --git a/lang-ko.js b/lang-ko.js index 3241c7f..8cedf44 100755 --- a/lang-ko.js +++ b/lang-ko.js @@ -185,7 +185,7 @@ SnapTranslator.dict.ko = { 'translator_e-mail': 'janggoons@gmail.com', // optional 'last_changed': - '2014-11-07', // this, too, will appear in the Translators tab + '2015-01-21', // this, too, will appear in the Translators tab // GUI // control bar: diff --git a/locale.js b/locale.js index 613ead9..5ec6013 100644 --- a/locale.js +++ b/locale.js @@ -42,7 +42,7 @@ /*global modules, contains*/ -modules.locale = '2015-January-12'; +modules.locale = '2015-January-21'; // Global stuff @@ -195,9 +195,9 @@ SnapTranslator.dict.ko = { 'language_translator': 'Yunjae Jang', 'translator_e-mail': - 'yunjae.jang@inc.korea.ac.kr', + 'janggoons@gmail.com', 'last_changed': - '2012-11-18' + '2015-01-21' }; SnapTranslator.dict.pt = { @@ -375,9 +375,9 @@ SnapTranslator.dict.ca = { 'language_translator': 'Bernat Romagosa Carrasquer', 'translator_e-mail': - 'tibabenfortlapalanca@gmail.com', + 'bromagosa@citilab.eu', 'last_changed': - '2013-11-26' + '2015-01-21' }; SnapTranslator.dict.fi = { -- cgit v1.3.1 From 82552d0b294092a009a40a9761eadf0b5d3e753f Mon Sep 17 00:00:00 2001 From: jmoenig Date: Wed, 21 Jan 2015 17:15:58 +0100 Subject: Fix speech bubbles of dragged nested sprites --- history.txt | 1 + objects.js | 12 +++++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/history.txt b/history.txt index ad0f53d..deb76a6 100755 --- a/history.txt +++ b/history.txt @@ -2425,3 +2425,4 @@ ______ * GUI, Store, BYOB: Generate ScriptsPaneTexture programmatically * GUI: Fix Zoom Dialog’s sample background in “flat” design * Updated Korean and Catalan translations, thanks, Yunjae Jang and Bernat Romagosa! +* Objects: Fix speech bubbles of dragged nested sprites diff --git a/objects.js b/objects.js index e59726f..981fb20 100644 --- a/objects.js +++ b/objects.js @@ -3155,14 +3155,11 @@ SpriteMorph.prototype.positionTalkBubble = function () { bubble.changed(); }; -// dragging and dropping adjustments b/c of talk bubbles +// dragging and dropping adjustments b/c of talk bubbles and parts SpriteMorph.prototype.prepareToBeGrabbed = function (hand) { - var bubble = this.talkBubble(); - this.recordLayers(); - if (!bubble) {return null; } this.removeShadow(); - bubble.hide(); + this.recordLayers(); if (!this.bounds.containsPoint(hand.position())) { this.setCenter(hand.position()); } @@ -4055,6 +4052,10 @@ SpriteMorph.prototype.recordLayers = function () { return; } this.layers = this.allParts(); + this.layers.forEach(function (part) { + var bubble = part.talkBubble(); + if (bubble) {bubble.hide(); } + }); this.layers.sort(function (x, y) { return stage.children.indexOf(x) < stage.children.indexOf(y) ? -1 : 1; @@ -4065,6 +4066,7 @@ SpriteMorph.prototype.restoreLayers = function () { if (this.layers && this.layers.length > 1) { this.layers.forEach(function (sprite) { sprite.comeToFront(); + sprite.positionTalkBubble(); }); } this.layers = null; -- cgit v1.3.1 From 2ca378c50bf67b6af5e16ba72af81f4f84db308c Mon Sep 17 00:00:00 2001 From: Jens Mönig Date: Wed, 21 Jan 2015 17:18:36 +0100 Subject: Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cf17e18..878274d 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ inspired by Scratch written by Jens Mönig and Brian Harvey jens@moenig.org, bh@cs.berkeley.edu -Copyright (C) 2014 by Jens Mönig and Brian Harvey +Copyright (C) 2015 by Jens Mönig and Brian Harvey Snap! is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as -- cgit v1.3.1