✅ WEB- ja WordPress -uutiset, -teemat, -laajennukset. Täällä jaamme vinkkejä ja parhaita verkkosivustoratkaisuja.

Luo asetussivu WordPress Block Editor (Gutenberg) -komponenttien avulla

17

Olemme aiemmin tarkastelleet vaihtoehtojen ja asetusten tallentamista WordPressin lohkoeditorilla (Gutenberg) ja laajentaneet Create Block Script -toimintoa ylimääräisten päätepisteiden mahdollistamiseksi. Tässä oppaassa kokoamme ne yhteen ja luomme asetussivun käyttämällä Gutenberg-komponentteja.

Asetussivu, jota olemme rakentamassa

Mutta ensin, ansioluettelo, jossa luotto maksetaan. Tämän oppaan inspiraation lähteenä on Hardeep Asranin WP-artikkelin Code: Making a Plugin Options Page with Gutenberg Components.

Edellytykset

Luo asetussivu PHP:llä

Edellytyksenä olevien ohjeiden mukaisesti avaa laajennuksen (tässä tapauksessa wholesome-plugin.php) PHP-juuritiedosto ja lisää seuraava:

Rekisteröi asetukset

Kuten tietojen tallennusvaihtoehtojen käyttöoppaassa, lisää tiedostoon seuraavat asetukset:

function wholesomecode_wholesome_plugin_register_settings() {
    register_setting(
        'wholesomecode_wholesome_plugin_settings',
        'wholesomecode_wholesome_plugin_example_select',
        [
            'default'      => '',
            'show_in_rest' => true,
            'type'         => 'string',
        ]
    );

    register_setting(
        'wholesomecode_wholesome_plugin_settings',
        'wholesomecode_wholesome_plugin_example_text',
        [
            'default'      => '',
            'show_in_rest' => true,
            'type'         => 'string',
        ]
    );

    register_setting(
        'wholesomecode_wholesome_plugin_settings',
        'wholesomecode_wholesome_plugin_example_text_2',
        [
            'default'      => '',
            'show_in_rest' => true,
            'type'         => 'string',
        ]
    );

    register_setting(
        'wholesomecode_wholesome_plugin_settings',
        'wholesomecode_wholesome_plugin_example_text_3',
        [
            'default'      => '',
            'show_in_rest' => true,
            'type'         => 'string',
        ]
    );

    register_setting(
        'wholesomecode_wholesome_plugin_settings',
        'wholesomecode_wholesome_plugin_example_toggle',
        [
            'default'      => '',
            'show_in_rest' => true,
            'type'         => 'string',
        ]
    );
}
add_action( 'init', 'wholesomecode_wholesome_plugin_register_settings', 10 );

Rekisteröi asetukset, joihin pääsemme asetussivulla. Muista asettaa show_in_restjokaiselle true, jotta niihin pääsee Gutenbergin kautta.

Rekisteröi asetussivu

Lisää koodilohko rekisteröidäksesi asetussivun:

function wholesomecode_wholesome_plugin_settings_page() {
    add_options_page(
        __( 'Wholesome Plugin Settings', 'wholesome-plugin' ),
        __( 'Wholesome Plugin Settings', 'wholesome-plugin' ),
        'manage_options',
        'wholesome_plugin_settings',
        function() {
            ?>
            <div id="wholesome-plugin-settings"></div>
            <?php
        },
    );
}
add_action( 'admin_menu', 'wholesomecode_wholesome_plugin_settings_page', 10 );

Yllä oleva koodi lisää uuden sivun asetusvalikkoon. Huomaa, että se tulostaa vain a <div>, jota käytämme React-pohjaisten Gutenberg-komponenttien hahmontamiseen.

Luo asetussivu WordPress Block Editor (Gutenberg) -komponenttien avullaAsetukset-valikkokohta

Jonota järjestelmänvalvojan resurssit

Jotta voisimme ottaa seuraavan koodilohkon, meidän on täytynyt noudattaa kaikkia oppaan Lisää aloituspisteitä Luo lohkokomentosarjaan -oppaan vaiheita. Varmista, että noudatat kaikkia tämän oppaan vaiheita ennen tätä vaihetta, ja palaa ja noudata tämän oppaan loput kohdat.

function wholesomecode_wholesome_plugin_admin_scripts() {
    $dir = __DIR__;

    $script_asset_path = "$dir/build/admin.asset.php";
    if (! file_exists( $script_asset_path)) {
        throw new Error(
            'You need to run `npm start` or `npm run build` for the "wholesomecode/wholesome-plugin" block first.'
        );
    }
    $admin_js     = 'build/admin.js';
    $script_asset = require( $script_asset_path );
    wp_enqueue_script(
        'wholesomecode-wholesome-plugin-admin-editor',
        plugins_url( $admin_js, __FILE__ ),
        $script_asset['dependencies'],
        $script_asset['version']
    );
    wp_set_script_translations( 'wholesomecode-wholesome-plugin-block-editor', 'wholesome-plugin' );

    $admin_css = 'build/admin.css';
    wp_enqueue_style(
        'wholesomecode-wholesome-plugin-admin',
        plugins_url( $admin_css, __FILE__ ),
        ['wp-components'],
        filemtime( "$dir/$admin_css") );
}
add_action( 'admin_enqueue_scripts', 'wholesomecode_wholesome_plugin_admin_scripts', 10 );

Rakenna järjestelmänvalvojasivu JavaScriptillä

Jos olet noudattanut kaikkia oppaan Lisää aloituspisteitä Luo lohkokomentosarjaan -oppaan vaiheita, sinulla pitäisi olla /src/admin.jstiedosto. Avaa tiedosto ja poista sen sisältö.

Renderöi komponentti

Muista ajaa npm startpäätteessäsi laajennuksen luontioppaan mukaisesti ja lisää alla oleva /src/admin.jstiedosto tiedostoosi.

import './admin.scss';
import { Icon } from '@wordpress/components';
import {
    Fragment,
    render,
    Component,
} from '@wordpress/element';
import { __ } from '@wordpress/i18n';

class App extends Component {
    constructor() {
        super( ...arguments );
    }

    render() {
        return (<Fragment>
                <div className="wholesome-plugin__header">
                    <div className="wholesome-plugin__container">
                        <div className="wholesome-plugin__title">
                            <h1>{ __( 'Wholesome Plugin Settings', 'wholesome-plugin') } <Icon icon="admin-plugins" /></h1>
                        </div>
                    </div>
                </div>
                <div className="wholesome-plugin__main"></div>
            </Fragment>) }
}

document.addEventListener( 'DOMContentLoaded',() => {
    const htmlOutput = document.getElementById( 'wholesome-plugin-settings' );

    if (htmlOutput) {
        render(
            <App />,
            htmlOutput
        );
    }
});

Jos siirryt selaimen asetussivulle, sinun pitäisi nyt nähdä seuraava:

Luo asetussivu WordPress Block Editor (Gutenberg) -komponenttien avullaKomponenttien renderöiminen asetusnäyttöön

Lisää asetuskentät

Muistatko lisävaiheet Tietojen tallennusvaihtoehtojen käyttäminen -oppaassa? No, aiomme liittää ne sanatarkasti tähän komponenttiin, joten sinun pitäisi päätyä johonkin koodiin, joka näyttää vähän tältä:

import './admin.scss';

import api from '@wordpress/api';

import {
    Button,
    Icon,
    Panel,
    PanelBody,
    PanelRow,
    Placeholder,
    SelectControl,
    Spinner,
    TextControl,
    ToggleControl,
} from '@wordpress/components';

import {
    Fragment,
    render,
    Component,
} from '@wordpress/element';

import { __ } from '@wordpress/i18n';

class App extends Component {
    constructor() {
        super( ...arguments );

        this.state = {
            exampleSelect: '',
            exampleText: '',
            exampleText2: '',
            exampleText3: '',
            exampleToggle: false,
            isAPILoaded: false,
        };
    }

    componentDidMount() {

        api.loadPromise.then(() => {
            this.settings = new api.models.Settings();

            const { isAPILoaded } = this.state;

            if (isAPILoaded === false) {
                this.settings.fetch().then( (response) => {
                    this.setState( {
                        exampleSelect: response[ 'wholesomecode_wholesome_plugin_example_select' ],
                        exampleText: response[ 'wholesomecode_wholesome_plugin_example_text' ],
                        exampleText2: response[ 'wholesomecode_wholesome_plugin_example_text_2' ],
                        exampleText3: response[ 'wholesomecode_wholesome_plugin_example_text_3' ],
                        exampleToggle: Boolean( response[ 'wholesomecode_wholesome_plugin_example_toggle' ] ),
                        isAPILoaded: true,
                    } );
                } );
            }
        } );
    }

    render() {
        const {
            exampleSelect,
            exampleText,
            exampleText2,
            exampleText3,
            exampleToggle,
            isAPILoaded,
        } = this.state;

        if (! isAPILoaded) {
            return (<Placeholder>
                    <Spinner />
                </Placeholder>
            );
        }

        return (<Fragment>
                <div className="wholesome-plugin__header">
                    <div className="wholesome-plugin__container">
                        <div className="wholesome-plugin__title">
                            <h1>{ __( 'Wholesome Plugin Settings', 'wholesome-plugin') } <Icon icon="admin-plugins" /></h1>
                        </div>
                    </div>
                </div>

                <div className="wholesome-plugin__main">
                    <Panel>
                        <PanelBody
                            title={ __( 'Panel Body One', 'wholesome-plugin') }
                            icon="admin-plugins"
                        >
                            <SelectControl
                                help={ __( 'An example dropdown field.', 'wholesome-plugin') }
                                label={ __( 'Example Select', 'wholesome-plugin') }
                                onChange={ (exampleSelect) => this.setState( { exampleSelect }) }
                                options={ [
                                    {
                                        label: __( 'Please Select...', 'wholesome-plugin' ),
                                        value: '',
                                    },
                                    {
                                        label: __( 'Option 1', 'wholesome-plugin' ),
                                        value: 'option-1',
                                    },
                                    {
                                        label: __( 'Option 2', 'wholesome-plugin' ),
                                        value: 'option-2',
                                    },
                                ] }
                                value={ exampleSelect }
                            />
                        </PanelBody>
                        <PanelBody
                            title={ __( 'Panel Body Two', 'wholesome-plugin') }
                            icon="admin-plugins"
                        >
                            <TextControl
                                help={ __( 'This is an example text field.', 'wholesome-plugin') }
                                label={ __( 'Example Text', 'wholesome-plugin') }
                                onChange={ (exampleText) => this.setState( { exampleText }) }
                                value={ exampleText }
                            />

                        </PanelBody>
                        <PanelBody
                            title={ __( 'Panel Body Three', 'wholesome-plugin') }
                            icon="admin-plugins"
                        >
                            <PanelRow>
                                <TextControl
                                    help={ __( 'Use PanelRow to place controls inline.', 'wholesome-plugin') }
                                    label={ __( 'Example Text 2', 'wholesome-plugin') }
                                    onChange={ (exampleText2) => this.setState( { exampleText2 }) }
                                    value={ exampleText2 }
                                />
                                <TextControl
                                    help={ __( 'This control is inline.', 'wholesome-plugin') }
                                    label={ __( 'Example Text 3', 'wholesome-plugin') }
                                    onChange={ (exampleText3) => this.setState( { exampleText3 }) }
                                    value={ exampleText3 }
                                />
                            </PanelRow>
                        </PanelBody>
                        <PanelBody
                            title={ __( 'Panel Body Four', 'wholesome-plugin') }
                            icon="admin-plugins"
                        >
                            <ToggleControl
                                checked={ exampleToggle }
                                help={ __( 'An example toggle.', 'wholesome-plugin') }
                                label={ __( 'Example Toggle', 'wholesome-plugin') }
                                onChange={ (exampleToggle) => this.setState( { exampleToggle }) }
                            />
                        </PanelBody>
                        <Button
                            isPrimary
                            isLarge
                            onClick={ () => {}}
                        >
                            { __( 'Save', 'wholesome-plugin') }
                        </Button>
                    </Panel>
                </div>
            </Fragment>) }
}

document.addEventListener( 'DOMContentLoaded', () => {
    const htmlOutput = document.getElementById( 'wholesome-plugin-settings' );

    if (htmlOutput) {
        render(
            <App />,
            htmlOutput
        );
    }
});

Lukuun ottamatta koodin poistamista, subscribejota componentDidMountaiemmin käytimme tallentamiseen kyseisessä oppaassa, ja painikkeen lisäämistä, koodi on melko pitkälti kopioi ja liitä.

Jos kaikki on hyvin, asetussivumme pitäisi nyt näyttää hieman tältä:

Luo asetussivu WordPress Block Editor (Gutenberg) -komponenttien avullaAsetuskenttien renderöiminen

Älä huoli, siivoamme tämän oppaan osiossa 4 olevat tyylit.

Käsittele Tallenna

Lisää komponentin onClickkäsittelijään <button>seuraava koodi:

<Button
  isPrimary
  isLarge
  onClick={ () => {
    const {
      exampleSelect,
      exampleText,
      exampleText2,
      exampleText3,
      exampleToggle,
    } = this.state;

    const settings = new api.models.Settings( {
      [ 'wholesomecode_wholesome_plugin_example_select' ]: exampleSelect,
      [ 'wholesomecode_wholesome_plugin_example_text' ]: exampleText,
      [ 'wholesomecode_wholesome_plugin_example_text_2' ]: exampleText2,
      [ 'wholesomecode_wholesome_plugin_example_text_3' ]: exampleText3,
      [ 'wholesomecode_wholesome_plugin_example_toggle' ]: exampleToggle? 'true': '',
    } );
    settings.save();
  }}
  >
  { __( 'Save', 'wholesome-plugin') }
</Button>

Tämä tallentaa vaihtoehtomme ja asetuksemme, kun painiketta napsautetaan. Mikään ei kuitenkaan viittaa siihen, että asetukset olisi tallennettu oletusarvoisesti.

Luo ilmoitus

Antaaksemme käyttäjällemme palautetta siitä, että valinnat ja asetukset on tallennettu, voimme ottaa käyttöön "snackbar"-ilmoituksen. Tämä on sama viestieditorin päänäytössä käytetty ilmoitus, jota lohkoeditori käyttää, kun viesti on tallennettu.

Tämän lisäämiseksi meidän on siirrettävä Gutenbergin ydinkomponentti koontiversioomme, koska ilmoitusluettelo ei ole käytettävissä tavallisilla tuontilausekkeilla.

Meidän on lisättävä tiedostoon seuraava koodi:

import { SnackbarList } from '@wordpress/components';

import {
    dispatch,
    useDispatch,
    useSelect,
} from '@wordpress/data';

import { store as noticesStore } from '@wordpress/notices';

const Notices = () => {
    const notices = useSelect( (select) =>
            select( noticesStore) .getNotices()
                .filter( (notice) => notice.type === 'snackbar' ),
        []
    );
    const { removeNotice } = useDispatch( noticesStore );
    return (<SnackbarList
            className="edit-site-notices"
            notices={ notices }
            onRemove={ removeNotice }
        />
    );
};

<App>Lisää sitten komponentin päähahmonnukseen seuraava ennen sulkemista </Fragment>:

<div className="wholesome-plugin__notices">
  <Notices/>
</div>

Lisää lopuksi onClickpainikkeen käsittelijään seuraava:

dispatch('core/notices').createNotice(
  'success',
  __( 'Settings Saved', 'wholesome-plugin' ),
  {
    type: 'snackbar',
    isDismissible: true,
  }
);

Tämä luo pienen "snackbar"-ponnahdusikkunan aina, kun asetukset tallennetaan.

Luo asetussivu WordPress Block Editor (Gutenberg) -komponenttien avullaSnackbar-ilmoitus toiminnassa

Tiedän, tiedän, meidän on vielä korjattava nuo tyylit.

Koko /src/admin.jstiedosto

Viitteeksi tässä on koko /src/admin.jstiedostokoodi:

import './admin.scss';

import api from '@wordpress/api';

import {
    Button,
    Icon,
    Panel,
    PanelBody,
    PanelRow,
    Placeholder,
    SelectControl,
    SnackbarList,
    Spinner,
    TextControl,
    ToggleControl,
} from '@wordpress/components';

import {
    dispatch,
    useDispatch,
    useSelect,
} from '@wordpress/data';

import {
    Fragment,
    render,
    Component,
} from '@wordpress/element';

import { __ } from '@wordpress/i18n';

import { store as noticesStore } from '@wordpress/notices';

const Notices = () => {
    const notices = useSelect( (select) =>
            select( noticesStore) .getNotices()
                .filter( (notice) => notice.type === 'snackbar' ),
        []
    );
    const { removeNotice } = useDispatch( noticesStore );
    return (<SnackbarList
            className="edit-site-notices"
            notices={ notices }
            onRemove={ removeNotice }
        />
    );
};

class App extends Component {
    constructor() {
        super( ...arguments );

        this.state = {
            exampleSelect: '',
            exampleText: '',
            exampleText2: '',
            exampleText3: '',
            exampleToggle: false,
            isAPILoaded: false,
        };
    }

    componentDidMount() {

        api.loadPromise.then( () => {
            this.settings = new api.models.Settings();

            const { isAPILoaded } = this.state;

            if (isAPILoaded === false) {
                this.settings.fetch().then( (response) => {
                    this.setState( {
                        exampleSelect: response[ 'wholesomecode_wholesome_plugin_example_select' ],
                        exampleText: response[ 'wholesomecode_wholesome_plugin_example_text' ],
                        exampleText2: response[ 'wholesomecode_wholesome_plugin_example_text_2' ],
                        exampleText3: response[ 'wholesomecode_wholesome_plugin_example_text_3' ],
                        exampleToggle: Boolean( response[ 'wholesomecode_wholesome_plugin_example_toggle' ] ),
                        isAPILoaded: true,
                    } );
                } );
            }
        } );
    }

    render() {
        const {
            exampleSelect,
            exampleText,
            exampleText2,
            exampleText3,
            exampleToggle,
            isAPILoaded,
        } = this.state;

        if (! isAPILoaded) {
            return (<Placeholder>
                    <Spinner />
                </Placeholder>
            );
        }

        return (<Fragment>
                <div className="wholesome-plugin__header">
                    <div className="wholesome-plugin__container">
                        <div className="wholesome-plugin__title">
                            <h1>{ __( 'Wholesome Plugin Settings', 'wholesome-plugin') } <Icon icon="admin-plugins" /></h1>
                        </div>
                    </div>
                </div>

                <div className="wholesome-plugin__main">
                    <Panel>
                        <PanelBody
                            title={ __( 'Panel Body One', 'wholesome-plugin') }
                            icon="admin-plugins"
                        >
                            <SelectControl
                                help={ __( 'An example dropdown field.', 'wholesome-plugin') }
                                label={ __( 'Example Select', 'wholesome-plugin') }
                                onChange={ (exampleSelect) => this.setState( { exampleSelect }) }
                                options={ [
                                    {
                                        label: __( 'Please Select...', 'wholesome-plugin' ),
                                        value: '',
                                    },
                                    {
                                        label: __( 'Option 1', 'wholesome-plugin' ),
                                        value: 'option-1',
                                    },
                                    {
                                        label: __( 'Option 2', 'wholesome-plugin' ),
                                        value: 'option-2',
                                    },
                                ] }
                                value={ exampleSelect }
                            />
                        </PanelBody>
                        <PanelBody
                            title={ __( 'Panel Body Two', 'wholesome-plugin') }
                            icon="admin-plugins"
                        >
                            <TextControl
                                help={ __( 'This is an example text field.', 'wholesome-plugin') }
                                label={ __( 'Example Text', 'wholesome-plugin') }
                                onChange={ (exampleText) => this.setState( { exampleText }) }
                                value={ exampleText }
                            />

                        </PanelBody>
                        <PanelBody
                            title={ __( 'Panel Body Three', 'wholesome-plugin') }
                            icon="admin-plugins"
                        >
                            <PanelRow>
                                <TextControl
                                    help={ __( 'Use PanelRow to place controls inline.', 'wholesome-plugin') }
                                    label={ __( 'Example Text 2', 'wholesome-plugin') }
                                    onChange={ (exampleText2) => this.setState( { exampleText2 }) }
                                    value={ exampleText2 }
                                />
                                <TextControl
                                    help={ __( 'This control is inline.', 'wholesome-plugin') }
                                    label={ __( 'Example Text 3', 'wholesome-plugin') }
                                    onChange={ (exampleText3) => this.setState( { exampleText3 }) }
                                    value={ exampleText3 }
                                />
                            </PanelRow>
                        </PanelBody>
                        <PanelBody
                            title={ __( 'Panel Body Four', 'wholesome-plugin') }
                            icon="admin-plugins"
                        >
                            <ToggleControl
                                checked={ exampleToggle }
                                help={ __( 'An example toggle.', 'wholesome-plugin') }
                                label={ __( 'Example Toggle', 'wholesome-plugin') }
                                onChange={ (exampleToggle) => this.setState( { exampleToggle }) }
                            />
                        </PanelBody>
                        <Button
                            isPrimary
                            isLarge
                            onClick={ () => {
                                const {
                                    exampleSelect,
                                    exampleText,
                                    exampleText2,
                                    exampleText3,
                                    exampleToggle,
                                } = this.state;

                                const settings = new api.models.Settings( {
                                    [ 'wholesomecode_wholesome_plugin_example_select' ]: exampleSelect,
                                    [ 'wholesomecode_wholesome_plugin_example_text' ]: exampleText,
                                    [ 'wholesomecode_wholesome_plugin_example_text_2' ]: exampleText2,
                                    [ 'wholesomecode_wholesome_plugin_example_text_3' ]: exampleText3,
                                    [ 'wholesomecode_wholesome_plugin_example_toggle' ]: exampleToggle? 'true': '',
                                } );
                                settings.save();

                                dispatch('core/notices').createNotice(
                                    'success',
                                    __( 'Settings Saved', 'wholesome-plugin' ),
                                    {
                                        type: 'snackbar',
                                        isDismissible: true,
                                    }
                                );
                            }}
                        >
                            { __( 'Save', 'wholesome-plugin') }
                        </Button>
                    </Panel>
                </div>

                <div className="wholesome-plugin__notices">
                    <Notices/>
                </div>

            </Fragment>) }
}

document.addEventListener( 'DOMContentLoaded', () => {
    const htmlOutput = document.getElementById( 'wholesome-plugin-settings' );

    if (htmlOutput) {
        render(
            <App />,
            htmlOutput
        );
    }
});

Lisää SCSS

Aika korjata nuo tyylit. Lisää vain seuraava /src/admin.scsstiedostoon (jonka olisit luonut Lisää aloituspisteitä Luo lohkokomentosarjaan -oppaassa.

#wholesome-plugin-settings {

    .components-placeholder {
        background: #f1f1f1;
    }

    .wholesome-plugin__header {
        background-color: #ffffff;
        box-shadow: 0 1px 0 rgba(213, 213, 213, .5), 0 1px 2px #eeeeee;
        margin-left: -2em;
        padding: 20px 10px;

        .wholesome-plugin__container {
            margin: 0 auto;
            max-width: 750px;

            .wholesome-plugin__title {
                align-items: center;
                display: flex;
                justify-content: center;

                .dashicon {
                    color: #757575;
                }
            }
        }
    }

    .wholesome-plugin__main {
        margin-left: auto;
        margin-right: auto;
        max-width: 750px;

        .components-panel {
            background: none;
            border: none;
        }

        .components-panel__body {
            background: #ffffff;
            border: 1px solid #e2e4e7;
            margin: 1rem 0;
        }
    }

    .components-base-control__help {
        margin-top: .5rem;
    }

    .components-panel__row {
        > div {
            flex-grow: 1;
            margin-right: 1rem;

            &:last-of-type {
                margin-right: 0;
            }
        }
    }

    .wholesome-plugin__notices {
        .components-snackbar {
            bottom: .5rem;
            position: fixed;
        }
    }
}

Asetussivun tarkastelu

Tässä lopputulos:

Luo asetussivu WordPress Block Editor (Gutenberg) -komponenttien avullaAsetukset-sivu

Joillakin laajennuksilla on "asetukset"-linkki laajennussivulla, esimerkiksi:

Luo asetussivu WordPress Block Editor (Gutenberg) -komponenttien avullaAsetukset-linkki Plugin Settings -sivupaneelissa

Tämän saavuttamiseksi lisää seuraava koodilohko laajennustiedostosi juureen (tässä tapauksessa wholesome-plugin.php):

function wholesomecode_wholesome_plugin_settings_link( $links ): array {
    $label = esc_html__( 'Settings', 'wholesome-plugin' );
    $slug  = 'wholesome_plugin_settings';

    array_unshift( $links, "<a href='options-general.php?page=$slug'>$label</a>" );

    return $links;
}
add_action( 'plugin_action_links_'. plugin_basename( __FILE__ ), 'wholesomecode_wholesome_plugin_settings_link', 10 );

Tämä verkkosivusto käyttää evästeitä parantaakseen käyttökokemustasi. Oletamme, että olet kunnossa, mutta voit halutessasi kieltäytyä. Hyväksyä Lisätietoja