<commit_before>import React, { Fragment, Component } from 'react'
import {
  Box,
  Flex,
  Container,
  Heading,
  Text,
  Section,
  Link,
  Card,
  Button,
  Icon,
  Image
} from '@hackclub/design-system'
import Helmet from 'react-helmet'
import Nav from 'components/Nav'
import Footer from 'components/Footer'
import IconButton from 'components/IconButton'
import Form from 'components/challenge/Form'
import Ended from 'components/challenge/Ended'
import Posts from 'components/challenge/Posts'
import DiscussChallenge from 'components/challenge/DiscussChallenge'
import { Modal, Overlay, CloseButton } from 'components/Modal'
import {
  DropdownContainer,
  DropdownMenu,
  DropdownMenuOption
} from 'components/Dropdown'
import { dt, tinyDt } from 'helpers'
import { isEmpty, keys } from 'lodash'
import api from 'api'
import storage from 'storage'

const sortByHumanized = {
  trending: 'Trending',
  top: 'Top-voted',
  newest: 'Newest',
  viewed: 'Top-viewed',
  random: 'Random'
}

class Help extends Component {
  state = { active: false }

  toggleRules = () => {
    this.setState(state => ({ active: !state.active }))
  }

  // Render a modal for challenge rules on button press
  render() {
    if (this.state.active) {
      return (
        <Fragment>
          <Modal align="left" my={4} p={[3, 4]}>
            <CloseButton onClick={this.toggleRules} />
            <Heading.h2>Challenge Rules</Heading.h2>
            <Text
              f={2}
              mt={3}
              color="info"
              py={2}
              px={3}
              bg="blue.0"
              style={{ borderRadius: 4 }}
            >
              For this challenge, your entry must be hosted on{' '}
              <Link href="https://repl.it" target="_blank" bold>
                repl.it
              </Link>{' '}
              and use{' '}
              <Link href="https://p5js.org" target="_blank" bold>
                p5.js
              </Link>{' '}
              in its code.
            </Text>
            <Text f={2} my={3}>
              Challenge is open to Hack Club members and repl.it users. It
              strictly follows Hack Club’s{' '}
              <Link href="https://conduct.hackclub.com" target="_blank">
                Code of Conduct
              </Link>. Anything breaking our Code of Conduct (ex. voter fraud)
              and contestants found cheating can be temporarily or permanently
              banned.
            </Text>
            <Text f={2}>
              If you think anyone has violated our Conduct or cheating policy,
              please reach out to us confidentially at{' '}
              <Link href="mailto:challenge@hackclub.com">
                challenge@hackclub.com
              </Link>.
            </Text>
          </Modal>
          <Overlay onClick={this.toggleRules} />
        </Fragment>
      )
    }
    return (
      <IconButton
        name="flag"
        size={16}
        children="Rules"
        inverted
        f={2}
        onClick={this.toggleRules}
        style={{ float: 'right' }}
        {...this.props}
      />
    )
  }
}

const Header = Section.withComponent('header').extend`
  padding: 0 !important;
  background-color: ${({ theme }) => theme.colors.red[5]};
  background-image: linear-gradient(
    32deg,
    ${({ theme }) => theme.colors.pink[5]},
    ${({ theme }) => theme.colors.red[5]}
  );
`

const HeaderContainer = Box.extend`
  display: grid;
  grid-gap: ${({ theme }) => theme.space[3]}px;
  max-width: 100%;
  ${({ theme }) => theme.mediaQueries.md} {
    grid-template-columns: repeat(2, 1fr);
    grid-gap: ${({ theme }) => theme.space[4]}px;
    max-width: 56rem;
  }
`

const HeaderCard = Card.extend`
  h2,
  p {
    color: ${({ theme }) => theme.colors.black} !important;
  }
`

const Title = Flex.extend`
  border-bottom: 1px solid ${({ theme }) => theme.colors.smoke};
`

const title = 'Hack Club Challenge'
const desc =
  'Join Hack Club’s high school coding challenge. Submit your entry to compete in our monthly programming contest and win prizes.'
const img = 'https://hackclub.com/challenge.png'

export default class extends Component {
  state = { status: 'loading', sortBy: 'random' }

  componentDidMount() {
    if (storage.get('authToken')) {
      api
        .get(`v1/users/current`)
        .then(user => {
          this.setState({ status: 'success', userId: user.id })
        })
        .catch(err => {
          if (err.status === 401) {
            this.setState({ status: 'needsToAuth' })
          } else {
            this.setState({ status: 'error' })
          }
        })
    } else {
      this.setState({ status: 'needsToAuth' })
    }
  }

  render() {
    const { data } = this.props
    const { userId, status, sortBy } = this.state
    if (isEmpty(data)) return null
    const challenge = data.publicJson
    const ended = Date.parse(new Date()) > Date.parse(challenge.end)
    return (
      <Fragment>
        <Helmet
          title={title}
          meta={[
            { name: 'description', content: desc },
            { name: 'twitter:title', content: title },
            { name: 'twitter:description', content: desc },
            { name: 'twitter:image', content: img },
            { property: 'og:title', content: title },
            { property: 'og:description', content: desc },
            { property: 'og:image', content: img },
            { property: 'og:url', content: 'https://hackclub.com/challenge' }
          ]}
        />

          <HeaderContainer px={3} py={0} mb={[3, 4]} align="left" mx="auto">
            <Box align={['center', null, 'right']}>
              <Text mb={[-2, -3]} f={3} bold caps>
                {status === 'success' ? (
                  <Fragment>
                    <Link href="https://repl.it" target="_blank" color="white">
                      repl.it
                    </Link>{' '}
                    + Hack Club
                  </Fragment>
                ) : (
                  'p5.js website'
                )}
              </Text>
              <Heading.h1 f={[6, 7]} my={0}>
                Challenge
              </Heading.h1>
            </Box>
            <Heading.h2 f={3} regular style={{ alignSelf: 'center' }}>
              Hack Club and{' '}
              <Link
                href="https://repl.it"
                target="_blank"
                color="white"
                bold
                underline
              >
                repl.it
              </Link>{' '}
              are partnering to run the largest challenge yet!
            </Heading.h2>
            <HeaderCard
              boxShadowSize="md"
              p={3}
              bg="pink.0"
              align="left"
              style={{
                alignSelf: status === 'success' ? 'flex-start' : 'auto'
              }}
            >
              <Text f={2}>
                🌟 Challenge: Create the coolest website w/{' '}
                <Link href="https://p5js.org/" target="_blank" underline>
                  {challenge.name}
                </Link>

                🛠 Build on{' '}
                <Link href="https://repl.it" target="_blank" underline>
                  repl.it
                </Link>{' '}
                using{' '}
                <Link href="https://p5js.org" target="_blank" underline>
                  p5.js
                </Link>{' '}
                (required)

                🎁 {challenge.description}

                ℹ Submissions open to{' '}
                <Link href="https://repl.it" target="_blank" underline>
                  repl.it
                </Link>{' '}
                and Hack Club users

                📖{' '}
                <Link
                  href="https://gist.github.com/zachlatta/abe14c8e1c7ab32c8d8297bdf986dbbb"
                  target="_blank"
                  underline
                >
                  Click here
                </Link>{' '}
                for help getting started

                🏅 Submissions due 5/20. Top 3 voted by 5/25 win!
              </Text>
            </HeaderCard>
            <HeaderCard boxShadowSize="md" p={3} bg="pink.0">
              <Help />
              <Form challengeId={challenge.id} status={status} />
            </HeaderCard>
          </HeaderContainer>
          <Flex
            mb={[3, 4]}
            align="center"
            justify="center"
            style={{ display: status === 'success' ? 'none' : null }}
          >
            <Link href="https://repl.it" target="_blank">
              <Image
                alt="repl.it logo"
                src="/replit-white.svg"
                w={[144, 224]}
              />
            </Link>
            <Text.span color="pink.1" f={[4, 5]} mx={[2, 3]} children="+" />
            <Text.span color="white" f={[4, 5, 6]} bold children="Hack Club" />
          </Flex>

        <Container maxWidth={48} pt={4} pb={5} px={[0, 3]}>
          {ended && <Ended />}
          <Title align="center" pb={2} px={[2, 0]}>
            <Flex align="center" wrap>
              <Heading.h2 color="black" f={5} mr={3}>
                Submissions
              </Heading.h2>
              <Text f={2} color="muted" align="left">
                {tinyDt(challenge.start)}–{tinyDt(challenge.end)}
              </Text>
            </Flex>
            <DropdownContainer ml="auto">
              <IconButton
                name="sort"
                size={16}
                bg="info"
                f={2}
                children={sortByHumanized[sortBy]}
              />
              <DropdownMenu>
                {keys(sortByHumanized).map(key => (
                  <DropdownMenuOption
                    active={sortBy === key}
                    onClick={() => this.setState({ sortBy: key })}
                    children={sortByHumanized[key]}
                    key={key}
                  />
                ))}
              </DropdownMenu>
            </DropdownContainer>
          </Title>
          <Posts
            challengeId={challenge.id}
            userId={userId}
            status={status}
            sortBy={sortBy}
          />
          <Flex mt={4} mb={[4, 0]} align="center" justify="center">
            <Link href="https://repl.it" target="_blank">
              <Image alt="repl.it logo" src="/replit.svg" w={128} />
            </Link>
            <Text.span color="muted" f={4} mx={3} children="+" />
            <Text.span color="primary" f={[4, 5]} bold children="Hack Club" />
          </Flex>
          <DiscussChallenge />
        </Container>

      </Fragment>
    )
  }
}
export const pageQuery = graphql`
  query ChallengeQuery {
    publicJson {
      id
      name
      description
      start
      end
    }
  }
`
<commit_msg>Sort challenges by votes<commit_after>import React, { Fragment, Component } from 'react'
import {
  Box,
  Flex,
  Container,
  Heading,
  Text,
  Section,
  Link,
  Card,
  Button,
  Icon,
  Image
} from '@hackclub/design-system'
import Helmet from 'react-helmet'
import Nav from 'components/Nav'
import Footer from 'components/Footer'
import IconButton from 'components/IconButton'
import Form from 'components/challenge/Form'
import Ended from 'components/challenge/Ended'
import Posts from 'components/challenge/Posts'
import DiscussChallenge from 'components/challenge/DiscussChallenge'
import { Modal, Overlay, CloseButton } from 'components/Modal'
import {
  DropdownContainer,
  DropdownMenu,
  DropdownMenuOption
} from 'components/Dropdown'
import { dt, tinyDt } from 'helpers'
import { isEmpty, keys } from 'lodash'
import api from 'api'
import storage from 'storage'

const sortByHumanized = {
  trending: 'Trending',
  top: 'Top-voted',
  newest: 'Newest',
  viewed: 'Top-viewed',
  random: 'Random'
}

class Help extends Component {
  state = { active: false }

  toggleRules = () => {
    this.setState(state => ({ active: !state.active }))
  }

  // Render a modal for challenge rules on button press
  render() {
    if (this.state.active) {
      return (
        <Fragment>
          <Modal align="left" my={4} p={[3, 4]}>
            <CloseButton onClick={this.toggleRules} />
            <Heading.h2>Challenge Rules</Heading.h2>
            <Text
              f={2}
              mt={3}
              color="info"
              py={2}
              px={3}
              bg="blue.0"
              style={{ borderRadius: 4 }}
            >
              For this challenge, your entry must be hosted on{' '}
              <Link href="https://repl.it" target="_blank" bold>
                repl.it
              </Link>{' '}
              and use{' '}
              <Link href="https://p5js.org" target="_blank" bold>
                p5.js
              </Link>{' '}
              in its code.
            </Text>
            <Text f={2} my={3}>
              Challenge is open to Hack Club members and repl.it users. It
              strictly follows Hack Club’s{' '}
              <Link href="https://conduct.hackclub.com" target="_blank">
                Code of Conduct
              </Link>. Anything breaking our Code of Conduct (ex. voter fraud)
              and contestants found cheating can be temporarily or permanently
              banned.
            </Text>
            <Text f={2}>
              If you think anyone has violated our Conduct or cheating policy,
              please reach out to us confidentially at{' '}
              <Link href="mailto:challenge@hackclub.com">
                challenge@hackclub.com
              </Link>.
            </Text>
          </Modal>
          <Overlay onClick={this.toggleRules} />
        </Fragment>
      )
    }
    return (
      <IconButton
        name="flag"
        size={16}
        children="Rules"
        inverted
        f={2}
        onClick={this.toggleRules}
        style={{ float: 'right' }}
        {...this.props}
      />
    )
  }
}

const Header = Section.withComponent('header').extend`
  padding: 0 !important;
  background-color: ${({ theme }) => theme.colors.red[5]};
  background-image: linear-gradient(
    32deg,
    ${({ theme }) => theme.colors.pink[5]},
    ${({ theme }) => theme.colors.red[5]}
  );
`

const HeaderContainer = Box.extend`
  display: grid;
  grid-gap: ${({ theme }) => theme.space[3]}px;
  max-width: 100%;
  ${({ theme }) => theme.mediaQueries.md} {
    grid-template-columns: repeat(2, 1fr);
    grid-gap: ${({ theme }) => theme.space[4]}px;
    max-width: 56rem;
  }
`

const HeaderCard = Card.extend`
  h2,
  p {
    color: ${({ theme }) => theme.colors.black} !important;
  }
`

const Title = Flex.extend`
  border-bottom: 1px solid ${({ theme }) => theme.colors.smoke};
`

const title = 'Hack Club Challenge'
const desc =
  'Join Hack Club’s high school coding challenge. Submit your entry to compete in our monthly programming contest and win prizes.'
const img = 'https://hackclub.com/challenge.png'

export default class extends Component {
  state = { status: 'loading', sortBy: 'top' }

  componentDidMount() {
    if (storage.get('authToken')) {
      api
        .get(`v1/users/current`)
        .then(user => {
          this.setState({ status: 'success', userId: user.id })
        })
        .catch(err => {
          if (err.status === 401) {
            this.setState({ status: 'needsToAuth' })
          } else {
            this.setState({ status: 'error' })
          }
        })
    } else {
      this.setState({ status: 'needsToAuth' })
    }
  }

  render() {
    const { data } = this.props
    const { userId, status, sortBy } = this.state
    if (isEmpty(data)) return null
    const challenge = data.publicJson
    const ended = Date.parse(new Date()) > Date.parse(challenge.end)
    return (
      <Fragment>
        <Helmet
          title={title}
          meta={[
            { name: 'description', content: desc },
            { name: 'twitter:title', content: title },
            { name: 'twitter:description', content: desc },
            { name: 'twitter:image', content: img },
            { property: 'og:title', content: title },
            { property: 'og:description', content: desc },
            { property: 'og:image', content: img },
            { property: 'og:url', content: 'https://hackclub.com/challenge' }
          ]}
        />

          <HeaderContainer px={3} py={0} mb={[3, 4]} align="left" mx="auto">
            <Box align={['center', null, 'right']}>
              <Text mb={[-2, -3]} f={3} bold caps>
                {status === 'success' ? (
                  <Fragment>
                    <Link href="https://repl.it" target="_blank" color="white">
                      repl.it
                    </Link>{' '}
                    + Hack Club
                  </Fragment>
                ) : (
                  'p5.js website'
                )}
              </Text>
              <Heading.h1 f={[6, 7]} my={0}>
                Challenge
              </Heading.h1>
            </Box>
            <Heading.h2 f={3} regular style={{ alignSelf: 'center' }}>
              Hack Club and{' '}
              <Link
                href="https://repl.it"
                target="_blank"
                color="white"
                bold
                underline
              >
                repl.it
              </Link>{' '}
              are partnering to run the largest challenge yet!
            </Heading.h2>
            <HeaderCard
              boxShadowSize="md"
              p={3}
              bg="pink.0"
              align="left"
              style={{
                alignSelf: status === 'success' ? 'flex-start' : 'auto'
              }}
            >
              <Text f={2}>
                🌟 Challenge: Create the coolest website w/{' '}
                <Link href="https://p5js.org/" target="_blank" underline>
                  {challenge.name}
                </Link>

                🛠 Build on{' '}
                <Link href="https://repl.it" target="_blank" underline>
                  repl.it
                </Link>{' '}
                using{' '}
                <Link href="https://p5js.org" target="_blank" underline>
                  p5.js
                </Link>{' '}
                (required)

                🎁 {challenge.description}

                ℹ Submissions open to{' '}
                <Link href="https://repl.it" target="_blank" underline>
                  repl.it
                </Link>{' '}
                and Hack Club users

                📖{' '}
                <Link
                  href="https://gist.github.com/zachlatta/abe14c8e1c7ab32c8d8297bdf986dbbb"
                  target="_blank"
                  underline
                >
                  Click here
                </Link>{' '}
                for help getting started

                🏅 Submissions due 5/20. Top 3 voted by 5/25 win!
              </Text>
            </HeaderCard>
            <HeaderCard boxShadowSize="md" p={3} bg="pink.0">
              <Help />
              <Form challengeId={challenge.id} status={status} />
            </HeaderCard>
          </HeaderContainer>
          <Flex
            mb={[3, 4]}
            align="center"
            justify="center"
            style={{ display: status === 'success' ? 'none' : null }}
          >
            <Link href="https://repl.it" target="_blank">
              <Image
                alt="repl.it logo"
                src="/replit-white.svg"
                w={[144, 224]}
              />
            </Link>
            <Text.span color="pink.1" f={[4, 5]} mx={[2, 3]} children="+" />
            <Text.span color="white" f={[4, 5, 6]} bold children="Hack Club" />
          </Flex>

        <Container maxWidth={48} pt={4} pb={5} px={[0, 3]}>
          {ended && <Ended />}
          <Title align="center" pb={2} px={[2, 0]}>
            <Flex align="center" wrap>
              <Heading.h2 color="black" f={5} mr={3}>
                Submissions
              </Heading.h2>
              <Text f={2} color="muted" align="left">
                {tinyDt(challenge.start)}–{tinyDt(challenge.end)}
              </Text>
            </Flex>
            <DropdownContainer ml="auto">
              <IconButton
                name="sort"
                size={16}
                bg="info"
                f={2}
                children={sortByHumanized[sortBy]}
              />
              <DropdownMenu>
                {keys(sortByHumanized).map(key => (
                  <DropdownMenuOption
                    active={sortBy === key}
                    onClick={() => this.setState({ sortBy: key })}
                    children={sortByHumanized[key]}
                    key={key}
                  />
                ))}
              </DropdownMenu>
            </DropdownContainer>
          </Title>
          <Posts
            challengeId={challenge.id}
            userId={userId}
            status={status}
            sortBy={sortBy}
          />
          <Flex mt={4} mb={[4, 0]} align="center" justify="center">
            <Link href="https://repl.it" target="_blank">
              <Image alt="repl.it logo" src="/replit.svg" w={128} />
            </Link>
            <Text.span color="muted" f={4} mx={3} children="+" />
            <Text.span color="primary" f={[4, 5]} bold children="Hack Club" />
          </Flex>
          <DiscussChallenge />
        </Container>

      </Fragment>
    )
  }
}
export const pageQuery = graphql`
  query ChallengeQuery {
    publicJson {
      id
      name
      description
      start
      end
    }
  }
`
<|endoftext|>


<commit_before>import { combineReducers } from "@reduxjs/toolkit";
import localUser from "./features/users/localUserReducer";
import localRoomMembers from "./features/users/localRoomMembersSlice";
import userSettings from "./features/settings/userSettingsReducer";
import users from "./features/users/usersSlice";
import rooms from "./features/rooms/roomsSlice";
import message from "./features/message/messageSlice";
import chatInput from "./features/chatInput/chatInputSlice";
import emoticonPicker from "./features/emoticon/emoticonPickerReducer";
import messageControls from "./features/messageControls/messageControlsSlice";
import imagePick from "./features/imagePick/imagePickReducer";
import socket from "./features/socket/socketSlice";
import version from "./features/version/versionSlice";
import room from "./features/room/roomSlice";
// import log from "./features/chat/logReducer";

const rootReducer = combineReducers({
	localUser,
	localRoomMembers,
	userSettings,
	users,
	rooms,
	message,
	chatInput,
	emoticonPicker,
	messageControls,
	imagePick,
	socket,
	version,
	room
	// log,
});

export default rootReducer;
<commit_msg>add logging back<commit_after>import { combineReducers } from "@reduxjs/toolkit";
import localUser from "./features/users/localUserReducer";
import localRoomMembers from "./features/users/localRoomMembersSlice";
import userSettings from "./features/settings/userSettingsReducer";
import users from "./features/users/usersSlice";
import rooms from "./features/rooms/roomsSlice";
import message from "./features/message/messageSlice";
import chatInput from "./features/chatInput/chatInputSlice";
import emoticonPicker from "./features/emoticon/emoticonPickerReducer";
import messageControls from "./features/messageControls/messageControlsSlice";
import imagePick from "./features/imagePick/imagePickReducer";
import socket from "./features/socket/socketSlice";
import version from "./features/version/versionSlice";
import room from "./features/room/roomSlice";
import log from "./features/chat/logReducer";

const rootReducer = combineReducers({
	localUser,
	localRoomMembers,
	userSettings,
	users,
	rooms,
	message,
	chatInput,
	emoticonPicker,
	messageControls,
	imagePick,
	socket,
	version,
	room,
	log,
});

export default rootReducer;
<|endoftext|>


<commit_before>GV.schemas.Documents = new SimpleSchema({

  // A document title
  title: {
    type: String,
    label: function() {
      return "Tittel";
    },
    optional: false,
    min: 3,
    max: 100,
    autoform: {
      placeholder: "schemaLabel"
    }
  },

  // A description that describe the content
  summary: {
    type: String,
    label: function() {
      return "Sammendrag";
    },
    optional: true,
    max: 500,
    autoform: {
      placeholder: "schemaLabel"
    }
  },

  // Tags that adds a dimension to the description and title fields
  tags: {
    type: [String],
    label: function() {
      return "Emneknagger";
    },
    optional: true,
    autoform: {
      type: "selectize",
      placeholder: "schemaLabel",
      afFieldInput: {
        multiple: true,
        selectizeOptions: {
          delimiter: ',',
          persist: false,

          // Can create new tags
          create: function(input) {

            GV.collections.Tags.insert({ value: input, text: input });

            return {
              value: input,
              text: input
            }
          },

          // How the tags suggestions are rendered
          render: {
            option: function(item, escape) {
              return '
' +
                '' +
                '' + item.text + '' +
                '' +
                '
';
            },
            option_create: function(data, escape) {
              return '
Legg til ' + escape(data.input) + '…
';
            }
          },

          // How to load new tags
          load: function(query, callback) {
            if (!query.length) return callback();

            if (GV.subscriptions['tags'])
              GV.subscriptions['tags'].stop();

            GV.subscriptions['tags'] = Meteor.subscribe('tagsByQuery', { text: { $regex: query, $options: 'i' } });
            var tags = GV.collections.Tags.find({ text: { $regex: query, $options: 'i' } }).fetch();

            callback(tags);
          }
        }
      }
    }
  },

  status: {
    type: String,
    optional: true,
    autoform: {
      placeholder: "schemaLabel"
    }
  },

  // The document was created on this date
  createdOn: {
    type: Date,
    optional: false,
    autoValue: function() {
      if (this.isInsert) {
        return new Date;
      }
      else if (this.isUpsert) {
        return {$setOnInsert: new Date};
      }
      else {
        this.unset();
      }
    }
  },

  // The document was updated on this date
  lastChanged: {
    type: Date,
    optional: false
  },

  // The document was shared by this user
  userId: {
    type: String,
    optional: false
  },

  // Is the whole document collapsed?
  collapsed: {
    type: Boolean,
    optional: true
  },

  // Is the whole document collapsed?
  children: {
    type: [String],
    optional: true
  }

});

// TODO: update these schema messages to represent the actual schema
GV.schemas.Documents.messages({
  // "required email": "Epost må fylles inn",
  // "required message": "Melding må vedlegges",
  // "required title": "Emnefeltet må fylles inn",
  // "maxString name": "Navnet må ikke overskride [max] tegn",
  // "minString title": "Emnefeltet må inneholde minimum [min] tegn",
  // "minString message": "Meldingen må inneholde minimum [min] tegn",
  // maxString: "[label] må ikke overskride [max] tegn",
  // expectedString: "[label] må inneholde en streng",
  // "regEx email": "Dette er ikke en gyldig epost"
});

// Attach the schema to the collection
GV.collections.Documents.attachSchema(GV.schemas.Documents);
<commit_msg>Adds a boolean value if the node is a root node.<commit_after>GV.schemas.Documents = new SimpleSchema({

  // A document title
  title: {
    type: String,
    label: function() {
      return "Tittel";
    },
    optional: false,
    min: 3,
    max: 100,
    autoform: {
      placeholder: "schemaLabel"
    }
  },

  // A description that describe the content
  summary: {
    type: String,
    label: function() {
      return "Sammendrag";
    },
    optional: true,
    max: 500,
    autoform: {
      placeholder: "schemaLabel"
    }
  },

  // Tags that adds a dimension to the description and title fields
  tags: {
    type: [String],
    label: function() {
      return "Emneknagger";
    },
    optional: true,
    autoform: {
      type: "selectize",
      placeholder: "schemaLabel",
      afFieldInput: {
        multiple: true,
        selectizeOptions: {
          delimiter: ',',
          persist: false,

          // Can create new tags
          create: function(input) {

            GV.collections.Tags.insert({ value: input, text: input });

            return {
              value: input,
              text: input
            }
          },

          // How the tags suggestions are rendered
          render: {
            option: function(item, escape) {
              return '
' +
                '' +
                '' + item.text + '' +
                '' +
                '
';
            },
            option_create: function(data, escape) {
              return '
Legg til ' + escape(data.input) + '…
';
            }
          },

          // How to load new tags
          load: function(query, callback) {
            if (!query.length) return callback();

            if (GV.subscriptions['tags'])
              GV.subscriptions['tags'].stop();

            GV.subscriptions['tags'] = Meteor.subscribe('tagsByQuery', { text: { $regex: query, $options: 'i' } });
            var tags = GV.collections.Tags.find({ text: { $regex: query, $options: 'i' } }).fetch();

            callback(tags);
          }
        }
      }
    }
  },

  status: {
    type: String,
    optional: true,
    autoform: {
      placeholder: "schemaLabel"
    }
  },

  // The document was created on this date
  createdOn: {
    type: Date,
    optional: false,
    autoValue: function() {
      if (this.isInsert) {
        return new Date;
      }
      else if (this.isUpsert) {
        return {$setOnInsert: new Date};
      }
      else {
        this.unset();
      }
    }
  },

  isRoot: {
    type: Boolean,
    optional: false,
    autoValue: function() {
      if (this.isInsert) {
        return true;
      }
      else {
        this.unset();
      }
    }
  },

  // The document was updated on this date
  lastChanged: {
    type: Date,
    optional: false
  },

  // The document was shared by this user
  userId: {
    type: String,
    optional: false
  },

  // Is the whole document collapsed?
  collapsed: {
    type: Boolean,
    optional: true
  },

  // Is the whole document collapsed?
  children: {
    type: [String],
    optional: true
  }

});

// TODO: update these schema messages to represent the actual schema
GV.schemas.Documents.messages({
  // "required email": "Epost må fylles inn",
  // "required message": "Melding må vedlegges",
  // "required title": "Emnefeltet må fylles inn",
  // "maxString name": "Navnet må ikke overskride [max] tegn",
  // "minString title": "Emnefeltet må inneholde minimum [min] tegn",
  // "minString message": "Meldingen må inneholde minimum [min] tegn",
  // maxString: "[label] må ikke overskride [max] tegn",
  // expectedString: "[label] må inneholde en streng",
  // "regEx email": "Dette er ikke en gyldig epost"
});

// Attach the schema to the collection
GV.collections.Documents.attachSchema(GV.schemas.Documents);
<|endoftext|>


<commit_before>/*jslint devel: false, browser: true, maxerr: 50, indent: 4*/
/*global global: false, module: false, $: false, jQuery: false, console: false, document: false, event: false, frames: false, history: false, Image: false, location: false, name: false, navigator: false, Option: false, parent: false, screen: false, setInterval: false, setTimeout: false, window: false, XMLHttpRequest: false */

/**
 *	@description
 *		Ding is a class used to manage elements, events and timers
 *	@author
 *		Brian Martin
 *	@version
 *		1.0.0
 *	@namespace
 *		Cliste
 */
(function () {
	'use strict';

	var http = require("http"),
		mime = require('mime'),
		createServer,
		server;

	server = {};

	server.initialize = function () {
		createServer();
	};

	server.config = function () {
		return {
			'weight': 10
		};
	};

	createServer = function () {
		var headers = global.cliste.core.cliste.getHeaders(),
			paths = global.cliste.core.path.getPaths(),
			aliases = global.cliste.core.alias.getAliases(),
			url,
			html,
			data;

		http.createServer(function(request, response) {

			try {

				if (typeof(paths[request.url]) !== 'undefined' || typeof(aliases[request.url]) !== 'undefined') {

					global.cliste.core.cliste.setHeader({
						'Content-Type': 'text/html'
					});

					response.writeHead(200, headers);

					if (typeof(aliases[request.url]) !== 'undefined') {
						url = aliases[request.url];
					} else {
						url = request.url;
					}

					html = global.cliste[paths[url].type][paths[url].module][paths[url].template]();

				} else {

					if (typeof(aliases[request.url]) !== 'undefined') {
						url = aliases[request.url];
					} else {
						url = request.url;
					}

					if (global.cliste.core.file.fileExists(global.cliste.settings.base + url)) {

						if (url.indexOf('/sites/all/file') !== -1) {
							global.cliste.core.cliste.setHeader({
								'Content-Type': mime.lookup(global.cliste.settings.base + request.url)
							});
							response.writeHead(200, headers);
							html = global.cliste.core.file.getFile(global.cliste.settings.base + url);
						} else {
							global.cliste.core.cliste.setHeader({
							'Content-Type': 'text/html'
							});
							response.writeHead(404, headers);
							html = global.cliste.core.theme.get404();
						}

					} else {

						global.cliste.core.cliste.setHeader({
							'Content-Type': 'text/html'
						});
						response.writeHead(404, headers);
						html = global.cliste.core.theme.get404();

					}

				}

				response.write(html);
				response.end();

			} catch (exception) {
				console.log(exception);
				global.cliste.core.cliste.setHeader({
					'Content-Type': 'text/html'
				});
				response.writeHead(404, headers);
				html = global.cliste.core.theme.get404();
				response.write(html);
				response.end();
			}

		}).listen(global.cliste.settings.port);

	};

	module.exports = server;

}());
<commit_msg>Added a tab, won't let me push. . .<commit_after>/*jslint devel: false, browser: true, maxerr: 50, indent: 4*/
/*global global: false, module: false, $: false, jQuery: false, console: false, document: false, event: false, frames: false, history: false, Image: false, location: false, name: false, navigator: false, Option: false, parent: false, screen: false, setInterval: false, setTimeout: false, window: false, XMLHttpRequest: false */

/**
 *	@description
 *		Ding is a class used to manage elements, events and timers
 *	@author
 *		Brian Martin
 *	@version
 *		1.0.0
 *	@namespace
 *		Cliste
 */
(function () {
	'use strict';

	var http = require("http"),
		mime = require('mime'),
		createServer,
		server;

	server = {};

	server.initialize = function () {
		createServer();
	};

	server.config = function () {
		return {
			'weight': 10
		};
	};

	createServer = function () {
		var headers = global.cliste.core.cliste.getHeaders(),
			paths = global.cliste.core.path.getPaths(),
			aliases = global.cliste.core.alias.getAliases(),
			url,
			html,
			data;

		http.createServer(function(request, response) {

			try {

				if (typeof(paths[request.url]) !== 'undefined' || typeof(aliases[request.url]) !== 'undefined') {

					global.cliste.core.cliste.setHeader({
						'Content-Type': 'text/html'
					});

					response.writeHead(200, headers);

					if (typeof(aliases[request.url]) !== 'undefined') {
						url = aliases[request.url];
					} else {
						url = request.url;
					}

					html = global.cliste[paths[url].type][paths[url].module][paths[url].template]();

				} else {

					if (typeof(aliases[request.url]) !== 'undefined') {
						url = aliases[request.url];
					} else {
						url = request.url;
					}

					if (global.cliste.core.file.fileExists(global.cliste.settings.base + url)) {

						if (url.indexOf('/sites/all/file') !== -1) {
							global.cliste.core.cliste.setHeader({
								'Content-Type': mime.lookup(global.cliste.settings.base + request.url)
							});
							response.writeHead(200, headers);
							html = global.cliste.core.file.getFile(global.cliste.settings.base + url);
						} else {
							global.cliste.core.cliste.setHeader({
								'Content-Type': 'text/html'
							});
							response.writeHead(404, headers);
							html = global.cliste.core.theme.get404();
						}

					} else {

						global.cliste.core.cliste.setHeader({
							'Content-Type': 'text/html'
						});
						response.writeHead(404, headers);
						html = global.cliste.core.theme.get404();

					}

				}

				response.write(html);
				response.end();

			} catch (exception) {
				console.log(exception);
				global.cliste.core.cliste.setHeader({
					'Content-Type': 'text/html'
				});
				response.writeHead(404, headers);
				html = global.cliste.core.theme.get404();
				response.write(html);
				response.end();
			}

		}).listen(global.cliste.settings.port);

	};

	module.exports = server;

}());
<|endoftext|>


<commit_before>/* eslint-disable no-console, global-require, import/no-dynamic-require */
import { readFile } from 'fs-extra';
import React from 'react';
import { combineReducers } from 'redux';
import ReactDOM from 'react-dom/server';
import { extractCritical } from 'emotion-server';
import { flushChunkNames } from 'react-universal-component/server';
import flushChunks from 'webpack-flush-chunks';
import { mapValues } from 'lodash';
import { addPackage } from 'worona-deps';
import { useStaticRendering } from 'mobx-react';
import { Helmet } from 'react-helmet';
import App from '../components/App';
import initStore from '../store';
import { getSettings } from './settings';
import pwaTemplate from './pwa-template';
import ampTemplate from './amp-template';
import { requireModules } from './requires';
import { parseQuery } from './utils';

const buildModule = require(`../packages/build/${process.env.MODE}`);
const settingsModule = require(`../packages/settings/${process.env.MODE}`);
// const analyticsModule = require(`../packages/analytics/${process.env.MODE}`);
const iframesModule = require(`../packages/iframes/${process.env.MODE}`);
const customCssModule = require(`../packages/customCss/${process.env.MODE}`);

const dev = process.env.NODE_ENV !== 'production';

const { buildPath } = require(`../../.build/${
  process.env.MODE
}/buildInfo.json`);

export default ({ clientStats }) => async (req, res) => {
  let status = 200;
  const {
    siteId,
    perPage,
    initialUrl,
    env,
    device,
    type,
    id,
    page,
  } = parseQuery(req.query);

  // Avoid observables in server.
  useStaticRendering(true);

  let app;
  try {
    if (!siteId) {
      status = 404;
      throw new Error(`'?siteid=' query not found in ${req.originalUrl}`);
    }

    // Get settings.
    const settings = await getSettings({ siteId, env });
    if (!settings) {
      status = 404;
      throw new Error(
        `Settings for ${siteId} not found in the ${env.toUpperCase()} database.`,
      );
    }

    // Define core modules.
    const coreModules = [
      { name: 'build', namespace: 'build', module: buildModule },
      { name: 'settings', namespace: 'settings', module: settingsModule },
      // { name: 'analytics', namespace: 'analytics', module: analyticsModule },
      { name: 'iframes', namespace: 'iframes', module: iframesModule },
      { name: 'customCss', namespace: 'customCss', module: customCssModule },
    ];

    // Extract activated packages array from settings.
    const packages = settings
      ? Object.values(settings)
          .filter(pkg => pkg.woronaInfo.namespace !== 'generalSite')
          .filter(pkg => pkg.woronaInfo.namespace !== 'generalApp')
          .reduce(
            (obj, pkg) => ({
              ...obj,
              [pkg.woronaInfo.namespace]: pkg.woronaInfo.name,
            }),
            {},
          )
      : {};

    // Load the activated modules.
    const pkgModules = await requireModules(Object.entries(packages));

    const stores = {};
    const reducers = {};
    const serverSagas = {};

    const addModules = pkg => {
      addPackage({ namespace: pkg.namespace, module: pkg.module });
    };

    const mapModules = pkg => {
      if (pkg.module.Store) pkg.module.store = pkg.module.Store.create({});
      if (pkg.module.store) stores[pkg.namespace] = pkg.module.store;
      if (pkg.module.reducers)
        reducers[pkg.namespace] = pkg.module.reducers(pkg.module.store);
      if (pkg.module.serverSagas)
        serverSagas[pkg.name] = pkg.module.serverSagas;
    };

    // Add packages to worona-devs.
    coreModules.forEach(addModules);
    pkgModules.forEach(addModules);

    // Load reducers and sagas.
    coreModules.forEach(mapModules);
    pkgModules.forEach(mapModules);

    // Init redux store.
    const store = initStore({ reducer: combineReducers(reducers) });

    // Notify that server is started.
    store.dispatch(buildModule.actions.serverStarted());

    // Add build to the state.
    store.dispatch(
      buildModule.actions.buildUpdated({
        siteId,
        env,
        packages,
        perPage,
        device,
        amp: process.env.MODE === 'amp',
        dev: req.query.dev,
        initialUrl,
      }),
    );

    // Add settings to the state.
    store.dispatch(settingsModule.actions.settingsUpdated({ settings }));

    // Run and wait until all the server sagas have run.
    const params = {
      selectedItem: { type, id, page },
      stores,
    };
    const startSagas = new Date();
    const sagaPromises = Object.values(serverSagas).map(
      saga => store.runSaga(saga, params).done,
    );
    store.dispatch(buildModule.actions.serverSagasInitialized());
    await Promise.all(sagaPromises);
    store.dispatch(
      buildModule.actions.serverFinished({
        timeToRunSagas: new Date() - startSagas,
      }),
    );

    // Generate React SSR.
    const render =
      process.env.MODE === 'amp'
        ? ReactDOM.renderToStaticMarkup
        : ReactDOM.renderToString;
    app = render(
      <App
        store={store}
        core={coreModules.map(({ name, module }) => ({
          name,
          Component: module.default,
        }))}
        packages={Object.values(packages)}
        stores={stores}
      />,
    );

    const { html, ids, css } = extractCritical(app);

    // Get static helmet strings.
    const helmet = Helmet.renderStatic();

    if (process.env.MODE === 'pwa') {
      // Flush chunk names and extract scripts, css and css<->scripts object.
      const chunkNames = flushChunkNames();
      const { cssHashRaw, scripts, stylesheets } = flushChunks(clientStats, {
        chunkNames,
      });

      const publicPath = req.query.static
        ? `${req.query.static.replace(/\/$/g, '')}/static/`
        : '/static/';
      const cssHash = JSON.stringify(
        mapValues(cssHashRaw, cssPath => `${publicPath}${cssPath}`),
      );
      const scriptsWithoutBootstrap = scripts.filter(
        script => !/bootstrap/.test(script),
      );
      const chunksForArray = scriptsWithoutBootstrap
        .map(script => `'${script}'`)
        .join(',');
      const bootstrapFileName = scripts.filter(script =>
        /bootstrap/.test(script),
      );
      const bootstrapString = await readFile(
        `${buildPath}/.build/${process.env.MODE}/client/${bootstrapFileName}`,
        'utf8',
      );
      const preloadScripts = scriptsWithoutBootstrap
        .map(
          script =>
            `<link rel="preload" href="${publicPath}${script}" as="script">`,
        )
        .join('\n');
      const styles = stylesheets
        .map(
          stylesheet =>
            `<link rel="stylesheet" charset="utf-8" type="text/css" href="${publicPath}${stylesheet}" />`,
        )
        .join('\n');

      console.log('URL', req.url);
      console.log('DYNAMIC CHUNK NAMES RENDERED', chunkNames);
      console.log('SCRIPTS SERVED', scripts);
      console.log('STYLESHEETS SERVED', stylesheets);

      res.status(status);
      res.send(
        pwaTemplate({
          helmet,
          css,
          styles,
          preloadScripts,
          html,
          cssHash,
          publicPath,
          ids,
          store,
          chunksForArray,
          bootstrapString,
        }),
      );
    } else if (process.env.MODE === 'amp') {
      console.log('URL', req.url);
      res.status(status);
      res.send(ampTemplate({ helmet, css, html }));
    }
  } catch (error) {
    console.error(error);
    if (dev) {
      const RedBox = require('redbox-react').RedBoxError;
      app = ReactDOM.renderToString(<RedBox error={error} />);
    } else {
      app = `<div>${error.message}</div>`;
    }
    res.status(status === 200 ? 500 : status);
    res.send(
      `<!doctype html>
        <html>
          <head>
            <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
          </head>
          <body>
            ${app}
          </body>
        </html>`,
    );
  }
};
<commit_msg>Injecting redux dispatch to stores as env (not tested)<commit_after>/* eslint-disable no-console, global-require, import/no-dynamic-require */
import { readFile } from 'fs-extra';
import React from 'react';
import { combineReducers } from 'redux';
import ReactDOM from 'react-dom/server';
import { extractCritical } from 'emotion-server';
import { flushChunkNames } from 'react-universal-component/server';
import flushChunks from 'webpack-flush-chunks';
import { mapValues } from 'lodash';
import { addPackage } from 'worona-deps';
import { useStaticRendering } from 'mobx-react';
import { Helmet } from 'react-helmet';
import App from '../components/App';
import initStore from '../store';
import { getSettings } from './settings';
import pwaTemplate from './pwa-template';
import ampTemplate from './amp-template';
import { requireModules } from './requires';
import { parseQuery } from './utils';

const buildModule = require(`../packages/build/${process.env.MODE}`);
const settingsModule = require(`../packages/settings/${process.env.MODE}`);
// const analyticsModule = require(`../packages/analytics/${process.env.MODE}`);
const iframesModule = require(`../packages/iframes/${process.env.MODE}`);
const customCssModule = require(`../packages/customCss/${process.env.MODE}`);

const dev = process.env.NODE_ENV !== 'production';

const { buildPath } = require(`../../.build/${
  process.env.MODE
}/buildInfo.json`);

export default ({ clientStats }) => async (req, res) => {
  let status = 200;
  const {
    siteId,
    perPage,
    initialUrl,
    env,
    device,
    type,
    id,
    page,
  } = parseQuery(req.query);

  // Avoid observables in server.
  useStaticRendering(true);

  let app;
  try {
    if (!siteId) {
      status = 404;
      throw new Error(`'?siteid=' query not found in ${req.originalUrl}`);
    }

    // Get settings.
    const settings = await getSettings({ siteId, env });
    if (!settings) {
      status = 404;
      throw new Error(
        `Settings for ${siteId} not found in the ${env.toUpperCase()} database.`,
      );
    }

    // Define core modules.
    const coreModules = [
      { name: 'build', namespace: 'build', module: buildModule },
      { name: 'settings', namespace: 'settings', module: settingsModule },
      // { name: 'analytics', namespace: 'analytics', module: analyticsModule },
      { name: 'iframes', namespace: 'iframes', module: iframesModule },
      { name: 'customCss', namespace: 'customCss', module: customCssModule },
    ];

    // Extract activated packages array from settings.
    const packages = settings
      ? Object.values(settings)
          .filter(pkg => pkg.woronaInfo.namespace !== 'generalSite')
          .filter(pkg => pkg.woronaInfo.namespace !== 'generalApp')
          .reduce(
            (obj, pkg) => ({
              ...obj,
              [pkg.woronaInfo.namespace]: pkg.woronaInfo.name,
            }),
            {},
          )
      : {};

    // Load the activated modules.
    const pkgModules = await requireModules(Object.entries(packages));

    const stores = {};
    const reducers = {};
    const serverSagas = {};

    const storesEnv = {};

    const addModules = pkg => {
      addPackage({ namespace: pkg.namespace, module: pkg.module });
    };

    const mapModules = pkg => {
      if (pkg.module.Store) pkg.module.store = pkg.module.Store.create({}, storesEnv);
      if (pkg.module.store) stores[pkg.namespace] = pkg.module.store;
      if (pkg.module.reducers)
        reducers[pkg.namespace] = pkg.module.reducers(pkg.module.store);
      if (pkg.module.serverSagas)
        serverSagas[pkg.name] = pkg.module.serverSagas;
    };

    // Add packages to worona-devs.
    coreModules.forEach(addModules);
    pkgModules.forEach(addModules);

    // Load reducers and sagas.
    coreModules.forEach(mapModules);
    pkgModules.forEach(mapModules);

    // Init redux store.
    const store = initStore({ reducer: combineReducers(reducers) });
    storesEnv.dispatch = store.dispatch;

    // Notify that server is started.
    store.dispatch(buildModule.actions.serverStarted());

    // Add build to the state.
    store.dispatch(
      buildModule.actions.buildUpdated({
        siteId,
        env,
        packages,
        perPage,
        device,
        amp: process.env.MODE === 'amp',
        dev: req.query.dev,
        initialUrl,
      }),
    );

    // Add settings to the state.
    store.dispatch(settingsModule.actions.settingsUpdated({ settings }));

    // Run and wait until all the server sagas have run.
    const params = {
      selectedItem: { type, id, page },
      stores,
    };
    const startSagas = new Date();
    const sagaPromises = Object.values(serverSagas).map(
      saga => store.runSaga(saga, params).done,
    );
    store.dispatch(buildModule.actions.serverSagasInitialized());
    await Promise.all(sagaPromises);
    store.dispatch(
      buildModule.actions.serverFinished({
        timeToRunSagas: new Date() - startSagas,
      }),
    );

    // Generate React SSR.
    const render =
      process.env.MODE === 'amp'
        ? ReactDOM.renderToStaticMarkup
        : ReactDOM.renderToString;
    app = render(
      <App
        store={store}
        core={coreModules.map(({ name, module }) => ({
          name,
          Component: module.default,
        }))}
        packages={Object.values(packages)}
        stores={stores}
      />,
    );

    const { html, ids, css } = extractCritical(app);

    // Get static helmet strings.
    const helmet = Helmet.renderStatic();

    if (process.env.MODE === 'pwa') {
      // Flush chunk names and extract scripts, css and css<->scripts object.
      const chunkNames = flushChunkNames();
      const { cssHashRaw, scripts, stylesheets } = flushChunks(clientStats, {
        chunkNames,
      });

      const publicPath = req.query.static
        ? `${req.query.static.replace(/\/$/g, '')}/static/`
        : '/static/';
      const cssHash = JSON.stringify(
        mapValues(cssHashRaw, cssPath => `${publicPath}${cssPath}`),
      );
      const scriptsWithoutBootstrap = scripts.filter(
        script => !/bootstrap/.test(script),
      );
      const chunksForArray = scriptsWithoutBootstrap
        .map(script => `'${script}'`)
        .join(',');
      const bootstrapFileName = scripts.filter(script =>
        /bootstrap/.test(script),
      );
      const bootstrapString = await readFile(
        `${buildPath}/.build/${process.env.MODE}/client/${bootstrapFileName}`,
        'utf8',
      );
      const preloadScripts = scriptsWithoutBootstrap
        .map(
          script =>
            `<link rel="preload" href="${publicPath}${script}" as="script">`,
        )
        .join('\n');
      const styles = stylesheets
        .map(
          stylesheet =>
            `<link rel="stylesheet" charset="utf-8" type="text/css" href="${publicPath}${stylesheet}" />`,
        )
        .join('\n');

      console.log('URL', req.url);
      console.log('DYNAMIC CHUNK NAMES RENDERED', chunkNames);
      console.log('SCRIPTS SERVED', scripts);
      console.log('STYLESHEETS SERVED', stylesheets);

      res.status(status);
      res.send(
        pwaTemplate({
          helmet,
          css,
          styles,
          preloadScripts,
          html,
          cssHash,
          publicPath,
          ids,
          store,
          chunksForArray,
          bootstrapString,
        }),
      );
    } else if (process.env.MODE === 'amp') {
      console.log('URL', req.url);
      res.status(status);
      res.send(ampTemplate({ helmet, css, html }));
    }
  } catch (error) {
    console.error(error);
    if (dev) {
      const RedBox = require('redbox-react').RedBoxError;
      app = ReactDOM.renderToString(<RedBox error={error} />);
    } else {
      app = `<div>${error.message}</div>`;
    }
    res.status(status === 200 ? 500 : status);
    res.send(
      `<!doctype html>
        <html>
          <head>
            <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
          </head>
          <body>
            ${app}
          </body>
        </html>`,
    );
  }
};
<|endoftext|>


<commit_before>goog.provide('ol.source.TiledVector');
goog.provide('ol.source.TiledVectorOptions');

goog.require('goog.array');
goog.require('goog.object');
goog.require('ol.Projection');
goog.require('ol.source.TileSource');
goog.require('ol.tilegrid.TileGrid');

/**
 * @typedef {{features: (ol.Collection|undefined),
 *            extent: (ol.Extent|undefined),
 *            projection: (ol.Projection|undefined),
 *            tileGrid: (ol.tilegrid.TileGrid|undefined)}}
 */
ol.source.TiledVectorOptions;

/**
 * @constructor
 * @extends {ol.Tile}
 * @param {ol.TileCoord} tileCoord Tile coordinate.
 * @param {ol.tilegrid.TileGrid} tileGrid Tile grid.
 * @private
 */
ol.VectorTile_ = function(tileCoord, tileGrid) {

  goog.base(this, tileCoord);

  this.state = ol.TileState.LOADING;

  /**
   * @private
   * @type {!goog.vec.Mat4.Number}
   */
  this.transform_ = goog.vec.Mat4.createNumber();

  /**
   * @private
   * @type {Object.<number, HTMLCanvasElement>}
   * FIXME needs to be cleared when the data changes
   */
  this.canvasByContext_ = {};

  /**
   * @private
   * @type {ol.renderer.canvas.Renderer}
   * FIXME Use a shared renderer and cut out tiles; keep track of rendered items
   */
  this.renderer_ = this.createRenderer_(tileGrid);

};
goog.inherits(ol.VectorTile_, ol.Tile);

/**
 * @private
 * @param {ol.tilegrid.TileGrid} tileGrid tileGrid.
 * @return {ol.renderer.canvas.Renderer} The renderer for this tile.
 */
ol.VectorTile_.prototype.createRenderer_ = function(tileGrid) {
  var tileSize = tileGrid.getTileSize();
  var canvas = /** @type {HTMLCanvasElement} */
      (goog.dom.createElement(goog.dom.TagName.CANVAS));
  canvas.width = tileSize.width;
  canvas.height = tileSize.height;

  var transform = this.transform_;
  var origin = tileGrid.getExtent().getTopLeft();
  var resolution = tileGrid.getResolution(this.tileCoord.z);
  goog.vec.Mat4.makeIdentity(transform);
  goog.vec.Mat4.scale(transform, resolution, resolution, 1);
  goog.vec.Mat4.translate(transform,
      origin.x / resolution, -origin.y / resolution, 0);

  this.canvasByContext_[goog.getUid(canvas.getContext('2d'))] = canvas;

  return new ol.renderer.canvas.Renderer(canvas, transform);
};

/**
 * @param {Array.<ol.geom.Geometry>} geometries Geometries for this tile.
 * @param {Array.<ol.style.LiteralSymbolizer>} symbolizers Symbolizers for the
 *   geometries, in the same order.
 */
ol.VectorTile_.prototype.setContent = function(geometries, symbolizers) {
  this.state = ol.TileState.LOADED;

  var uniqueSymbolizers = [];
  goog.array.removeDuplicates(symbolizers, uniqueSymbolizers);

  var geometriesBySymbolizer = goog.array.bucket(geometries, function(e, i) {
    var index = goog.array.indexOf(uniqueSymbolizers, symbolizers[i]);
    return index === -1 ? undefined : index;
  });

  var buckets,
      renderer = this.renderer_,
      type = {line: 'line', point: 'point', polygon: 'polygon'};
  function sortByGeometryType(geometry) {
    if (geometry instanceof ol.geom.LineString) {
      return type['line'];
    } else if (geometry instanceof ol.geom.Point) {
      return type['point'];
    } else if (geometry instanceof ol.geom.Polygon) {
      return type['polygon'];
    }
  }
  for (var i = 0, ii = uniqueSymbolizers.length; i < ii; ++i) {
    buckets = goog.array.bucket(geometriesBySymbolizer[i], sortByGeometryType);
    renderer.renderLineStrings(buckets[type['line']], uniqueSymbolizers[i]);
    renderer.renderPoints(buckets[type['point']], uniqueSymbolizers[i]);
    renderer.renderPolygons(buckets[type['polygon']], uniqueSymbolizers[i]);
  }
};

/**
 * @inheritDoc
 */
ol.VectorTile_.prototype.getImage = function(opt_context) {
  var key = goog.isDef(opt_context) ? goog.getUid(opt_context) : -1;
  goog.asserts.assert(key in this.canvasByContext_);
  return this.canvasByContext_[key];
};

/**
 * @constructor
 * @extends {ol.source.TileSource}
 * @param {ol.source.TiledVectorOptions} options options.
 */
ol.source.TiledVector = function(options) {

  /**
   * @type {ol.Collection}
   * @private
   */
  this.features_ = goog.isDef(options.features) ?
      options.features : new ol.Collection();

  /**
   * @private
   * @type {Object.<string, ol.VectorTile_>}
   * FIXME will need to expire elements from this cache
   * FIXME will need to invalidate tiles when data changes
   */
  this.tileCache_ = {};

  var projection = ol.Projection.createProjection(
      options.projection, 'EPSG:3857');

  goog.base(this, {
    extent: goog.isDef(options.extent) ?
        options.extent : projection.getExtent(),
    projection: projection,
    tileGrid: goog.isDef(options.tileGrid) ?
        options.tileGrid : ol.tilegrid.createForProjection(projection)
  });

};
goog.inherits(ol.source.TiledVector, ol.source.TileSource);

/**
 * @return {ol.Collection} This layer's features.
 */
ol.source.TiledVector.prototype.getFeatures = function() {
  return this.features_;
};

/**
 * @inheritDoc
 */
ol.source.TiledVector.prototype.getTile = function(tileCoord) {
  var extent = this.tileGrid.getTileCoordExtent(tileCoord);
  var key = tileCoord.toString();
  if (goog.object.containsKey(this.tileCache_, key)) {
    return this.tileCache_[key];
  } else {
    var tile = new ol.VectorTile_(tileCoord, this.tileGrid),
        geometries = [],
        symbolizers = [];
    // FIXME only pass geometries for tile extent
    this.features_.forEach(function(feature) {
      geometries.push(feature.getGeometry());
      symbolizers.push(feature.getSymbolizer());
    });
    tile.setContent(geometries, symbolizers);

    this.tileCache_[key] = tile;
    return tile;
  }
};
<commit_msg>Cache and bucket fixes<commit_after>goog.provide('ol.source.TiledVector');
goog.provide('ol.source.TiledVectorOptions');

goog.require('goog.array');
goog.require('goog.object');
goog.require('ol.Projection');
goog.require('ol.source.TileSource');
goog.require('ol.tilegrid.TileGrid');

/**
 * @typedef {{features: (ol.Collection|undefined),
 *            extent: (ol.Extent|undefined),
 *            projection: (ol.Projection|undefined),
 *            tileGrid: (ol.tilegrid.TileGrid|undefined)}}
 */
ol.source.TiledVectorOptions;

/**
 * @constructor
 * @extends {ol.Tile}
 * @param {ol.TileCoord} tileCoord Tile coordinate.
 * @param {ol.tilegrid.TileGrid} tileGrid Tile grid.
 * @private
 */
ol.VectorTile_ = function(tileCoord, tileGrid) {

  goog.base(this, tileCoord);

  this.state = ol.TileState.LOADING;

  /**
   * @private
   * @type {!goog.vec.Mat4.Number}
   */
  this.transform_ = goog.vec.Mat4.createNumber();

  /**
   * @private
   * @type {Object.<number, HTMLCanvasElement>}
   */
  this.canvasByContext_ = {};

  /**
   * @private
   * @type {HTMLCanvasElement>}
   */
  this.canvas_;

  /**
   * @private
   * @type {ol.renderer.canvas.Renderer}
   * FIXME Use a shared renderer and cut out tiles; keep track of rendered items
   */
  this.renderer_ = this.createRenderer_(tileGrid);

};
goog.inherits(ol.VectorTile_, ol.Tile);

/**
 * @private
 * @param {ol.tilegrid.TileGrid} tileGrid tileGrid.
 * @return {ol.renderer.canvas.Renderer} The renderer for this tile.
 */
ol.VectorTile_.prototype.createRenderer_ = function(tileGrid) {
  var tileSize = tileGrid.getTileSize();
  var canvas = /** @type {HTMLCanvasElement} */
      (goog.dom.createElement(goog.dom.TagName.CANVAS));
  canvas.width = tileSize.width;
  canvas.height = tileSize.height;

  var transform = this.transform_;
  var origin = tileGrid.getOrigin(this.tileCoord.z);
  var resolution = tileGrid.getResolution(this.tileCoord.z);
  goog.vec.Mat4.makeIdentity(transform);
  goog.vec.Mat4.scale(transform, resolution, resolution, 1);
  goog.vec.Mat4.translate(transform,
      origin.x / resolution, -origin.y / resolution, 0);

  this.canvasByContext_[goog.getUid(canvas.getContext('2d'))] = canvas;
  this.canvas_ = canvas;

  return new ol.renderer.canvas.Renderer(canvas, transform);
};

/**
 * @param {Array.<ol.geom.Geometry>} geometries Geometries for this tile.
 * @param {Array.<ol.style.LiteralSymbolizer>} symbolizers Symbolizers for the
 *   geometries, in the same order.
 */
ol.VectorTile_.prototype.setContent = function(geometries, symbolizers) {
  this.state = ol.TileState.LOADED;

  var uniqueSymbolizers = [];
  goog.array.removeDuplicates(symbolizers, uniqueSymbolizers);

  var geometriesBySymbolizer = goog.array.bucket(geometries, function(e, i) {
    var index = goog.array.indexOf(uniqueSymbolizers, symbolizers[i]);
    return index === -1 ? undefined : index;
  });

  var buckets,
      renderer = this.renderer_,
      type = {line: 'line', point: 'point', polygon: 'polygon'};
  function sortByGeometryType(geometry) {
    if (geometry instanceof ol.geom.LineString) {
      return type['line'];
    } else if (geometry instanceof ol.geom.Point) {
      return type['point'];
    } else if (geometry instanceof ol.geom.Polygon) {
      return type['polygon'];
    }
  }
  for (var i = 0, ii = uniqueSymbolizers.length; i < ii; ++i) {
    buckets = goog.array.bucket(geometriesBySymbolizer[i], sortByGeometryType);
    if (goog.object.containsKey(buckets, type['line'])) {
      renderer.renderLineStrings(buckets[type['line']], uniqueSymbolizers[i]);
    }
    if (goog.object.containsKey(buckets, type['point'])) {
      renderer.renderPoints(buckets[type['point']], uniqueSymbolizers[i]);
    }
    if (goog.object.containsKey(buckets, type['polygon'])) {
      renderer.renderPolygons(buckets[type['polygon']], uniqueSymbolizers[i]);
    }
  }
};

/**
 * @inheritDoc
 */
ol.VectorTile_.prototype.getImage = function(opt_context) {
  var key = goog.isDef(opt_context) ? goog.getUid(opt_context) : -1;
  if (key in this.canvasByContext_) {
    return this.canvasByContext_[key];
  } else {
    this.canvasByContext_[key] = this.canvas_;
    return this.canvas_;
  }
};

/**
 * @constructor
 * @extends {ol.source.TileSource}
 * @param {ol.source.TiledVectorOptions} options options.
 */
ol.source.TiledVector = function(options) {

  /**
   * @type {ol.Collection}
   * @private
   */
  this.features_ = goog.isDef(options.features) ?
      options.features : new ol.Collection();

  /**
   * @private
   * @type {Object.<string, ol.VectorTile_>}
   * FIXME will need to expire elements from this cache
   * FIXME will need to invalidate tiles when data changes
   */
  this.tileCache_ = {};

  var projection = ol.Projection.createProjection(
      options.projection, 'EPSG:3857');

  goog.base(this, {
    extent: goog.isDef(options.extent) ?
        options.extent : projection.getExtent(),
    projection: projection,
    tileGrid: goog.isDef(options.tileGrid) ?
        options.tileGrid : ol.tilegrid.createForProjection(projection)
  });

};
goog.inherits(ol.source.TiledVector, ol.source.TileSource);

/**
 * @return {ol.Collection} This layer's features.
 */
ol.source.TiledVector.prototype.getFeatures = function() {
  return this.features_;
};

/**
 * @inheritDoc
 */
ol.source.TiledVector.prototype.getTile = function(tileCoord) {
  var extent = this.tileGrid.getTileCoordExtent(tileCoord);
  var key = tileCoord.toString();
  if (goog.object.containsKey(this.tileCache_, key)) {
    return this.tileCache_[key];
  } else {
    var tile = new ol.VectorTile_(tileCoord, this.tileGrid),
        geometries = [],
        symbolizers = [];
    // FIXME only pass geometries for tile extent
    this.features_.forEach(function(feature) {
      geometries.push(feature.getGeometry());
      symbolizers.push(feature.getSymbolizer());
    });
    tile.setContent(geometries, symbolizers);

    this.tileCache_[key] = tile;
    return tile;
  }
};
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2019 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @fileoverview Utility methods for colour manipulation.
 * These methods are not specific to Blockly, and could be factored out into
 * a JavaScript framework such as Closure.
 * @author fraser@google.com (Neil Fraser)
 */
'use strict';

/**
 * @name Blockly.utils.colour
 * @namespace
 */
goog.provide('Blockly.utils.colour');

goog.require('Blockly.utils');

/**
 * Parses a colour from a string.
 * .parse('red') -> '#ff0000'
 * .parse('#f00') -> '#ff0000'
 * .parse('#ff0000') -> '#ff0000'
 * .parse('0xff0000') -> '#ff0000'
 * .parse('rgb(255, 0, 0)') -> '#ff0000'
 * @param {string} str Colour in some CSS format.
 * @return {string|null} A string containing a hex representation of the colour,
 *   or null if can't be parsed.
 */
Blockly.utils.colour.parse = function(str) {
  str = String(str).toLowerCase().trim();
  var hex = Blockly.utils.colour.names[str];
  if (hex) {
    // e.g. 'red'
    return hex;
  }
  hex = str.substring(0, 2) == '0x' ? '#' + str.substring(2) : str;
  hex = hex[0] == '#' ? hex : '#' + hex;
  if (/^#[0-9a-f]{6}$/.test(hex)) {
    // e.g. '#00ff88'
    return hex;
  }
  if (/^#[0-9a-f]{3}$/.test(hex)) {
    // e.g. '#0f8'
    return ['#', hex[1], hex[1], hex[2], hex[2], hex[3], hex[3]].join('');
  }
  var rgb = str.match(/^(?:rgb)?\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/);
  if (rgb) {
    // e.g. 'rgb(0, 128, 255)'
    var r = Number(rgb[1]);
    var g = Number(rgb[2]);
    var b = Number(rgb[3]);
    if (r >= 0 && r < 256 && g >= 0 && g < 256 && b >= 0 && b < 256) {
      return Blockly.utils.colour.rgbToHex(r, g, b);
    }
  }
  return null;
};

/**
 * Converts a colour from RGB to hex representation.
 * @param {number} r Amount of red, int between 0 and 255.
 * @param {number} g Amount of green, int between 0 and 255.
 * @param {number} b Amount of blue, int between 0 and 255.
 * @return {string} Hex representation of the colour.
 */
Blockly.utils.colour.rgbToHex = function(r, g, b) {
  var rgb = (r << 16) | (g << 8) | b;
  if (r < 0x10) {
    return '#' + (0x1000000 | rgb).toString(16).substr(1);
  }
  return '#' + rgb.toString(16);
};

/**
 * Converts a hex representation of a colour to RGB.
 * @param {string} hexColour Colour in '#ff0000' format.
 * @return {!Array.<number>} RGB representation of the colour.
 */
Blockly.utils.colour.hexToRgb = function(hexColour) {
  var hex = Blockly.utils.colour.parse(hexColour);
  if (!hex) {
    return [0, 0, 0];
  }

  var rgb = parseInt(hex.substr(1), 16);
  var r = rgb >> 16;
  var g = (rgb >> 8) & 255;
  var b = rgb & 255;

  return [r, g, b];
};

/**
 * Converts an HSV triplet to hex representation.
 * @param {number} h Hue value in [0, 360].
 * @param {number} s Saturation value in [0, 1].
 * @param {number} v Brightness in [0, 255].
 * @return {string} Hex representation of the colour.
 */
Blockly.utils.colour.hsvToHex = function(h, s, v) {
  var red = 0;
  var green = 0;
  var blue = 0;
  if (s == 0) {
    red = v;
    green = v;
    blue = v;
  } else {
    var sextant = Math.floor(h / 60);
    var remainder = (h / 60) - sextant;
    var val1 = v * (1 - s);
    var val2 = v * (1 - (s * remainder));
    var val3 = v * (1 - (s * (1 - remainder)));
    switch (sextant) {
      case 1:
        red = val2;
        green = v;
        blue = val1;
        break;
      case 2:
        red = val1;
        green = v;
        blue = val3;
        break;
      case 3:
        red = val1;
        green = val2;
        blue = v;
        break;
      case 4:
        red = val3;
        green = val1;
        blue = v;
        break;
      case 5:
        red = v;
        green = val1;
        blue = val2;
        break;
      case 6:
      case 0:
        red = v;
        green = val3;
        blue = val1;
        break;
    }
  }
  return Blockly.utils.colour.rgbToHex(
      Math.floor(red), Math.floor(green), Math.floor(blue));
};

/**
 * Blend two colours together, using the specified factor to indicate the
 * weight given to the first colour.
 * @param {string} colour1 First colour.
 * @param {string} colour2 Second colour.
 * @param {number} factor The weight to be given to colour1 over colour2.
 *     Values should be in the range [0, 1].
 * @return {string} Combined colour represented in hex.
 */
Blockly.utils.colour.blend = function(colour1, colour2, factor) {
  var rgb1 = Blockly.utils.colour.hexToRgb(Blockly.utils.colour.parse(colour1));
  var rgb2 = Blockly.utils.colour.hexToRgb(Blockly.utils.colour.parse(colour2));
  var r = Math.round(rgb2[0] + factor * (rgb1[0] - rgb2[0]));
  var g = Math.round(rgb2[1] + factor * (rgb1[1] - rgb2[1]));
  var b = Math.round(rgb2[2] + factor * (rgb1[2] - rgb2[2]));
  return Blockly.utils.colour.rgbToHex(r, g, b);
};

/**
 * A map that contains the 16 basic colour keywords as defined by W3C:
 * https://www.w3.org/TR/2018/REC-css-color-3-20180619/#html4
 * The keys of this map are the lowercase "readable" names of the colours,
 * while the values are the "hex" values.
 *
 * @type {!Object<string, string>}
 */
Blockly.utils.colour.names = {
  'aqua': '#00ffff',
  'black': '#000000',
  'blue': '#0000ff',
  'fuchsia': '#ff00ff',
  'gray': '#808080',
  'green': '#008000',
  'lime': '#00ff00',
  'maroon': '#800000',
  'navy': '#000080',
  'olive': '#808000',
  'purple': '#800080',
  'red': '#ff0000',
  'silver': '#c0c0c0',
  'teal': '#008080',
  'white': '#ffffff',
  'yellow': '#ffff00'
};
<commit_msg>Update jsdoc and parameter to reflect additional colour parsing<commit_after>/**
 * @license
 * Copyright 2019 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @fileoverview Utility methods for colour manipulation.
 * These methods are not specific to Blockly, and could be factored out into
 * a JavaScript framework such as Closure.
 * @author fraser@google.com (Neil Fraser)
 */
'use strict';

/**
 * @name Blockly.utils.colour
 * @namespace
 */
goog.provide('Blockly.utils.colour');

goog.require('Blockly.utils');

/**
 * Parses a colour from a string.
 * .parse('red') -> '#ff0000'
 * .parse('#f00') -> '#ff0000'
 * .parse('#ff0000') -> '#ff0000'
 * .parse('0xff0000') -> '#ff0000'
 * .parse('rgb(255, 0, 0)') -> '#ff0000'
 * @param {string} str Colour in some CSS format.
 * @return {string|null} A string containing a hex representation of the colour,
 *   or null if can't be parsed.
 */
Blockly.utils.colour.parse = function(str) {
  str = String(str).toLowerCase().trim();
  var hex = Blockly.utils.colour.names[str];
  if (hex) {
    // e.g. 'red'
    return hex;
  }
  hex = str.substring(0, 2) == '0x' ? '#' + str.substring(2) : str;
  hex = hex[0] == '#' ? hex : '#' + hex;
  if (/^#[0-9a-f]{6}$/.test(hex)) {
    // e.g. '#00ff88'
    return hex;
  }
  if (/^#[0-9a-f]{3}$/.test(hex)) {
    // e.g. '#0f8'
    return ['#', hex[1], hex[1], hex[2], hex[2], hex[3], hex[3]].join('');
  }
  var rgb = str.match(/^(?:rgb)?\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/);
  if (rgb) {
    // e.g. 'rgb(0, 128, 255)'
    var r = Number(rgb[1]);
    var g = Number(rgb[2]);
    var b = Number(rgb[3]);
    if (r >= 0 && r < 256 && g >= 0 && g < 256 && b >= 0 && b < 256) {
      return Blockly.utils.colour.rgbToHex(r, g, b);
    }
  }
  return null;
};

/**
 * Converts a colour from RGB to hex representation.
 * @param {number} r Amount of red, int between 0 and 255.
 * @param {number} g Amount of green, int between 0 and 255.
 * @param {number} b Amount of blue, int between 0 and 255.
 * @return {string} Hex representation of the colour.
 */
Blockly.utils.colour.rgbToHex = function(r, g, b) {
  var rgb = (r << 16) | (g << 8) | b;
  if (r < 0x10) {
    return '#' + (0x1000000 | rgb).toString(16).substr(1);
  }
  return '#' + rgb.toString(16);
};

/**
 * Converts a colour to RGB.
 * @param {string} colour String representing colour in any
 *     colour format ('#ff0000', 'red', '0xff000', etc).
 * @return {!Array.<number>} RGB representation of the colour.
 */
Blockly.utils.colour.hexToRgb = function(colour) {
  var hex = Blockly.utils.colour.parse(colour);
  if (!hex) {
    return [0, 0, 0];
  }

  var rgb = parseInt(hex.substr(1), 16);
  var r = rgb >> 16;
  var g = (rgb >> 8) & 255;
  var b = rgb & 255;

  return [r, g, b];
};

/**
 * Converts an HSV triplet to hex representation.
 * @param {number} h Hue value in [0, 360].
 * @param {number} s Saturation value in [0, 1].
 * @param {number} v Brightness in [0, 255].
 * @return {string} Hex representation of the colour.
 */
Blockly.utils.colour.hsvToHex = function(h, s, v) {
  var red = 0;
  var green = 0;
  var blue = 0;
  if (s == 0) {
    red = v;
    green = v;
    blue = v;
  } else {
    var sextant = Math.floor(h / 60);
    var remainder = (h / 60) - sextant;
    var val1 = v * (1 - s);
    var val2 = v * (1 - (s * remainder));
    var val3 = v * (1 - (s * (1 - remainder)));
    switch (sextant) {
      case 1:
        red = val2;
        green = v;
        blue = val1;
        break;
      case 2:
        red = val1;
        green = v;
        blue = val3;
        break;
      case 3:
        red = val1;
        green = val2;
        blue = v;
        break;
      case 4:
        red = val3;
        green = val1;
        blue = v;
        break;
      case 5:
        red = v;
        green = val1;
        blue = val2;
        break;
      case 6:
      case 0:
        red = v;
        green = val3;
        blue = val1;
        break;
    }
  }
  return Blockly.utils.colour.rgbToHex(
      Math.floor(red), Math.floor(green), Math.floor(blue));
};

/**
 * Blend two colours together, using the specified factor to indicate the
 * weight given to the first colour.
 * @param {string} colour1 First colour.
 * @param {string} colour2 Second colour.
 * @param {number} factor The weight to be given to colour1 over colour2.
 *     Values should be in the range [0, 1].
 * @return {string} Combined colour represented in hex.
 */
Blockly.utils.colour.blend = function(colour1, colour2, factor) {
  var rgb1 = Blockly.utils.colour.hexToRgb(Blockly.utils.colour.parse(colour1));
  var rgb2 = Blockly.utils.colour.hexToRgb(Blockly.utils.colour.parse(colour2));
  var r = Math.round(rgb2[0] + factor * (rgb1[0] - rgb2[0]));
  var g = Math.round(rgb2[1] + factor * (rgb1[1] - rgb2[1]));
  var b = Math.round(rgb2[2] + factor * (rgb1[2] - rgb2[2]));
  return Blockly.utils.colour.rgbToHex(r, g, b);
};

/**
 * A map that contains the 16 basic colour keywords as defined by W3C:
 * https://www.w3.org/TR/2018/REC-css-color-3-20180619/#html4
 * The keys of this map are the lowercase "readable" names of the colours,
 * while the values are the "hex" values.
 *
 * @type {!Object<string, string>}
 */
Blockly.utils.colour.names = {
  'aqua': '#00ffff',
  'black': '#000000',
  'blue': '#0000ff',
  'fuchsia': '#ff00ff',
  'gray': '#808080',
  'green': '#008000',
  'lime': '#00ff00',
  'maroon': '#800000',
  'navy': '#000080',
  'olive': '#808000',
  'purple': '#800080',
  'red': '#ff0000',
  'silver': '#c0c0c0',
  'teal': '#008080',
  'white': '#ffffff',
  'yellow': '#ffff00'
};
<|endoftext|>


<commit_before>/**
@license
Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/

import * as utils from './utils.js';
import {NodePatches} from './patches/Node.js';
import {OutsideDescriptors, InsideDescriptors} from './patch-instances.js';
import {ParentNodePatches} from './patches/ParentNode.js';
import {DocumentOrFragmentPatches} from './patches/DocumentOrFragment.js';
import {DocumentOrShadowRootPatches} from './patches/DocumentOrShadowRoot.js';
import {ElementOrShadowRootPatches} from './patches/ElementOrShadowRoot.js';
import {ShadowRootPatches} from './patches/ShadowRoot.js';

const patchShadyAccessors = (proto, prefix) => {
  utils.patchProperties(proto,
    utils.getOwnPropertyDescriptors(ShadowRootPatches), true, prefix);
  utils.patchProperties(proto,
    utils.getOwnPropertyDescriptors(DocumentOrShadowRootPatches), true, prefix);
  utils.patchProperties(proto,
    utils.getOwnPropertyDescriptors(ElementOrShadowRootPatches), true, prefix);
  // we ensure ParentNode accessors since these do not exist in Edge/IE on DocumentFragments
  utils.patchProperties(proto,
    utils.getOwnPropertyDescriptors(ParentNodePatches), true, prefix);
  // Ensure `shadowRoot` has basic descriptors when we cannot rely
  // on them coming from DocumentFragment.
  // noPatching case: ensure all Node descriptors are on ShadowRoot
  if (utils.settings.noPatch) {
    utils.patchProperties(proto,
      utils.getOwnPropertyDescriptors(NodePatches), true, prefix);
    utils.patchProperties(proto,
      utils.getOwnPropertyDescriptors(DocumentOrFragmentPatches), true, prefix);
  // bad descriptors case: ensure only accessors are on ShadowRoot.
  } else if (!utils.settings.hasDescriptors) {
    utils.patchProperties(proto, OutsideDescriptors, true);
    utils.patchProperties(proto, InsideDescriptors, true);
  }
}

export const patchShadyRoot = (proto) => {
  proto.__proto__ = DocumentFragment.prototype;

  // patch both prefixed and not, even when noPatch == true.
  patchShadyAccessors(proto, utils.SHADY_PREFIX);
  patchShadyAccessors(proto);

  // Ensure native properties are all safely wrapped since ShadowRoot is not an
  // actual DocumentFragment instance.
  Object.defineProperties(proto, {
    nodeType: {
      value: Node.DOCUMENT_FRAGMENT_NODE,
      configurable: true
    },
    nodeName: {
      value: '#document-fragment',
      configurable: true
    },
    nodeValue: {
      value: null,
      configurable: true
    }
  });

  // make undefined
  [
    'localName',
    'namespaceURI',
    'prefix'
  ].forEach((prop) => {
    Object.defineProperty(proto, prop, {
      value: undefined,
      configurable: true
    });
  });

  // defer properties to host
  [
    'ownerDocument',
    'baseURI',
    'isConnected'
  ].forEach((prop) => {
    Object.defineProperty(proto, prop, {
      get() {
        return this.host[prop];
      },
      configurable: true
    });
  });
}<commit_msg>Update comments aobut how/when to patch properties of ShadyRoot<commit_after>/**
@license
Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/

import * as utils from './utils.js';
import {NodePatches} from './patches/Node.js';
import {OutsideDescriptors, InsideDescriptors} from './patch-instances.js';
import {ParentNodePatches} from './patches/ParentNode.js';
import {DocumentOrFragmentPatches} from './patches/DocumentOrFragment.js';
import {DocumentOrShadowRootPatches} from './patches/DocumentOrShadowRoot.js';
import {ElementOrShadowRootPatches} from './patches/ElementOrShadowRoot.js';
import {ShadowRootPatches} from './patches/ShadowRoot.js';

const patchShadyAccessors = (proto, prefix) => {
  utils.patchProperties(proto,
    utils.getOwnPropertyDescriptors(ShadowRootPatches), true, prefix);
  utils.patchProperties(proto,
    utils.getOwnPropertyDescriptors(DocumentOrShadowRootPatches), true, prefix);
  utils.patchProperties(proto,
    utils.getOwnPropertyDescriptors(ElementOrShadowRootPatches), true, prefix);
  // We ensure ParentNode accessors since these do not exist in Edge/IE on DocumentFragments.
  utils.patchProperties(proto,
    utils.getOwnPropertyDescriptors(ParentNodePatches), true, prefix);
  // Ensure `shadowRoot` has basic descriptors when we cannot rely
  // on them coming from DocumentFragment.
  //
  // Case 1, noPatching: Because we want noPatch ShadyRoots to have native property
  // names so that they do not have to be wrapped...
  // When we do *not* patch Node/DocumentFragment.prototype
  // we must manually install those properties on ShadyRoot's prototype.
  // Note, it's important to only install these in this mode so as not to stomp
  // over CustomElements polyfill's patches on Node/DocumentFragment methods.
  if (utils.settings.noPatch && !prefix) {
    utils.patchProperties(proto,
      utils.getOwnPropertyDescriptors(NodePatches), true, prefix);
    utils.patchProperties(proto,
      utils.getOwnPropertyDescriptors(DocumentOrFragmentPatches), true, prefix);
  // Case 2, bad descriptors: Ensure accessors are on ShadowRoot.
  // These descriptors are normally used for instance patching but because
  // ShadyRoot can always be patched, just do it to the prototype.
  } else if (!utils.settings.hasDescriptors) {
    utils.patchProperties(proto, OutsideDescriptors, true);
    utils.patchProperties(proto, InsideDescriptors, true);
  }
}

export const patchShadyRoot = (proto) => {
  proto.__proto__ = DocumentFragment.prototype;

  // patch both prefixed and not, even when noPatch == true.
  patchShadyAccessors(proto, utils.SHADY_PREFIX);
  patchShadyAccessors(proto);

  // Ensure native properties are all safely wrapped since ShadowRoot is not an
  // actual DocumentFragment instance.
  Object.defineProperties(proto, {
    nodeType: {
      value: Node.DOCUMENT_FRAGMENT_NODE,
      configurable: true
    },
    nodeName: {
      value: '#document-fragment',
      configurable: true
    },
    nodeValue: {
      value: null,
      configurable: true
    }
  });

  // make undefined
  [
    'localName',
    'namespaceURI',
    'prefix'
  ].forEach((prop) => {
    Object.defineProperty(proto, prop, {
      value: undefined,
      configurable: true
    });
  });

  // defer properties to host
  [
    'ownerDocument',
    'baseURI',
    'isConnected'
  ].forEach((prop) => {
    Object.defineProperty(proto, prop, {
      get() {
        return this.host[prop];
      },
      configurable: true
    });
  });
}<|endoftext|>


<commit_before>/*
 * Paper.js
 *
 * This file is part of Paper.js, a JavaScript Vector Graphics Library,
 * based on Scriptographer.org and designed to be largely API compatible.
 * http://paperjs.org/
 * http://scriptographer.org/
 *
 * Distributed under the MIT license. See LICENSE file for details.
 *
 * Copyright (c) 2011, Juerg Lehni & Jonathan Puckey
 * http://lehni.org/ & http://jonathanpuckey.com/
 *
 * All rights reserved.
 */

// An Algorithm for Automatically Fitting Digitized Curves
// by Philip J. Schneider
// from "Graphics Gems", Academic Press, 1990
var PathFitter = Base.extend({
	initialize: function(path, error) {
		this.points = [];
		var segments = path._segments,
			prev;
		for (var i = 0, l = segments.length; i < l; i++) {
			var point = segments[i].point.clone();
			if (!prev || !prev.equals(point)) {
				this.points[i] = point;
				prev = point;
			}
		}
		this.error = error;
	},

	fit: function() {
		this.segments = [new Segment(this.points[0])];
		this.fitCubic(0, this.points.length - 1,
				// Left Tangent
				this.points[1].subtract(this.points[0]).normalize(),
				// Right Tangent
				this.points[this.points.length - 2].subtract(
					this.points[this.points.length - 1]).normalize());
		return this.segments;
	},

	// Fit a Bezier curve to a (sub)set of digitized points
	fitCubic: function(first, last, tan1, tan2) {
		//	Use heuristic if region only has two points in it
		if (last - first == 1) {
			var pt1 = this.points[first],
				pt2 = this.points[last],
				dist = pt1.getDistance(pt2) / 3;
			this.addCurve([pt1, pt1.add(tan1.normalize(dist)),
					pt2.add(tan2.normalize(dist)), pt2]);
			return;
		}
		// Parameterize points, and attempt to fit curve
		var uPrime = this.chordLengthParameterize(first, last),
			maxError = Math.max(this.error, this.error * this.error),
			error,
			split;
		// Try 4 iterations
		for (var i = 0; i <= 4; i++) {
			var curve = this.generateBezier(first, last, uPrime, tan1, tan2);
			//	Find max deviation of points to fitted curve
			var max = this.findMaxError(first, last, curve, uPrime);
			if (max.error < this.error) {
				this.addCurve(curve);
				return;
			}
			split = max.index;
			// If error not too large, try reparameterization and iteration
			if (max.error >= maxError)
				break;
			this.reparameterize(first, last, uPrime, curve);
			maxError = max.error;
		}
		// Fitting failed -- split at max error point and fit recursively
		var V1 = this.points[split - 1].subtract(this.points[split]),
			V2 = this.points[split].subtract(this.points[split + 1]),
			tanCenter = V1.add(V2).divide(2).normalize();
		this.fitCubic(first, split, tan1, tanCenter);
		this.fitCubic(split, last, tanCenter.negate(), tan2);
	},

	addCurve: function(curve) {
		var prev = this.segments[this.segments.length - 1];
		prev.setHandleOut(curve[1].subtract(curve[0]));
		this.segments.push(
				new Segment(curve[3], curve[2].subtract(curve[3])));
	},

	// Use least-squares method to find Bezier control points for region.
	generateBezier: function(first, last, uPrime, tan1, tan2) {
		var epsilon = Numerical.TOLERANCE,
			pt1 = this.points[first],
			pt2 = this.points[last],
			// Create the C and X matrices
		 	C = [[0, 0], [0, 0]],
			X = [0, 0];

		for (var i = 0, l = last - first + 1; i < l; i++) {
			var u = uPrime[i],
				t = 1 - u,
				b = 3 * u * t,
				b0 = t * t * t,
				b1 = b * t,
				b2 = b * u,
				b3 = u * u * u,
				a1 = tan1.normalize(b1),
				a2 = tan2.normalize(b2),
				tmp = this.points[first + i]
					.subtract(pt1.multiply(b0 + b1))
					.subtract(pt2.multiply(b2 + b3));
			C[0][0] += a1.dot(a1);
			C[0][1] += a1.dot(a2);
			// C[1][0] += a1.dot(a2);
			C[1][0] = C[0][1];
			C[1][1] += a2.dot(a2);
			X[0] += a1.dot(tmp);
			X[1] += a2.dot(tmp);
		}

		// Compute the determinants of C and X
		var detC0C1 = C[0][0] * C[1][1] - C[1][0] * C[0][1],
			alpha1, alpha2;
		if (Math.abs(detC0C1) > epsilon) {
			// Kramer's rule
			var detC0X  = C[0][0] * X[1]    - C[1][0] * X[0],
				detXC1  = X[0]    * C[1][1] - X[1]    * C[0][1];
			// Derive alpha values
			alpha1 = detXC1 / detC0C1;
			alpha2 = detC0X / detC0C1;
		} else {
			// Matrix is under-determined, try assuming alpha1 == alpha2
			var c0 = C[0][0] + C[0][1],
				c1 = C[1][0] + C[1][1];
			if (Math.abs(c0) > epsilon) {
				alpha1 = alpha2 = X[0] / c0;
			} else if (Math.abs(c0) > epsilon) {
				alpha1 = alpha2 = X[1] / c1;
			} else {
				// Handle below
				alpha1 = alpha2 = 0.;
			}
		}

		// If alpha negative, use the Wu/Barsky heuristic (see text)
		// (if alpha is 0, you get coincident control points that lead to
		// divide by zero in any subsequent NewtonRaphsonRootFind() call.
		var segLength = pt2.getDistance(pt1);
		epsilon *= segLength;
		if (alpha1 < epsilon || alpha2 < epsilon) {
			// fall back on standard (probably inaccurate) formula,
			// and subdivide further if needed.
			alpha1 = alpha2 = segLength / 3;
		}

		// First and last control points of the Bezier curve are
		// positioned exactly at the first and last data points
		// Control points 1 and 2 are positioned an alpha distance out
		// on the tangent vectors, left and right, respectively
		return [pt1, pt1.add(tan1.normalize(alpha1)),
				pt2.add(tan2.normalize(alpha2)), pt2];
	},

	// Given set of points and their parameterization, try to find
	// a better parameterization.
	reparameterize: function(first, last, u, curve) {
		for (var i = first; i <= last; i++) {
			u[i - first] = this.findRoot(curve, this.points[i], u[i - first]);
		}
	},

	// Use Newton-Raphson iteration to find better root.
	findRoot: function(curve, point, u) {
		var curve1 = [],
			curve2 = [];
		// Generate control vertices for Q'
		for (var i = 0; i <= 2; i++) {
			curve1[i] = curve[i + 1].subtract(curve[i]).multiply(3);
		}
		// Generate control vertices for Q''
		for (var i = 0; i <= 1; i++) {
			curve2[i] = curve1[i + 1].subtract(curve1[i]).multiply(2);
		}
		// Compute Q(u), Q'(u) and Q''(u)
		var pt = this.evaluate(3, curve, u),
		 	pt1 = this.evaluate(2, curve1, u),
		 	pt2 = this.evaluate(1, curve2, u),
		 	diff = pt.subtract(point),
			df = pt1.dot(pt1) + diff.dot(pt2);
		// Compute f(u) / f'(u)
		if (Math.abs(df) < Numerical.TOLERANCE)
			return u;
		// u = u - f(u) / f'(u)
		return u - pt.dot(pt1) / df;
	},

	// Evaluate a Bezier curve at a particular parameter value
	evaluate: function(degree, curve, t) {
		// Copy array
		var tmp = curve.slice();
		// Triangle computation
		for (var i = 1; i <= degree; i++) {
			for (var j = 0; j <= degree - i; j++) {
				tmp[j] = tmp[j].multiply(1 - t).add(tmp[j + 1].multiply(t));
			}
		}
		return tmp[0];
	},

	// Assign parameter values to digitized points
	// using relative distances between points.
	chordLengthParameterize: function(first, last) {
		var u = [0];
		for (var i = first + 1; i <= last; i++) {
			u[i - first] = u[i - first - 1]
					+ this.points[i].getDistance(this.points[i - 1]);
		}
		for (var i = 1, m = last - first; i <= m; i++) {
			u[i] /= u[m];
		}
		return u;
	},

	// Find the maximum squared distance of digitized points to fitted curve.
	findMaxError: function(first, last, curve, u) {
		var index = Math.floor((last - first + 1) / 2),
			maxDist = 0;
		for (var i = first + 1; i < last; i++) {
			var P = this.evaluate(3, curve, u[i - first]);
			var v = P.subtract(this.points[i]);
			var dist = v.x * v.x + v.y * v.y; // squared
			if (dist >= maxDist) {
				maxDist = dist;
				index = i;
			}
		}
		return {
			error: maxDist,
			index: index
		};
	}
});
<commit_msg>Add comments.<commit_after>/*
 * Paper.js
 *
 * This file is part of Paper.js, a JavaScript Vector Graphics Library,
 * based on Scriptographer.org and designed to be largely API compatible.
 * http://paperjs.org/
 * http://scriptographer.org/
 *
 * Distributed under the MIT license. See LICENSE file for details.
 *
 * Copyright (c) 2011, Juerg Lehni & Jonathan Puckey
 * http://lehni.org/ & http://jonathanpuckey.com/
 *
 * All rights reserved.
 */

// An Algorithm for Automatically Fitting Digitized Curves
// by Philip J. Schneider
// from "Graphics Gems", Academic Press, 1990
var PathFitter = Base.extend({
	initialize: function(path, error) {
		this.points = [];
		var segments = path._segments,
			prev;
		// Copy over points from path and filter out adjacent duplicates.
		for (var i = 0, l = segments.length; i < l; i++) {
			var point = segments[i].point.clone();
			if (!prev || !prev.equals(point)) {
				this.points[i] = point;
				prev = point;
			}
		}
		this.error = error;
	},

	fit: function() {
		this.segments = [new Segment(this.points[0])];
		this.fitCubic(0, this.points.length - 1,
				// Left Tangent
				this.points[1].subtract(this.points[0]).normalize(),
				// Right Tangent
				this.points[this.points.length - 2].subtract(
					this.points[this.points.length - 1]).normalize());
		return this.segments;
	},

	// Fit a Bezier curve to a (sub)set of digitized points
	fitCubic: function(first, last, tan1, tan2) {
		//	Use heuristic if region only has two points in it
		if (last - first == 1) {
			var pt1 = this.points[first],
				pt2 = this.points[last],
				dist = pt1.getDistance(pt2) / 3;
			this.addCurve([pt1, pt1.add(tan1.normalize(dist)),
					pt2.add(tan2.normalize(dist)), pt2]);
			return;
		}
		// Parameterize points, and attempt to fit curve
		var uPrime = this.chordLengthParameterize(first, last),
			maxError = Math.max(this.error, this.error * this.error),
			error,
			split;
		// Try 4 iterations
		for (var i = 0; i <= 4; i++) {
			var curve = this.generateBezier(first, last, uPrime, tan1, tan2);
			//	Find max deviation of points to fitted curve
			var max = this.findMaxError(first, last, curve, uPrime);
			if (max.error < this.error) {
				this.addCurve(curve);
				return;
			}
			split = max.index;
			// If error not too large, try reparameterization and iteration
			if (max.error >= maxError)
				break;
			this.reparameterize(first, last, uPrime, curve);
			maxError = max.error;
		}
		// Fitting failed -- split at max error point and fit recursively
		var V1 = this.points[split - 1].subtract(this.points[split]),
			V2 = this.points[split].subtract(this.points[split + 1]),
			tanCenter = V1.add(V2).divide(2).normalize();
		this.fitCubic(first, split, tan1, tanCenter);
		this.fitCubic(split, last, tanCenter.negate(), tan2);
	},

	addCurve: function(curve) {
		var prev = this.segments[this.segments.length - 1];
		prev.setHandleOut(curve[1].subtract(curve[0]));
		this.segments.push(
				new Segment(curve[3], curve[2].subtract(curve[3])));
	},

	// Use least-squares method to find Bezier control points for region.
	generateBezier: function(first, last, uPrime, tan1, tan2) {
		var epsilon = Numerical.TOLERANCE,
			pt1 = this.points[first],
			pt2 = this.points[last],
			// Create the C and X matrices
		 	C = [[0, 0], [0, 0]],
			X = [0, 0];

		for (var i = 0, l = last - first + 1; i < l; i++) {
			var u = uPrime[i],
				t = 1 - u,
				b = 3 * u * t,
				b0 = t * t * t,
				b1 = b * t,
				b2 = b * u,
				b3 = u * u * u,
				a1 = tan1.normalize(b1),
				a2 = tan2.normalize(b2),
				tmp = this.points[first + i]
					.subtract(pt1.multiply(b0 + b1))
					.subtract(pt2.multiply(b2 + b3));
			C[0][0] += a1.dot(a1);
			C[0][1] += a1.dot(a2);
			// C[1][0] += a1.dot(a2);
			C[1][0] = C[0][1];
			C[1][1] += a2.dot(a2);
			X[0] += a1.dot(tmp);
			X[1] += a2.dot(tmp);
		}

		// Compute the determinants of C and X
		var detC0C1 = C[0][0] * C[1][1] - C[1][0] * C[0][1],
			alpha1, alpha2;
		if (Math.abs(detC0C1) > epsilon) {
			// Kramer's rule
			var detC0X  = C[0][0] * X[1]    - C[1][0] * X[0],
				detXC1  = X[0]    * C[1][1] - X[1]    * C[0][1];
			// Derive alpha values
			alpha1 = detXC1 / detC0C1;
			alpha2 = detC0X / detC0C1;
		} else {
			// Matrix is under-determined, try assuming alpha1 == alpha2
			var c0 = C[0][0] + C[0][1],
				c1 = C[1][0] + C[1][1];
			if (Math.abs(c0) > epsilon) {
				alpha1 = alpha2 = X[0] / c0;
			} else if (Math.abs(c0) > epsilon) {
				alpha1 = alpha2 = X[1] / c1;
			} else {
				// Handle below
				alpha1 = alpha2 = 0.;
			}
		}

		// If alpha negative, use the Wu/Barsky heuristic (see text)
		// (if alpha is 0, you get coincident control points that lead to
		// divide by zero in any subsequent NewtonRaphsonRootFind() call.
		var segLength = pt2.getDistance(pt1);
		epsilon *= segLength;
		if (alpha1 < epsilon || alpha2 < epsilon) {
			// fall back on standard (probably inaccurate) formula,
			// and subdivide further if needed.
			alpha1 = alpha2 = segLength / 3;
		}

		// First and last control points of the Bezier curve are
		// positioned exactly at the first and last data points
		// Control points 1 and 2 are positioned an alpha distance out
		// on the tangent vectors, left and right, respectively
		return [pt1, pt1.add(tan1.normalize(alpha1)),
				pt2.add(tan2.normalize(alpha2)), pt2];
	},

	// Given set of points and their parameterization, try to find
	// a better parameterization.
	reparameterize: function(first, last, u, curve) {
		for (var i = first; i <= last; i++) {
			u[i - first] = this.findRoot(curve, this.points[i], u[i - first]);
		}
	},

	// Use Newton-Raphson iteration to find better root.
	findRoot: function(curve, point, u) {
		var curve1 = [],
			curve2 = [];
		// Generate control vertices for Q'
		for (var i = 0; i <= 2; i++) {
			curve1[i] = curve[i + 1].subtract(curve[i]).multiply(3);
		}
		// Generate control vertices for Q''
		for (var i = 0; i <= 1; i++) {
			curve2[i] = curve1[i + 1].subtract(curve1[i]).multiply(2);
		}
		// Compute Q(u), Q'(u) and Q''(u)
		var pt = this.evaluate(3, curve, u),
		 	pt1 = this.evaluate(2, curve1, u),
		 	pt2 = this.evaluate(1, curve2, u),
		 	diff = pt.subtract(point),
			df = pt1.dot(pt1) + diff.dot(pt2);
		// Compute f(u) / f'(u)
		if (Math.abs(df) < Numerical.TOLERANCE)
			return u;
		// u = u - f(u) / f'(u)
		return u - pt.dot(pt1) / df;
	},

	// Evaluate a Bezier curve at a particular parameter value
	evaluate: function(degree, curve, t) {
		// Copy array
		var tmp = curve.slice();
		// Triangle computation
		for (var i = 1; i <= degree; i++) {
			for (var j = 0; j <= degree - i; j++) {
				tmp[j] = tmp[j].multiply(1 - t).add(tmp[j + 1].multiply(t));
			}
		}
		return tmp[0];
	},

	// Assign parameter values to digitized points
	// using relative distances between points.
	chordLengthParameterize: function(first, last) {
		var u = [0];
		for (var i = first + 1; i <= last; i++) {
			u[i - first] = u[i - first - 1]
					+ this.points[i].getDistance(this.points[i - 1]);
		}
		for (var i = 1, m = last - first; i <= m; i++) {
			u[i] /= u[m];
		}
		return u;
	},

	// Find the maximum squared distance of digitized points to fitted curve.
	findMaxError: function(first, last, curve, u) {
		var index = Math.floor((last - first + 1) / 2),
			maxDist = 0;
		for (var i = first + 1; i < last; i++) {
			var P = this.evaluate(3, curve, u[i - first]);
			var v = P.subtract(this.points[i]);
			var dist = v.x * v.x + v.y * v.y; // squared
			if (dist >= maxDist) {
				maxDist = dist;
				index = i;
			}
		}
		return {
			error: maxDist,
			index: index
		};
	}
});
<|endoftext|>